From 349bb7b42c6fcbee4d0ed2fff28c41157f44d9c5 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Mon, 8 Apr 2024 19:57:36 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[#39]=20feat:=20recoil,=20recoil-persist=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 34 ++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 36 insertions(+) diff --git a/package-lock.json b/package-lock.json index 41db090..8c7d1a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,8 @@ "react-dom": "^18", "react-hook-form": "^7.51.1", "react-use-pagination": "^2.0.1", + "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "yup": "^1.4.0" }, "devDependencies": { @@ -4891,6 +4893,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -6518,6 +6525,33 @@ "node": ">=8.10.0" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/recoil-persist": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-5.1.0.tgz", + "integrity": "sha512-sew4k3uBVJjRWKCSFuBw07Y1p1pBOb0UxLJPxn4G2bX/9xNj+r2xlqYy/BRfyofR/ANfqBU04MIvulppU4ZC0w==", + "peerDependencies": { + "recoil": "^0.7.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", diff --git a/package.json b/package.json index 1283035..6a4ac3c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "react-dom": "^18", "react-hook-form": "^7.51.1", "react-use-pagination": "^2.0.1", + "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "yup": "^1.4.0" }, "devDependencies": { From 5a7369ca0b47a49125bc368b49e105c8ec74fc6a Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Mon, 8 Apr 2024 19:58:14 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[#38]=20feat:=20=EB=82=B4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 디자인은 아직 미구현 상태입니다. --- src/app/profile/page.tsx | 5 ++ src/containers/profile/Profile.module.css | 0 src/containers/profile/Profile.tsx | 70 +++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/app/profile/page.tsx create mode 100644 src/containers/profile/Profile.module.css create mode 100644 src/containers/profile/Profile.tsx diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx new file mode 100644 index 0000000..81dcad6 --- /dev/null +++ b/src/app/profile/page.tsx @@ -0,0 +1,5 @@ +import Profile from "@/containers/profile/Profile"; + +export default function ProfilePage() { + return ; +} diff --git a/src/containers/profile/Profile.module.css b/src/containers/profile/Profile.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/containers/profile/Profile.tsx b/src/containers/profile/Profile.tsx new file mode 100644 index 0000000..56c8446 --- /dev/null +++ b/src/containers/profile/Profile.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { ResponseAdmin, getAdminName } from "@/services/admin/admin"; +import styles from "./Profile.module.css"; +import { Spinner } from "@nextui-org/react"; + +export default function Profile() { + const [accessToken, setAccessToken] = useState(null); + const [admin, setAdmin] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem("accessToken"); + if (token) { + setIsLoading(true); + setAccessToken(token); + + const response = getAdminName(); + response + .then((data) => { + setAdmin(data || null); + setIsLoading(false); + }) + .catch((error) => { + console.error("[Error] 관리자 정보 불러오기 실패:", error); + setIsLoading(false); + }); + } else { + setIsLoading(false); + } + }, [accessToken]); + + return ( + <> + {isLoading && ( +
+ +
+ )} + + {!isLoading && ( +
+
+ {accessToken ? ( +
+ {admin ? ( +
+
+

{admin.name}님 환영합니다!

+

관리자 페이지입니다.

+
+
+ ) : ( +
+

관리자 정보를 불러오는 중입니다.

+
+ )} +
+ ) : ( +
+

로그인이 필요합니다.

+
+ )} +
+
+ )} + + ); +} From 83364f299272fad0b2af7a43d69e5c3eba8aba88 Mon Sep 17 00:00:00 2001 From: Yehyeok Bang Date: Mon, 8 Apr 2024 19:59:06 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[#39]=20refactor:=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EC=86=8D?= =?UTF-8?q?=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에는 화면이 그려질 때마다 API를 호출했었습니다. - 관리자 정보를 전역 상태로 관리하여 API 호출을 최소화하여 속도를 개선합니다. --- src/app/layout.tsx | 13 +- src/components/nav/Nav.module.css | 23 +- src/components/nav/Nav.tsx | 1 - src/containers/login/Login.tsx | 7 +- src/containers/main/Main.module.css | 11 +- src/containers/main/Main.tsx | 96 ++++--- .../policy/bill/BillPolicy.module.css | 11 +- src/containers/policy/bill/BillPolicy.tsx | 250 +++++++++++++----- src/containers/support/Support.module.css | 7 + src/containers/support/Support.tsx | 177 ++++++++++--- src/services/admin/admin.ts | 6 +- src/services/login/login.ts | 2 +- src/states/admin.ts | 15 ++ src/states/token.ts | 20 ++ src/utils/RecoilRootProvider.tsx | 11 + 15 files changed, 473 insertions(+), 177 deletions(-) create mode 100644 src/states/admin.ts create mode 100644 src/states/token.ts create mode 100644 src/utils/RecoilRootProvider.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3206e06..5cdd244 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { Providers } from "./providers"; +import RecoilRootProvider from "@/utils/RecoilRootProvider"; const inter = Inter({ subsets: ["latin"] }); @@ -16,10 +17,12 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - {children} - - + + + + {children} + + + ); } diff --git a/src/components/nav/Nav.module.css b/src/components/nav/Nav.module.css index 8b36308..e9d11b0 100644 --- a/src/components/nav/Nav.module.css +++ b/src/components/nav/Nav.module.css @@ -39,7 +39,6 @@ } .text_button { - padding: 8px 16px; box-sizing: border-box; border-radius: 4px; font-style: normal; @@ -52,26 +51,36 @@ border: none; } -.text_button:hover { - font-weight: 600; -} - .button { - padding: 8px 23px; + padding-top: 0.7rem; + padding-bottom: 0.7rem; border-radius: 4px; font-style: normal; - width: 150px; font-weight: 500; font-size: 16px; line-height: 30px; text-align: center; cursor: pointer; + width: 8rem; + min-width: 8rem; border: none; background-color: #e4d5fe; color: #926dff; } +.text_button p { + padding: 1rem; +} + +.text_button a { + padding: 1rem; +} + +.button a { + padding: 1rem 2rem 1rem 2rem; +} + .button:hover { background-color: #D9C3FF; } \ No newline at end of file diff --git a/src/components/nav/Nav.tsx b/src/components/nav/Nav.tsx index 73ed603..83c994a 100644 --- a/src/components/nav/Nav.tsx +++ b/src/components/nav/Nav.tsx @@ -5,7 +5,6 @@ import { NavbarContent, NavbarItem, Link, - Button, } from "@nextui-org/react"; import styles from "./Nav.module.css"; diff --git a/src/containers/login/Login.tsx b/src/containers/login/Login.tsx index f481c57..46b3a80 100644 --- a/src/containers/login/Login.tsx +++ b/src/containers/login/Login.tsx @@ -7,8 +7,12 @@ import Spacer from "@/components/Spacer"; import styles from "./Login.module.css"; import Link from "next/link"; import { login } from "@/services/login/login"; +import { useSetRecoilState } from "recoil"; +import { tokenState } from "@/states/token"; export default function Login() { + const setAccessToken = useSetRecoilState(tokenState); + const validationSchema = yup.object().shape({ id: yup .string() @@ -40,8 +44,9 @@ export default function Login() { console.log(response); if (response) { + setAccessToken(response.token); localStorage.setItem("accessToken", response.token); - window.location.href = "/"; + location.href = "/"; } else { alert("아이디와 비밀번호를 확인해주세요."); } diff --git a/src/containers/main/Main.module.css b/src/containers/main/Main.module.css index 7fef6d9..6a1968e 100644 --- a/src/containers/main/Main.module.css +++ b/src/containers/main/Main.module.css @@ -73,7 +73,16 @@ } .content1_1 { - padding: 8rem; + padding: 8rem 4rem 8rem 4rem; + display: flex; + flex-direction: column; + gap: 35px; +} + +.content1_2 { + min-width: 500px; + padding-right: 4rem; + padding-left: 4rem; display: flex; flex-direction: column; gap: 35px; diff --git a/src/containers/main/Main.tsx b/src/containers/main/Main.tsx index d74909a..60aaeec 100644 --- a/src/containers/main/Main.tsx +++ b/src/containers/main/Main.tsx @@ -1,5 +1,7 @@ +/* eslint-disable react-hooks/exhaustive-deps */ "use client"; +import React from "react"; import Spacer from "@/components/Spacer"; import PositiveButton from "@/components/buttons/PositiveButton"; import Footer from "@/components/footer/Footer"; @@ -8,7 +10,8 @@ import styles from "./Main.module.css"; import Image from "next/image"; import Link from "next/link"; import { useState, useEffect } from "react"; -import { ResponseAdmin, getAdminName } from "@/services/admin/admin"; +import { getAdminName } from "@/services/admin/admin"; +import { logout } from "@/services/login/login"; import { Dropdown, DropdownTrigger, @@ -16,32 +19,46 @@ import { DropdownItem, Spinner, } from "@nextui-org/react"; -import { logout } from "@/services/login/login"; +import { useRecoilValue, useRecoilState } from "recoil"; +import { tokenState } from "@/states/token"; +import { adminState } from "@/states/admin"; export default function Main() { - const [accessToken, setAccessToken] = useState(null); - const [admin, setAdmin] = useState(null); + const accessToken = useRecoilValue(tokenState); + const [admin, setAdmin] = useRecoilState(adminState); const [isLoading, setIsLoading] = useState(true); useEffect(() => { - const token = localStorage.getItem("accessToken"); - if (token) { - setIsLoading(true); - setAccessToken(token); - - const response = getAdminName(); - response - .then((data) => { - setAdmin(data || null); - setIsLoading(false); - }) - .catch((error) => { - console.error("[Error] 관리자 정보 불러오기 실패:", error); - setIsLoading(false); - }); - } else { + if (!accessToken) { + setIsLoading(false); + return; + } + + if (admin?.id != null) { setIsLoading(false); + return; } + + const response = getAdminName(); + response + .then((data) => { + setAdmin( + data + ? { + id: data.id, + name: data.name, + phoneNumber: data.phoneNumber, + } + : null + ); + setIsLoading(false); + }) + .catch((error) => { + console.error("[Error] 관리자 정보 불러오기 실패:", error); + setIsLoading(false); + }); + + setIsLoading(true); }, [accessToken]); function requestLogout() { @@ -60,23 +77,19 @@ export default function Main() { {!isLoading && (
- {accessToken ? ( -
@@ -136,6 +145,7 @@ export default function Main() { alt="Image of something relevant" width={500} height={250} + className={styles.content1_2} />
diff --git a/src/containers/policy/bill/BillPolicy.module.css b/src/containers/policy/bill/BillPolicy.module.css index 6b02406..da7a3b1 100644 --- a/src/containers/policy/bill/BillPolicy.module.css +++ b/src/containers/policy/bill/BillPolicy.module.css @@ -2,6 +2,13 @@ padding-top: 2rem; } +.loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + .content { height: calc(100vh - 263px - 84px); } @@ -61,10 +68,6 @@ color: #7f17ab; } -.text_button { - color: #000000; -} - .button { color: #926dff; } \ No newline at end of file diff --git a/src/containers/policy/bill/BillPolicy.tsx b/src/containers/policy/bill/BillPolicy.tsx index eb27368..2edc400 100644 --- a/src/containers/policy/bill/BillPolicy.tsx +++ b/src/containers/policy/bill/BillPolicy.tsx @@ -1,90 +1,194 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +"use client"; + import Spacer from "@/components/Spacer"; import Footer from "@/components/footer/Footer"; import Nav from "@/components/nav/Nav"; -import { Card, CardHeader, CardBody, Divider, Link } from "@nextui-org/react"; + +import React, { useState, useEffect } from "react"; +import { getAdminName } from "@/services/admin/admin"; +import { useRecoilState } from "recoil"; +import { logout } from "@/services/login/login"; +import { + Card, + CardHeader, + CardBody, + Divider, + Dropdown, + DropdownTrigger, + DropdownMenu, + DropdownItem, + Spinner, + Link, +} from "@nextui-org/react"; import styles from "./BillPolicy.module.css"; +import { adminState } from "@/states/admin"; export default function BillPolicy() { + const [accessToken, setAccessToken] = useState(null); + const [admin, setAdmin] = useRecoilState(adminState); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (!accessToken) { + setIsLoading(false); + return; + } + + if (admin?.id != null) { + setIsLoading(false); + return; + } + + const response = getAdminName(); + response + .then((data) => { + setAdmin( + data + ? { + id: data.id, + name: data.name, + phoneNumber: data.phoneNumber, + } + : null + ); + setIsLoading(false); + }) + .catch((error) => { + console.error("[Error] 관리자 정보 불러오기 실패:", error); + setIsLoading(false); + }); + + setIsLoading(true); + }, [accessToken]); + + function requestLogout() { + logout(); + window.location.href = "/"; + } + return ( <>
-