diff --git a/components/Headers/Login.tsx b/components/Headers/Login.tsx index c8cf2d2..2da1219 100644 --- a/components/Headers/Login.tsx +++ b/components/Headers/Login.tsx @@ -1,11 +1,12 @@ import useOutsideClick from 'hooks/useOutsideClick'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Profile } from 'types/profile'; import Menu from '../Menu'; import { useSnackbar } from 'context/SnackBarContext'; +import { ProfileContext } from 'context/ProfileContext'; interface LoginProps { isMobile: boolean; @@ -21,17 +22,21 @@ interface LoginProps { * @param isLoggedIn 로그인 여부 판별 */ -export default function Login({ isMobile, isLoggedIn, profile }: LoginProps) { +export default function Login({ isMobile }: LoginProps) { const [isOpen, setIsOpen] = useState(false); const [profileMenu, setProfileMenu] = useState([]); const { showSnackbar } = useSnackbar(); + // ProfileContext에서 상태 가져오기 + const profileContext = useContext(ProfileContext); + const { isAuthenticated, profile, setAccessToken } = profileContext || {}; const profileImage = profile?.image ?? '/icon/icon-profile.svg'; + const router = useRouter(); const loginMenuRef = useRef(null); const updateProfileMenu = useCallback(() => { - if (!isLoggedIn) { + if (!isAuthenticated) { if (isMobile) return ['로그인', '위키목록', '자유게시판']; } else if (isMobile) { return ['위키목록', '자유게시판', '알림', '마이페이지', '로그아웃']; @@ -39,7 +44,7 @@ export default function Login({ isMobile, isLoggedIn, profile }: LoginProps) { return ['마이페이지', '로그아웃']; } return []; - }, [isLoggedIn, isMobile]); // 의존성 배열에 필요한 값만 포함 + }, [isAuthenticated, isMobile]); // 의존성 배열에 필요한 값만 포함 useEffect(() => { setProfileMenu(updateProfileMenu()); @@ -55,8 +60,11 @@ export default function Login({ isMobile, isLoggedIn, profile }: LoginProps) { } else if (option === '로그인') { await router.push('/login'); } else if (option === '로그아웃') { - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); + // ProfileContext의 setAccessToken을 호출하여 로그아웃 처리 + if (setAccessToken) { + setAccessToken(null); // accessToken 상태를 null로 설정 + } + localStorage.removeItem('refreshToken'); // refreshToken 제거 await router.push('/'); showSnackbar('로그아웃 되었습니다.', 'fail'); } @@ -64,7 +72,7 @@ export default function Login({ isMobile, isLoggedIn, profile }: LoginProps) { useOutsideClick(loginMenuRef, () => setIsOpen(false)); - return isLoggedIn ? ( + return isAuthenticated ? (
void; // 토큰 설정 함수 } export const ProfileContext = createContext(null); @@ -19,83 +25,68 @@ export const ProfileProvider: React.FC<{ children: ReactNode }> = ({ }) => { const [isAuthenticated, setIsAuthenticated] = useState(false); const [profile, setProfile] = useState(null); + const [accessToken, setAccessTokenState] = useState(null); + + // accessToken 상태를 업데이트하면서 localStorage와 동기화 + const setAccessToken = (token: string | null) => { + if (token) { + localStorage.setItem('accessToken', token); + } else { + localStorage.removeItem('accessToken'); + } + setAccessTokenState(token); + }; + + const getProfile = useCallback(async () => { + if (!accessToken) { + console.log('[디버그] 토큰 없음. 프로필을 불러올 수 없습니다.'); + return; + } - //사용자 프로필 가져오기 - const getProfile = async () => { - const accessToken = localStorage.getItem('accessToken'); try { const res = await instance.get('/users/me', { - headers: { - Authorization: `Bearer ${accessToken}`, - }, + headers: { Authorization: `Bearer ${accessToken}` }, }); const profileData = res.data.profile; if (!profileData) { setProfile(null); + setIsAuthenticated(false); return; } - const code = profileData.code; - - if (!code) { + // 추가 정보 가져오기 + if (profileData.code) { + const profileRes = await instance.get( + `/profiles/${profileData.code}` + ); + setProfile(profileRes.data); + } else { setProfile(profileData); - return; } - - const profileRes = await instance.get(`/profiles/${code}`); - - setProfile(profileRes.data); + setIsAuthenticated(true); } catch { setProfile(null); + setIsAuthenticated(false); } - }; + }, [accessToken]); - //인증 상태 업데이트 + // accessToken 상태 변화 감지 useEffect(() => { - const handleLocalStorageChange = () => { - const accessToken = localStorage.getItem('accessToken'); - - if (accessToken !== null) { - setIsAuthenticated(true); - getProfile().catch(() => { - console.error('프로필을 불러오던 중 에러가 발생했습니다.'); - setProfile(null); - }); - } else { - setIsAuthenticated(false); - setProfile(null); - } - }; - - const originalSetItem = localStorage.setItem.bind(localStorage); - const originalRemoveItem = localStorage.removeItem.bind(localStorage); - - localStorage.setItem = (key: string, value: string) => { - originalSetItem.call(localStorage, key, value); - if (key === 'accessToken') { - handleLocalStorageChange(); - } - }; - - localStorage.removeItem = (key: string) => { - originalRemoveItem.call(localStorage, key); - if (key === 'accessToken') { - handleLocalStorageChange(); - } - }; - - handleLocalStorageChange(); - - return () => { - localStorage.setItem = originalSetItem; - localStorage.removeItem = originalRemoveItem; - }; - }, []); + if (accessToken) { + setIsAuthenticated(true); + getProfile(); + } else { + setIsAuthenticated(false); + setProfile(null); + } + }, [accessToken, getProfile]); // accessToken이 변경될 때마다 실행 return ( - + {children} ); diff --git a/pages/login/index.tsx b/pages/login/index.tsx index 5f607ef..5710186 100644 --- a/pages/login/index.tsx +++ b/pages/login/index.tsx @@ -1,11 +1,12 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import Button from '@/components/Button'; import InputField from '@/components/Input'; import { AuthAPI } from '@/services/api/auth'; import { useSnackbar } from 'context/SnackBarContext'; +import { ProfileContext } from 'context/ProfileContext'; export default function Login() { const [email, setEmail] = useState(''); @@ -15,6 +16,7 @@ export default function Login() { email: false, password: false, }); + const profileContext = useContext(ProfileContext); const { showSnackbar } = useSnackbar(); const router = useRouter(); @@ -38,22 +40,20 @@ export default function Login() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (isSubmitting || !isFormValid) return; + if (!profileContext) { + showSnackbar('ProfileContext를 찾을 수 없습니다.', 'fail'); + return; + } setIsSubmitting(true); try { - const response = (await AuthAPI.signin({ - email, - password, - })) as { accessToken: string }; + await AuthAPI.signin({ email, password }, profileContext.setAccessToken); - localStorage.setItem('accessToken', response.accessToken); + router.push('/'); showSnackbar('로그인이 완료되었습니다', 'success'); // 페이지 이동 전까지는 버튼을 비활성화 상태로 유지 - setTimeout(() => { - router.push('/'); - }, 1000); } catch (error) { if (error instanceof Error) { showSnackbar(error.message, 'fail'); diff --git a/services/api/auth.ts b/services/api/auth.ts index 38e0b37..908c1ec 100644 --- a/services/api/auth.ts +++ b/services/api/auth.ts @@ -2,14 +2,22 @@ import { AxiosError } from 'axios'; import instance from '../../lib/axios-client'; +// AuthAPI 정의 export const AuthAPI = { - signin: async (data: { email: string; password: string }) => { + signin: async ( + data: { email: string; password: string }, + setAccessToken: (token: string | null) => void // ProfileContext의 setAccessToken을 주입받음 + ) => { try { const res = await instance.post('/auth/signIn', data); // 로그인 성공 시 토큰 저장 const { accessToken, refreshToken } = res.data; - localStorage.setItem('accessToken', accessToken); + + // setAccessToken을 통해 ProfileProvider 상태 업데이트 + setAccessToken(accessToken); + + // refreshToken은 여전히 localStorage에 직접 저장 localStorage.setItem('refreshToken', refreshToken); return res.data;