-
Notifications
You must be signed in to change notification settings - Fork 4
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/boostcampwm-2024/web33-Nocta …
…into Feature/#156_server에_Page별_EditorCRDT_적용 # Conflicts: # client/src/App.tsx # client/src/features/editor/Editor.tsx # client/src/features/editor/components/block/Block.tsx # client/tsconfig.tsbuildinfo
- Loading branch information
Showing
45 changed files
with
1,238 additions
and
242 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,35 +1,70 @@ | ||
import { useMutation } from "@tanstack/react-query"; | ||
import axios from "axios"; | ||
|
||
export const useSignupMutation = () => { | ||
const fetcher = ({ | ||
name, | ||
email, | ||
password, | ||
}: { | ||
name: string; | ||
email: string; | ||
password: string; | ||
}) => { | ||
return axios.post("/auth/register", { name, email, password }); | ||
}; | ||
import { useMutation, useQuery } from "@tanstack/react-query"; | ||
import { useUserActions } from "@stores/useUserStore"; | ||
import { unAuthorizationFetch, fetch } from "./axios"; | ||
|
||
const authKey = { | ||
all: ["auth"] as const, | ||
refresh: () => [...authKey.all, "refresh"] as const, | ||
}; | ||
|
||
export const useSignupMutation = (onSuccess: () => void) => { | ||
const fetcher = ({ name, email, password }: { name: string; email: string; password: string }) => | ||
unAuthorizationFetch.post("/auth/register", { name, email, password }); | ||
|
||
return useMutation({ | ||
mutationFn: fetcher, | ||
onSuccess: () => { | ||
onSuccess(); | ||
}, | ||
}); | ||
}; | ||
|
||
export const useLoginMutation = (onSuccess: () => void) => { | ||
const { setUserInfo } = useUserActions(); | ||
|
||
const fetcher = ({ email, password }: { email: string; password: string }) => | ||
unAuthorizationFetch.post("/auth/login", { email, password }); | ||
|
||
return useMutation({ | ||
mutationFn: fetcher, | ||
onSuccess: (response) => { | ||
const { id, name } = response.data; | ||
const [, accessToken] = response.headers.authorization.split(" "); | ||
setUserInfo(id, name, accessToken); | ||
onSuccess(); | ||
}, | ||
}); | ||
}; | ||
|
||
export const useLoginMutation = () => { | ||
const fetcher = ({ email, password }: { email: string; password: string }) => { | ||
return axios.post("/auth/login", { email, password }); | ||
}; | ||
export const useLogoutMutation = (onSuccess: () => void) => { | ||
const { removeUserInfo } = useUserActions(); | ||
|
||
const fetcher = () => fetch.post("/auth/logout"); | ||
|
||
return useMutation({ | ||
mutationFn: fetcher, | ||
// TODO 성공했을 경우 accessToken 저장 (zustand? localStorage? cookie?) | ||
// accessToken: cookie (쿠기 다 때려넣기...) / localStorage / zustand (번거로움..귀찮음.. 안해봤음..) | ||
// refreshToken: cookie, | ||
// onSuccess: (data) => { | ||
// }, | ||
onSuccess: () => { | ||
removeUserInfo(); | ||
onSuccess(); | ||
}, | ||
}); | ||
}; | ||
|
||
export const useRefreshQuery = () => { | ||
const { updateAccessToken } = useUserActions(); | ||
|
||
const fetcher = () => fetch.get("/auth/refresh"); | ||
|
||
return useQuery({ | ||
queryKey: authKey.refresh(), | ||
queryFn: async () => { | ||
const response = await fetcher(); | ||
|
||
const [, accessToken] = response.headers.authorization.split(" "); | ||
updateAccessToken(accessToken); | ||
|
||
return response.data; | ||
}, | ||
enabled: false, | ||
}); | ||
}; |
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,102 @@ | ||
import axios, { AxiosError, CreateAxiosDefaults, InternalAxiosRequestConfig } from "axios"; | ||
import { useErrorStore } from "@stores/useErrorStore"; | ||
import { useUserStore } from "@stores/useUserStore"; | ||
|
||
interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig { | ||
_retry?: boolean; | ||
} | ||
|
||
const baseConfig: CreateAxiosDefaults = { | ||
baseURL: `${import.meta.env.VITE_API_URL}`, | ||
withCredentials: true, | ||
}; | ||
|
||
interface ApiError { | ||
message: string; | ||
code?: string; | ||
} | ||
|
||
const getErrorMessage = (error: AxiosError<ApiError>) => { | ||
return error.response?.data?.message || error.message || "알 수 없는 오류가 발생했습니다."; | ||
}; | ||
|
||
const handleGlobalError = (error: AxiosError<ApiError>) => { | ||
const errorStore = useErrorStore.getState(); | ||
const errorMessage = getErrorMessage(error); | ||
|
||
if (!error.response) { | ||
errorStore.setErrorModal(true, "네트워크 연결을 확인해주세요."); | ||
return; | ||
} | ||
|
||
// 에러 상태 코드에 따른 처리 | ||
switch (error.response.status) { | ||
case 401: | ||
// 이미 access token. refresh token이 만료된 경우 여기로 넘어옴. | ||
useUserStore.getState().actions.removeUserInfo(); | ||
errorStore.setErrorModal(true, "유효하지 않은 사용자입니다. 다시 로그인해주세요."); | ||
break; | ||
|
||
case 500: | ||
case 502: | ||
case 503: | ||
errorStore.setErrorModal(true, "서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요."); | ||
break; | ||
default: | ||
errorStore.setErrorModal(true, errorMessage); | ||
} | ||
}; | ||
|
||
export const unAuthorizationFetch = axios.create(baseConfig); | ||
|
||
export const fetch = axios.create(baseConfig); | ||
|
||
fetch.interceptors.request.use( | ||
function (config) { | ||
const { accessToken } = useUserStore.getState(); | ||
|
||
if (accessToken) { | ||
config.headers.Authorization = `Bearer ${accessToken}`; | ||
} | ||
|
||
return config; | ||
}, | ||
function (error) { | ||
handleGlobalError(error); | ||
return Promise.reject(error); | ||
}, | ||
); | ||
|
||
fetch.interceptors.response.use( | ||
function (response) { | ||
return response; | ||
}, | ||
async function (error: AxiosError) { | ||
const originalRequest: CustomAxiosRequestConfig | undefined = error.config; | ||
|
||
if (error.response?.status === 401 && originalRequest && !originalRequest._retry) { | ||
originalRequest._retry = true; | ||
|
||
try { | ||
// access token이 만료된 경우 refresh token으로 새로운 access token을 발급받음 | ||
// 이때 refresh token만 있기에 unAuthorizationFetch를 사용. | ||
// 만약 fetch를 사용하면 401 에러가 무한으로 발생함. | ||
const response = await unAuthorizationFetch.get("/auth/refresh"); | ||
|
||
const [, accessToken] = response.headers.authorization.split(" "); | ||
|
||
useUserStore.setState({ accessToken }); | ||
originalRequest.headers.Authorization = `Bearer ${accessToken}`; | ||
|
||
return fetch(originalRequest); | ||
} catch (refreshError) { | ||
handleGlobalError(refreshError as AxiosError<ApiError>); | ||
return Promise.reject(refreshError); | ||
} | ||
} | ||
|
||
handleGlobalError(error as AxiosError<ApiError>); | ||
return Promise.reject(error); | ||
}, | ||
); | ||
``; |
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 |
---|---|---|
@@ -1,17 +1,15 @@ | ||
import { useModal } from "@components/modal/useModal"; | ||
import { AuthModal } from "@src/features/auth/AuthModal"; | ||
import { useUserInfo } from "@stores/useUserStore"; | ||
import { menuItemWrapper, imageBox, textBox } from "./MenuButton.style"; | ||
|
||
export const MenuButton = () => { | ||
const { isOpen, openModal, closeModal } = useModal(); | ||
const { name } = useUserInfo(); | ||
|
||
return ( | ||
<> | ||
<button className={menuItemWrapper} onClick={openModal}> | ||
<button className={menuItemWrapper}> | ||
<div className={imageBox}></div> | ||
<p className={textBox}>Noctturn</p> | ||
<p className={textBox}>{name ?? "Nocta"}</p> | ||
</button> | ||
<AuthModal isOpen={isOpen} onClose={closeModal} /> | ||
</> | ||
); | ||
}; |
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,49 @@ | ||
import { useLogoutMutation } from "@apis/auth"; | ||
import { useCheckLogin } from "@stores/useUserStore"; | ||
import { TextButton } from "@components/button/textButton"; | ||
import { Modal } from "@components/modal/modal"; | ||
import { useModal } from "@components/modal/useModal"; | ||
import { AuthModal } from "./AuthModal"; | ||
|
||
export const AuthButton = () => { | ||
const isLogin = useCheckLogin(); | ||
|
||
const { | ||
isOpen: isAuthModalOpen, | ||
openModal: openAuthModal, | ||
closeModal: closeAuthModal, | ||
} = useModal(); | ||
|
||
const { | ||
isOpen: isLogoutModalOpen, | ||
openModal: openLogoutModal, | ||
closeModal: closeLogoutModal, | ||
} = useModal(); | ||
|
||
const { mutate: logout } = useLogoutMutation(closeLogoutModal); | ||
|
||
return ( | ||
<> | ||
{isLogin ? ( | ||
<TextButton variant="secondary" onClick={openLogoutModal}> | ||
로그아웃 | ||
</TextButton> | ||
) : ( | ||
<TextButton variant="secondary" onClick={openAuthModal}> | ||
로그인 | ||
</TextButton> | ||
)} | ||
|
||
<AuthModal isOpen={isAuthModalOpen} onClose={closeAuthModal} /> | ||
<Modal | ||
isOpen={isLogoutModalOpen} | ||
primaryButtonLabel="로그아웃" | ||
primaryButtonOnClick={logout} | ||
secondaryButtonLabel="취소" | ||
secondaryButtonOnClick={closeLogoutModal} | ||
> | ||
<p>로그아웃 하시겠습니까?</p> | ||
</Modal> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.