From 12413795f413f14c80da2b8d5ce423415c9e3ee6 Mon Sep 17 00:00:00 2001 From: wallace Date: Mon, 24 Nov 2025 23:31:05 +0900 Subject: [PATCH] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/NavBar.tsx | 46 +++++++++- src/app/login/page.tsx | 20 +++-- src/app/menu/AccountDeleteModal.tsx | 89 +++++++++++++++++-- src/app/menu/account/page.tsx | 132 ++++++++++++++++++++++++++-- 4 files changed, 267 insertions(+), 20 deletions(-) diff --git a/src/app/NavBar.tsx b/src/app/NavBar.tsx index 7333835..cd10b6c 100644 --- a/src/app/NavBar.tsx +++ b/src/app/NavBar.tsx @@ -15,11 +15,55 @@ const NAV_ITEMS = [ export default function NavBar() { const pathname = usePathname(); const [isUserMenuOpen, setIsUserMenuOpen] = useState(false); + const [userName, setUserName] = useState(''); const userMenuRef = useRef(null); const userButtonRef = useRef(null); const hideCenterNav = /^\/[^/]+\/review$/.test(pathname); const isAdminPage = pathname.startsWith('/admin'); + // 사용자 정보 가져오기 + useEffect(() => { + let isMounted = true; + + async function fetchUserInfo() { + try { + const token = localStorage.getItem('token'); + if (!token) { + return; + } + + const response = await fetch('https://hrdi.coconutmeet.net/auth/me', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + if (response.status === 401) { + // 토큰이 만료되었거나 유효하지 않은 경우 + localStorage.removeItem('token'); + } + return; + } + + const data = await response.json(); + if (isMounted && data.name) { + setUserName(data.name); + } + } catch (error) { + console.error('사용자 정보 조회 오류:', error); + } + } + + fetchUserInfo(); + + return () => { + isMounted = false; + }; + }, []); + useEffect(() => { if (!isUserMenuOpen) return; const onDown = (e: MouseEvent) => { @@ -91,7 +135,7 @@ export default function NavBar() { aria-expanded={isUserMenuOpen} className="flex items-center gap-1 px-4 py-2 text-[16px] font-semibold text-white cursor-pointer" > - 김이름 + {userName || '사용자'} { + if (onConfirm) { + onConfirm(); + return; + } + + setIsLoading(true); + try { + const token = localStorage.getItem('token'); + if (!token) { + alert('로그인이 필요합니다.'); + setIsLoading(false); + onClose(); + router.push('/login'); + return; + } + + console.log('회원 탈퇴 요청 시작, 토큰 존재:', !!token); + console.log('토큰 길이:', token?.length); + console.log('토큰 시작 부분:', token?.substring(0, 20)); + + const response = await fetch('https://hrdi.coconutmeet.net/auth/delete/me', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + console.log('회원 탈퇴 응답 상태:', response.status); + + if (!response.ok) { + let errorMessage = `회원 탈퇴 실패 (${response.status})`; + try { + const errorData = await response.json(); + console.error('회원 탈퇴 API 오류 응답:', errorData); + if (errorData.error) { + errorMessage = errorData.error; + } else if (errorData.message) { + errorMessage = errorData.message; + } else if (errorData.errorMessage) { + errorMessage = errorData.errorMessage; + } else if (response.statusText) { + errorMessage = `${response.statusText} (${response.status})`; + } + } catch (parseError) { + console.error('응답 파싱 오류:', parseError); + if (response.statusText) { + errorMessage = `${response.statusText} (${response.status})`; + } + } + console.error('회원 탈퇴 실패:', errorMessage, '상태 코드:', response.status); + alert(errorMessage); + setIsLoading(false); + return; + } + + // 성공 시 토큰 제거 및 로그인 페이지로 이동 + localStorage.removeItem('token'); + onClose(); + router.push('/login'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.'; + console.error('회원 탈퇴 오류:', errorMessage); + alert(errorMessage); + setIsLoading(false); + } + }; + if (!open) return null; return ( @@ -20,7 +94,7 @@ export default function AccountDeleteModal({ open, onClose, onConfirm }: Props)
{/* header */}
-

회원 탈퇴

+

회원 탈퇴

diff --git a/src/app/menu/account/page.tsx b/src/app/menu/account/page.tsx index 9ed9cfc..9890afc 100644 --- a/src/app/menu/account/page.tsx +++ b/src/app/menu/account/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; import ChangePasswordModal from "../ChangePasswordModal"; import PasswordChangeDoneModal from "../PasswordChangeDoneModal"; import AccountDeleteModal from "../AccountDeleteModal"; @@ -8,11 +9,90 @@ import MenuAccountOption from "@/app/menu/account/MenuAccountOption"; type VerificationState = 'initial' | 'sent' | 'verified' | 'failed' | 'changed'; +type UserInfo = { + email?: string; + name?: string; + phone?: string; +}; + export default function AccountPage() { + const router = useRouter(); const [open, setOpen] = useState(false); const [verificationState, setVerificationState] = useState('initial'); const [doneOpen, setDoneOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); + const [userInfo, setUserInfo] = useState({}); + const [isLoading, setIsLoading] = useState(true); + + // 페이지 로드 시 사용자 정보 가져오기 + useEffect(() => { + let isMounted = true; + + async function fetchUserInfo() { + try { + const token = localStorage.getItem('token'); + if (!token) { + if (isMounted) { + router.push('/login'); + } + return; + } + + const response = await fetch('https://hrdi.coconutmeet.net/auth/me', { + 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; + }; + }, []); // 개발 옵션에서 'changed'로 전환하면 완료 모달 표시 useEffect(() => { @@ -22,7 +102,7 @@ export default function AccountPage() { return (
-

내 정보 수정

+

내 정보 수정

@@ -31,7 +111,9 @@ export default function AccountPage() { 아이디 (이메일)
- skyblue@edu.com + + {isLoading ? '로딩 중...' : (userInfo.email || '이메일 정보 없음')} +
@@ -40,7 +122,7 @@ export default function AccountPage() {
- ●●●●●●●●●● + ●●●●●●●●●●