diff --git a/src/apis/auth.ts b/src/apis/auth.ts index 6e827ad..b7df4e1 100644 --- a/src/apis/auth.ts +++ b/src/apis/auth.ts @@ -1,7 +1,10 @@ + +import { getAccess } from '@/hooks/auth/useLocalStorage'; import instance from './instance'; export const getNewToken = async () => { - const expiredToken = localStorage.getItem('access'); + const expiredToken = getAccess(); + try { const response = await instance.post('/api/reissue', { expiredToken: expiredToken }); const newAccessToken = response.data.accessToken; @@ -13,4 +16,4 @@ export const getNewToken = async () => { } }; -export default getNewToken; \ No newline at end of file +export default getNewToken; diff --git a/src/apis/instance.ts b/src/apis/instance.ts index 25b88e0..9842536 100644 --- a/src/apis/instance.ts +++ b/src/apis/instance.ts @@ -1,5 +1,7 @@ import axios from 'axios'; -import getNewToken from './auth'; +import { getNewToken } from './auth'; +import { getAccess, setAccess } from '@/hooks/auth/useLocalStorage'; + const instance = axios.create({ baseURL: import.meta.env.VITE_BASE_URL, @@ -9,7 +11,7 @@ const instance = axios.create({ }); instance.interceptors.request.use(async (config) => { - const access = localStorage.getItem('access'); + const access = getAccess(); if (access) { config.headers['Authorization'] = `Bearer ${access}`; } @@ -20,12 +22,13 @@ instance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; + if (error.response?.status === 401 && !originalRequest._alreadyRefreshed) { originalRequest._alreadyRefreshed = true; // 무한 요청 방지 try { const newAccessToken = await getNewToken(); if (newAccessToken) { - localStorage.setItem('access', newAccessToken); + setAccess(newAccessToken); originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`; return instance(originalRequest); } @@ -34,6 +37,7 @@ instance.interceptors.response.use( window.location.href = '/login'; } } + throw error; }, ); diff --git a/src/apis/mypage.ts b/src/apis/mypage.ts index ccec867..2ffcb33 100644 --- a/src/apis/mypage.ts +++ b/src/apis/mypage.ts @@ -2,22 +2,40 @@ import { MypageArticles } from '@/types/mypage'; import instance from './instance'; /** MYPAGE 게시글 조회 */ -export const getMypageArticles = async ({ - page = 0, - size = 10, - value, - email, -}: MypageArticles) => { - const response = await instance.get('/api/users/profile', { - params: { - page, - size, - value, - email, - }, - }); +export const getMypageArticles = async ({ page, size, value, email }: MypageArticles) => { + try { + const response = await instance.get('/api/users/profile', { + params: { + page, + size, + value, + email, + }, + }); - const data = response.data.posts; - const nextPage = response.data.pageInfo.isDone ? null : page + 1; - return { data, nextPage }; + const posts = response.data.posts; + const nextPage = response.data.pageInfo.isDone ? null : page + 1; + return { posts, nextPage }; + } catch (error) { + throw new Error('실패'); + } +}; + +/** 타인 프로필 조회 */ +export const getOthersProfile = async (email: string | null) => { + try { + const response = await instance.get('/api/users/profile', { + params: { + page: 0, + size: 10, + value: 'created', + email, + }, + }); + + const user = response.data.user; + return user; + } catch (error) { + throw new Error('실패'); + } }; diff --git a/src/apis/user.ts b/src/apis/user.ts index 8e3cd3f..b816e36 100644 --- a/src/apis/user.ts +++ b/src/apis/user.ts @@ -1,56 +1,31 @@ import { Login, SignUp, User } from '@/types'; import instance from './instance'; -import axios from 'axios'; /** 로그인 **/ export const postLogin = async ({ email, password }: Login) => { const data = { email, password }; - try { - const response = await instance.post('/api/login', data); - return response.data; - } catch (error) { - if (axios.isAxiosError(error) && error.response?.status === 401) { - throw error; - } - throw error; - } + const response = await instance.post('/api/login', data); + return response.data; }; /** 회원가입 */ -export const postSignup = async ({ - email, - nickname, - password, - checkPassword, - github, - blog, - introduce, - profileImage, -}: SignUp) => { - const data = { - email, - password, - checkPassword, - nickname, - github, - blog, - introduce, - profileImage, - }; +export const postSignup = async (data: SignUp) => { const response = await instance.post('/api/signup', data); return response.data; }; /** User 정보 불러오기 */ export const getUserInfo = async () => { - const response = await instance.get('/api/mypage/users'); - console.log(response.data); - return response.data as User; + try { + const response = await instance.get('/api/mypage/users'); + return response.data as User; + } catch { + throw new Error('회원정보 불러오기 실패'); + } }; /** User 정보 수정 */ export const putUser = async (data: Omit) => { const response = await instance.put('/api/mypage/users', data); - console.log(response.data); return response.data; }; diff --git a/src/components/loginForm/index.tsx b/src/components/loginForm/index.tsx index 3fdbac1..47e73fc 100644 --- a/src/components/loginForm/index.tsx +++ b/src/components/loginForm/index.tsx @@ -1,10 +1,11 @@ -import * as S from './styles.ts'; +import * as S from './styles'; import { useForm, SubmitHandler } from 'react-hook-form'; -import { Icon, InputBox, StyledButton } from '../shared/index.ts'; +import { Icon, InputBox, StyledButton } from '../shared/index'; import { Link, useNavigate } from 'react-router-dom'; import loginImage from '@/assets/images/loginImage.jpg'; -import { postLogin } from '@/apis/user.ts'; -import { Login } from '@/types/auth.ts'; +import { postLogin } from '@/apis/user'; +import { Login } from '@/types/auth'; +import { setAccess } from '@/hooks/auth/useLocalStorage'; export default function LoginForm() { const navigate = useNavigate(); @@ -21,7 +22,7 @@ export default function LoginForm() { try { const response = await postLogin(data); const accessToken = response.accessToken; - localStorage.setItem('access', accessToken); + setAccess(accessToken); console.log('로그인 성공:', response); navigate(-1); } catch (error) { @@ -79,12 +80,8 @@ export default function LoginForm() {

카카오 로그인

- - -

Google 로그인

-
); -} \ No newline at end of file +} diff --git a/src/components/mypage/articles/index.tsx b/src/components/mypage/articles/index.tsx index b873ccc..31ede4a 100644 --- a/src/components/mypage/articles/index.tsx +++ b/src/components/mypage/articles/index.tsx @@ -1,6 +1,5 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import * as S from './styles'; -import { useEmail } from '@/stores/mypage'; import { useEffect, useRef } from 'react'; import { getMypageArticles } from '@/apis/mypage'; import { MypageArticles } from '@/types/mypage'; @@ -9,22 +8,23 @@ import SkeletonBox from '@/components/loader/skeleton'; import Article from '@/components/shared/article'; const ArticlesMenu = ({ value }: Pick) => { - const { email } = useEmail(); + const queryEmail = new URLSearchParams(location.search).get('user'); const loader = useRef(null); - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ - queryKey: [value, email], - queryFn: ({ pageParam }) => - getMypageArticles({ - page: pageParam, - size: 10, - value: value, - email: email, - }), - initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage.nextPage, - staleTime: 1000 * 60 * 5, // 5분 - }); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useInfiniteQuery({ + queryKey: [value, queryEmail], + queryFn: ({ pageParam }) => + getMypageArticles({ + page: pageParam, + size: 10, + value: value, + email: queryEmail, + }), + initialPageParam: 0, + getNextPageParam: (lastPage) => lastPage.nextPage, + staleTime: 1000 * 60 * 5, // 5분 + }); useEffect(() => { const io = new IntersectionObserver( @@ -43,15 +43,16 @@ const ArticlesMenu = ({ value }: Pick) => { return ( - {isFetchingNextPage ? ( + {isLoading ? ( ) : ( data?.pages.map((page) => - page.data.map((post: ArticleType) => ( + page.posts.map((post: ArticleType) => { + console.log('포스트', post);
-
- )), + ; + }), ) )}
diff --git a/src/components/mypage/liked/index.tsx b/src/components/mypage/liked/index.tsx deleted file mode 100644 index 899b51b..0000000 --- a/src/components/mypage/liked/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; -import * as S from '../created/styles'; -import { useNickname } from '@/stores/mypage.ts'; -import { useEffect, useRef } from 'react'; -import { getMypageArticles } from '@/apis/mypage'; -import { ArticleType } from '@/types'; - -const SolvedMenu = () => { - const loginUserId = localStorage.getItem('userId'); - const { nickname } = useNickname(); - const loader = useRef(null); - - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ - queryKey: ['liked', nickname, loginUserId], - queryFn: ({ pageParam }) => - getMypageArticles({ - pageIndex: pageParam, - isDone: false, - value: 'liked', - email: nickname, - loginUserId: loginUserId, - }), - initialPageParam: 1, - getNextPageParam: (lastPage) => lastPage.nextPage, - staleTime: 1000 * 60 * 5, // 5분 - }); - - useEffect(() => { - const io = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting && hasNextPage) { - fetchNextPage(); - } - }, - { threshold: 0.5 }, - ); - if (loader.current) { - io.observe(loader.current); - } - return () => io.disconnect(); - }, [fetchNextPage, hasNextPage, isFetchingNextPage]); - - return ( - - {data?.pages.map((page) => - page.data.map((post: ArticleType) => ( -
{/* 게시글 공통 컴포넌트로 표시 */}
- )), - )} -
- - ); -}; - -export default SolvedMenu; diff --git a/src/components/mypage/profileTable/index.tsx b/src/components/mypage/profileTable/index.tsx index c8155f6..7ae9fcd 100644 --- a/src/components/mypage/profileTable/index.tsx +++ b/src/components/mypage/profileTable/index.tsx @@ -1,32 +1,63 @@ -import * as S from './styles.ts'; - +import { useEffect } from 'react'; +import * as S from './styles'; +import { getUserInfo, putUser } from '@/apis/user'; +import { User } from '@/types/auth'; +import { UserCircle } from '@/components/shared'; +import { SubmitHandler, useForm } from 'react-hook-form'; +interface UserModify extends Omit {} const ProfileTable = () => { + const { register, handleSubmit, reset } = useForm(); + + useEffect(() => { + const fetchUserData = async () => { + const userData = await getUserInfo(); + reset(userData); + }; + fetchUserData(); + }, []); + + const onSubmit: SubmitHandler = async (data: UserModify) => { + try { + await putUser(data); + alert('수정 완료'); + } catch (error) { + alert('수정 실패'); + } + }; + return ( - 프로필 사진 - 별명 - 깃허브 - 블로그 + + 프로필 사진 + 별명 + 깃허브 + 블로그 + - 프로필 사진 - - - - - - - - - + + + + + + + + + + + + + + - 적용 - 취소 + 적용 + reset()}>취소 +
); }; diff --git a/src/components/mypage/solved/SolvedBox.tsx b/src/components/mypage/solved/SolvedBox.tsx new file mode 100644 index 0000000..3d0ea7e --- /dev/null +++ b/src/components/mypage/solved/SolvedBox.tsx @@ -0,0 +1,13 @@ +import { SolvedArticles } from '@/types'; +import * as S from './styles'; + +export const SolvedBox = ({ data }: { data: SolvedArticles }) => { + return ( + +
{data.postId}
+
{data.title}
+
+ ); +}; + +export default SolvedBox; diff --git a/src/components/mypage/solved/index.tsx b/src/components/mypage/solved/index.tsx index e315a67..f6e3459 100644 --- a/src/components/mypage/solved/index.tsx +++ b/src/components/mypage/solved/index.tsx @@ -1,28 +1,29 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import * as S from '../articles/styles'; import { getMypageArticles } from '@/apis/mypage'; -import { useEmail } from '@/stores/mypage'; import { useEffect, useRef } from 'react'; -import { ArticleType, MypageArticles } from '@/types'; +import { MypageArticles, SolvedArticles } from '@/types'; import SkeletonBox from '@/components/loader/skeleton'; +import SolvedBox from './SolvedBox'; const SolvedMenu = ({ value }: Pick) => { - const { email } = useEmail(); + const queryEmail = new URLSearchParams(location.search).get('user'); const loader = useRef(null); - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ - queryKey: [value, email], - queryFn: ({ pageParam }) => - getMypageArticles({ - page: pageParam, - size: 10, - value: value, - email: email, - }), - initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage.nextPage, - staleTime: 1000 * 60 * 5, // 5분 - }); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useInfiniteQuery({ + queryKey: [value, queryEmail], + queryFn: ({ pageParam }) => + getMypageArticles({ + page: pageParam, + size: 10, + value: value, + email: queryEmail, + }), + initialPageParam: 0, + getNextPageParam: (lastPage) => lastPage.nextPage, + staleTime: 1000 * 60 * 5, // 5분 + }); useEffect(() => { const io = new IntersectionObserver( @@ -41,12 +42,14 @@ const SolvedMenu = ({ value }: Pick) => { return ( - {isFetchingNextPage ? ( + {isLoading ? ( ) : ( data?.pages.map((page) => - page.data.map((post: ArticleType) => ( -
{/* solved list component */}
+ page.posts.map((post: SolvedArticles) => ( +
+ +
)), ) )} diff --git a/src/components/mypage/solved/styles.ts b/src/components/mypage/solved/styles.ts new file mode 100644 index 0000000..d0aad1c --- /dev/null +++ b/src/components/mypage/solved/styles.ts @@ -0,0 +1,9 @@ +import styled from '@emotion/styled'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + gap: 43px; +`; diff --git a/src/components/mypage/tabMenu/index.tsx b/src/components/mypage/tabMenu/index.tsx index 03847af..6c80321 100644 --- a/src/components/mypage/tabMenu/index.tsx +++ b/src/components/mypage/tabMenu/index.tsx @@ -1,10 +1,9 @@ -import { useLocation, Link } from 'react-router-dom'; import { ArticlesMenu, SolvedMenu } from '@/components/mypage'; import * as S from './styles.ts'; +import { useState } from 'react'; const TabMenu = () => { - const location = useLocation(); - const currentTab = new URLSearchParams(location.search).get('value') || 'created'; + const [currentTab, setCurrentTab] = useState('created'); const tabs = [ { id: 'created', label: '만든 문제', component: ArticlesMenu }, @@ -16,8 +15,12 @@ const TabMenu = () => { {tabs.map((tab) => ( - - {tab.label} + setCurrentTab(tab.id)} + > + {tab.label} ))} diff --git a/src/components/mypage/tabMenu/styles.ts b/src/components/mypage/tabMenu/styles.ts index 2a146b5..20df9e1 100644 --- a/src/components/mypage/tabMenu/styles.ts +++ b/src/components/mypage/tabMenu/styles.ts @@ -18,4 +18,5 @@ export const TabBox = styled.li<{ active: boolean }>` padding: 0 80px 16px; border-bottom: ${({ active }) => (active ? `8px solid ${colors.yellow}` : 'none')}; color: ${({ active }) => (active ? `${colors.yellowActive}` : 'black')}; + cursor: pointer; `; diff --git a/src/components/mypage/userInfo/index.tsx b/src/components/mypage/userInfo/index.tsx index d3def41..3805804 100644 --- a/src/components/mypage/userInfo/index.tsx +++ b/src/components/mypage/userInfo/index.tsx @@ -2,59 +2,56 @@ import { Icon, UserCircle } from '@/components/shared'; import * as S from './styles'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; - import { User } from '@/types/auth.ts'; import { IconValues } from '@/components/shared/icon'; -import { useEmail } from '@/stores/mypage'; import { getUserInfo } from '@/apis/user'; +import { getOthersProfile } from '@/apis/mypage'; const UserInfo = () => { const navigate = useNavigate(); - const { setEmail } = useEmail(); - const [data, setData] = useState({ - email: '', - nickname: '', - github: '', - blog: '', - introduce: '', - profileImage: '', - }); + const [data, setData] = useState(null); + + const updateUserData = (userData: User) => { + setData(userData); + }; useEffect(() => { const fetchUserData = async () => { - const data = await getUserInfo(); - if (data) { - setData({ - email: data.email, - nickname: data.nickname, - github: data.github, - blog: data.blog, - introduce: data.introduce, - profileImage: data.profileImage, - }); - setEmail(data.email); + const queryEmail = new URLSearchParams(location.search).get('user'); + let userData; + + if (queryEmail) { + // 타인 프로필 조회 + userData = await getOthersProfile(queryEmail); + } else { + // 내 프로필 조회 + userData = await getUserInfo(); + navigate(`?user=${userData.email}`, { replace: true }); } - }; + if (userData) { + updateUserData(userData); + } + }; fetchUserData(); - }, []); + }, [location.search, navigate]); const contactInfo = [ - { key: 'email', value: data.email, icon: 'email' }, - { key: 'github', value: data.github, icon: 'github' }, - { key: 'blog', value: data.blog, icon: 'blog' }, + { key: 'email', value: data?.email, icon: 'email' }, + { key: 'github', value: data?.github, icon: 'github' }, + { key: 'blog', value: data?.blog, icon: 'blog' }, ]; return ( - + navigate('modify')}> - {data.nickname} + {data?.nickname} {contactInfo.map( (info) => @@ -66,7 +63,7 @@ const UserInfo = () => { ), )} - {data.introduce} + {data?.introduce} ); diff --git a/src/components/signup/optionalForm/index.tsx b/src/components/signup/optionalForm/index.tsx index 6bd6e45..8dd471c 100644 --- a/src/components/signup/optionalForm/index.tsx +++ b/src/components/signup/optionalForm/index.tsx @@ -1,12 +1,14 @@ -import * as S from './styles.ts'; +import * as S from './styles'; import { InputBox, StyledButton, Icon } from '@/components/shared'; import { useEffect, useState } from 'react'; -import { useModalContext } from '@/contexts/ModalContext.tsx'; +import { useModalContext } from '@/contexts/ModalContext'; import { useNavigate } from 'react-router-dom'; -import useForm from '@/hooks/useForm.ts'; +import useForm from '@/hooks/useForm'; import React from 'react'; +import { User } from '@/types/auth'; +import { putUser } from '@/apis/user'; -const OptionalForm = () => { +const OptionalForm = ({ nickname }: Pick) => { const navigate = useNavigate(); const { open } = useModalContext(); const [isFocus, setIsFocus] = useState(false); @@ -33,6 +35,19 @@ const OptionalForm = () => { }); }, []); + const onSubmit = () => { + const data = { + nickname: nickname, + github: github, + blog: blog, + introduce: text, + profileImage: '', + }; + const response = putUser(data); + console.log('정보 수정', response); + navigate('/'); + }; + return ( @@ -68,7 +83,7 @@ const OptionalForm = () => { {`${text.length}/100 자`}
- navigate('/')}> + 확인
diff --git a/src/components/signup/signupForm/index.tsx b/src/components/signup/signupForm/index.tsx index d718536..b96ae84 100644 --- a/src/components/signup/signupForm/index.tsx +++ b/src/components/signup/signupForm/index.tsx @@ -2,9 +2,9 @@ import * as S from './styles.ts'; import { useForm, SubmitHandler } from 'react-hook-form'; import { StyledButton, Icon, InputBox } from '@/components/shared'; import { useNavigate } from 'react-router-dom'; -//import { useSignupStore } from '@/stores/signupStore'; -import { postLogin, postSignup } from '@/apis/user.ts'; +import { postLogin, postSignup } from '@/apis/user'; import { SignUp } from '@/types/auth.ts'; +import { setAccess } from '@/hooks/auth/useLocalStorage.ts'; import { AxiosError } from 'axios'; const SignupForm = () => { @@ -19,34 +19,41 @@ const SignupForm = () => { } = useForm({ mode: 'onChange', }); + const onSubmit: SubmitHandler = async (data) => { try { // 선택사항 빈 값으로 보냄 const fullData = { ...data, github: '', blog: '', introduce: '' }; - let response = await postSignup(fullData); - console.log('회원가입 성공:', response); + const signupResponse = await postSignup(fullData); - // 자동 로그인 - response = await postLogin(data); - const accessToken = response.accessToken; - localStorage.setItem('access', accessToken); - localStorage.setItem('userId', data.email); - console.log('로그인 성공:', response); - navigate('/signup/optional'); + if (signupResponse) { + // 자동 로그인 + const loginResponse = await postLogin(data); + const accessToken = loginResponse.accessToken; + setAccess(accessToken); + navigate('/signup/optional', { + state: { nickname: data.nickname }, + }); + } } catch (error) { const axiosError = error as AxiosError<{ message: string }>; if (axiosError.response) { const errorMessage = axiosError.response.data.message; - if (errorMessage.includes('[❎ ERROR] 이미 존재하는 닉네임입니다')) { + if (errorMessage.includes('닉네임')) { setError('nickname', { type: 'manual', message: '이미 존재하는 닉네임입니다', }); - } else if (errorMessage.includes('[❎ ERROR] 이미 존재하는 이메일입니다')) { + } else if (errorMessage.includes('이메일')) { setError('email', { type: 'manual', message: '이미 존재하는 이메일입니다.', }); + } else { + setError('email', { + type: 'manual', + message: '[회원가입 오류] 다시 시도해주세요.', + }); } } } @@ -55,70 +62,79 @@ const SignupForm = () => { return ( - - - - - - ,.?/-]).{6,20}$/, - message: '비밀번호는 영문, 숫자 및 특수문자를 모두 포함해야 합니다.', - }, - })} - label="비밀번호" - type="password" - error={errors.password?.message} - /> - - - value === watch('password') || '비밀번호가 일치하지 않습니다.', - })} - label="비밀번호 확인" - type="password" - error={errors.checkPassword?.message} - /> + +
+ +
+
+ +
+
+ ,.?/-]).{6,20}$/, + message: '비밀번호는 영문, 숫자 및 특수문자를 모두 포함해야 합니다.', + }, + })} + label="비밀번호" + type="password" + error={errors.password?.message} + /> +
+
+ + value === watch('password') || '비밀번호가 일치하지 않습니다.', + })} + label="비밀번호 확인" + type="password" + error={errors.checkPassword?.message} + /> +
회원가입 -
+
); }; diff --git a/src/components/signup/signupForm/styles.ts b/src/components/signup/signupForm/styles.ts index 1bafd96..f40786e 100644 --- a/src/components/signup/signupForm/styles.ts +++ b/src/components/signup/signupForm/styles.ts @@ -9,11 +9,11 @@ export const Container = styled.div` padding: 0 170px; `; -export const Blank = styled.div` - margin: 25px; -`; - -export const Form = styled.form` +export const Block = styled.form` + position: relative; + display: flex; + flex-direction: column; width: 600px; - margin: 80px 0; + margin: 80px 0 60px; + gap: 25px; `; diff --git a/src/hooks/auth/useLocalStorage.ts b/src/hooks/auth/useLocalStorage.ts new file mode 100644 index 0000000..2f53489 --- /dev/null +++ b/src/hooks/auth/useLocalStorage.ts @@ -0,0 +1,15 @@ +export const LOCAL_STORAGE = { + ACCESS: 'access', +}; + +export const setAccess = (accessToken: string) => { + localStorage.setItem(LOCAL_STORAGE.ACCESS, accessToken); +}; + +export const getAccess = () => { + return localStorage.getItem(LOCAL_STORAGE.ACCESS); +}; + +export const removeAccess = () => { + return localStorage.removeItem(LOCAL_STORAGE.ACCESS); +}; diff --git a/src/pages/mypage/index.tsx b/src/pages/mypage/index.tsx index b02516d..941d360 100644 --- a/src/pages/mypage/index.tsx +++ b/src/pages/mypage/index.tsx @@ -2,17 +2,8 @@ import { UserInfo, TabMenu } from '@/components/mypage'; import * as S from './styles'; import { MainContainer } from '../main/styles'; import Nav from '@/components/nav/Nav'; -import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; const MyPage = () => { - const navigate = useNavigate(); - useEffect(() => { - if (!localStorage.getItem('access')) { - navigate('/login'); - } - }, [navigate]); - return (