/** * 마이페이지 — 프로필 조회/수정, 검사내역, 로그아웃 (PRD §3.5.3, §3.6.3) * 열람 → 대시보드(열람페이지)로 이동 * Phase D-2: MD3 디자인 토큰 + 공용 컴포넌트(PillButton, StatusBadge, MatIcon) 적용 */ import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { api, http } from '../lib/api'; import { useAuthStore } from '../store/auth'; import PillButton from '../components/PillButton'; import StatusBadge from '../components/StatusBadge'; import MatIcon from '../components/MatIcon'; const PAGE_SIZE = 10; interface HistoryItem { round: number; testDate: string; startTime?: string; status: string; paid: boolean; profileCode: string | null; } const Container = styled.div` padding-top: 16px; `; const Card = styled.div` background: var(--md3-surface); border-radius: 16px; padding: 20px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06); border: 1px solid var(--md3-outline-variant); `; const SectionTitle = styled.h3` font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 18px; font-weight: 700; color: var(--md3-on-surface); margin: 0 0 12px; `; const Field = styled.div` display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--md3-outline-variant); font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 14px; &:last-of-type { border-bottom: none; } .k { color: var(--md3-on-surface-variant); } .v { font-weight: 600; color: var(--md3-on-surface); text-align: right; } `; const EditProfileLink = styled(Link)` display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: 100%; height: 44px; margin-top: 12px; border-radius: 9999px; background: var(--md3-secondary-container); color: var(--md3-on-secondary-container); font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 14px; font-weight: 600; text-decoration: none; `; const Hist = styled(Link)` display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 14px 0; border-bottom: 1px solid var(--md3-outline-variant); font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 14px; text-decoration: none; color: var(--md3-on-surface); &:last-child { border-bottom: none; } .left { display: flex; align-items: center; gap: 10px; min-width: 0; } .icon { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 12px; background: var(--md3-primary-fixed-dim); color: var(--md3-primary); flex-shrink: 0; } .meta { min-width: 0; } .title { font-weight: 600; color: var(--md3-on-surface); margin-bottom: 2px; } .sub { font-size: 12px; color: var(--md3-on-surface-variant); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } `; const EmptyText = styled.p` font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 13px; color: var(--md3-on-surface-variant); margin: 0; `; const SectionHeader = styled.div` display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; `; const PageNav = styled.div` display: flex; align-items: center; gap: 8px; font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 13px; color: var(--md3-on-surface-variant); `; const PageBtn = styled.button` display: inline-flex; align-items: center; justify-content: center; gap: 4px; padding: 6px 12px; border: 1px solid var(--md3-outline); border-radius: 9999px; background: var(--md3-surface); color: var(--md3-on-surface); font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.15s; &:disabled { opacity: 0.4; cursor: not-allowed; } &:hover:not(:disabled) { background: var(--md3-surface-container-highest); } `; const LogoutButton = styled(PillButton)` && { margin-top: 4px; } `; function statusLabel(status: string) { if (status === 'completed') return '열람'; if (status === 'in_progress') return '진행중'; return status; } function statusType(status: string): 'normal' | 'warning' | 'borderline' | 'incomplete' | 'info' { if (status === 'completed') return 'normal'; if (status === 'in_progress') return 'warning'; return 'incomplete'; } export default function MyPage() { const nav = useNavigate(); const user = useAuthStore((s) => s.user); const logout = useAuthStore((s) => s.logout); const [histories, setHistories] = useState([]); const [page, setPage] = useState(0); useEffect(() => { http.get<{ histories: HistoryItem[] }>('/api/history').then((r) => setHistories(r.histories)).catch(() => {}); }, []); const totalPages = Math.ceil(histories.length / PAGE_SIZE) || 1; const start = page * PAGE_SIZE; const visible = histories.slice(start, start + PAGE_SIZE); const onLogout = async () => { const refresh = localStorage.getItem('pcnt_refresh_token'); try { if (refresh) await api('/api/auth/logout', { method: 'POST', json: { refreshToken: refresh } }, false); } catch { /* ignore */ } logout(); nav('/signin'); }; return ( 내 프로필 이메일{user?.email} 이름{user?.name} 닉네임{user?.nickname ?? '미설정'} 성별{user?.gender === 'MALE' ? '남' : user?.gender === 'FEMALE' ? '여' : '미설정'} 국가{user?.country ?? '미설정'} 프로필 수정 검사내역 {histories.length > PAGE_SIZE && ( setPage((p) => p - 1)}> 이전 {page + 1} / {totalPages} = totalPages - 1} onClick={() => setPage((p) => p + 1)}> 다음 )} {histories.length === 0 && 검사내역이 없습니다.} {visible.map((h) => { const time = h.startTime ? new Date(h.startTime).toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', hour12: false }) : ''; return ( {h.round}회차 결과 {h.testDate}{time ? ` ${time}` : ''}{h.profileCode ? ` · ${h.profileCode}` : ''}{h.paid ? ' · 결제완료' : ''} {h.paid ? '결제' : statusLabel(h.status)} ); })} 로그아웃 ); }