From 144ca8c695cd83db74ae59a010b10bb9f361581f Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 10:16:17 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[feat]=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20Container=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/App.jsx | 3 ++- src/adminPage/pages/LoginPage.jsx | 9 +++++++++ src/adminPage/shared/components/Container.jsx | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/adminPage/pages/LoginPage.jsx create mode 100644 src/adminPage/shared/components/Container.jsx diff --git a/src/adminPage/App.jsx b/src/adminPage/App.jsx index 68a41729..213d0a2c 100644 --- a/src/adminPage/App.jsx +++ b/src/adminPage/App.jsx @@ -1,4 +1,5 @@ import { Route, Routes } from "react-router-dom"; +import LoginPage from "./pages/LoginPage.jsx"; function App() { return ( @@ -13,7 +14,7 @@ function App() { 이벤트 목록 화면} /> 기대평 화면} /> 기대평 검색 화면} /> - 로그인 화면} /> + } /> hello} /> diff --git a/src/adminPage/pages/LoginPage.jsx b/src/adminPage/pages/LoginPage.jsx new file mode 100644 index 00000000..4f5632e8 --- /dev/null +++ b/src/adminPage/pages/LoginPage.jsx @@ -0,0 +1,9 @@ +import Container from "@admin/components/Container.jsx"; +function LoginPage() +{ + return +
컨테이너 테스트
+
+} + +export default LoginPage; \ No newline at end of file diff --git a/src/adminPage/shared/components/Container.jsx b/src/adminPage/shared/components/Container.jsx new file mode 100644 index 00000000..a50abaa8 --- /dev/null +++ b/src/adminPage/shared/components/Container.jsx @@ -0,0 +1,11 @@ +function Container({children}) +{ + return
+
내비게이션 바
+
+ {children} +
+
+} + +export default Container; \ No newline at end of file From 00911162e310bb8081f2850c638f900a994efaf1 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 10:26:34 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[feat]=20gnb=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/pages/LoginPage.jsx | 2 +- src/adminPage/shared/components/Container.jsx | 4 +++- src/adminPage/shared/components/NavBar.jsx | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/adminPage/shared/components/NavBar.jsx diff --git a/src/adminPage/pages/LoginPage.jsx b/src/adminPage/pages/LoginPage.jsx index 4f5632e8..86e758b7 100644 --- a/src/adminPage/pages/LoginPage.jsx +++ b/src/adminPage/pages/LoginPage.jsx @@ -2,7 +2,7 @@ import Container from "@admin/components/Container.jsx"; function LoginPage() { return -
컨테이너 테스트
+
컨테이너 테스트
} diff --git a/src/adminPage/shared/components/Container.jsx b/src/adminPage/shared/components/Container.jsx index a50abaa8..6144b00d 100644 --- a/src/adminPage/shared/components/Container.jsx +++ b/src/adminPage/shared/components/Container.jsx @@ -1,7 +1,9 @@ +import NavBar from "./NavBar.jsx"; + function Container({children}) { return
-
내비게이션 바
+
{children}
diff --git a/src/adminPage/shared/components/NavBar.jsx b/src/adminPage/shared/components/NavBar.jsx new file mode 100644 index 00000000..9c13bf91 --- /dev/null +++ b/src/adminPage/shared/components/NavBar.jsx @@ -0,0 +1,16 @@ +import { Link } from "react-router-dom"; + +function NavBar() +{ + return +} + + +export default NavBar; \ No newline at end of file From 853a9ca148d4de798855ad7b56b9c2e8d8f832f0 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 11:11:11 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[feat]=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=EB=B0=94=20=EA=B0=81=EA=B0=81=EC=9D=98=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=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 --- src/adminPage/App.jsx | 3 ++- src/adminPage/pages/EventsPage.jsx | 9 ++++++++ src/adminPage/shared/components/NavBar.jsx | 10 ++++---- .../shared/components/NavBarItem.jsx | 23 +++++++++++++++++++ 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 src/adminPage/pages/EventsPage.jsx create mode 100644 src/adminPage/shared/components/NavBarItem.jsx diff --git a/src/adminPage/App.jsx b/src/adminPage/App.jsx index 213d0a2c..77cb89ee 100644 --- a/src/adminPage/App.jsx +++ b/src/adminPage/App.jsx @@ -1,5 +1,6 @@ import { Route, Routes } from "react-router-dom"; import LoginPage from "./pages/LoginPage.jsx"; +import EventsPage from "./pages/EventsPage.jsx"; function App() { return ( @@ -11,7 +12,7 @@ function App() { element={
event 생성 화면
} /> event 보는 화면
} /> - 이벤트 목록 화면} /> + } /> 기대평 화면} /> 기대평 검색 화면} /> } /> diff --git a/src/adminPage/pages/EventsPage.jsx b/src/adminPage/pages/EventsPage.jsx new file mode 100644 index 00000000..413464e9 --- /dev/null +++ b/src/adminPage/pages/EventsPage.jsx @@ -0,0 +1,9 @@ +import Container from "@admin/components/Container.jsx"; +function EventsPage() +{ + return +
이벤트 테스트
+
+} + +export default EventsPage; \ No newline at end of file diff --git a/src/adminPage/shared/components/NavBar.jsx b/src/adminPage/shared/components/NavBar.jsx index 9c13bf91..a61c6792 100644 --- a/src/adminPage/shared/components/NavBar.jsx +++ b/src/adminPage/shared/components/NavBar.jsx @@ -1,13 +1,13 @@ -import { Link } from "react-router-dom"; +import NavBarItem from "./NavBarItem.jsx"; function NavBar() { return } diff --git a/src/adminPage/shared/components/NavBarItem.jsx b/src/adminPage/shared/components/NavBarItem.jsx new file mode 100644 index 00000000..94bee2f0 --- /dev/null +++ b/src/adminPage/shared/components/NavBarItem.jsx @@ -0,0 +1,23 @@ +import {NavLink} from "react-router-dom"; + +function NavBarItem({to, onClick, children}) +{ + const commonStyle = `w-full h-full flex justify-center items-center + hover:border-2 hover:border-white + active:border-2 active:border-neutral-400`; + + const commonTextStyle = `text-neutral-200 hover:text-white active:text-neutral-400 invalid:text-neutral-600`; + + const navLink = `${commonStyle} ${isActive ? "text-blue-400" : commonTextStyle}` }> + {children} + ; + + const button = ; + + return
  • + {to ? navLink : button} +
  • +} + +export default NavBarItem; \ No newline at end of file From 14156d0c62c42c235f98d129b0018579c8e8d9e8 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 11:38:10 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[feat]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/pages/LoginPage.jsx | 3 ++- src/adminPage/shared/auth/LoginSection.jsx | 29 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/adminPage/shared/auth/LoginSection.jsx diff --git a/src/adminPage/pages/LoginPage.jsx b/src/adminPage/pages/LoginPage.jsx index 86e758b7..5db26c38 100644 --- a/src/adminPage/pages/LoginPage.jsx +++ b/src/adminPage/pages/LoginPage.jsx @@ -1,8 +1,9 @@ import Container from "@admin/components/Container.jsx"; +import LoginSection from "@admin/auth/LoginSection.jsx"; function LoginPage() { return -
    컨테이너 테스트
    +
    } diff --git a/src/adminPage/shared/auth/LoginSection.jsx b/src/adminPage/shared/auth/LoginSection.jsx new file mode 100644 index 00000000..ddd96f71 --- /dev/null +++ b/src/adminPage/shared/auth/LoginSection.jsx @@ -0,0 +1,29 @@ +import { useState } from "react"; + +import Input from "@common/components/Input.jsx"; +import Button from "@common/components/Button.jsx"; + +function LoginSection() +{ + const [id, setId] = useState(""); + const [password, setPassword] = useState(""); + function onSubmit(e) + { + e.preventDefault(); + } + return
    + + + +
    +} + +export default LoginSection; \ No newline at end of file From bf785d27e7647fc51782b853bf4dacc149aa1f0d Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 11:42:52 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[design]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8F=BC=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/shared/auth/LoginSection.jsx | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/adminPage/shared/auth/LoginSection.jsx b/src/adminPage/shared/auth/LoginSection.jsx index ddd96f71..25ac8ae9 100644 --- a/src/adminPage/shared/auth/LoginSection.jsx +++ b/src/adminPage/shared/auth/LoginSection.jsx @@ -11,17 +11,19 @@ function LoginSection() { e.preventDefault(); } - return
    - - + return +
    + + +
    } From 7468b900239966f8fdeb6cd3d70d2aa2a0eb365a Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 13:41:09 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[feat]=20fetchServer=EC=97=90=20=EB=AF=B8?= =?UTF-8?q?=EB=93=A4=EC=9B=A8=EC=96=B4=20=EC=B6=94=EA=B0=80,=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/dataFetch/fetchServer.js | 81 ++++++++++++++++---- src/common/dataFetch/initLogoutMiddleware.js | 16 ++++ src/mainPage/App.jsx | 4 +- src/mainPage/features/fcfs/mock.js | 2 + 4 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/common/dataFetch/initLogoutMiddleware.js diff --git a/src/common/dataFetch/fetchServer.js b/src/common/dataFetch/fetchServer.js index ca2c8311..ae9dd42a 100644 --- a/src/common/dataFetch/fetchServer.js +++ b/src/common/dataFetch/fetchServer.js @@ -15,7 +15,9 @@ class ServerCloseError extends Error { } } -function fetchServer(url, options = {}) { +// fetchServer의 옵션을 생성합니다. +function createFetchOptions(options = {}) +{ // 기본적으로 옵션을 그대로 가져오지만, body가 존재하고 header.content-type을 설정하지 않는다면 // json으로 간주하여 option을 생성합니다. const fetchOptions = { ...options }; @@ -40,25 +42,72 @@ function fetchServer(url, options = {}) { }; } - const promise = fetch(url, fetchOptions) - .then((e) => { - if (e.status >= 400 && e.status <= 599) throw new HTTPError(e); - return e; - }) - .then((e) => e.json()) - .catch(async (e) => { - if (e instanceof HTTPError) { - e.data = await e.response.json(); - } - if (e instanceof TypeError && e.message === "Failed to fetch") { - throw new ServerCloseError(); - } - throw e; - }); + return fetchOptions; +} + +// 기본적인 fetchServer의 동작을 수행합니다. +async function fetchServerBase(url, options = {}) { + try { + const response = await fetch(url, createFetchOptions(options)); + if (response.status >= 400 && response.status <= 599) throw new HTTPError(response); + return await response.json(); + } + catch( e ) + { + if( e instanceof HTTPError ) { + e.data = await e.response.json(); + } + if( e instanceof TypeError && e.message === "Failed to fetch" ) { + throw new ServerCloseError(); + } + throw e; + } +} + +// fetchServer의 동작을 수행한 뒤, 미들웨어를 차례대로 실행시킵니다. +async function fetchServer(url, options = {}) { + let promise = fetchServerBase(url, options); + if(fetchServer.middlewares.size === 0) return promise; + + let shouldProgress = false; + const next = ()=>shouldProgress = true; + + for(let middleware of fetchServer.middlewares) + { + try { + const value = await promise; + promise = (async () => { + const result = await middleware({value}, next); + return result ?? value; + })(); + if(!shouldProgress) return value; + } + catch(error) { + promise = (async () => { + const result = await middleware({error}, next); + if(result !== undefined && result !== null) return result; + throw error; + })(); + if(!shouldProgress) throw error; + } + + shouldProgress = false; + } return promise; } +// 미들웨어를 등록시키고 제거합니다. +fetchServer.middlewares = new Set(); +fetchServer.use = function(middleware) +{ + fetchServer.middlewares.add( middleware ); +} +fetchServer.unuse = function(middleware) +{ + fetchServer.middlewares.delete( middleware ); +} + function handleError(errorDescriptor) { return (error) => { if (error instanceof HTTPError) { diff --git a/src/common/dataFetch/initLogoutMiddleware.js b/src/common/dataFetch/initLogoutMiddleware.js new file mode 100644 index 00000000..f2b5fa63 --- /dev/null +++ b/src/common/dataFetch/initLogoutMiddleware.js @@ -0,0 +1,16 @@ +import { useEffect } from "react"; +import { fetchServer, HTTPError } from "@common/dataFetch/fetchServer.js"; + +export default function useLogoutMiddleware(logout) { + useEffect( ()=>{ + function middleware( {error}, next ) { + if(error instanceof HTTPError && error.status === 401) { + logout(); + } + next(); + } + + fetchServer.use( middleware ); + return ()=>fetchServer.unuse( middleware ); + }, [logout] ); +} \ No newline at end of file diff --git a/src/mainPage/App.jsx b/src/mainPage/App.jsx index a57160e1..fd7e1720 100644 --- a/src/mainPage/App.jsx +++ b/src/mainPage/App.jsx @@ -11,7 +11,8 @@ import QnA from "./features/qna"; import Footer from "./features/footer"; import Modal from "@common/modal/modal.jsx"; -import { initLoginState } from "@main/auth/store.js"; +import { initLoginState, logout } from "@main/auth/store.js"; +import useLogoutMiddleware from "@common/dataFetch/initLogoutMiddleware"; function App() { useEffect(() => { @@ -19,6 +20,7 @@ function App() { history.scrollRestoration = "manual"; initLoginState(); }, []); + useLogoutMiddleware(logout); return ( <> diff --git a/src/mainPage/features/fcfs/mock.js b/src/mainPage/features/fcfs/mock.js index 79da75ee..3acf7dce 100644 --- a/src/mainPage/features/fcfs/mock.js +++ b/src/mainPage/features/fcfs/mock.js @@ -31,6 +31,8 @@ const handlers = [ const token = request.headers.get("authorization"); if (token === null) return HttpResponse.json(false, { status: 401 }); + if (token !== "Bearer test_token") + return HttpResponse.json(false, { status: 401 }); if (typeof eventAnswer !== "number") return HttpResponse.json(false, { status: 400 }); From a60a78b955ee6c63dbbd125abc9f3837cc0b4546 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 14:33:26 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/App.jsx | 35 ++++++++++++++++++-------- src/adminPage/pages/ProtectedRoute.jsx | 19 ++++++++++++++ src/adminPage/pages/RootRoute.jsx | 11 ++++++++ src/adminPage/shared/auth/store.js | 27 ++++++++++++++++++++ 4 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 src/adminPage/pages/ProtectedRoute.jsx create mode 100644 src/adminPage/pages/RootRoute.jsx create mode 100644 src/adminPage/shared/auth/store.js diff --git a/src/adminPage/App.jsx b/src/adminPage/App.jsx index 77cb89ee..c44cb926 100644 --- a/src/adminPage/App.jsx +++ b/src/adminPage/App.jsx @@ -1,22 +1,37 @@ +import { useEffect } from "react"; import { Route, Routes } from "react-router-dom"; import LoginPage from "./pages/LoginPage.jsx"; import EventsPage from "./pages/EventsPage.jsx"; +import ProtectedRoute from "./pages/ProtectedRoute.jsx"; +import RootRoute from "./pages/RootRoute.jsx"; + +import { initLoginState, logout } from "@admin/auth/store.js"; +import useLogoutMiddleware from "@common/dataFetch/initLogoutMiddleware"; function App() { + useEffect(() => { + window.scrollTo(0, 0); + history.scrollRestoration = "manual"; + initLoginState(); + }, []); + useLogoutMiddleware(logout); + return ( <> - event 생성 화면} - /> - event 보는 화면} /> - } /> - 기대평 화면} /> - 기대평 검색 화면} /> + }> + event 생성 화면} + /> + event 보는 화면} /> + } /> + 기대평 화면} /> + 기대평 검색 화면} /> + } /> - hello} /> + } /> ); diff --git a/src/adminPage/pages/ProtectedRoute.jsx b/src/adminPage/pages/ProtectedRoute.jsx new file mode 100644 index 00000000..ba875108 --- /dev/null +++ b/src/adminPage/pages/ProtectedRoute.jsx @@ -0,0 +1,19 @@ +import { useEffect } from "react"; +import { useNavigate, Outlet } from "react-router-dom"; + +import Container from "@admin/components/Container.jsx"; +import useUserStore from "@admin/auth/store.js"; + +function ProtectedRoute() { + const isLogin = useUserStore( store=>store.isLogin ); + const navigate = useNavigate(); + + useEffect( ()=>{ + if (!isLogin) navigate("/login", {replace: true}); + }, [isLogin]); + + if(!isLogin) return + return ; +} + +export default ProtectedRoute; \ No newline at end of file diff --git a/src/adminPage/pages/RootRoute.jsx b/src/adminPage/pages/RootRoute.jsx new file mode 100644 index 00000000..8d80520f --- /dev/null +++ b/src/adminPage/pages/RootRoute.jsx @@ -0,0 +1,11 @@ +import { Navigate, Outlet } from "react-router-dom"; +import useUserStore from "@admin/auth/store.js"; + +function RootRoute() { + const isLogin = useUserStore( store=>store.isLogin ); + + if (isLogin) return ; + return ; +} + +export default RootRoute; \ No newline at end of file diff --git a/src/adminPage/shared/auth/store.js b/src/adminPage/shared/auth/store.js new file mode 100644 index 00000000..2879606f --- /dev/null +++ b/src/adminPage/shared/auth/store.js @@ -0,0 +1,27 @@ +import { create } from "zustand"; +import tokenSaver from "@common/dataFetch/tokenSaver.js"; +import { ADMIN_TOKEN_ID } from "@common/constants.js"; + +const userStore = create(() => ({ + isLogin: false, +})); + +export function login(token) { + tokenSaver.set(token); + userStore.setState(() => ({ isLogin: true })); +} + +export function logout() { + tokenSaver.remove(); + userStore.setState(() => ({ isLogin: false })); +} + +export function initLoginState() { + tokenSaver.init(ADMIN_TOKEN_ID); + const token = tokenSaver.get(ADMIN_TOKEN_ID); + if (token === null) + userStore.setState(() => ({ isLogin: false })); + else userStore.setState(() => ({ isLogin: true })); +} + +export default userStore; From 8da06a219bd99fb39f966005e5aa42f1eb69bdc6 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 14:50:48 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EC=B8=A1=EA=B3=BC=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/mock.js | 3 +- src/adminPage/shared/auth/LoginSection.jsx | 32 ++++++++++++++++++++-- src/adminPage/shared/auth/mock.js | 14 ++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/adminPage/shared/auth/mock.js diff --git a/src/adminPage/mock.js b/src/adminPage/mock.js index b9188b12..4f1446f8 100644 --- a/src/adminPage/mock.js +++ b/src/adminPage/mock.js @@ -1,6 +1,7 @@ import { setupWorker } from "msw/browser"; +import authHandler from "@admin/auth/mock.js"; // mocking은 기본적으로 각 feature 폴더 내의 mock.js로 정의합니다. // 새로운 feature의 mocking을 추가하셨으면, mock.js의 setupWorker 내부 함수에 인자를 spread 연산자를 이용해 추가해주세요. // 예시 : export default setupWorker(...authHandler, ...questionHandler, ...articleHandler); -export default setupWorker(); +export default setupWorker(...authHandler); diff --git a/src/adminPage/shared/auth/LoginSection.jsx b/src/adminPage/shared/auth/LoginSection.jsx index 25ac8ae9..a2c716e3 100644 --- a/src/adminPage/shared/auth/LoginSection.jsx +++ b/src/adminPage/shared/auth/LoginSection.jsx @@ -1,17 +1,40 @@ import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { login } from "./store.js"; +import { fetchServer, handleError } from "@common/dataFetch/fetchServer.js"; import Input from "@common/components/Input.jsx"; import Button from "@common/components/Button.jsx"; +const loginErrorHandler = { + 400: "잘못된 입력입니다!", + 401: "로그인에 실패했습니다!" +} + function LoginSection() { + const navigate = useNavigate(); const [id, setId] = useState(""); const [password, setPassword] = useState(""); - function onSubmit(e) + const [errorMessage, setErrorMessage] = useState(""); + async function onSubmit(e) { e.preventDefault(); + const config = {method: "post", body:{userName: id, password}}; + setErrorMessage(""); + fetchServer("/api/v1/admin/auth/signin", config) + .then( ({token})=>{ + login(token); + navigate("/events", {replace: true}); + } ) + .catch(handleError(loginErrorHandler)) + .catch( e=>{ + setId(""); + setPassword(""); + setErrorMessage(e.message); + } ); } - return
    + return
    - +
    + + {errorMessage} +
    } diff --git a/src/adminPage/shared/auth/mock.js b/src/adminPage/shared/auth/mock.js new file mode 100644 index 00000000..38686c32 --- /dev/null +++ b/src/adminPage/shared/auth/mock.js @@ -0,0 +1,14 @@ +import { http, HttpResponse } from "msw"; + +const handlers = [ + http.post("/api/v1/admin/auth/signin", async ({ request }) => { + const { username, password } = await request.json(); + if (username !== "admin" && password !== "password1!") { + return HttpResponse.json({ return: false }, {status: 401}); + } + + return HttpResponse.json({ token: "test_token" }); + }), +]; + +export default handlers; From e06a2967823ccadf2745e41156076b93b6d32287 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 15:01:50 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[design]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=8B=9C=20=EC=A2=8C?= =?UTF-8?q?=EB=8B=A8=20nav=EB=B0=94=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adminPage/shared/components/NavBar.jsx | 17 ++++++++++++++--- src/adminPage/shared/components/NavBarItem.jsx | 6 +++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/adminPage/shared/components/NavBar.jsx b/src/adminPage/shared/components/NavBar.jsx index a61c6792..a161e20a 100644 --- a/src/adminPage/shared/components/NavBar.jsx +++ b/src/adminPage/shared/components/NavBar.jsx @@ -1,13 +1,24 @@ +import { useNavigate } from "react-router-dom"; + import NavBarItem from "./NavBarItem.jsx"; +import useAuthStore, { logout } from "@admin/auth/store.js"; function NavBar() { + const navigate = useNavigate(); + const isLogin = useAuthStore( store=>store.isLogin ); + function onLogoutClick() + { + logout(); + navigate("/login"); + } + return } diff --git a/src/adminPage/shared/components/NavBarItem.jsx b/src/adminPage/shared/components/NavBarItem.jsx index 94bee2f0..9b778e3d 100644 --- a/src/adminPage/shared/components/NavBarItem.jsx +++ b/src/adminPage/shared/components/NavBarItem.jsx @@ -1,6 +1,6 @@ import {NavLink} from "react-router-dom"; -function NavBarItem({to, onClick, children}) +function NavBarItem({to, onClick, disabled, children}) { const commonStyle = `w-full h-full flex justify-center items-center hover:border-2 hover:border-white @@ -9,11 +9,11 @@ function NavBarItem({to, onClick, children}) const commonTextStyle = `text-neutral-200 hover:text-white active:text-neutral-400 invalid:text-neutral-600`; const navLink = `${commonStyle} ${isActive ? "text-blue-400" : commonTextStyle}` }> + className={ ({isActive})=>`${commonStyle} ${disabled ? "pointer-events-none text-neutral-600": ""} ${isActive ? "text-blue-400" : commonTextStyle}` }> {children} ; - const button = ; + const button = ; return
  • {to ? navLink : button} From ee027c6c1b3b07d72ddb739fc3f91003859fbf4f Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 13 Aug 2024 15:09:57 +0900 Subject: [PATCH 10/10] [chore] linting&prettier --- src/adminPage/pages/EventsPage.jsx | 13 +- src/adminPage/pages/LoginPage.jsx | 13 +- src/adminPage/pages/ProtectedRoute.jsx | 12 +- src/adminPage/pages/RootRoute.jsx | 6 +- src/adminPage/shared/auth/LoginSection.jsx | 111 +++++++++++------- src/adminPage/shared/auth/mock.js | 2 +- src/adminPage/shared/auth/store.js | 3 +- src/adminPage/shared/components/Container.jsx | 19 +-- src/adminPage/shared/components/NavBar.jsx | 41 ++++--- .../shared/components/NavBarItem.jsx | 43 ++++--- src/common/dataFetch/fetchServer.js | 48 ++++---- src/common/dataFetch/initLogoutMiddleware.js | 14 +-- 12 files changed, 180 insertions(+), 145 deletions(-) diff --git a/src/adminPage/pages/EventsPage.jsx b/src/adminPage/pages/EventsPage.jsx index 413464e9..02a103ff 100644 --- a/src/adminPage/pages/EventsPage.jsx +++ b/src/adminPage/pages/EventsPage.jsx @@ -1,9 +1,10 @@ import Container from "@admin/components/Container.jsx"; -function EventsPage() -{ - return -
    이벤트 테스트
    -
    +function EventsPage() { + return ( + +
    이벤트 테스트
    +
    + ); } -export default EventsPage; \ No newline at end of file +export default EventsPage; diff --git a/src/adminPage/pages/LoginPage.jsx b/src/adminPage/pages/LoginPage.jsx index 5db26c38..7e2d2975 100644 --- a/src/adminPage/pages/LoginPage.jsx +++ b/src/adminPage/pages/LoginPage.jsx @@ -1,10 +1,11 @@ import Container from "@admin/components/Container.jsx"; import LoginSection from "@admin/auth/LoginSection.jsx"; -function LoginPage() -{ - return - - +function LoginPage() { + return ( + + + + ); } -export default LoginPage; \ No newline at end of file +export default LoginPage; diff --git a/src/adminPage/pages/ProtectedRoute.jsx b/src/adminPage/pages/ProtectedRoute.jsx index ba875108..73b4d963 100644 --- a/src/adminPage/pages/ProtectedRoute.jsx +++ b/src/adminPage/pages/ProtectedRoute.jsx @@ -5,15 +5,15 @@ import Container from "@admin/components/Container.jsx"; import useUserStore from "@admin/auth/store.js"; function ProtectedRoute() { - const isLogin = useUserStore( store=>store.isLogin ); + const isLogin = useUserStore((store) => store.isLogin); const navigate = useNavigate(); - useEffect( ()=>{ - if (!isLogin) navigate("/login", {replace: true}); - }, [isLogin]); + useEffect(() => { + if (!isLogin) navigate("/login", { replace: true }); + }, [isLogin, navigate]); - if(!isLogin) return + if (!isLogin) return ; return ; } -export default ProtectedRoute; \ No newline at end of file +export default ProtectedRoute; diff --git a/src/adminPage/pages/RootRoute.jsx b/src/adminPage/pages/RootRoute.jsx index 8d80520f..c80b5931 100644 --- a/src/adminPage/pages/RootRoute.jsx +++ b/src/adminPage/pages/RootRoute.jsx @@ -1,11 +1,11 @@ -import { Navigate, Outlet } from "react-router-dom"; +import { Navigate } from "react-router-dom"; import useUserStore from "@admin/auth/store.js"; function RootRoute() { - const isLogin = useUserStore( store=>store.isLogin ); + const isLogin = useUserStore((store) => store.isLogin); if (isLogin) return ; return ; } -export default RootRoute; \ No newline at end of file +export default RootRoute; diff --git a/src/adminPage/shared/auth/LoginSection.jsx b/src/adminPage/shared/auth/LoginSection.jsx index a2c716e3..eac639f3 100644 --- a/src/adminPage/shared/auth/LoginSection.jsx +++ b/src/adminPage/shared/auth/LoginSection.jsx @@ -7,51 +7,72 @@ import Input from "@common/components/Input.jsx"; import Button from "@common/components/Button.jsx"; const loginErrorHandler = { - 400: "잘못된 입력입니다!", - 401: "로그인에 실패했습니다!" -} + 400: "잘못된 입력입니다!", + 401: "로그인에 실패했습니다!", +}; -function LoginSection() -{ - const navigate = useNavigate(); - const [id, setId] = useState(""); - const [password, setPassword] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); - async function onSubmit(e) - { - e.preventDefault(); - const config = {method: "post", body:{userName: id, password}}; - setErrorMessage(""); - fetchServer("/api/v1/admin/auth/signin", config) - .then( ({token})=>{ - login(token); - navigate("/events", {replace: true}); - } ) - .catch(handleError(loginErrorHandler)) - .catch( e=>{ - setId(""); - setPassword(""); - setErrorMessage(e.message); - } ); - } - return
    -
    - - -
    -
    - - {errorMessage} -
    -
    +function LoginSection() { + const navigate = useNavigate(); + const [id, setId] = useState(""); + const [password, setPassword] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + async function onSubmit(e) { + e.preventDefault(); + const config = { method: "post", body: { userName: id, password } }; + setErrorMessage(""); + fetchServer("/api/v1/admin/auth/signin", config) + .then(({ token }) => { + login(token); + navigate("/events", { replace: true }); + }) + .catch(handleError(loginErrorHandler)) + .catch((e) => { + setId(""); + setPassword(""); + setErrorMessage(e.message); + }); + } + return ( +
    +
    + + +
    +
    + + + {errorMessage} + +
    +
    + ); } -export default LoginSection; \ No newline at end of file +export default LoginSection; diff --git a/src/adminPage/shared/auth/mock.js b/src/adminPage/shared/auth/mock.js index 38686c32..74b91375 100644 --- a/src/adminPage/shared/auth/mock.js +++ b/src/adminPage/shared/auth/mock.js @@ -4,7 +4,7 @@ const handlers = [ http.post("/api/v1/admin/auth/signin", async ({ request }) => { const { username, password } = await request.json(); if (username !== "admin" && password !== "password1!") { - return HttpResponse.json({ return: false }, {status: 401}); + return HttpResponse.json({ return: false }, { status: 401 }); } return HttpResponse.json({ token: "test_token" }); diff --git a/src/adminPage/shared/auth/store.js b/src/adminPage/shared/auth/store.js index 2879606f..8109e3b8 100644 --- a/src/adminPage/shared/auth/store.js +++ b/src/adminPage/shared/auth/store.js @@ -19,8 +19,7 @@ export function logout() { export function initLoginState() { tokenSaver.init(ADMIN_TOKEN_ID); const token = tokenSaver.get(ADMIN_TOKEN_ID); - if (token === null) - userStore.setState(() => ({ isLogin: false })); + if (token === null) userStore.setState(() => ({ isLogin: false })); else userStore.setState(() => ({ isLogin: true })); } diff --git a/src/adminPage/shared/components/Container.jsx b/src/adminPage/shared/components/Container.jsx index 6144b00d..10fa95f8 100644 --- a/src/adminPage/shared/components/Container.jsx +++ b/src/adminPage/shared/components/Container.jsx @@ -1,13 +1,14 @@ import NavBar from "./NavBar.jsx"; -function Container({children}) -{ - return
    - -
    - {children} -
    -
    +function Container({ children }) { + return ( +
    + +
    + {children} +
    +
    + ); } -export default Container; \ No newline at end of file +export default Container; diff --git a/src/adminPage/shared/components/NavBar.jsx b/src/adminPage/shared/components/NavBar.jsx index a161e20a..f551cb75 100644 --- a/src/adminPage/shared/components/NavBar.jsx +++ b/src/adminPage/shared/components/NavBar.jsx @@ -3,25 +3,28 @@ import { useNavigate } from "react-router-dom"; import NavBarItem from "./NavBarItem.jsx"; import useAuthStore, { logout } from "@admin/auth/store.js"; -function NavBar() -{ - const navigate = useNavigate(); - const isLogin = useAuthStore( store=>store.isLogin ); - function onLogoutClick() - { - logout(); - navigate("/login"); - } +function NavBar() { + const navigate = useNavigate(); + const isLogin = useAuthStore((store) => store.isLogin); + function onLogoutClick() { + logout(); + navigate("/login"); + } - return + return ( + + ); } - -export default NavBar; \ No newline at end of file +export default NavBar; diff --git a/src/adminPage/shared/components/NavBarItem.jsx b/src/adminPage/shared/components/NavBarItem.jsx index 9b778e3d..c88493aa 100644 --- a/src/adminPage/shared/components/NavBarItem.jsx +++ b/src/adminPage/shared/components/NavBarItem.jsx @@ -1,23 +1,38 @@ -import {NavLink} from "react-router-dom"; +import { NavLink } from "react-router-dom"; -function NavBarItem({to, onClick, disabled, children}) -{ - const commonStyle = `w-full h-full flex justify-center items-center +function NavBarItem({ to, onClick, disabled, children }) { + const commonStyle = `w-full h-full flex justify-center items-center hover:border-2 hover:border-white active:border-2 active:border-neutral-400`; - const commonTextStyle = `text-neutral-200 hover:text-white active:text-neutral-400 invalid:text-neutral-600`; + const commonTextStyle = `text-neutral-200 hover:text-white active:text-neutral-400 invalid:text-neutral-600`; - const navLink = `${commonStyle} ${disabled ? "pointer-events-none text-neutral-600": ""} ${isActive ? "text-blue-400" : commonTextStyle}` }> - {children} - ; + const navLink = ( + + `${commonStyle} ${disabled ? "pointer-events-none text-neutral-600" : ""} ${isActive ? "text-blue-400" : commonTextStyle}` + } + > + {children} + + ); - const button = ; + const button = ( + + ); - return
  • - {to ? navLink : button} -
  • + return ( +
  • + {to ? navLink : button} +
  • + ); } -export default NavBarItem; \ No newline at end of file +export default NavBarItem; diff --git a/src/common/dataFetch/fetchServer.js b/src/common/dataFetch/fetchServer.js index ae9dd42a..d63afbb5 100644 --- a/src/common/dataFetch/fetchServer.js +++ b/src/common/dataFetch/fetchServer.js @@ -16,8 +16,7 @@ class ServerCloseError extends Error { } // fetchServer의 옵션을 생성합니다. -function createFetchOptions(options = {}) -{ +function createFetchOptions(options = {}) { // 기본적으로 옵션을 그대로 가져오지만, body가 존재하고 header.content-type을 설정하지 않는다면 // json으로 간주하여 option을 생성합니다. const fetchOptions = { ...options }; @@ -49,15 +48,14 @@ function createFetchOptions(options = {}) async function fetchServerBase(url, options = {}) { try { const response = await fetch(url, createFetchOptions(options)); - if (response.status >= 400 && response.status <= 599) throw new HTTPError(response); + if (response.status >= 400 && response.status <= 599) + throw new HTTPError(response); return await response.json(); - } - catch( e ) - { - if( e instanceof HTTPError ) { + } catch (e) { + if (e instanceof HTTPError) { e.data = await e.response.json(); } - if( e instanceof TypeError && e.message === "Failed to fetch" ) { + if (e instanceof TypeError && e.message === "Failed to fetch") { throw new ServerCloseError(); } throw e; @@ -67,28 +65,26 @@ async function fetchServerBase(url, options = {}) { // fetchServer의 동작을 수행한 뒤, 미들웨어를 차례대로 실행시킵니다. async function fetchServer(url, options = {}) { let promise = fetchServerBase(url, options); - if(fetchServer.middlewares.size === 0) return promise; + if (fetchServer.middlewares.size === 0) return promise; let shouldProgress = false; - const next = ()=>shouldProgress = true; + const next = () => (shouldProgress = true); - for(let middleware of fetchServer.middlewares) - { + for (let middleware of fetchServer.middlewares) { try { const value = await promise; promise = (async () => { - const result = await middleware({value}, next); + const result = await middleware({ value }, next); return result ?? value; })(); - if(!shouldProgress) return value; - } - catch(error) { + if (!shouldProgress) return value; + } catch (error) { promise = (async () => { - const result = await middleware({error}, next); - if(result !== undefined && result !== null) return result; + const result = await middleware({ error }, next); + if (result !== undefined && result !== null) return result; throw error; })(); - if(!shouldProgress) throw error; + if (!shouldProgress) throw error; } shouldProgress = false; @@ -99,14 +95,12 @@ async function fetchServer(url, options = {}) { // 미들웨어를 등록시키고 제거합니다. fetchServer.middlewares = new Set(); -fetchServer.use = function(middleware) -{ - fetchServer.middlewares.add( middleware ); -} -fetchServer.unuse = function(middleware) -{ - fetchServer.middlewares.delete( middleware ); -} +fetchServer.use = function (middleware) { + fetchServer.middlewares.add(middleware); +}; +fetchServer.unuse = function (middleware) { + fetchServer.middlewares.delete(middleware); +}; function handleError(errorDescriptor) { return (error) => { diff --git a/src/common/dataFetch/initLogoutMiddleware.js b/src/common/dataFetch/initLogoutMiddleware.js index f2b5fa63..f64c43a8 100644 --- a/src/common/dataFetch/initLogoutMiddleware.js +++ b/src/common/dataFetch/initLogoutMiddleware.js @@ -2,15 +2,15 @@ import { useEffect } from "react"; import { fetchServer, HTTPError } from "@common/dataFetch/fetchServer.js"; export default function useLogoutMiddleware(logout) { - useEffect( ()=>{ - function middleware( {error}, next ) { - if(error instanceof HTTPError && error.status === 401) { + useEffect(() => { + function middleware({ error }, next) { + if (error instanceof HTTPError && error.status === 401) { logout(); } next(); } - fetchServer.use( middleware ); - return ()=>fetchServer.unuse( middleware ); - }, [logout] ); -} \ No newline at end of file + fetchServer.use(middleware); + return () => fetchServer.unuse(middleware); + }, [logout]); +}