1장. 마이페이지 개요
마이페이지(/auth/mypage)는 로그인 회원이 자신의 정보를 확인하고 관리하는 페이지입니다. 단일 PHP 파일(core/auth/mypage.php)이 10개 탭을 모두 처리하며, 탭 파라미터(tab=...)에 따라 데이터를 동적으로 로드하고 렌더링합니다. 최종 출력은 현재 활성 테마의 layout/main.php를 통해 래핑됩니다.
1.1 진입 경로 및 접근 제어
URL: /auth/mypage
?tab=profile|notifications|memo|points|exp|scraps|purchases|friends|blocks|social
&sub=my|followers (friends 탭 전용)
&mbox=received_all|received_friend|sent_all|sent_friend|send (memo 탭 전용)
&p=1 (페이지 번호, 기본 20개/페이지)
비로그인 접근 시:
→ /auth/login?redirect=/auth/mypage 로 자동 리다이렉트
파일 위치:
core/auth/mypage.php ← 메인 로직 (탭 데이터 로드 + 렌더링)
assets/css/mypage.css ← 마이페이지 전용 CSS (dx_head 훅으로 자동 로드)
1.2 10개 탭 구성
| 탭 (tab=) |
아이콘 |
기능 |
| profile |
👤 |
프로필 수정, 비밀번호 변경, 이미지 업로드, SNS 연결, 회원 탈퇴 |
| notifications |
🔔 |
실시간 알림 전체 목록 조회, 읽음 처리, 삭제 |
| memo |
✉️ |
쪽지(메모) 수신함/발신함, 보내기, 읽음 처리, 삭제 |
| points |
💰 |
포인트 내역 조회 (변동·잔액·유형), 페이지네이션 |
| exp |
⭐ |
경험치 내역, 레벨 프로그레스바, 레벨 기준표 |
| scraps |
🔖 |
스크랩한 게시글 목록, 스크랩 해제 |
| purchases |
🛒 |
포인트샵 구매 내역, 상태(구매완료/환불됨) |
| friends |
👥 |
내가 추가한 친구(sub=my), 나를 추가한 사람(sub=followers), 서로친구 배지 |
| blocks |
🚫 |
차단한 회원 목록, 차단 해제 |
| social |
🔗 |
소셜 계정 연결 현황(카카오/네이버/구글/GitHub), 연결하기 링크 |
1.3 전체 처리 흐름
GET /auth/mypage?tab=profile
↓
1. 비로그인 체크 → 리다이렉트
2. 최신 회원 정보 조회 (dx_members WHERE id=?)
3. dx_head 훅으로 mypage.css 로드
4. tab 파라미터 파싱
5. POST면 CSRF 검증 → _action 처리 (profile 수정 / withdraw 탈퇴)
6. 탭별 데이터 로드 (points/exp/scraps/friends/blocks/purchases/notifications)
7. ob_start() → HTML 렌더링 → ob_get_clean() → $dx_content 저장
8. _dx_mypage_pagination() 헬퍼 함수 정의
9. DxTheme::resolve("layout/main.php") → require → 테마 래핑 출력
2장. 프로필 헤더 카드
모든 탭에서 공통으로 표시되는 회원 정보 요약 카드입니다. 아바타•이름•레벨 배지•경험치 바•포인트/EXP/친구 수 스탯으로 구성됩니다.
2.1 구성 요소
| 요소 |
설명 및 CSS 클래스 |
| 아바타 |
profile_img 있으면 <img> 태그. 없으면 이름 첫 글자 div (.mp-avatar). 62px 원형 |
| 이름 + 레벨 배지 |
.mp-name + .mp-lv-badge. Lv.N + 레벨명 (DxPoint::getLevelName) |
| 로그인 아이디 |
.mp-loginid. 회색 소문자로 표시 |
| 경험치 바 |
.mp-exp-bar → .mp-exp-fill (width: $progress%). 다음 레벨까지 EXP 표시 |
| 스탯 (PC) |
.mp-stats-pc. 포인트(금색)·EXP(보라)·친구수(어두운). 600px 이상에서만 표시 |
| 스탯 (모바일) |
.mp-stats-mo. 카드 하단 3분할 가로 배치. 600px 미만에서만 표시 |
2.2 레벨•경험치 계산
// PHP 마이페이지에서
$level = (int)$member["level"];
$exp = (int)$member["exp"];
$progress = DxPoint::getLevelProgress($exp); // 0~100 (현재 레벨 내 진행률 %)
$nextExp = DxPoint::getNextLevelExp($exp); // 다음 레벨까지 필요 EXP. null이면 최고레벨
// 친구 수 — 1분 DxCache 캐시 적용
$_fcVal = DxFriend::getFriendCount($memberId);
// DxCache::get("friend_cnt_{$memberId}") → 없으면 조회 후 60초 캐시
2.3 탭 버튼 렌더링
탭 버튼은 가로 스크롤 가능한 row로 렌더링됩니다. 현재 탭은 bg-blue-600 text-white, 나머지는 bg-white text-slate-500 border 스타일입니다. 메모 탭에는 미읽음 쪽지 수 빨간 배지(mp-memo-badge)가 표시됩니다.
// 탭 배열 정의
$tabs = array(
"profile" => array("icon"=>"👤", "label"=>"프로필"),
"notifications" => array("icon"=>"🔔", "label"=>"알림"),
"memo" => array("icon"=>"✉️", "label"=>"메모"),
"points" => array("icon"=>"💰", "label"=>"포인트"),
"exp" => array("icon"=>"⭐", "label"=>"경험치"),
"scraps" => array("icon"=>"북마크", "label"=>"스크랩"),
"purchases" => array("icon"=>"🛒", "label"=>"구매내역"),
"friends" => array("icon"=>"👥", "label"=>"친구"),
"blocks" => array("icon"=>"🚫", "label"=>"차단"),
"social" => array("icon"=>"🔗", "label"=>"소셜연결"),
);
// 메모 미읽음 수 — 30초 DxCache 캐시
$_unreadMemo = 0; // memos WHERE to_id=? AND is_read=0 AND is_deleted_receiver=0
3장. POST 처리 — 프로필 수정 및 회원 탈퇴
3.1 프로필 수정 (_action=profile)
form enctype="multipart/form-data" 으로 POST 전송됩니다. CSRF 검증 후 아래 필드를 처리합니다.
| 필드 (name) |
필수 |
처리 방식 |
| name |
★ 필수 |
DxSanitizer::text(). 빈값이면 오류 |
| email |
선택 |
filter_var FILTER_VALIDATE_EMAIL 검증. 빈값이면 업데이트 생략 |
| bio |
선택 |
300자 초과 시 오류. DxSanitizer::text() 처리 |
| website / blog |
선택 |
http로 시작하지 않으면 https:// 자동 추가 (cleanUrl 클로저) |
| sns_instagram / twitter / facebook / youtube / github |
선택 |
DxSanitizer::text(). members 테이블에 해당 컬럼 있을 때만 저장 |
| cur_pw / new_pw |
변경 시 |
cur_pw: password_verify(). new_pw: 6자 이상. password_hash(BCRYPT) |
| profile_img (파일) |
선택 |
jpg/png/gif/webp, 10MB 이내. GD로 400×400px JPEG 85품질 리사이즈 |
| delete_profile_img |
선택 |
"1"이면 기존 이미지 파일 unlink + DB 컬럼 빈값으로 업데이트 |
3.2 프로필 이미지 업로드 상세
// 저장 경로: data/uploads/profiles/
// 파일명: YYYYMMDD_{8자 랜덤hex}.jpg (항상 .jpg로 통일)
// GD 리사이즈 로직
if (extension_loaded("gd") && function_exists("imagecreatefromstring")) {
$raw = file_get_contents($imgTmp);
$src = imagecreatefromstring($raw); // 확장자 무관, 바이너리로 판별
// 비율 유지 축소 (이미 400×400 이하면 그대로)
$ratio = min(400/$srcW, 400/$srcH);
$dst = imagecreatetruecolor($dstW, $dstH);
// PNG/GIF/WebP 투명 → 흰색 배경 변환
$white = imagecolorallocate($dst, 255, 255, 255);
imagefill($dst, 0, 0, $white);
imagecopyresampled($dst, $src, 0,0,0,0, $dstW, $dstH, $srcW, $srcH);
imagejpeg($dst, $imgFinal, 85); // JPEG 품질 85
// GD 없거나 실패 시: move_uploaded_file()로 원본 확장자 그대로 저장
}
// 성공 시: 기존 이미지 unlink 후 "profiles/{파일명}" DB 저장
$upd["profile_img"] = "profiles/" . $imgSave;
⚠️ 컬럼 존재 여부 동적 체크
bio, website, blog, sns_* 컬럼은 추가 설치된 경우에만 존재합니다.
SHOW COLUMNS FROM dx_members 결과를 DxCache(1시간)로 캐시하여
in_array()로 컬럼 존재 여부를 확인 후 UPDATE합니다.
→ 컬럼이 없어도 오류 없이 기본 정보(name, email, password)만 업데이트됩니다.
3.3 회원 탈퇴 (_action=withdraw)
// 탈퇴 확인 폼 필드
// 1. withdraw_confirm: "DELETE" 텍스트 입력 (클라이언트 측 확인용)
// 2. withdraw_pw: 현재 비밀번호 (서버 측 password_verify 검증)
// 처리 로직
if (!password_verify($curPw, $member["password"])) {
// 소셜 로그인 회원(password 없음)은 비밀번호 검증 생략
$withdrawMsg = array("type"=>"error", "text"=>"비밀번호가 올바르지 않습니다.");
} else {
// 실제 삭제 아님! status=0으로 비활성화
$db->query("UPDATE dx_members SET status=0 WHERE login_id=?", [$member["login_id"]]);
$auth->logout(); // 세션 삭제 + Remember Me 쿠키 제거
dx_set_flash("회원 탈퇴가 완료되었습니다. 이용해 주셔서 감사합니다.");
dx_redirect(dx_base_url());
}
// 탈퇴 결과: 데이터 보존 (게시글, 댓글, 포인트 그대로)
// 계정 접근만 불가 (status=0 → 로그인 시 "비활성 계정" 처리)
4장. 알림 탭 (tab=notifications)
4.1 알림 탭 데이터 로드
// DxNotification::getList() 호출
$notifList = DxNotification::getList($memberId, $pp, ($page-1)*$pp);
// $pp = 20개/페이지
// 총 건수
$total = (int)$db->value("SELECT COUNT(*) FROM dx_notifications WHERE to_member_id=?", [$memberId]);
4.2 알림 타입별 이모지
| type 값 |
이모지 및 표시 레이블 |
| comment |
💬 댓글 |
| comment_reply |
↩️ 답글 |
| friend |
👥 친구 |
| scrap |
⭐ 스크랩 |
| memo |
✉️ 쪽지 |
| (기타) |
🔔 (fallback) |
4.3 알림 액션 API
| 액션 |
설명 |
| POST /api/notification (_sub=read) |
개별 읽음 처리. is_read=1, 미읽음 수 반환 |
| POST /api/notification (_sub=read_all) |
전체 읽음 처리. 배지 초기화 |
| POST /api/notification (_sub=delete) |
개별 삭제. id 파라미터. 삭제 후 unread 수 반환 |
| POST /api/notification (_sub=delete_all) |
전체 삭제. 목록 비움 |
4.4 알림 탭 UI 흐름
- 미읽음 항목 — 배경 #eff6ff(연파랑) + 파란 점 표시
- 읽은 항목 — 배경 #fff + 점 없음
- "모두 읽음" 버튼 — mpNotifReadAll() → POST read_all → 배경색 초기화 + 헤더 벨 배지 초기화
- "전체 삭제" 버튼 — mpNotifDelAll() → POST delete_all → 목록 비움
- 개별 삭제 ✕ 버튼 — mpNotifDel(id) → POST delete → 해당 행 fadeOut 제거
- 헤더 벨 배지 동기화 — dx-notif-badge 요소의 숫자를 r.unread 값으로 갱신
5장. 메모(쪽지) 탭 (tab=memo)
5.1 메모함 구분
| mbox 값 |
설명 |
| received_all (기본) |
나에게 온 전체 쪽지 (is_deleted_receiver=0) |
| received_friend |
친구에게서 받은 쪽지 (from_id IN 친구ID 목록) |
| sent_all |
내가 보낸 전체 쪽지 (is_deleted_sender=0) |
| sent_friend |
친구에게 보낸 쪽지 (to_id IN 친구ID 목록) |
| send |
쪽지 보내기 폼 화면 |
5.2 쪽지 목록 쿼리 구조
// 받은 쪽지 (received_all)
SELECT m.*, u.name AS other_name, u.profile_img AS other_img, u.id AS other_id
FROM dx_memos m
LEFT JOIN dx_members u ON m.from_id = u.id -- 발신자 정보
WHERE m.to_id = ? AND m.is_deleted_receiver = 0
ORDER BY m.id DESC LIMIT 20 OFFSET ?
// 보낸 쪽지 (sent_all)
SELECT m.*, u.name AS other_name, ...
FROM dx_memos m
LEFT JOIN dx_members u ON m.to_id = u.id -- 수신자 정보
WHERE m.from_id = ? AND m.is_deleted_sender = 0
ORDER BY m.id DESC
// 친구 필터 (received_friend)
WHERE m.to_id = ? AND m.is_deleted_receiver = 0
AND m.from_id IN ({친구ID 목록}) -- 쉼표 구분 정수 목록
5.3 쪽지 목록 UI 기능
- 클릭 펼치기 — mxToggleMemo(id, isRead) — 본문 영역 toggle. 미읽음이면 /api/memo POST read 호출
- 읽음 처리 — 행 배경 변경 + NEW 배지 제거 + mp-memo-badge 숫자 감소 + mx-badge-new 감소
- 답장 버튼 — mxReply(toId, toName) — /auth/mypage?tab=memo&mbox=send&to_id=N&to_name=이름 으로 이동
- 개별 삭제 — mxDeleteMemo(id, box) — box=received or sent. 행 fadeOut 후 제거
- 전체 삭제 — mxDeleteAll(mbox) — 현재 목록 모든 항목 순차 삭제
- 친구 메모 표시 — fr-row friend-memo 클래스 추가 + 👥 친구 배지 표시
- 발신함 읽음 확인 — 수신자가 읽었으면 ✓ 읽음 배지 표시
5.4 쪽지 보내기 폼 (mbox=send)
- 받는 사람 검색 입력 → mxSearchUser() → GET /api/memo?_sub=search_user&q= 호출
- 검색 결과 버튼 클릭 → mxSelectUser(id, name) → MX_TO_ID 변수에 ID 저장
- 내용 입력 (최대 1000자) + 글자 수 실시간 표시
- 보내기 버튼 → mxDoSend() → POST /api/memo (_sub=send)
- 전송 성공 시: "메모가 전송되었습니다!" 메시지 표시
- 소켓 DM 전달: dm 기능 활성화 + 연결 중이면 sendEvent({type:"dm", to_login_id, from_name, content_preview})
5.5 메모 API 엔드포인트
| 엔드포인트 |
설명 |
| GET /api/memo?_sub=search_user&q=검색어 |
회원 검색. name 또는 login_id로 검색. 최대 10건 반환 |
| POST /api/memo (_sub=send) |
쪽지 발송. to_id, content 필수. dx_memos 테이블 INSERT |
| POST /api/memo (_sub=read) |
읽음 처리. memo_id, is_read=1 업데이트. 미읽음 수 반환 |
| POST /api/memo (_sub=delete) |
삭제. memo_id, box(received/sent). 해당 deleted 컬럼 1로 변경 |
5.6 dx_memos 테이블 구조
| 컬럼 |
타입 |
설명 |
| id |
BIGINT UNSIGNED |
PK (AUTO_INCREMENT) |
| from_id |
INT UNSIGNED |
발신자 회원 ID |
| to_id |
INT UNSIGNED |
수신자 회원 ID |
| content |
TEXT |
쪽지 내용 |
| is_read |
TINYINT(1) |
0=미읽음, 1=읽음 |
| read_at |
DATETIME |
읽은 시각 (NULL=미읽음) |
| is_deleted_sender |
TINYINT(1) |
발신자가 삭제했으면 1. 실제 행 삭제 아님 |
| is_deleted_receiver |
TINYINT(1) |
수신자가 삭제했으면 1. 양쪽 모두 1이면 실질적 삭제 |
| created_at |
DATETIME |
발송 일시 |
6장. 포인트 탭 (tab=points)
6.1 포인트 내역 표
포인트 내역은 5개 컬럼으로 표시됩니다: 일시(m.d H:i) • 유형(DxPoint::typeName()) • 변동(+N P / -N P) • 잔액 • 메모(note). 변동이 양수면 td-plus(초록), 음수면 td-minus(빨강) 스타일이 적용됩니다.
// 포인트 내역 조회
$pointLogs = DxPoint::getLogs($memberId, $pp, $offset);
// → dx_point_log 테이블 ORDER BY id DESC LIMIT 20 OFFSET ?
// 컬럼: created_at, type, point, balance, note
// DxPoint::typeName($type): 유형 코드 → 한글 이름 변환
// 예: "write" → "게시글 작성", "comment" → "댓글 작성", "admin" → "관리자 지급" 등
6.2 포인트 헤더 카드
탭 헤더 우측에 현재 보유 포인트를 금색 배경 카드로 강조 표시합니다. number_format()으로 천 단위 구분자가 적용됩니다.
7장. 경험치 탭 (tab=exp)
7.1 레벨 프로그레스바
// 현재 레벨 내 진행률 (0~100%)
$progress = DxPoint::getLevelProgress($exp);
// 다음 레벨까지 필요 EXP
$nextExp = DxPoint::getNextLevelExp($exp); // null이면 최고레벨
// HTML: 보라색 그라디언트 바
<div style="height:10px;background:#e2e8f0;border-radius:5px">
<div style="width:{$progress}%;background:linear-gradient(90deg,#7c3aed,#a78bfa)"></div>
</div>
7.2 레벨 기준표
경험치 탭에는 details/summary 토글로 레벨 기준표가 펼쳐집니다. DxPoint::getThresholds()가 {레벨=>필요EXP} 배열을 반환하며, 현재 레벨은 보라색 강조 박스로 표시됩니다.
// DxPoint::getThresholds() 반환 예시
array(
1 => 0,
2 => 100,
3 => 300,
4 => 600,
5 => 1000,
// ...
)
8장. 스크랩 탭 (tab=scraps)
8.1 스크랩 목록
// 스크랩 목록 조회
$scraps = DxFriend::getScrapList($memberId, $pp, $offset);
// → dx_scraps JOIN dx_posts JOIN dx_boards
// 컬럼: post_id, title, board_key, board_name, created_at, memo
// 삭제된 게시글 처리: title이 비어있으면 "삭제된 게시글" 표시
8.2 스크랩 해제
// 해제 버튼 onclick
window.dxUnscrap = function(postId, btn) {
if (!confirm("스크랩을 취소할까요?")) return;
// POST /api/scrap {action:"remove", post_id:postId}
// 성공 시 해당 행 display:none
};
8.3 dx_scraps 테이블
| 컬럼 |
타입 |
설명 |
| id |
INT UNSIGNED |
PK (AUTO_INCREMENT) |
| member_id |
INT UNSIGNED |
스크랩한 회원 ID |
| post_id |
BIGINT UNSIGNED |
스크랩한 게시글 ID |
| memo |
VARCHAR(191) |
스크랩 메모 (선택) |
| created_at |
DATETIME |
스크랩 일시 |
9장. 친구 탭 (tab=friends)
9.1 두 가지 서브탭
| 서브탭 (sub=) |
설명 |
| my (기본) |
내가 추가한 친구 목록. dx_friends WHERE member_id=? AND type="friend" |
| followers |
나를 추가한 사람 목록. dx_friends WHERE target_id=? AND type="friend" |
9.2 서로친구 배지 로직
// sub=my (내가 추가한 친구) 탭에서
// 나를 추가한 사람 ID 목록(followerIds)을 별도 조회
$followerIds = array_column(
$db->rows("SELECT member_id FROM dx_friends WHERE target_id=? AND type='friend' AND status=1", [$memberId]),
"member_id"
);
// 렌더링 시: 상대방이 followerIds에 포함되면 "서로친구" 배지 표시
$_fMutual = in_array((int)$_f["target_id"], array_map("intval", $followerIds));
// sub=followers 탭에서도 반대로 동일한 로직 적용
$_fwAdded = in_array((int)$_fw["member_id"], array_map("intval", $_myFriendIds));
9.3 친구 액션 API
// window.dxFriendAction(targetId, action, btn, mode)
// POST /api/friend {action, target_id}
// action 종류:
// "remove_friend" — 내 친구 목록에서 삭제 (dx_friends status=0)
// "block" — 차단 (dx_friends type="block"으로 변경)
// "unblock" — 차단 해제
// "add_friend" — 나를 추가한 사람을 나도 맞추가
// mode="row"이면 성공 시 해당 fr-row 엘리먼트 fadeOut 후 제거
9.4 dx_friends 테이블
| 컬럼 |
타입 |
설명 |
| id |
INT UNSIGNED |
PK |
| member_id |
INT UNSIGNED |
요청자 (추가한 사람) |
| target_id |
INT UNSIGNED |
대상자 (추가된 사람) |
| type |
ENUM(friend, block) |
friend: 친구, block: 차단 |
| status |
TINYINT(1) |
1=활성, 0=삭제(논리 삭제) |
| created_at |
DATETIME |
관계 생성 일시 |
💡 서로친구 개념
DXCMS의 친구는 단방향(follow) 구조입니다.
내가 A를 추가 → dx_friends(member_id=나, target_id=A, type=friend)
A도 나를 추가 → dx_friends(member_id=A, target_id=나, type=friend)
양쪽 레코드가 모두 있으면 "서로친구" 배지 표시
10장. 소셜 연결 탭 (tab=social)
10.1 소셜 연결 현황 표시
// 활성화된 소셜 프로바이더 목록
$_allProviders = DxSocialAuth::providers();
// → ["kakao", "naver", "google", "github"] 등
// 현재 회원의 연결된 소셜 계정 조회
$stmt = $pdo->prepare("SELECT * FROM dx_social_accounts WHERE member_id=?");
$stmt->execute([$memberId]);
$_linkedAccounts = [$row["provider"] => $row, ...];
// 렌더링 조건: cfg["enabled"] && cfg["client_id"] 가 모두 있는 경우만 표시
10.2 소셜 연결 UI
| 상태 |
UI |
| 연결됨 |
"✓ 연결됨" 초록 배지. 연결된 이메일(또는 이름) + 마지막 로그인 날짜 표시 |
| 미연결 |
"연결하기" 버튼 → /auth/social?provider=카카오&redirect=/auth/mypage?tab=social |
11장. 공개 프로필 페이지 (/auth/profile)
/auth/profile?id={member_id} 는 다른 사람의 프로필을 볼 수 있는 공개 페이지입니다. core/auth/profile.php 파일이 처리하며, 회원 본인이 보면 "프로필 수정" 버튼이, 타 회원이 보면 "메모 보내기" + "1:1 채팅" 버튼이 표시됩니다.
11.1 표시 정보
- 커버 + 아바타 — var(--p) → 인디고 그라디언트 커버. 76px 원형 아바타 (사진 또는 이름 첫 글자)
- 이름 + 레벨 — 이름 h1 + Lv.N + 가입일
- 통계 — 게시글 수 / 댓글 수 / 포인트 / 경험치 (4개 가로 배치)
- 자기소개 — bio 컬럼. nl2br()로 줄바꿈 표시
- 링크 카드 — website, blog, sns_instagram/twitter/facebook/youtube/github 있을 때만 표시
11.2 버튼 표시 조건
| 상황 |
표시 버튼 |
| 타 회원이 볼 때 (로그인) |
메모 보내기 (파란색) + 1:1 채팅 (초록색, DM 기능 ON일 때만 표시) |
| 내 프로필 볼 때 ($isSelf) |
프로필 수정 버튼 → /auth/mypage |
| 비로그인 |
버튼 없음 |
11.3 1:1 채팅 초대 버튼
// profile.php의 1:1 채팅 버튼 (id="dx-chat-invite-btn")
// 기본 display:none → DM 기능 ON + 소켓 연결 시 JS가 자동으로 표시
<button id="dx-chat-invite-btn" '이름')"
style="display:none">
1:1 채팅
</button>
// socket-core.js.php가 features.dm ON이면 display:inline-flex로 변경
// dxSendChatInvite()는 sendEvent({type:"dm_invite", to_login_id, from_name, from_login_id, room_id})
12장. CSS 구조 (mypage.css)
12.1 주요 CSS 클래스
| 클래스 |
역할 |
| .mp-profile-card |
프로필 헤더 카드 래퍼. 흰 배경, 18px 둥근 모서리, 미세 그림자 |
| .mp-avatar |
아바타 div. 62px 원형. 파란→인디고 그라디언트 |
| .mp-profile-img |
프로필 사진 img. 62px 원형, object-fit:cover |
| .mp-lv-badge |
레벨 배지. 파란 배경, 흰 텍스트, 99px 둥근 |
| .mp-exp-bar / .mp-exp-fill |
경험치 바. fill은 파란→인디고 그라디언트 |
| .mp-stats-mo / .mp-stats-pc |
스탯 모바일(하단 3분할) / PC(우측 3열). 600px 기준 전환 |
| .mp-hd |
탭 헤더 영역. flex justify-between |
| .mp-tbl |
포인트/경험치 내역 테이블. fixed 레이아웃 |
| .mp-type-badge |
포인트 유형 배지. 회색 배경, 작은 글씨 |
| .mp-empty |
빈 데이터 상태. 중앙 정렬 아이콘+텍스트 |
| .pf-section |
프로필 수정 폼 섹션. 패딩+하단 구분선 |
| .pf-section-title |
섹션 제목. 대문자, 트래킹, 우측 선 자동 연장 (::after) |
| .pf-row |
입력 2컬럼 그리드. 600px 이하에서 1컬럼 |
| .fr-row |
친구 목록 행. flex align-center |
| .fr-ava |
친구 아바타 원형. 40px |
| .fr-mutual-badge |
"서로친구" 초록 배지 |
| .mx-tab / .mx-tab.on |
메모함 탭 버튼 / 활성 탭 |
| .mx-row / .mx-memo-unread |
메모 목록 행 / 미읽음 행 (파란 배경) |
| .mx-badge-new |
빨간 NEW 배지. 원형, 중앙정렬 |
12.2 반응형 브레이크포인트
| 조건 |
적용 변경 |
| min-width: 600px |
아바타 62→72px, .mp-stats-mo 숨김, .mp-stats-pc 표시 |
| max-width: 600px |
.pf-row 1컬럼 |
| max-width: 480px |
탭 버튼 패딩/폰트 축소 (.mp-tab-btn) |
13장. 관련 DB 스키마 총정리
13.1 dx_members — 회원 정보
| 컬럼 |
타입 |
마이페이지 사용 방식 |
| id |
INT UNSIGNED |
회원 식별자. 모든 탭 데이터 조회 기준 |
| name |
VARCHAR(100) |
이름/닉네임. 프로필 헤더 + 수정 폼 |
| login_id |
VARCHAR(50) |
아이디. 헤더 카드 + 탈퇴 처리 |
| email |
VARCHAR(191) |
이메일. 수정 폼 + FILTER_VALIDATE_EMAIL 검증 |
| password |
VARCHAR(255) |
BCrypt 해시. 비밀번호 변경 + 탈퇴 검증 |
| profile_img |
VARCHAR(500) |
"profiles/{파일명}" 형식. 헤더 카드 아바타 |
| bio |
TEXT |
자기소개. 프로필 탭 textarea |
| website / blog |
VARCHAR(500) |
개인 사이트/블로그. 프로필 폼 + 공개 프로필 |
| sns_instagram ~ sns_github |
VARCHAR(300) |
SNS URL. 수정 폼 5개 입력란 |
| level |
SMALLINT |
현재 레벨. 헤더 배지 + 경험치 탭 |
| exp |
INT |
누적 경험치. 프로그레스바 계산 |
| point |
INT |
보유 포인트. 헤더 스탯 + 포인트 탭 |
| status |
TINYINT |
0=비활성(탈퇴). withdraw 처리 시 0으로 변경 |
| join_date |
DATETIME |
가입일. 프로필 탭 가입일 표시 |
| last_login |
DATETIME |
마지막 로그인. 프로필 탭 표시 |
13.2 dx_notifications — 알림
| 컬럼 |
타입 |
설명 |
| id |
BIGINT UNSIGNED |
PK |
| to_member_id |
INT UNSIGNED |
수신자 회원 ID. 알림 탭 조회 기준 |
| from_member_id |
INT UNSIGNED |
발신자 회원 ID. 0이면 시스템 알림 |
| type |
VARCHAR(30) |
comment/comment_reply/friend/scrap/memo 등 |
| message |
VARCHAR(255) |
알림 메시지 본문 |
| url |
VARCHAR(500) |
클릭 시 이동 URL |
| is_read |
TINYINT(1) |
0=미읽음, 1=읽음 |
| created_at |
DATETIME |
알림 생성 일시 |
13.3 헬퍼 함수 _dx_mypage_pagination()
// mypage.php 하단에 로컬 정의된 페이지네이션 헬퍼
function _dx_mypage_pagination($pages, $current, $baseUrl) {
if ($pages <= 1) return;
// 현재 페이지 ±4 범위만 표시 (최대 9개 버튼)
for ($i = max(1,$current-4); $i <= min($pages,$current+4); $i++) {
// 현재 페이지: bg-blue-600 text-white
// 나머지: bg-slate-50 text-slate-500
echo '<a href="' . $baseUrl . '&p=' . $i . '">' . $i . '</a>';
}
}
// 각 탭에서 호출 예시:
_dx_mypage_pagination($pages, $page, $baseUrl . "?tab=points");
14장. 사용방법 정리
14.1 회원이 마이페이지를 사용하는 방법
- 로그인 후 헤더의 [회원이름]님 또는 "마이페이지" 링크 클릭
- 프로필 탭: 이름•이메일•자기소개•사진 변경 후 "프로필 저장" 클릭
- 비밀번호 변경: 현재 비밀번호 입력 후 새 비밀번호 입력 → 프로필 저장
- SNS 연결: 소셜연결 탭에서 "연결하기" 버튼 클릭 → 소셜 로그인 완료
- 메모: 메모 탭 → "메모 보내기" 클릭 → 받는 사람 검색 → 내용 작성 → 보내기
- 친구: 게시판 글쓴이 클릭 → 공개 프로필 → "친구 추가" 버튼
- 스크랩 해제: 스크랩 탭에서 "해제" 버튼 클릭
- 회원 탈퇴: 프로필 탭 하단 "탈퇴하기" → "DELETE" 입력 → 비밀번호 확인 → 탈퇴
14.2 개발자가 마이페이지를 커스텀하는 방법
- 탭 추가 — $tabs 배열에 새 항목 추가 후 해당 elseif 블록 추가
- CSS 수정 — assets/css/mypage.css 직접 수정 또는 테마 CSS에서 오버라이드
- 프로필 필드 추가 — dx_members 테이블에 컬럼 추가 후 SHOW COLUMNS 캐시 만료 대기 (1시간) 또는 DxCache 초기화
- 포인트 유형 추가 — DxPoint::typeName() 메서드에 새 type 코드와 한글명 추가
- 메모 API 확장 — core/api/memo.php에 새 _sub 액션 추가
14.3 주의사항
- 프로필 이미지는 data/uploads/profiles/ 폴더에 저장됩니다. 폴더가 없으면 자동 생성됩니다.
- GD 확장이 없는 서버에서는 리사이즈 없이 원본 파일 그대로 저장됩니다.
- 회원 탈퇴는 물리적 삭제가 아닌 status=0 비활성화입니다. 데이터는 보존됩니다.
- 소셜 연결은 DxSocialAuth 설정(client_id)이 완료된 프로바이더만 표시됩니다.
- 메모•친구•차단 API는 모두 CSRF 토큰 검증이 적용됩니다.
- 친구 수는 1분, 메모 미읽음 수는 30초 DxCache로 캐시됩니다.