Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions components/Headers/Login.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,25 +22,29 @@ 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<string[]>([]);
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<HTMLDivElement>(null);

const updateProfileMenu = useCallback(() => {
if (!isLoggedIn) {
if (!isAuthenticated) {
if (isMobile) return ['로그인', '위키목록', '자유게시판'];
} else if (isMobile) {
return ['위키목록', '자유게시판', '알림', '마이페이지', '로그아웃'];
} else {
return ['마이페이지', '로그아웃'];
}
return [];
}, [isLoggedIn, isMobile]); // 의존성 배열에 필요한 값만 포함
}, [isAuthenticated, isMobile]); // 의존성 배열에 필요한 값만 포함

useEffect(() => {
setProfileMenu(updateProfileMenu());
Expand All @@ -55,16 +60,19 @@ 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');
}
};

useOutsideClick(loginMenuRef, () => setIsOpen(false));

return isLoggedIn ? (
return isAuthenticated ? (
<div ref={loginMenuRef} className="flex">
<div
role="button"
Expand Down
107 changes: 49 additions & 58 deletions context/ProfileContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { createContext, ReactNode, useEffect, useState } from 'react';
import {
createContext,
ReactNode,
useCallback,
useEffect,
useState,
} from 'react';
import { Profile } from 'types/profile';

import instance from '@/lib/axios-client';

interface UserProfileResponse {
Expand All @@ -10,6 +15,7 @@ interface UserProfileResponse {
interface ProfileContextType {
isAuthenticated: boolean;
profile: Profile | null;
setAccessToken: (token: string | null) => void; // 토큰 설정 함수
}

export const ProfileContext = createContext<ProfileContextType | null>(null);
Expand All @@ -19,83 +25,68 @@ export const ProfileProvider: React.FC<{ children: ReactNode }> = ({
}) => {
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const [profile, setProfile] = useState<Profile | null>(null);
const [accessToken, setAccessTokenState] = useState<string | null>(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<UserProfileResponse>('/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<Profile>(
`/profiles/${profileData.code}`
);
setProfile(profileRes.data);
} else {
setProfile(profileData);
return;
}

const profileRes = await instance.get<Profile>(`/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 (
<ProfileContext.Provider value={{ isAuthenticated, profile }}>
<ProfileContext.Provider
value={{ isAuthenticated, profile, setAccessToken }}
>
{children}
</ProfileContext.Provider>
);
Expand Down
18 changes: 9 additions & 9 deletions pages/login/index.tsx
Original file line number Diff line number Diff line change
@@ -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('');
Expand All @@ -15,6 +16,7 @@ export default function Login() {
email: false,
password: false,
});
const profileContext = useContext(ProfileContext);
const { showSnackbar } = useSnackbar();
const router = useRouter();

Expand All @@ -38,22 +40,20 @@ export default function Login() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
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');
Expand Down
12 changes: 10 additions & 2 deletions services/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading