From 20dd853b2052e2ebcdeac10331025988a6eb47b4 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:32:52 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[feat]=20=ED=8A=B9=EC=A0=95=20page?= =?UTF-8?q?=EC=97=90=EC=84=9C=20header=20=EC=88=A8=EA=B8=B0=EA=B8=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/App.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/admin/src/App.jsx b/admin/src/App.jsx index 0620511..ba5dcf4 100644 --- a/admin/src/App.jsx +++ b/admin/src/App.jsx @@ -1,15 +1,18 @@ import React from 'react'; -import { Outlet } from 'react-router-dom'; +import { Outlet, useLocation } from 'react-router-dom'; import AdminHeader from '@/components/header/AdminHeader'; import TabHeader from '@/components/header/TabHeader'; import { DateProvider } from '@/context/dateContext'; function App() { + const location = useLocation(); + const hideHeader = ['/login', '/error'].includes(location.pathname); + return (
- - + {!hideHeader && } + {!hideHeader && }
From a98cd5bf8db28dacb3f69103cdb046dddff165e0 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:33:21 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8A=94=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=20login=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=A1=9C=20=EB=9D=BC=EC=9A=B0=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/pages/ProtectedRoute.jsx | 13 ++++++ admin/src/router.jsx | 66 ++++++++++++++++++------------ 2 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 admin/src/pages/ProtectedRoute.jsx diff --git a/admin/src/pages/ProtectedRoute.jsx b/admin/src/pages/ProtectedRoute.jsx new file mode 100644 index 0000000..cbc2288 --- /dev/null +++ b/admin/src/pages/ProtectedRoute.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; + +const ProtectedRoute = () => { + const accessToken = sessionStorage.getItem('userInfo'); + + if (!accessToken) { + return ; + } + return ; +}; + +export default ProtectedRoute; diff --git a/admin/src/router.jsx b/admin/src/router.jsx index 0e0b0d2..8fbb225 100644 --- a/admin/src/router.jsx +++ b/admin/src/router.jsx @@ -8,40 +8,52 @@ import Reward from '@/pages/reward/Reward'; import AdminEventStatus from '@/pages/AdminEventStatus/AdminEventStatus'; import UploadReward from '@/pages/UploadReward/UploadReward'; import UploadPrize from '@/pages/UploadPrize/UploadPrize'; +import Login from '@/pages/login/Login'; +import ProtectedRoute from '@/pages/ProtectedRoute'; +import ErrorPage from '@/pages/ErrorPage'; const router = createBrowserRouter([ + { + path: '/login', + element: , // Login 페이지를 루트로 설정 + }, { path: '/', element: , children: [ - { index: true, element: }, - { - path: 'miniQuizAnswer', - element: , - }, - { - path: 'draw', - element: , - }, - { - path: 'reward', - element: , - }, - { - path: 'adminEventStatus', - element: , - }, - { - path: 'adminEventStatus', - element: , - }, - { - path: 'uploadReward', - element: , - }, { - path: 'uploadPrize', - element: , + element: , + children: [ + { index: true, element: }, + { + path: 'miniQuizAnswer', + element: , + }, + { + path: 'draw', + element: , + }, + { + path: 'reward', + element: , + }, + { + path: 'adminEventStatus', + element: , + }, + { + path: 'uploadReward', + element: , + }, + { + path: 'uploadPrize', + element: , + }, + { + path: 'error', + element: , + }, + ], }, ], }, From 25c31e9550c732ca60cbdc79631d6d2ce89604c0 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:33:53 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[fix]=20sessionStorage=EB=A1=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/api/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/src/api/index.js b/admin/src/api/index.js index b61a926..5987066 100644 --- a/admin/src/api/index.js +++ b/admin/src/api/index.js @@ -1,5 +1,5 @@ const ApiRequest = async (url, method, body) => { - const accessToken = localStorage.getItem('userInfo'); + const accessToken = sessionStorage.getItem('userInfo'); try { const options = { From f56a5435568b117b8a833d1db054d964b34404c1 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:34:09 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[fix]=20miniQuiz=20api=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/api/miniQuiz/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/src/api/miniQuiz/index.js b/admin/src/api/miniQuiz/index.js index d42c2dc..3a4e5b4 100644 --- a/admin/src/api/miniQuiz/index.js +++ b/admin/src/api/miniQuiz/index.js @@ -2,8 +2,8 @@ import { post, get, patch, put, del } from '@/api/index'; const getAdminMiniQuiz = day => get(`/admin/quiz/${day}`); -const putAdminMiniQuiz = body => - put(`/admin/quiz`, { +const putAdminMiniQuiz = (day, body) => + put(`/admin/quiz/${day}`, { quizId: body.quizId, quizDescription: body.quizDescription, quizQuestions: { From 9df92d36ef5e266c34fcf291fd8a266710228221 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:34:24 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[feat]=20auth=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/api/auth/index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 admin/src/api/auth/index.js diff --git a/admin/src/api/auth/index.js b/admin/src/api/auth/index.js new file mode 100644 index 0000000..387dd1a --- /dev/null +++ b/admin/src/api/auth/index.js @@ -0,0 +1,16 @@ +import { post, get, patch, put, del } from '@/api/index'; + +const loginPhone = phoneNumber => { + return post('/login/phone', { + phoneNumber: phoneNumber, + }); +}; + +const loginCode = (phoneNumber, validateCode) => { + return post('/login/code', { + phoneNumber: phoneNumber, + code: validateCode, + }); +}; + +export { loginPhone, loginCode }; From 7b638e898fe09be3f616d9f5f63565d38b581a0b Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:34:42 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[feat]=20useFetch=20=ED=9B=85=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/hooks/useFetch.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/admin/src/hooks/useFetch.js b/admin/src/hooks/useFetch.js index be41cb0..cc3392f 100644 --- a/admin/src/hooks/useFetch.js +++ b/admin/src/hooks/useFetch.js @@ -1,6 +1,8 @@ import { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; function useFetch(api, params) { + const navigate = useNavigate(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -8,9 +10,23 @@ function useFetch(api, params) { const fetchData = useCallback(async () => { try { const response = await api(params); - if (typeof response === 'object') { - setData(response); + if (response.code === 'UNAUTHORIZED') { + navigate('/error'); + } else if (initialData.code === 'NO_QUIZ_INFO') { + setQuizData({ + quizId: '0', + quizDescription: '', + quizQuestions: { + 1: '', + 2: '', + 3: '', + 4: '', + }, + }); + } else { + setData(response); + } } else if (typeof response === 'string') { setData({ message: response }); } else { From bafc3897a9551dcf0fc8a1d46e84443dae3252a5 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:35:12 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[fix]=20BlackButton=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/components/buttons/BlackButton.jsx | 6 ++++-- .../src/components/buttons/BlackButton.jsx | 20 ------------------- 2 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 service/src/components/buttons/BlackButton.jsx diff --git a/admin/src/components/buttons/BlackButton.jsx b/admin/src/components/buttons/BlackButton.jsx index 352b9e0..1991d20 100644 --- a/admin/src/components/buttons/BlackButton.jsx +++ b/admin/src/components/buttons/BlackButton.jsx @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -function BlackButton({ value, onClickFunc }) { +function BlackButton({ value, onClickFunc, disabled = false }) { return ( @@ -15,6 +16,7 @@ function BlackButton({ value, onClickFunc }) { BlackButton.propTypes = { value: PropTypes.string.isRequired, onClickFunc: PropTypes.func.isRequired, + disabled: PropTypes.bool, }; //memo를 이용하여 rerender 방지 diff --git a/service/src/components/buttons/BlackButton.jsx b/service/src/components/buttons/BlackButton.jsx deleted file mode 100644 index 3861fc9..0000000 --- a/service/src/components/buttons/BlackButton.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function BlackButton({ onClickFunc }) { - return ( - - ); -} - -BlackButton.propTypes = { - onClickFunc: PropTypes.func.isRequired, -}; - -//memo를 이용하여 rerender 방지 -export default React.memo(BlackButton); From 978b4d057fa1006c6ea6a1af807eee45e7202cfd Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:37:44 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[feat]=20admin=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=EB=95=8C=20ErrorPage=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/pages/ErrorPage.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 admin/src/pages/ErrorPage.jsx diff --git a/admin/src/pages/ErrorPage.jsx b/admin/src/pages/ErrorPage.jsx new file mode 100644 index 0000000..39242b1 --- /dev/null +++ b/admin/src/pages/ErrorPage.jsx @@ -0,0 +1,10 @@ +import React from 'react'; + +function ErrorPage() { + return ( +
+ 접근 권한이 없습니다!! +
+ ); +} +export default ErrorPage; From 6b8f0d1a213b1d86a824d699bf422bd8980250de Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:38:01 +0900 Subject: [PATCH 09/12] [feat] AdminLoginPage --- admin/src/pages/login/Login.jsx | 112 ++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 admin/src/pages/login/Login.jsx diff --git a/admin/src/pages/login/Login.jsx b/admin/src/pages/login/Login.jsx new file mode 100644 index 0000000..d1abb73 --- /dev/null +++ b/admin/src/pages/login/Login.jsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import isValidPhoneNumber from '@/utils/isValidPhoneNumber'; +import { loginPhone, loginCode } from '@/api/auth/index'; +import phoneNumberFormatting from '@/utils/phoneNumberFormatting'; +import BlackButton from '@/components/buttons/BlackButton'; +import { useNavigate } from 'react-router-dom'; + +function Login() { + const navigate = useNavigate(); + const [inputPhone, setInputPhone] = useState(''); + const [phoneValid, setPhoneValid] = useState(false); + const [firstPhoneClick, setFirstPhoneClick] = useState(false); + const [showAuth, setShowAuth] = useState(false); + + const [validateCode, setValidateCode] = useState(''); + const [codeValid, setCodeValid] = useState(false); + const [firstCodeClick, setFirstCodeClick] = useState(false); + const [alertText, setAlertText] = useState('인증번호 형식이 맞지 않습니다!'); + + const handlePhoneInput = e => { + setInputPhone(e.target.value); + setPhoneValid(isValidPhoneNumber(e.target.value)); + }; + + const handleCodeInput = e => { + setValidateCode(e.target.value); + setCodeValid(e.target.value.length === 6); + }; + + const handlePhoneAuth = async () => { + try { + const formattedPhone = phoneNumberFormatting(inputPhone); + await loginPhone(formattedPhone); + setInputPhone(formattedPhone); + setShowAuth(true); + } catch (error) { + console.error('loginPhone API 통신 실패:', error); + } + }; + + const handleCodeAuth = async () => { + if (validateCode === '111111') { + try { + const response = await loginCode(inputPhone, validateCode); + sessionStorage.setItem('userInfo', response.accessToken); + navigate('/'); + } catch (error) { + console.error('API 통신 실패:', error); + } + } else { + setAlertText('인증번호를 다시 확인해주세요!'); + setCodeValid(false); + } + }; + + const handleFirstPhoneClick = () => setFirstPhoneClick(true); + const handleFirstCodeClick = () => setFirstCodeClick(true); + + return ( +
+ {showAuth ? ( + <> +

+ 인증번호를 입력해주세요 +

+ + {!codeValid && firstCodeClick && ( + + {alertText} + + )} + + + ) : ( + <> +

+ 핸드폰 번호를 입력해주세요 +

+ + {!phoneValid && firstPhoneClick && ( + + 전화번호 형식이 맞지 않습니다! + + )} + + + )} +
+ ); +} + +export default Login; From 176d02869616f9afcf1b29591600e293cf74c4a2 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:38:14 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[feat]=20MiniQuiz=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/pages/miniQuiz/MiniQuiz.jsx | 26 ++++++++++++++------ admin/src/pages/miniQuiz/MiniQuizContent.jsx | 3 ++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/admin/src/pages/miniQuiz/MiniQuiz.jsx b/admin/src/pages/miniQuiz/MiniQuiz.jsx index 4e28843..5a3d0fb 100644 --- a/admin/src/pages/miniQuiz/MiniQuiz.jsx +++ b/admin/src/pages/miniQuiz/MiniQuiz.jsx @@ -1,6 +1,6 @@ import React, { useState, useContext, useEffect } from 'react'; import AdminEditHeader from '@/components/header/AdminEditHeader'; -import AdminEditMiniQuizContent from './MiniQuizContent'; +import MiniQuizContent from './MiniQuizContent'; import BlackButton from '@/components/buttons/BlackButton'; import { getAdminMiniQuiz, putAdminMiniQuiz } from '@/api/miniQuiz/index'; import { DateContext } from '@/context/dateContext'; @@ -24,15 +24,25 @@ function MiniQuiz() { } }, [initialData]); - const handleChange = (field, value) => { - setQuizData(prevState => ({ - ...prevState, - [field]: value, - })); + const handleChange = (key, value) => { + if (key === 'quizDescription') { + setQuizData(prevState => ({ + ...prevState, + [key]: value, + })); + } else { + setQuizData(prevState => ({ + ...prevState, + quizQuestions: { + ...prevState.quizQuestions, + [key]: value, + }, + })); + } }; const handleSubmit = async () => { - const response = await putAdminMiniQuiz(quizData); + const response = await putAdminMiniQuiz(dateInfo, quizData); if (response.status === 200) { await refetch(); } else { @@ -57,7 +67,7 @@ function MiniQuiz() {
- + setOpenModal(true)} />
{openModal && ( diff --git a/admin/src/pages/miniQuiz/MiniQuizContent.jsx b/admin/src/pages/miniQuiz/MiniQuizContent.jsx index 1d03a58..9ba55cb 100644 --- a/admin/src/pages/miniQuiz/MiniQuizContent.jsx +++ b/admin/src/pages/miniQuiz/MiniQuizContent.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import InputForm from '@/components/form/InputForm'; function MiniQuizContent({ response, onChange }) { + // console.log(response); return ( <> onChange(`quizQuestions.${key}`, value)} + onChange={value => onChange(key, value)} /> ))} From eb46c53e52f7344c9f9fb5bf4104153243b0a3a6 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:39:03 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[feat]=20phoneNumber=20=ED=8F=AC=EB=A7=B7?= =?UTF-8?q?=ED=8C=85=20=EB=B0=8F=20=EC=A0=95=ED=99=95=EC=84=B1=20=ED=8C=90?= =?UTF-8?q?=EB=8B=A8=20=EC=9C=A0=EB=AC=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/utils/isValidPhoneNumber.js | 9 +++++++++ admin/src/utils/phoneNumberFormatting.js | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 admin/src/utils/isValidPhoneNumber.js create mode 100644 admin/src/utils/phoneNumberFormatting.js diff --git a/admin/src/utils/isValidPhoneNumber.js b/admin/src/utils/isValidPhoneNumber.js new file mode 100644 index 0000000..0dc90b8 --- /dev/null +++ b/admin/src/utils/isValidPhoneNumber.js @@ -0,0 +1,9 @@ +function isValidPhoneNumber(number) { + if (number.length < 9 || number.length > 11) { + return false; + } + const result = /^(01[016789]{1})[0-9]{3,4}[0-9]{4}$/; + return result.test(number); +} + +export default isValidPhoneNumber; diff --git a/admin/src/utils/phoneNumberFormatting.js b/admin/src/utils/phoneNumberFormatting.js new file mode 100644 index 0000000..3c7c728 --- /dev/null +++ b/admin/src/utils/phoneNumberFormatting.js @@ -0,0 +1,5 @@ +function phoneNumberFormatting(phoneNumber) { + return phoneNumber.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'); +} + +export default phoneNumberFormatting; From a2b7d169fb30651f273d7c454237ac22c75c2289 Mon Sep 17 00:00:00 2001 From: subsub-e Date: Fri, 16 Aug 2024 16:45:38 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[fix]=20miniQuiz=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/hooks/useFetch.js | 11 ----------- admin/src/pages/miniQuiz/MiniQuiz.jsx | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/admin/src/hooks/useFetch.js b/admin/src/hooks/useFetch.js index cc3392f..725d0ce 100644 --- a/admin/src/hooks/useFetch.js +++ b/admin/src/hooks/useFetch.js @@ -13,17 +13,6 @@ function useFetch(api, params) { if (typeof response === 'object') { if (response.code === 'UNAUTHORIZED') { navigate('/error'); - } else if (initialData.code === 'NO_QUIZ_INFO') { - setQuizData({ - quizId: '0', - quizDescription: '', - quizQuestions: { - 1: '', - 2: '', - 3: '', - 4: '', - }, - }); } else { setData(response); } diff --git a/admin/src/pages/miniQuiz/MiniQuiz.jsx b/admin/src/pages/miniQuiz/MiniQuiz.jsx index 5a3d0fb..61fac10 100644 --- a/admin/src/pages/miniQuiz/MiniQuiz.jsx +++ b/admin/src/pages/miniQuiz/MiniQuiz.jsx @@ -20,7 +20,20 @@ function MiniQuiz() { useEffect(() => { if (initialData) { - setQuizData(initialData); + if (initialData.code === 'NO_QUIZ_INFO') { + setQuizData({ + quizId: '0', + quizDescription: '', + quizQuestions: { + 1: '', + 2: '', + 3: '', + 4: '', + }, + }); + } else { + setQuizData(initialData); + } } }, [initialData]);