diff --git a/client/src/pages/Home/index.tsx b/client/src/pages/Home/index.tsx
index bac9f50a..aeb911e0 100644
--- a/client/src/pages/Home/index.tsx
+++ b/client/src/pages/Home/index.tsx
@@ -1,14 +1,16 @@
-import Gnb from '@features/Gnb';
-import Sidebar from '@features/Sidebar';
+import CreateCommunityModal from '@components/Modals/CreateCommunityModal';
+import Gnb from '@layouts/Gnb';
+import Sidebar from '@layouts/Sidebar';
import React from 'react';
import { Outlet } from 'react-router-dom';
const Home = () => {
return (
-
+
+
);
};
diff --git a/client/src/pages/Root/index.tsx b/client/src/pages/Root/index.tsx
index 7c44f79c..ced43a86 100644
--- a/client/src/pages/Root/index.tsx
+++ b/client/src/pages/Root/index.tsx
@@ -1,7 +1,21 @@
+import { useMyInfo } from '@hooks/useMyInfoQuery';
+import { useTokenStore } from '@stores/tokenStore';
import React from 'react';
+import { Navigate } from 'react-router-dom';
+/**
+ * @description
+ * ## 인증 상태에 따라 리다이렉트 분기처리하는 페이지
+ * - 로그인 되어있으면 **`/dms`** 로 이동한다.
+ * - 로그인 되어있지 않으면, **`/sign-in`** 으로 이동한다.
+ * - 조건문에 user || accessToken 중 하나라도 없으면 **`/sign-in`** -> **`/`** -> **`/sign-in`** ... 무한루프 발생함.
+ */
const Root = () => {
- return
;
+ const user = useMyInfo();
+ const accessToken = useTokenStore((state) => state.accessToken);
+
+ if (user || accessToken) return
;
+ return
;
};
export default Root;
diff --git a/client/src/pages/SignIn/index.tsx b/client/src/pages/SignIn/index.tsx
index 9848110e..84d87bc2 100644
--- a/client/src/pages/SignIn/index.tsx
+++ b/client/src/pages/SignIn/index.tsx
@@ -1,91 +1,51 @@
+import type { SignInRequest } from '@apis/auth';
+
import AuthInput from '@components/AuthInput';
import Button from '@components/Button';
import ErrorMessage from '@components/ErrorMessage';
import Logo from '@components/Logo';
import TextButton from '@components/TextButton';
-import { API_URL } from '@constants/url';
+import REGEX from '@constants/regex';
+import defaultErrorHandler from '@errors/defaultErrorHandler';
+import useSignInMutation from '@hooks/useSignInMutation';
import { useTokenStore } from '@stores/tokenStore';
-import { useMutation } from '@tanstack/react-query';
-import axios, { AxiosError } from 'axios';
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
-import { Navigate, useNavigate } from 'react-router-dom';
-import { toast } from 'react-toastify';
-
-interface SignInFields {
- id: string;
- password: string;
-}
-
-interface SuccessResponse
{
- statusCode: number;
- result: T;
-}
-
-type SignInApi = (
- fields: SignInFields,
-) => Promise>;
-
-const endPoint = `${API_URL}/api/user/auth/signin`;
+import { useNavigate } from 'react-router-dom';
-const signInApi: SignInApi = ({ id, password }) => {
- return axios
- .post(endPoint, { id, password })
- .then((response) => response.data);
+const signUpFormDefaultValues = {
+ id: '',
+ password: '',
};
-// 액세스 토큰으로 다시 유저 정보 요청해야함
-// _id, id(이메일), nickname, status, profileUrl, description
const SignIn = () => {
- const accessToken = useTokenStore((state) => state.accessToken);
const setAccessToken = useTokenStore((state) => state.setAccessToken);
- const { control, handleSubmit, reset } = useForm({
+ const { control, handleSubmit, reset } = useForm({
mode: 'all',
- defaultValues: {
- id: '',
- password: '',
- },
+ defaultValues: signUpFormDefaultValues,
});
const navigate = useNavigate();
- const signInMutate = useMutation(['signIn'], signInApi, {
+ const signInMutation = useSignInMutation({
onSuccess: (data) => {
setAccessToken(data.result.accessToken);
+ navigate('/dms');
},
onError: (error) => {
reset();
- if (error instanceof AxiosError) {
- const errorMessage =
- error?.response?.data?.message || '에러가 발생했습니다!';
-
- if (Array.isArray(errorMessage)) {
- errorMessage.forEach((message) => {
- toast.error(message);
- });
- return;
- }
-
- toast.error(errorMessage);
- return;
- }
-
- toast.error('Unknown Error');
+ defaultErrorHandler(error);
},
});
- const handleSubmitSignInForm = ({ id, password }: SignInFields) => {
- signInMutate.mutate({ id, password });
+ const handleSubmitSignInForm = ({ id, password }: SignInRequest) => {
+ signInMutation.mutate({ id, password });
};
const handleNavigateSignUpPage = () => {
navigate('/sign-up');
};
- if (accessToken) {
- return ;
- }
-
return (
@@ -102,7 +62,7 @@ const SignIn = () => {
control={control}
rules={{
pattern: {
- value: /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
+ value: REGEX.EMAIL,
message: '아이디는 이메일 형식으로 입력해야 합니다!',
},
}}
@@ -149,7 +109,7 @@ const SignIn = () => {
size="md"
type="submit"
minWidth={340}
- disabled={signInMutate.isLoading}
+ disabled={signInMutation.isLoading}
>
로그인
diff --git a/client/src/pages/SignUp/index.tsx b/client/src/pages/SignUp/index.tsx
index 00c1b267..59163f27 100644
--- a/client/src/pages/SignUp/index.tsx
+++ b/client/src/pages/SignUp/index.tsx
@@ -1,84 +1,52 @@
+import type { SignUpRequest } from '@apis/auth';
+
import AuthInput from '@components/AuthInput';
import Button from '@components/Button';
import ErrorMessage from '@components/ErrorMessage';
import Logo from '@components/Logo';
import SuccessMessage from '@components/SuccessMessage';
import TextButton from '@components/TextButton';
-import { API_URL } from '@constants/url';
-import { useMutation } from '@tanstack/react-query';
-import axios, { AxiosError } from 'axios';
+import REGEX from '@constants/regex';
+import defaultErrorHandler from '@errors/defaultErrorHandler';
+import useSignUpMutation from '@hooks/useSignUpMutation';
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
-interface SignUpFields {
- id: string;
- nickname: string;
- password: string;
+interface SignUpFormFields extends SignUpRequest {
passwordCheck: string;
}
-interface SuccessResponse
{
- statusCode: number;
- result: T;
-}
-
-type SignUpApi = (
- fields: Omit,
-) => Promise>;
-
-const endPoint = `${API_URL}/api/user/auth/signup`;
-
-const signUpApi: SignUpApi = ({ id, nickname, password }) => {
- return axios
- .post(endPoint, { id, nickname, password })
- .then((response) => response.data);
+const signUpFormDefaultValues = {
+ id: '',
+ nickname: '',
+ password: '',
+ passwordCheck: '',
};
const SignUp = () => {
- // TODO: 리팩토링 하자
- const { control, handleSubmit, watch, reset } = useForm({
+ const { control, handleSubmit, watch, reset } = useForm({
mode: 'all',
- defaultValues: {
- id: '',
- nickname: '',
- password: '',
- passwordCheck: '',
- },
+ defaultValues: signUpFormDefaultValues,
});
+ const password = watch('password');
+
const navigate = useNavigate();
- const signUpMutate = useMutation(['signUp'], signUpApi, {
+ const signUpMutation = useSignUpMutation({
onSuccess: () => {
toast.success('회원가입에 성공했습니다.');
reset();
},
onError: (error) => {
- if (error instanceof AxiosError) {
- const errorMessage =
- error?.response?.data?.message || '에러가 발생했습니다!';
-
- if (Array.isArray(errorMessage)) {
- errorMessage.forEach((message) => {
- toast.error(message);
- });
- return;
- }
-
- toast.error(errorMessage);
- return;
- }
-
- toast.error('Unknown Error');
+ defaultErrorHandler(error);
},
});
- const password = watch('password');
-
- const handleSubmitSignUpForm = (fields: SignUpFields) => {
- signUpMutate.mutate(fields);
+ const handleSubmitSignUpForm = (fields: SignUpFormFields) => {
+ signUpMutation.mutate(fields);
};
const handleNavigateSignInPage = () => {
@@ -101,7 +69,7 @@ const SignUp = () => {
control={control}
rules={{
pattern: {
- value: /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
+ value: REGEX.EMAIL,
message: '아이디는 이메일 형식으로 입력해야 합니다!',
},
required: '필수 요소입니다!',
@@ -220,7 +188,7 @@ const SignUp = () => {
size="md"
type="submit"
minWidth={340}
- disabled={signUpMutate.isLoading}
+ disabled={signUpMutation.isLoading}
>
회원가입
diff --git a/client/src/pages/UnAuthorizedLayer/index.tsx b/client/src/pages/UnAuthorizedLayer/index.tsx
new file mode 100644
index 00000000..94963043
--- /dev/null
+++ b/client/src/pages/UnAuthorizedLayer/index.tsx
@@ -0,0 +1,37 @@
+import { useMyInfo } from '@hooks/useMyInfoQuery';
+import useReissueTokenMutation from '@hooks/useReissueTokenMutation';
+import { useTokenStore } from '@stores/tokenStore';
+import React, { useEffect, useState } from 'react';
+import { Outlet, Navigate, useLocation } from 'react-router-dom';
+
+/**
+ * ## 로그인 하지 않은 유저들만 머무를 수 있는 페이지.
+ * - 새로고침시 토큰 갱신을 시도하며, 로그인한(유저 상태나 액세스 토큰 상태가 있는) 유저가 접근하면 **`/`** 로 리다이렉트된다.
+ * - 토큰 갱신 요청시, 유효하지 않은 토큰 에러나 알 수 없는 에러가 발생하면 페이지 이동 없이 그대로 유지한다.
+ */
+const UnAuthorizedLayer = () => {
+ const user = useMyInfo();
+ const location = useLocation();
+
+ const accessToken = useTokenStore((state) => state.accessToken);
+ const [isTryingReissueToken, setIsTryingReissueToken] = useState(true);
+
+ const handleReissueTokenError = () => setIsTryingReissueToken(false);
+
+ const reissueTokenMutation = useReissueTokenMutation(
+ handleReissueTokenError,
+ handleReissueTokenError,
+ );
+
+ useEffect(() => {
+ if (user) return;
+ reissueTokenMutation.mutate();
+ }, []);
+
+ if (user || accessToken) return ;
+ if (location.state?.alreadyTriedReissueToken) return ;
+ if (isTryingReissueToken) return 로딩중...
;
+ return ;
+};
+
+export default UnAuthorizedLayer;
diff --git a/client/src/pages/UnknownError/index.tsx b/client/src/pages/UnknownError/index.tsx
new file mode 100644
index 00000000..f7f0485a
--- /dev/null
+++ b/client/src/pages/UnknownError/index.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const UnknownError = () => {
+ console.log(`[${location.pathname}]`);
+
+ return Unknown Error
;
+};
+
+export default UnknownError;
diff --git a/client/src/pages/UserSearch/index.tsx b/client/src/pages/UserSearch/index.tsx
deleted file mode 100644
index 356d07d3..00000000
--- a/client/src/pages/UserSearch/index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react';
-
-const UserSearch = () => {
- return UserSearch
;
-};
-
-export default UserSearch;
diff --git a/client/src/queryKeyCreator.ts b/client/src/queryKeyCreator.ts
new file mode 100644
index 00000000..31e04bce
--- /dev/null
+++ b/client/src/queryKeyCreator.ts
@@ -0,0 +1,22 @@
+const directMessageQueryKey = {
+ all: ['directMessages'] as const,
+ list: () => [...directMessageQueryKey.all] as const,
+ detail: (id: string) => [...directMessageQueryKey.all, id] as const,
+} as const;
+
+const queryKeyCreator = {
+ me: () => ['me'] as const,
+ signUp: () => ['signUp'] as const,
+ signIn: () => ['signIn'] as const,
+ followings: (): [string] => ['followings'],
+ followers: (): [string] => ['followers'],
+ reissueToken: () => ['reissueToken'] as const,
+ userSearch: (filter: string) => ['userSearch', { filter }],
+ directMessage: directMessageQueryKey,
+} as const;
+
+export default queryKeyCreator;
+
+type QueryKeyCreatorType = typeof queryKeyCreator;
+export type QueryKeyCreator =
+ QueryKeyCreatorType[T];
diff --git a/client/src/stores/modalSlice.ts b/client/src/stores/modalSlice.ts
new file mode 100644
index 00000000..4fca1f10
--- /dev/null
+++ b/client/src/stores/modalSlice.ts
@@ -0,0 +1,15 @@
+import type { StateCreator } from 'zustand';
+
+export interface ModalSlice {
+ createCommunityModal: {
+ open: boolean;
+ };
+}
+
+export const createModalSlice: StateCreator = (
+ set,
+) => ({
+ createCommunityModal: {
+ open: false,
+ },
+});
diff --git a/client/src/stores/rootStore.ts b/client/src/stores/rootStore.ts
new file mode 100644
index 00000000..f4421b24
--- /dev/null
+++ b/client/src/stores/rootStore.ts
@@ -0,0 +1,14 @@
+import type { ModalSlice } from '@stores/modalSlice';
+
+import store from 'zustand';
+import { devtools } from 'zustand/middleware';
+
+import { createModalSlice } from './modalSlice';
+
+export type Store = ModalSlice;
+
+export const useStore = store()(
+ devtools((...a) => ({
+ ...createModalSlice(...a),
+ })),
+);
diff --git a/client/src/stores/tokenStore.ts b/client/src/stores/tokenStore.ts
index b13ad94a..854fdd91 100644
--- a/client/src/stores/tokenStore.ts
+++ b/client/src/stores/tokenStore.ts
@@ -9,7 +9,7 @@ type TokenStore = {
export const tokenStore = createVanillaStore()(
devtools((set) => ({
- accessToken: null,
+ accessToken: process.env.NODE_ENV === 'development' ? 'null' : null,
setAccessToken: (newAccessToken) =>
set(() => ({ accessToken: newAccessToken })),
})),
diff --git a/client/src/types/apis/process.d.ts b/client/src/types/apis/process.d.ts
new file mode 100644
index 00000000..cac93912
--- /dev/null
+++ b/client/src/types/apis/process.d.ts
@@ -0,0 +1,6 @@
+declare namespace NodeJS {
+ interface ProcessEnv {
+ NODE_ENV?: string;
+ API_URL?: string;
+ }
+}
diff --git a/client/src/types/apis/response.ts b/client/src/types/apis/response.ts
new file mode 100644
index 00000000..4d63b12f
--- /dev/null
+++ b/client/src/types/apis/response.ts
@@ -0,0 +1,10 @@
+export interface SuccessResponse {
+ statusCode: number;
+ result: T;
+}
+
+export interface ErrorResponse {
+ statusCode: number;
+ message: string | string[];
+ error: string;
+}
diff --git a/client/src/utils/axios.ts b/client/src/utils/axios.ts
new file mode 100644
index 00000000..b78d783e
--- /dev/null
+++ b/client/src/utils/axios.ts
@@ -0,0 +1,48 @@
+import { API_URL } from '@constants/url';
+import { tokenStore } from '@stores/tokenStore';
+import axios from 'axios';
+
+const { getState } = tokenStore;
+
+/**
+ * ## Asnity api server 전용 Axios instance
+ * - `baseURL`은 Asnity server 이다. 따라서 엔드포인트 작성시 `baseURL`이후 부분만 적는다.
+ * - Api 요청시 전역 상태에서 관리하는 accessToken을 Authorization header에 삽입하고 보낸다.
+ * - accessToken이 없다면 요청 Promise가 Reject된다.
+ * - 토큰 만료 응답시 Response interceptors에서 재발급 후 Request 재요청 하는 로직은 추후에 추가할 예정.
+ */
+export const tokenAxios = axios.create({
+ baseURL: API_URL,
+});
+
+/**
+ * ## Asnity api server 전용 Axios instance
+ * - `baseURL`은 Asnity server 이다. 따라서 엔드포인트 작성시 `baseURL`이후 부분만 적는다.
+ * - accessToken이 필요없는 요청을 보낼 때 사용한다.
+ */
+export const publicAxios = axios.create({
+ baseURL: API_URL,
+});
+
+tokenAxios.interceptors.request.use(
+ (config) => {
+ const { accessToken } = getState();
+
+ console.warn('tokenAxios 사용 확인용 로그. 무시하시면 됩니다.');
+
+ if (!accessToken) {
+ console.warn(`accessToken이 없습니다.`);
+ return Promise.reject(
+ `tokenAxios instance로 요청을 보내기 위해서는 accessToken이 필요합니다.`,
+ );
+ }
+
+ config.headers = {
+ Authorization: `Bearer ${accessToken}`,
+ };
+ return config;
+ },
+ function (error) {
+ return Promise.reject(error);
+ },
+);
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 929f22a5..d7bde1a6 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -1,7 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
- "lib": ["DOM"],
+ "esModuleInterop": true,
+ "moduleResolution": "Node",
+ "lib": ["DOM", "es2015.iterable"],
"jsx": "react-jsx",
"baseUrl": ".",
"outDir": "./build",
@@ -14,11 +16,13 @@
"@icons/*": ["src/assets/icons/*"],
"@constants/*": ["src/constants/*"],
"@apis/*": ["src/apis/*"],
+ "@hooks/*": ["src/hooks/*"],
+ "@errors/*": ["src/errors/*"],
+ "@@types/*": ["src/types/*"],
+ "@/*": ["src/*"]
}
},
"include": ["src", "config"],
"exclude": ["node_modules", "build", "dist"],
- "references": [
- { "path": "../shared" }
- ]
-}
\ No newline at end of file
+ "references": [{ "path": "../shared" }]
+}
diff --git a/shared/lib/user.ts b/shared/lib/user.ts
index 2b1bf864..ba778278 100644
--- a/shared/lib/user.ts
+++ b/shared/lib/user.ts
@@ -1,15 +1,10 @@
+export type UserStatus = 'online' | 'offline' | 'afk';
+
export interface User {
_id: string;
id: string;
nickname: string;
- status: string;
+ status: UserStatus;
profileUrl: string;
descrption: string;
}
-
-export interface GetUsersReponse {
- statusCode: number;
- result: {
- users: User[];
- };
-}
diff --git a/yarn.lock b/yarn.lock
index f9ae5416..ad0153df 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1844,6 +1844,11 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
+"@faker-js/faker@^7.6.0":
+ version "7.6.0"
+ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-7.6.0.tgz#9ea331766084288634a9247fcd8b84f16ff4ba07"
+ integrity sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==
+
"@graphql-tools/merge@8.3.11":
version "8.3.11"
resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.11.tgz#f5eab764e8d7032c1b7e32d5dc6dea5b2f5bb21e"
@@ -3223,6 +3228,13 @@
dependencies:
"@types/react" "*"
+"@types/react-modal@^3.13.1":
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-3.13.1.tgz#5b9845c205fccc85d9a77966b6e16dc70a60825a"
+ integrity sha512-iY/gPvTDIy6Z+37l+ibmrY+GTV4KQTHcCyR5FIytm182RQS69G5ps4PH2FxtC7bAQ2QRHXMevsBgck7IQruHNg==
+ dependencies:
+ "@types/react" "*"
+
"@types/react@*", "@types/react@^18.0.25":
version "18.0.25"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.25.tgz#8b1dcd7e56fe7315535a4af25435e0bb55c8ae44"
@@ -3674,6 +3686,11 @@ acorn@^8.0.4, acorn@^8.1.0, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
+add-px-to-style@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a"
+ integrity sha512-YMyxSlXpPjD8uWekCQGuN40lV4bnZagUwqa2m/uFv1z/tNImSk9fnXVMUI5qwME/zzI3MMQRvjZ+69zyfSSyew==
+
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -5157,6 +5174,15 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"
+dom-css@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/dom-css/-/dom-css-2.1.0.tgz#fdbc2d5a015d0a3e1872e11472bbd0e7b9e6a202"
+ integrity sha512-w9kU7FAbaSh3QKijL6n59ofAhkkmMJ31GclJIz/vyQdjogfyxcB6Zf8CZyibOERI5o0Hxz30VmJS7+7r5fEj2Q==
+ dependencies:
+ add-px-to-style "1.0.0"
+ prefix-style "2.0.1"
+ to-camel-case "1.0.0"
+
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
@@ -5808,6 +5834,11 @@ execa@^5.0.0:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
+exenv@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
+ integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==
+
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -8151,7 +8182,7 @@ logform@^2.3.2, logform@^2.4.0:
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -9006,6 +9037,11 @@ pause@0.0.1:
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+ integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
+
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -9330,6 +9366,11 @@ postcss@^8.4.17, postcss@^8.4.18, postcss@^8.4.19:
picocolors "^1.0.0"
source-map-js "^1.0.2"
+prefix-style@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/prefix-style/-/prefix-style-2.0.1.tgz#66bba9a870cfda308a5dc20e85e9120932c95a06"
+ integrity sha512-gdr1MBNVT0drzTq95CbSNdsrBDoHGlb2aDJP/FoY+1e+jSDPOb1Cv554gH2MGiSr2WTcXi/zu+NaFzfcHQkfBQ==
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -9406,7 +9447,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.8.1:
+prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -9473,6 +9514,13 @@ quick-lru@^5.1.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+raf@^3.1.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+ integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
+ dependencies:
+ performance-now "^2.1.0"
+
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -9495,6 +9543,15 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
+react-custom-scrollbars-2@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/react-custom-scrollbars-2/-/react-custom-scrollbars-2-4.5.0.tgz#cff18e7368bce9d570aea0be780045eda392c745"
+ integrity sha512-/z0nWAeXfMDr4+OXReTpYd1Atq9kkn4oI3qxq3iMXGQx1EEfwETSqB8HTAvg1X7dEqcCachbny1DRNGlqX5bDQ==
+ dependencies:
+ dom-css "^2.0.0"
+ prop-types "^15.5.10"
+ raf "^3.1.0"
+
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -9523,6 +9580,21 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
+react-lifecycles-compat@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+ integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+
+react-modal@^3.16.1:
+ version "3.16.1"
+ resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b"
+ integrity sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==
+ dependencies:
+ exenv "^1.2.0"
+ prop-types "^15.7.2"
+ react-lifecycles-compat "^3.0.0"
+ warning "^4.0.3"
+
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
@@ -10638,11 +10710,23 @@ tmpl@1.0.5:
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
+to-camel-case@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46"
+ integrity sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==
+ dependencies:
+ to-space-case "^1.0.0"
+
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+to-no-case@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a"
+ integrity sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==
+
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -10650,6 +10734,13 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
+to-space-case@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17"
+ integrity sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==
+ dependencies:
+ to-no-case "^1.0.0"
+
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
@@ -11011,6 +11102,13 @@ walker@^1.0.8:
dependencies:
makeerror "1.0.12"
+warning@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"