-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dev' of https://github.com/prgrms-web-devcourse-final-p…
…roject/WEB1_1_ZeroOne_FE into feat/#82-gathering-api
- Loading branch information
Showing
17 changed files
with
463 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,60 @@ | ||
import axios from 'axios'; | ||
import type { ReactNode } from 'react'; | ||
import { createContext, useContext, useRef, useState } from 'react'; | ||
import { useEffect } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; | ||
|
||
import api from '@/shared/api/baseApi'; | ||
import { getLocalAccessToken, removeLocalAccessToken } from '@/features/auth/auth.api'; | ||
import { useUserStore } from '@/features/user/model/user.store'; | ||
import { getMyProfile } from '@/features/user/user.api'; | ||
import { setInterceptorEvents } from '@/shared/api/baseApi'; | ||
import { customConfirm } from '@/shared/ui'; | ||
|
||
interface AuthContextType { | ||
accessToken: string | null; | ||
setAccessToken: (token: string | null) => void; | ||
reissueToken: () => Promise<void>; | ||
logout: () => Promise<void>; | ||
removeToken: () => void; | ||
} | ||
|
||
const AuthContext = createContext<AuthContextType | undefined>(undefined); | ||
|
||
const AuthProvider = ({ children }: { children: ReactNode }) => { | ||
const [accessToken, setAccessToken] = useState<string | null>( | ||
localStorage.getItem('accessToken'), | ||
); | ||
const isRefreshing = useRef(false); | ||
|
||
const reissueToken = async () => { | ||
if (isRefreshing.current) return; | ||
|
||
isRefreshing.current = true; | ||
|
||
try { | ||
const response = await axios.post('/token/reissue', {}, { withCredentials: true }); | ||
|
||
const newAccessToken = (response.headers['authorization'] as string)?.split(' ')[1]; | ||
|
||
if (newAccessToken) { | ||
setAccessToken(newAccessToken); | ||
localStorage.setItem('accessToken', newAccessToken); | ||
} else { | ||
throw new Error('accessToken이 헤더에 포함되어 있지 않습니다.'); | ||
const navigate = useNavigate(); | ||
const accessToken = getLocalAccessToken(); | ||
const { setUserData, clearUserData } = useUserStore(state => state.actions); | ||
|
||
useEffect(() => { | ||
const getUserData = async () => { | ||
try { | ||
if (accessToken) { | ||
const userData = await getMyProfile().then(res => res.data); | ||
if (!userData) throw new Error('유저 정보를 찾을 수가 없습니다.'); | ||
setUserData(userData); | ||
return userData; | ||
} | ||
clearUserData(); | ||
} catch (error) { | ||
console.error('유저 데이터를 불러오는 중 오류가 발생했습니다.', error); | ||
} | ||
} catch (error) { | ||
console.error('토큰 재발급 실패:', error); | ||
removeToken(); | ||
} finally { | ||
// eslint-disable-next-line require-atomic-updates | ||
isRefreshing.current = false; | ||
} | ||
}; | ||
|
||
const removeToken = () => { | ||
setAccessToken(null); | ||
localStorage.removeItem('accessToken'); | ||
customConfirm({ title: '로그인', text: '로그인 페이지로 이동합니다.', icon: 'info' }); | ||
}; | ||
|
||
const logout = async () => { | ||
try { | ||
await api.post('/user/logout'); | ||
|
||
setAccessToken(null); | ||
localStorage.removeItem('accessToken'); | ||
} catch (error) { | ||
console.error('로그아웃 실패:', error); | ||
} | ||
}; | ||
}; | ||
|
||
void getUserData(); | ||
}, []); | ||
|
||
//인터셉터 함수 등록 effects | ||
useEffect(() => { | ||
const logout = async (text: string) => { | ||
try { | ||
await customConfirm({ | ||
title: '로그아웃', | ||
text, | ||
icon: 'error', | ||
showCancelButton: false, | ||
}); | ||
removeLocalAccessToken(); | ||
clearUserData(); | ||
navigate('/'); | ||
} catch (error) { | ||
console.error('로그아웃 실패:', error); | ||
} | ||
}; | ||
|
||
return ( | ||
<AuthContext.Provider | ||
value={{ accessToken, setAccessToken, reissueToken, logout, removeToken }} | ||
> | ||
{children} | ||
</AuthContext.Provider> | ||
); | ||
}; | ||
setInterceptorEvents('logout', (text: string) => { | ||
void logout(text); | ||
}); | ||
}, []); | ||
|
||
export const useAuth = () => { | ||
const context = useContext(AuthContext); | ||
if (!context) { | ||
throw new Error('AuthProvider Error'); | ||
} | ||
return context; | ||
return <>{children}</>; | ||
}; | ||
|
||
export default AuthProvider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { AxiosError } from 'axios'; | ||
|
||
import type { TokenApiResponse } from './auth.dto'; | ||
import type { PostUserResponseDTO } from '../user/user.dto'; | ||
|
||
import api from '@/shared/api/baseApi'; | ||
|
||
export const loginWithToken = async (token: string) => { | ||
const response = await api.get<TokenApiResponse>(`/token/issue?token=${token}`); | ||
const newAccessToken = response.headers['authorization']; | ||
if (newAccessToken) { | ||
setLocalAccessToken(newAccessToken); | ||
} | ||
|
||
return newAccessToken as string; | ||
}; | ||
|
||
export const logout = async () => { | ||
try { | ||
await api.post<PostUserResponseDTO>('/user/logout'); | ||
removeLocalAccessToken(); | ||
} catch (error) { | ||
console.error('로그아웃 실패:', error); | ||
} | ||
}; | ||
|
||
export const getLocalAccessToken = () => { | ||
return localStorage.getItem('accessToken'); | ||
}; | ||
|
||
export const setLocalAccessToken = (token: string) => { | ||
localStorage.setItem('accessToken', token); | ||
}; | ||
|
||
export const removeLocalAccessToken = () => { | ||
localStorage.removeItem('accessToken'); | ||
}; | ||
|
||
export const reissueToken = async () => { | ||
try { | ||
const response = await api.post('/token/reissue'); | ||
|
||
const newAccessToken = response.headers['authorization']; | ||
|
||
if (newAccessToken) { | ||
setLocalAccessToken(newAccessToken); | ||
return newAccessToken as string; | ||
} else { | ||
throw new Error('accessToken이 헤더에 포함되어 있지 않습니다.'); | ||
} | ||
} catch (error) { | ||
if (error instanceof AxiosError) { | ||
throw new Error(`토큰 재발행 중 오류가 발생했습니다. ${error.message}`); | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { ApiResponse } from '@/shared/api'; | ||
|
||
export interface TokenResponse { | ||
message: string; | ||
expiresIn: number; | ||
} | ||
|
||
export type TokenApiResponse = ApiResponse<TokenResponse>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { useEffect } from 'react'; | ||
import { useNavigate, useSearchParams } from 'react-router-dom'; | ||
|
||
import { loginWithToken, removeLocalAccessToken, setLocalAccessToken } from '../auth.api'; | ||
|
||
import type { UserDataState } from '@/features/user/model/user.store'; | ||
import { useUserStore } from '@/features/user/model/user.store'; | ||
import { getMyProfile } from '@/features/user/user.api'; | ||
import { customConfirm } from '@/shared/ui'; | ||
|
||
export const LoginLoading = () => { | ||
const [params] = useSearchParams(); | ||
const navigate = useNavigate(); | ||
const { setUserData } = useUserStore(state => state.actions); | ||
const token = params.get('token') ?? ''; | ||
|
||
useEffect(() => { | ||
const login = async () => { | ||
try { | ||
if (!token) { | ||
await customConfirm({ | ||
title: '잘못된 접근', | ||
text: '메인 페이지로 이동합니다.', | ||
icon: 'error', | ||
showCancelButton: false, | ||
}); | ||
return; | ||
} | ||
|
||
const newAccessToken = await loginWithToken(token); | ||
|
||
if (newAccessToken) { | ||
setLocalAccessToken(newAccessToken); | ||
} | ||
|
||
const userData = (await getMyProfile().then(res => res.data)) as UserDataState; | ||
setUserData(userData); | ||
|
||
navigate('/'); | ||
} catch (error) { | ||
console.error(error); | ||
await customConfirm({ | ||
title: '로그인 실패', | ||
text: '메인 페이지로 이동합니다.', | ||
icon: 'error', | ||
showCancelButton: false, | ||
}); | ||
removeLocalAccessToken(); | ||
navigate('/'); | ||
} | ||
}; | ||
|
||
void login(); | ||
}, []); | ||
|
||
return <div>Redirect...</div>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import type { PostPortfolioApiResponse, PostPortfolioDTO } from './portfolio.dto'; | ||
|
||
import api from '@/shared/api/baseApi'; | ||
|
||
export const postCreatePortfolio = (data: PostPortfolioDTO) => | ||
api.post<PostPortfolioApiResponse>('/portfolio', data).then(res => res.data); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { PostUserResponseDTO } from '../user/user.dto'; | ||
|
||
import type { ApiResponse } from '@/shared/api'; | ||
|
||
export interface PostPortfolioDTO { | ||
portfolioURL: string; | ||
} | ||
|
||
export type PostPortfolioApiResponse = ApiResponse<PostUserResponseDTO>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { create } from 'zustand'; | ||
import { immer } from 'zustand/middleware/immer'; | ||
|
||
import type { UserRole } from '../user.dto'; | ||
|
||
export interface UserDataState { | ||
userId: number; | ||
name: string; | ||
imageUrl: string; | ||
role: UserRole; | ||
} | ||
|
||
interface UserState { | ||
userData: UserDataState | null; | ||
} | ||
|
||
interface UserActions { | ||
actions: { | ||
setUserData: (data: UserDataState | null) => void; | ||
updateUserData: (updatedData: Partial<UserDataState>) => void; | ||
clearUserData: () => void; | ||
}; | ||
} | ||
|
||
const initialState = { | ||
userId: -1, | ||
userData: null, | ||
}; | ||
|
||
export const useUserStore = create( | ||
immer<UserState & UserActions>(set => ({ | ||
userData: null, | ||
actions: { | ||
setUserData: data => { | ||
set({ userData: data }); | ||
}, | ||
updateUserData: updatedData => { | ||
set(state => { | ||
if (state.userData) { | ||
Object.assign(state.userData, updatedData); | ||
} | ||
}); | ||
}, | ||
clearUserData: () => { | ||
set(initialState); | ||
}, | ||
}, | ||
})), | ||
); |
Oops, something went wrong.