Skip to content

Commit

Permalink
Merge pull request #109 from softeerbootcamp4th/feat/#108/AdminLoginPage
Browse files Browse the repository at this point in the history
Feat/#108/adminLoginPage
  • Loading branch information
yoonc01 authored Aug 16, 2024
2 parents d632e44 + a2b7d16 commit 28b89cd
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 67 deletions.
9 changes: 6 additions & 3 deletions admin/src/App.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<DateProvider>
<div className="bg-[#DADADA] flex flex-col items-center min-w-[700px]">
<AdminHeader />
<TabHeader />
{!hideHeader && <AdminHeader />}
{!hideHeader && <TabHeader />}
<div className="w-[90%] flex flex-col items-center pb-2000">
<Outlet />
</div>
Expand Down
16 changes: 16 additions & 0 deletions admin/src/api/auth/index.js
Original file line number Diff line number Diff line change
@@ -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 };
2 changes: 1 addition & 1 deletion admin/src/api/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const ApiRequest = async (url, method, body) => {
const accessToken = localStorage.getItem('userInfo');
const accessToken = sessionStorage.getItem('userInfo');

try {
const options = {
Expand Down
4 changes: 2 additions & 2 deletions admin/src/api/miniQuiz/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 4 additions & 2 deletions admin/src/components/buttons/BlackButton.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';

function BlackButton({ value, onClickFunc }) {
function BlackButton({ value, onClickFunc, disabled = false }) {
return (
<button
onClick={onClickFunc}
className={`text-body-3-semibold text-white rounded py-200 px-1000 bg-neutral-700 hover:bg-neutral-black`}
className={`${disabled ? 'opacity-30' : 'opacity-100 hover:bg-neutral-black'} text-body-3-semibold text-white rounded py-200 px-1000 bg-neutral-700`}
disabled={disabled}
>
{value}
</button>
Expand All @@ -15,6 +16,7 @@ function BlackButton({ value, onClickFunc }) {
BlackButton.propTypes = {
value: PropTypes.string.isRequired,
onClickFunc: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};

//memo를 이용하여 rerender 방지
Expand Down
9 changes: 7 additions & 2 deletions admin/src/hooks/useFetch.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
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);

const fetchData = useCallback(async () => {
try {
const response = await api(params);

if (typeof response === 'object') {
setData(response);
if (response.code === 'UNAUTHORIZED') {
navigate('/error');
} else {
setData(response);
}
} else if (typeof response === 'string') {
setData({ message: response });
} else {
Expand Down
10 changes: 10 additions & 0 deletions admin/src/pages/ErrorPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

function ErrorPage() {
return (
<div className="w-screen h-screen bg-white text-heading-banner-title text-neutral-black set-center">
접근 권한이 없습니다!!
</div>
);
}
export default ErrorPage;
13 changes: 13 additions & 0 deletions admin/src/pages/ProtectedRoute.jsx
Original file line number Diff line number Diff line change
@@ -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 <Navigate to="/login" replace />;
}
return <Outlet />;
};

export default ProtectedRoute;
112 changes: 112 additions & 0 deletions admin/src/pages/login/Login.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col items-center justify-center w-screen h-screen">
{showAuth ? (
<>
<p className="text-body-3-bold text-neutral-black mb-900">
인증번호를 입력해주세요
</p>
<input
placeholder="인증번호 입력 (6자리)"
onClick={handleFirstCodeClick}
onChange={handleCodeInput}
value={validateCode}
className={`w-[640px] h-[80px] px-[30px] pl-[40px] mb-1500 text-body-3-regular text-neutral-black placeholder:text-body-3-regular placeholder-neutral-black placeholder-opacity-50 border-solid ${!codeValid && firstCodeClick ? 'border-red-500 focus:border-red-500' : 'border-neutral-black focus:border-primary-blue'} border-[4px] rounded-[10px] outline-none`}
/>
{!codeValid && firstCodeClick && (
<span className="absolute top-[54%] left-[32%] text-red-500 text-detail-3-regular">
{alertText}
</span>
)}
<BlackButton
value="인증번호 확인"
onClickFunc={handleCodeAuth}
disabled={!codeValid}
/>
</>
) : (
<>
<p className="text-body-3-bold text-neutral-black mb-900">
핸드폰 번호를 입력해주세요
</p>
<input
placeholder="핸드폰 번호 입력 (01XXXXXXXXX)"
onClick={handleFirstPhoneClick}
onChange={handlePhoneInput}
value={inputPhone}
className={`w-[640px] h-[80px] px-[30px] pl-[40px] mb-800 text-body-3-regular text-neutral-black placeholder:text-body-3-regular placeholder-neutral-black placeholder-opacity-50 border-solid ${!phoneValid && firstPhoneClick ? 'border-red-500 focus:border-red-500' : 'border-neutral-black focus:border-primary-blue'} border-[4px] rounded-[10px] outline-none`}
/>
{!phoneValid && firstPhoneClick && (
<span className="absolute top-[54%] left-[32%] text-red-500 text-detail-3-regular">
전화번호 형식이 맞지 않습니다!
</span>
)}
<BlackButton
value="본인인증하기"
onClickFunc={handlePhoneAuth}
disabled={!phoneValid}
/>
</>
)}
</div>
);
}

export default Login;
41 changes: 32 additions & 9 deletions admin/src/pages/miniQuiz/MiniQuiz.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -20,19 +20,42 @@ 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]);

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 {
Expand All @@ -57,7 +80,7 @@ function MiniQuiz() {
<div className="w-[100%] mt-1000">
<AdminEditHeader info="미니퀴즈 질문 수정" />
<div className="flex-col w-[100%] set-center bg-neutral-white rounded-b-[10px] py-1000">
<AdminEditMiniQuizContent response={quizData} onChange={handleChange} />
<MiniQuizContent response={quizData} onChange={handleChange} />
<BlackButton value="수정하기" onClickFunc={() => setOpenModal(true)} />
</div>
{openModal && (
Expand Down
3 changes: 2 additions & 1 deletion admin/src/pages/miniQuiz/MiniQuizContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import InputForm from '@/components/form/InputForm';

function MiniQuizContent({ response, onChange }) {
// console.log(response);
return (
<>
<InputForm
Expand All @@ -20,7 +21,7 @@ function MiniQuizContent({ response, onChange }) {
id={`quizSelect_${key}`}
placeholder={response.quizQuestions[key]}
value={response.quizQuestions[key]}
onChange={value => onChange(`quizQuestions.${key}`, value)}
onChange={value => onChange(key, value)}
/>
))}
</>
Expand Down
Loading

0 comments on commit 28b89cd

Please sign in to comment.