diff --git a/src/app/router.tsx b/src/app/router.tsx
index c44821ea..f82c0d75 100644
--- a/src/app/router.tsx
+++ b/src/app/router.tsx
@@ -12,14 +12,12 @@ import LogIn from '@/pages/logIn';
import Game from '@/pages/play/game';
import LobbyPage, { getLobbyInfo } from '@/pages/play/lobby';
import Mode from '@/pages/play/mode';
-import Ranking from '@/pages/ranking';
+import Ranking, { getRank } from '@/pages/ranking';
import Result, { getGameResult } from '@/pages/result';
import Settings from '@/pages/settings';
import Tutorial from '@/pages/tutorial';
-import { checkToken } from '@/services/auth';
-import { getRank } from '@/services/rank';
-
+import { checkToken } from '@/features/auth';
import { ROUTE } from '@/shared/constants';
const router = createBrowserRouter([
@@ -111,6 +109,8 @@ const router = createBrowserRouter([
{
path: ROUTE.game,
element: ,
+ loader: checkToken,
+ errorElement: ,
},
]);
diff --git a/src/components/frame/with-buttons/index.tsx b/src/components/frame/with-buttons/index.tsx
index b0d7935d..322e0afb 100644
--- a/src/components/frame/with-buttons/index.tsx
+++ b/src/components/frame/with-buttons/index.tsx
@@ -38,7 +38,7 @@ const BackButton = ({ pathname }: { pathname: string }) => {
const DISABLED = {
rightButtons: new Set([ROUTE.error, ROUTE.result]),
- menuButton: new Set([ROUTE.tutorial]),
+ menuButton: [ROUTE.tutorial, ROUTE.error],
};
const BasicContentFrame = ({ children }: { children: ReactNode }) => {
@@ -46,7 +46,9 @@ const BasicContentFrame = ({ children }: { children: ReactNode }) => {
const rightButtonsDisabled = DISABLED.rightButtons.has(pathname);
- const menuButtonDisabled = DISABLED.menuButton.has(pathname);
+ const menuButtonDisabled = DISABLED.menuButton.some((route) =>
+ pathname.startsWith(route),
+ );
return (
diff --git a/src/features/auth/api.ts b/src/features/auth/api.ts
index d7e8e596..89d24b3b 100644
--- a/src/features/auth/api.ts
+++ b/src/features/auth/api.ts
@@ -8,22 +8,37 @@ const setToken = (token: string, status: number) => {
return false;
};
+const axiosConfig = {
+ headers: {
+ 'X-Bypass-Authorization': true,
+ },
+};
+
export const loginAsMember = async (account: Account) => {
- const { data, status } = await client.post
(`/user/login`, account, {
- headers: {
- 'X-Bypass-Authorization': true,
- },
- });
+ const { data, status } = await client.post(
+ `/user/login`,
+ account,
+ axiosConfig,
+ );
return setToken(data, status);
};
export const loginAsGuest = async () => {
return client
- .get('/user/guest', {
- headers: {
- 'X-Bypass-Authorization': true,
- },
- })
+ .get('/user/guest', axiosConfig)
.then(({ data, status }) => setToken(data, status))
.catch(() => false);
};
+
+export const checkToken = async () => {
+ const isTokenValid = await client
+ .get('/user/token')
+ .then(({ data }) => data);
+
+ if (!isTokenValid) {
+ localStorage.removeItem('token');
+ throw new Error();
+ }
+
+ return isTokenValid;
+};
diff --git a/src/features/auth/hooks.ts b/src/features/auth/hooks.ts
index d7c102f4..e4be9e82 100644
--- a/src/features/auth/hooks.ts
+++ b/src/features/auth/hooks.ts
@@ -1,18 +1,20 @@
+import { useQueryClient } from '@tanstack/react-query';
import { type AxiosError } from 'axios';
import { useNavigate } from 'react-router-dom';
import { loginAsGuest, loginAsMember } from './api';
-import { removeUserQuery } from '@/models/user';
+import { userQueryKey } from '@/models/user';
import { ROUTE } from '@/shared/constants';
import { setFullScreen } from '@/shared/utils';
export const useLogout = () => {
const navigate = useNavigate();
+ const queryClient = useQueryClient();
const logout = () => {
window.localStorage.removeItem('token');
- removeUserQuery();
+ queryClient.removeQueries({ queryKey: userQueryKey });
navigate('/');
};
@@ -21,9 +23,10 @@ export const useLogout = () => {
export const useLogin = () => {
const navigate = useNavigate();
+ const queryClient = useQueryClient();
- const login = async (account: Account) => {
- removeUserQuery();
+ const memberLogin = async (account: Account) => {
+ queryClient.removeQueries({ queryKey: userQueryKey });
return loginAsMember(account)
.then((isSuccess) => {
@@ -42,14 +45,8 @@ export const useLogin = () => {
});
};
- return { login };
-};
-
-export const useGuestLogin = () => {
- const navigate = useNavigate();
-
- const login = async () => {
- removeUserQuery();
+ const guestLogin = async () => {
+ queryClient.removeQueries({ queryKey: userQueryKey });
const isSuccess = await loginAsGuest();
if (!isSuccess) {
@@ -59,5 +56,5 @@ export const useGuestLogin = () => {
return navigate(ROUTE.tutorial, { replace: true });
};
- return { guestLogin: login };
+ return { memberLogin, guestLogin };
};
diff --git a/src/features/auth/index.ts b/src/features/auth/index.ts
index 4cc90d02..8c350af1 100644
--- a/src/features/auth/index.ts
+++ b/src/features/auth/index.ts
@@ -1 +1,2 @@
export * from './hooks';
+export { checkToken } from './api';
diff --git a/src/features/sign-up/api.ts b/src/features/sign-up/api.ts
index 9ba0ddb1..e74f1df7 100644
--- a/src/features/sign-up/api.ts
+++ b/src/features/sign-up/api.ts
@@ -1,6 +1,5 @@
import type { AxiosError } from 'axios';
-import { invalidateUserQuery } from '@/models/user';
import { client } from '@/shared/api';
const axiosConfig = {
@@ -22,7 +21,6 @@ export const upgradeToMember = async (formData: SignUpForm) => {
.post('/user/guest', formData)
.then(({ data }) => {
console.debug(data);
- invalidateUserQuery();
return true;
})
.catch(({ response }: AxiosError) => {
diff --git a/src/features/sign-up/hooks.ts b/src/features/sign-up/hooks.ts
new file mode 100644
index 00000000..03bd50c4
--- /dev/null
+++ b/src/features/sign-up/hooks.ts
@@ -0,0 +1,21 @@
+import { useQueryClient } from '@tanstack/react-query';
+
+import { registerMember, upgradeToMember } from './api';
+
+import { userQueryKey } from '@/models/user';
+
+export const useSignUp = (isGuest: boolean) => {
+ const queryClient = useQueryClient();
+
+ if (!isGuest) {
+ return registerMember;
+ }
+
+ return async (formData: SignUpForm) => {
+ const isSuccess = await upgradeToMember(formData);
+ if (isSuccess) {
+ await queryClient.invalidateQueries({ queryKey: userQueryKey });
+ }
+ return isSuccess;
+ };
+};
diff --git a/src/features/sign-up/ui.tsx b/src/features/sign-up/ui.tsx
index 0ef2b38a..2a6b500c 100644
--- a/src/features/sign-up/ui.tsx
+++ b/src/features/sign-up/ui.tsx
@@ -1,7 +1,8 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import { checkIdDuplicate, registerMember, upgradeToMember } from './api';
+import { checkIdDuplicate } from './api';
+import { useSignUp } from './hooks';
import { validateSignUpForm } from './utils';
import ColoredButton from '@/components/button/ColoredButton';
@@ -17,8 +18,9 @@ interface Props {
isGuest?: boolean;
}
-const SignUp = ({ closeModal, isGuest }: Props) => {
+const SignUp = ({ closeModal, isGuest = false }: Props) => {
const navigate = useNavigate();
+ const signUp = useSignUp(isGuest);
const [errorMessage, setErrorMessage] = useState(ERROR_MESSAGE.welcome);
const [form, setForm] = useState({
id: '',
@@ -48,7 +50,6 @@ const SignUp = ({ closeModal, isGuest }: Props) => {
return;
}
- const signUp = isGuest ? upgradeToMember : registerMember;
const isSuccess = await signUp(form);
if (!isSuccess) {
return;
diff --git a/src/models/user/hooks.ts b/src/models/user/hooks.ts
index cf518270..aae6d93b 100644
--- a/src/models/user/hooks.ts
+++ b/src/models/user/hooks.ts
@@ -1,12 +1,12 @@
-import { useQuery } from '@tanstack/react-query';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
import { getDefaultStore, useAtomValue, useSetAtom } from 'jotai';
import { getUser } from './api';
import { queryKey } from './constants';
import { updateUserCodeAtom, userCodeAtom, userStaleAtom } from './store';
-import { invalidateUserQuery } from './utils';
export const useUserInfo = () => {
+ const queryClient = useQueryClient();
const setUserCode = useSetAtom(updateUserCodeAtom);
const store = getDefaultStore();
const isStale = store.get(userStaleAtom);
@@ -18,14 +18,15 @@ export const useUserInfo = () => {
setUserCode(user.userCode);
return user;
},
- throwOnError: true,
refetchOnWindowFocus: false,
refetchOnMount: isStale,
});
+ const refetchUser = () => queryClient.invalidateQueries({ queryKey });
+
return {
user: data,
- refetchUser: invalidateUserQuery,
+ refetchUser,
};
};
diff --git a/src/models/user/index.ts b/src/models/user/index.ts
index fd70c425..28cd58ea 100644
--- a/src/models/user/index.ts
+++ b/src/models/user/index.ts
@@ -1,2 +1,2 @@
export * from './hooks';
-export * from './utils';
+export { queryKey as userQueryKey } from './constants';
diff --git a/src/models/user/utils.ts b/src/models/user/utils.ts
deleted file mode 100644
index ed47d356..00000000
--- a/src/models/user/utils.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { queryKey } from './constants';
-
-import { queryClient } from '@/shared/api';
-
-export const removeUserQuery = () => queryClient.removeQueries({ queryKey });
-
-export const invalidateUserQuery = () =>
- queryClient.invalidateQueries({ queryKey });
diff --git a/src/pages/error/constants.ts b/src/pages/error/constants.ts
index 834e1560..1a55e297 100644
--- a/src/pages/error/constants.ts
+++ b/src/pages/error/constants.ts
@@ -1,6 +1,6 @@
export const ERROR_MESSAGE = {
'401': '로그인 정보가 유효하지 않습니다!\n다시 로그인 하시겠습니까?',
- '404': '페이지를 찾을 수 없습니다!\n이전 화면으로 돌아가시겠습니까?',
+ '404': '페이지를 찾을 수 없습니다!\n홈으로 돌아가시겠습니까?',
'500': '서버에 에러가 발생했습니다!\n 개발자에게 문의하세요.',
'400': '네트워크 연결 상태를\n확인해주세요',
};
diff --git a/src/pages/error/index.tsx b/src/pages/error/index.tsx
index ea52c5d9..cefe08ab 100644
--- a/src/pages/error/index.tsx
+++ b/src/pages/error/index.tsx
@@ -1,29 +1,30 @@
import { useNavigate, useParams } from 'react-router-dom';
-import * as constants from './constants';
+import { ERROR_MESSAGE } from './constants';
import * as styles from './index.css';
import ColoredButton from '@/components/button/ColoredButton';
-import type { errorCode } from '@/pages/error/type';
-
import { ROUTE } from '@/shared/constants';
const Error = () => {
const navigate = useNavigate();
- const code = useParams().code as errorCode;
+ const code = useParams().code as ErrorCode;
const goBack = () => {
- if (code === '401') {
- navigate(ROUTE.main, { replace: true });
- return;
+ switch (code) {
+ case '401':
+ return navigate(ROUTE.main, { replace: true });
+ case '404':
+ return navigate(ROUTE.home, { replace: true });
+ default:
+ return navigate(-1);
}
- navigate(-1);
};
return (
-
{constants.ERROR_MESSAGE[code]}
+
{ERROR_MESSAGE[code]}
);
diff --git a/src/pages/error/type.d.ts b/src/pages/error/type.d.ts
deleted file mode 100644
index ac93db31..00000000
--- a/src/pages/error/type.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { ERROR_MESSAGE } from '@/pages/error/constants';
-
-type errorCode = keyof typeof ERROR_MESSAGE;
diff --git a/src/pages/error/types.d.ts b/src/pages/error/types.d.ts
new file mode 100644
index 00000000..13d42f29
--- /dev/null
+++ b/src/pages/error/types.d.ts
@@ -0,0 +1 @@
+type ErrorCode = keyof typeof import('./constants').ERROR_MESSAGE;
diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx
index 96a3e6a3..f9aaab75 100644
--- a/src/pages/landing/index.tsx
+++ b/src/pages/landing/index.tsx
@@ -4,12 +4,12 @@ import * as styles from './index.css';
import ColoredButton from '@/components/button/ColoredButton/index';
-import { useGuestLogin } from '@/features/auth';
+import { useLogin } from '@/features/auth';
import { ROUTE } from '@/shared/constants';
const Landing = () => {
const navigate = useNavigate();
- const { guestLogin } = useGuestLogin();
+ const { guestLogin } = useLogin();
const clickLogIn = () => {
navigate(ROUTE.login);
diff --git a/src/pages/logIn/index.tsx b/src/pages/logIn/index.tsx
index 858ce984..3c4a3dcb 100644
--- a/src/pages/logIn/index.tsx
+++ b/src/pages/logIn/index.tsx
@@ -17,7 +17,7 @@ const LogIn = () => {
id: '',
password: '',
});
- const { login } = useLogin();
+ const { memberLogin } = useLogin();
const { modalRef, openModal, closeModal } = useModal();
@@ -30,7 +30,7 @@ const LogIn = () => {
};
const handleLogInClick = async () => {
- const isLoginSuccess = await login(logInInfo);
+ const isLoginSuccess = await memberLogin(logInInfo);
if (!isLoginSuccess) {
setIsClicked(false);
openModal();
diff --git a/src/pages/ranking/api.ts b/src/pages/ranking/api.ts
new file mode 100644
index 00000000..079e0d33
--- /dev/null
+++ b/src/pages/ranking/api.ts
@@ -0,0 +1,13 @@
+import { client } from '@/shared/api';
+
+export const getRank = async () => {
+ return client
+ .get<{
+ refreshTime: string;
+ players: Profile[];
+ }>('rank/list')
+ .then(({ data }) => {
+ console.debug('rank:', data);
+ return data.players;
+ });
+};
diff --git a/src/pages/ranking/index.tsx b/src/pages/ranking/index.tsx
index 5c994cda..76af409e 100644
--- a/src/pages/ranking/index.tsx
+++ b/src/pages/ranking/index.tsx
@@ -1,42 +1,2 @@
-import { useLoaderData } from 'react-router-dom';
-
-import * as constants from './constants';
-import * as styles from './index.css';
-
-import RankingItemBox from '@/components/ranking';
-
-import { useUserInfo } from '@/models/user';
-
-const Ranking = () => {
- const { rankList } = useLoaderData() as {
- rankList: Profile[];
- };
- const { user: myRank } = useUserInfo();
-
- if (!myRank) {
- return null;
- }
-
- return (
-
-
-
{constants.RANKING_TITLE}
- {myRank.guest && (
-
{constants.GUEST_TEXT}
- )}
-
-
- {rankList.map((item) => (
-
- ))}
-
- {myRank && (
-
-
-
- )}
-
- );
-};
-
-export default Ranking;
+export { getRank } from './api';
+export { default } from './page';
diff --git a/src/pages/ranking/page.tsx b/src/pages/ranking/page.tsx
new file mode 100644
index 00000000..d2ffb649
--- /dev/null
+++ b/src/pages/ranking/page.tsx
@@ -0,0 +1,40 @@
+import { useLoaderData } from 'react-router-dom';
+
+import * as constants from './constants';
+import * as styles from './index.css';
+
+import RankingItemBox from '@/components/ranking';
+
+import { useUserInfo } from '@/models/user';
+
+const Ranking = () => {
+ const rankList = useLoaderData() as Profile[];
+ const { user: myRank } = useUserInfo();
+
+ if (!myRank) {
+ return null;
+ }
+
+ return (
+
+
+
{constants.RANKING_TITLE}
+ {myRank.guest && (
+
{constants.GUEST_TEXT}
+ )}
+
+
+ {rankList.map((item) => (
+
+ ))}
+
+ {myRank && (
+
+
+
+ )}
+
+ );
+};
+
+export default Ranking;
diff --git a/src/pages/settings/NicknameChange.tsx b/src/pages/settings/NicknameChange.tsx
index 8e69a68c..dc38b25e 100644
--- a/src/pages/settings/NicknameChange.tsx
+++ b/src/pages/settings/NicknameChange.tsx
@@ -1,12 +1,12 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
+import { patchNicknameChange } from './api';
import * as styles from './index.css';
import ColoredButton from '@/components/button/ColoredButton';
import Input from '@/components/input';
-import { patchNicknameChange } from '@/services/auth';
import { ERROR_MESSAGE, ROUTE } from '@/shared/constants';
diff --git a/src/pages/settings/PasswordChange.tsx b/src/pages/settings/PasswordChange.tsx
index a98ac9ab..bc56cec1 100644
--- a/src/pages/settings/PasswordChange.tsx
+++ b/src/pages/settings/PasswordChange.tsx
@@ -1,11 +1,11 @@
import React, { useState } from 'react';
+import { patchPasswordChange } from './api';
import * as styles from './index.css';
import ColoredButton from '@/components/button/ColoredButton';
import Input from '@/components/input';
-import { patchPasswordChange } from '@/services/auth';
import { ERROR_MESSAGE } from '@/shared/constants';
diff --git a/src/pages/settings/api.ts b/src/pages/settings/api.ts
new file mode 100644
index 00000000..f3c8d423
--- /dev/null
+++ b/src/pages/settings/api.ts
@@ -0,0 +1,47 @@
+import { client } from '@/shared/api';
+
+export const patchNicknameChange = async (nickname: string) => {
+ try {
+ await client.patch('/user/nickname', { nickname });
+ return true;
+ } catch (e) {
+ return false;
+ }
+};
+
+export const patchPasswordChange = async (
+ prePassword: string,
+ newPassword: string,
+) => {
+ try {
+ await client.patch('/user/password', {
+ prePassword,
+ newPassword,
+ });
+ return true;
+ } catch (e) {
+ return false;
+ }
+};
+
+export const postConfirmPassword = async (password: string) => {
+ try {
+ const response = await client.post('/user/confirm', { password });
+ // console.log(response.data);
+ return response.data;
+ } catch (e) {
+ return false;
+ }
+};
+
+export const deleteUserInfo = async () => {
+ await client
+ .delete('/user')
+ .then(() => {
+ window.location.href = '/';
+ })
+ // eslint-disable-next-line
+ .catch((e) => {
+ console.log(e);
+ });
+};
diff --git a/src/pages/settings/delete-account.tsx b/src/pages/settings/delete-account.tsx
index 6111f65f..7dea2238 100644
--- a/src/pages/settings/delete-account.tsx
+++ b/src/pages/settings/delete-account.tsx
@@ -1,11 +1,11 @@
import React, { useState } from 'react';
+import { deleteUserInfo, postConfirmPassword } from './api';
import * as styles from './index.css';
import ColoredButton from '@/components/button/ColoredButton';
import Input from '@/components/input';
-import { deleteUserInfo, postConfirmPassword } from '@/services/auth';
import { ERROR_MESSAGE } from '@/shared/constants';
diff --git a/src/services/auth.ts b/src/services/auth.ts
deleted file mode 100644
index 8ea474f5..00000000
--- a/src/services/auth.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { NicknameInfo, PasswordInfo } from '@/types/auth';
-
-import { api } from '@/shared/api';
-
-export const patchNicknameChange = async (nickname: string) => {
- try {
- const changeNickname: NicknameInfo = {
- nickname,
- };
- await api.patch(
- true,
- '/user/nickname',
- changeNickname,
- );
- return true;
- } catch (e) {
- return false;
- }
-};
-
-export const patchPasswordChange = async (
- prePassword: string,
- newPassword: string,
-) => {
- try {
- const changePassword: PasswordInfo = {
- prePassword,
- newPassword,
- };
-
- await api.patch(
- true,
- '/user/password',
- changePassword,
- );
- return true;
- } catch (e) {
- return false;
- }
-};
-
-export const postConfirmPassword = async (password: string) => {
- try {
- const response = await api.post(
- true,
- '/user/confirm',
- { password },
- );
- // console.log(response.data);
- return response.data;
- } catch (e) {
- return false;
- }
-};
-
-export const deleteUserInfo = async () => {
- await api
- .delete(true, '/user')
- .then(() => {
- window.location.href = '/';
- })
- // eslint-disable-next-line
- .catch((e) => {
- console.log(e);
- });
-};
-
-export const checkToken = async () => {
- return api
- .get(true, '/user/token')
- .then((response) => {
- const isTokenValid = response.data;
- if (!isTokenValid) {
- throw new Error();
- }
- return isTokenValid;
- })
- .catch((err) => {
- localStorage.removeItem('token');
- throw err;
- });
-};
diff --git a/src/services/rank.ts b/src/services/rank.ts
deleted file mode 100644
index c47fcc8a..00000000
--- a/src/services/rank.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { api } from '@/shared/api';
-
-const requestRankList = async () => {
- return api.get(true, 'rank/list').then((res) => {
- return res.data.players;
- });
-};
-
-export const getRank = async () => {
- const rankList = await requestRankList();
-
- return { rankList };
-};
diff --git a/src/types/auth.d.ts b/src/types/auth.d.ts
deleted file mode 100644
index 914ebfa0..00000000
--- a/src/types/auth.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface NicknameInfo {
- nickname: string;
-}
-
-export interface PasswordInfo {
- prePassword: string;
- newPassword: string;
-}
diff --git a/src/types/rank.d.ts b/src/types/rank.d.ts
deleted file mode 100644
index 7de94732..00000000
--- a/src/types/rank.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-interface RankInfo {
- refreshTime: string;
- players: Profile[];
-}