Files
xrlms/src/app/menu/account/page.tsx

240 lines
8.1 KiB
TypeScript
Raw Normal View History

2025-11-18 06:19:26 +09:00
'use client';
2025-11-18 07:53:09 +09:00
import { useEffect, useState } from "react";
2025-11-24 23:31:05 +09:00
import { useRouter } from "next/navigation";
2025-11-18 06:19:26 +09:00
import ChangePasswordModal from "../ChangePasswordModal";
2025-11-18 07:53:09 +09:00
import PasswordChangeDoneModal from "../PasswordChangeDoneModal";
import AccountDeleteModal from "../AccountDeleteModal";
import MenuAccountOption from "@/app/menu/account/MenuAccountOption";
type VerificationState = 'initial' | 'sent' | 'verified' | 'failed' | 'changed';
2025-11-18 06:19:26 +09:00
2025-11-24 23:31:05 +09:00
type UserInfo = {
email?: string;
name?: string;
phone?: string;
};
2025-11-18 06:19:26 +09:00
export default function AccountPage() {
2025-11-24 23:31:05 +09:00
const router = useRouter();
2025-11-18 06:19:26 +09:00
const [open, setOpen] = useState(false);
2025-11-18 07:53:09 +09:00
const [verificationState, setVerificationState] = useState<VerificationState>('initial');
const [doneOpen, setDoneOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
2025-11-24 23:31:05 +09:00
const [userInfo, setUserInfo] = useState<UserInfo>({});
const [isLoading, setIsLoading] = useState(true);
// 페이지 로드 시 사용자 정보 가져오기
useEffect(() => {
let isMounted = true;
async function fetchUserInfo() {
try {
2025-11-26 21:40:56 +09:00
// localStorage와 쿠키 모두에서 토큰 확인
const localStorageToken = localStorage.getItem('token');
const cookieToken = document.cookie
.split('; ')
.find(row => row.startsWith('token='))
?.split('=')[1];
const token = localStorageToken || cookieToken;
2025-11-24 23:31:05 +09:00
if (!token) {
if (isMounted) {
router.push('/login');
}
return;
}
2025-11-26 21:40:56 +09:00
// localStorage에 토큰이 없고 쿠키에만 있으면 localStorage에도 저장 (동기화)
if (!localStorageToken && cookieToken) {
localStorage.setItem('token', cookieToken);
}
const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me`
: 'https://hrdi.coconutmeet.net/auth/me';
const response = await fetch(apiUrl, {
2025-11-24 23:31:05 +09:00
method: 'GET',
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
},
});
if (!response.ok) {
if (response.status === 401) {
// 토큰이 만료되었거나 유효하지 않은 경우
localStorage.removeItem('token');
if (isMounted) {
router.push('/login');
}
return;
}
let errorMessage = `사용자 정보 조회 실패 (${response.status})`;
try {
const errorData = await response.json();
if (errorData.error) {
errorMessage = errorData.error;
} else if (errorData.message) {
errorMessage = errorData.message;
}
} catch (parseError) {
// ignore
}
console.error('사용자 정보 조회 실패:', errorMessage);
if (isMounted) {
setIsLoading(false);
}
return;
}
const data = await response.json();
if (isMounted) {
setUserInfo(data);
setIsLoading(false);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.';
console.error('사용자 정보 조회 오류:', errorMessage);
if (isMounted) {
setIsLoading(false);
}
}
}
fetchUserInfo();
return () => {
isMounted = false;
};
}, []);
2025-11-18 07:53:09 +09:00
// 개발 옵션에서 'changed'로 전환하면 완료 모달 표시
useEffect(() => {
setDoneOpen(verificationState === 'changed');
}, [verificationState]);
2025-11-18 06:19:26 +09:00
return (
<main className="flex w-full flex-col">
<div className="flex h-[100px] items-center px-8">
2025-11-24 23:31:05 +09:00
<h1 className="text-[24px] font-bold leading-normal text-[#1b2027]"> </h1>
2025-11-18 06:19:26 +09:00
</div>
<div className="px-8 pb-20">
<div className="rounded-lg border border-input-border bg-white p-8">
2025-11-18 06:19:26 +09:00
<div className="flex flex-col gap-2">
<label className="w-[100px] text-[15px] font-semibold leading-[1.5] text-text-label">
2025-11-18 06:19:26 +09:00
()
</label>
<div className="h-10 rounded-lg border border-input-border bg-neutral-50 px-3 py-2">
<span className="text-[16px] leading-normal text-neutral-700">
2025-11-24 23:31:05 +09:00
{isLoading ? '로딩 중...' : (userInfo.email || '이메일 정보 없음')}
</span>
2025-11-18 06:19:26 +09:00
</div>
</div>
<div className="mt-6 flex flex-col gap-2">
<label className="w-[100px] text-[15px] font-semibold leading-[1.5] text-text-label">
2025-11-18 06:19:26 +09:00
</label>
<div className="flex items-center gap-3">
<div className="h-10 flex-1 rounded-lg border border-input-border bg-neutral-50 px-3 py-2">
<span className="text-[16px] leading-normal text-neutral-700"></span>
2025-11-18 06:19:26 +09:00
</div>
<button
type="button"
onClick={() => setOpen(true)}
className="h-10 rounded-lg bg-bg-gray-light px-4 text-[16px] font-semibold leading-[1.5] text-basic-text"
2025-11-18 06:19:26 +09:00
>
</button>
</div>
</div>
</div>
<div className="mt-6">
2025-11-18 07:53:09 +09:00
<button
type="button"
onClick={() => setDeleteOpen(true)}
className="text-[15px] font-medium leading-[1.5] text-[#f64c4c] underline cursor-pointer"
>
2025-11-18 06:19:26 +09:00
</button>
</div>
</div>
<ChangePasswordModal
open={open}
onClose={() => setOpen(false)}
onSubmit={() => {
// TODO: integrate API
}}
2025-11-18 10:42:48 +09:00
devVerificationState={
verificationState === 'changed' ? 'verified' : verificationState
}
2025-11-26 21:40:56 +09:00
initialEmail={userInfo.email}
2025-11-18 07:53:09 +09:00
/>
<MenuAccountOption
verificationState={verificationState}
setVerificationState={setVerificationState}
deleteOpen={deleteOpen}
setDeleteOpen={setDeleteOpen}
/>
<PasswordChangeDoneModal
open={doneOpen}
onClose={() => setDoneOpen(false)}
/>
<AccountDeleteModal
open={deleteOpen}
onClose={() => setDeleteOpen(false)}
2025-11-24 23:31:05 +09:00
onConfirm={async () => {
try {
const token = localStorage.getItem('token');
const response = await fetch('https://hrdi.coconutmeet.net/auth/delete/me', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
},
});
if (!response.ok) {
let errorMessage = `회원 탈퇴 실패 (${response.status})`;
try {
const errorData = await response.json();
if (errorData.error) {
errorMessage = errorData.error;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (response.statusText) {
errorMessage = `${response.statusText} (${response.status})`;
}
} catch (parseError) {
if (response.statusText) {
errorMessage = `${response.statusText} (${response.status})`;
}
}
console.error('회원 탈퇴 실패:', errorMessage);
alert(errorMessage);
return;
}
// 성공 시 토큰 제거 및 로그인 페이지로 이동
localStorage.removeItem('token');
setDeleteOpen(false);
router.push('/login');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.';
console.error('회원 탈퇴 오류:', errorMessage);
alert(errorMessage);
}
2025-11-18 07:53:09 +09:00
}}
2025-11-18 06:19:26 +09:00
/>
</main>
);
}