From fe7cf10f03498f3606820c6c7882b75ecab65fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EA=B8=B0=EC=98=81?= Date: Fri, 2 Aug 2024 11:30:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Quizset=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A1=B0=ED=9A=8C,=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EC=9E=84=EC=8B=9C=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 --- frontend/src/Router.jsx | 5 + frontend/src/components/Header/Header.jsx | 32 +++++-- frontend/src/components/QuizForm/QuizCard.jsx | 31 ++++--- .../components/QuizForm/QuizCard.module.css | 45 ++++++++- .../src/components/QuizForm/QuizsetForm.jsx | 91 ++++++++++++------- .../QuizForm/QuizsetForm.module.css | 70 +++++++++++++- .../QuizsetDetail/QuizsetDetail.jsx | 24 +++++ .../QuizsetDetail/QuizsetDetail.module.css | 36 ++++++++ .../src/components/QuizsetDetail/index.js | 1 + frontend/src/hooks/api/useAuth.js | 13 ++- .../QuizsetDetailPage/QuizsetDetailPage.jsx | 7 +- .../QuizsetWritePage/QuizsetWritePage.jsx | 26 +++--- 12 files changed, 307 insertions(+), 74 deletions(-) create mode 100644 frontend/src/components/QuizsetDetail/QuizsetDetail.jsx create mode 100644 frontend/src/components/QuizsetDetail/QuizsetDetail.module.css create mode 100644 frontend/src/components/QuizsetDetail/index.js diff --git a/frontend/src/Router.jsx b/frontend/src/Router.jsx index 62ef9ae..0b2dad1 100644 --- a/frontend/src/Router.jsx +++ b/frontend/src/Router.jsx @@ -28,6 +28,7 @@ const LectureCreatePage = lazy(async () => await import('./pages/LectureCreatePa const LectureEditPage = lazy(async () => await import('./pages/LectureEditPage')); const QuizsetListPage = lazy(async () => await import('./pages/QuizsetListPage')); const QuizsetWritePage = lazy(async () => await import('./pages/QuizsetWritePage')); +const QuizsetDetailPage = lazy(async () => await import('./pages/QuizsetDetailPage')); const router = createBrowserRouter([ { @@ -127,6 +128,10 @@ const router = createBrowserRouter([ path: 'write', element: , }, + { + path: ':quizsetId', + element: , + }, ], }, ], diff --git a/frontend/src/components/Header/Header.jsx b/frontend/src/components/Header/Header.jsx index 991584b..a54d7f4 100644 --- a/frontend/src/components/Header/Header.jsx +++ b/frontend/src/components/Header/Header.jsx @@ -1,7 +1,16 @@ -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import styles from './Header.module.css'; +import useBoundStore from '../../store'; +import { useAuth } from '../../hooks/api/useAuth'; export default function Header() { + const navigate = useNavigate(); + const userType = useBoundStore((state) => state.userType); + const { logout } = useAuth(); + const handleClick = () => { + logout().then(navigate('/')); + }; + return (
diff --git a/frontend/src/components/QuizForm/QuizCard.jsx b/frontend/src/components/QuizForm/QuizCard.jsx index ceafd50..237299e 100644 --- a/frontend/src/components/QuizForm/QuizCard.jsx +++ b/frontend/src/components/QuizForm/QuizCard.jsx @@ -31,6 +31,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) { return (
+ + Tip: 선택지를 넣지 않는다면 단답형 문제가 됩니다
- - +
+ + +
{choices.map?.((choice, idx) => (
+ { setQuizzes([...quizzes, { question: '', answer: '', choices: [] }]); @@ -16,37 +20,62 @@ export default function QuizsetForm({ onSubmit }) { setQuizzes(updatedQuizzes); }; + const handleFileChange = (e) => { + const file = e.target.files?.[0]; + setImageFile(file); + }; + return ( -
onSubmit(e, title, quizzes)} - > - setTitle(e.target.value)} - placeholder="퀴즈셋 제목을 입력해주세요" - /> - {quizzes.map((quiz, index) => ( - +
+ + + {headerTitle} + +
{topic}
+
+ onSubmit(e, title, quizzes, imageFile)} + > + setTitle(e.target.value)} + placeholder="퀴즈셋 제목을 입력해주세요" /> - ))} - - - + {quizzes.map((quiz, index) => ( + + ))} + + + + + +
); } diff --git a/frontend/src/components/QuizForm/QuizsetForm.module.css b/frontend/src/components/QuizForm/QuizsetForm.module.css index dc61987..7881354 100644 --- a/frontend/src/components/QuizForm/QuizsetForm.module.css +++ b/frontend/src/components/QuizForm/QuizsetForm.module.css @@ -1,6 +1,74 @@ +.quizsetForm { + background: var(--background-color); + width: 100%; + max-width: 900px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 40px; +} + +.header { + display: flex; + flex-direction: column; + align-items: start; + gap: 8px; +} + +.goBack { + display: flex; + align-items: center; + gap: 4px; + font-size: 20px; + line-height: 1.2; + font-weight: 400; + color: var(--text-color-secondary); + stroke: var(--text-color-secondary); +} + +.title { + font-size: 32px; + line-height: 1.2; + font-weight: 800; +} + .form { display: flex; flex-direction: column; flex-wrap: nowrap; - gap: 8px; + gap: 20px; +} + +.removeButton { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border: 1px solid var(--accent-color); + background-color: var(--accent-color); + color: var(--on-primary); + stroke: var(--on-primary); + font-size: 16px; + line-height: 1.4; + font-weight: 700; + align-self: end; + border-radius: 8px; + cursor: pointer; +} + +.button { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border: 1px solid var(--primary-color); + background-color: var(--primary-color); + color: var(--on-primary); + stroke: var(--on-primary); + font-size: 16px; + line-height: 1.4; + font-weight: 700; + align-self: end; + border-radius: 8px; + cursor: pointer; } diff --git a/frontend/src/components/QuizsetDetail/QuizsetDetail.jsx b/frontend/src/components/QuizsetDetail/QuizsetDetail.jsx new file mode 100644 index 0000000..32e06e5 --- /dev/null +++ b/frontend/src/components/QuizsetDetail/QuizsetDetail.jsx @@ -0,0 +1,24 @@ +import BackIcon from '/src/assets/icons/back.svg?react'; +import { Link } from 'react-router-dom'; +import styles from './QuizsetDetail.module.css'; + +export default function QuizsetDetail({ topic, title }) { + // TODO: 답변 작성 기능 추가 + + return ( +
+
+ + + {topic} + +
+

{title}

+
+
+
+ ); +} diff --git a/frontend/src/components/QuizsetDetail/QuizsetDetail.module.css b/frontend/src/components/QuizsetDetail/QuizsetDetail.module.css new file mode 100644 index 0000000..0fa2a99 --- /dev/null +++ b/frontend/src/components/QuizsetDetail/QuizsetDetail.module.css @@ -0,0 +1,36 @@ +.quizsetDetail { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + background-color: var(--background-default); + color: var(--text-color); + box-sizing: border-box; + margin: 0; + padding: 0; +} + +.header { + display: flex; + flex-direction: column; + align-items: start; + gap: 8px; +} + +.goBack { + display: flex; + align-items: center; + gap: 4px; + font-size: 20px; + line-height: 1.2; + font-weight: 400; + color: var(--text-color-secondary); + stroke: var(--text-color-secondary); +} + +.title { + font-size: 32px; + line-height: 1.2; + font-weight: 800; + margin: 0; +} diff --git a/frontend/src/components/QuizsetDetail/index.js b/frontend/src/components/QuizsetDetail/index.js new file mode 100644 index 0000000..150fb21 --- /dev/null +++ b/frontend/src/components/QuizsetDetail/index.js @@ -0,0 +1 @@ +export { default as QuizsetDetail } from './QuizsetDetail'; diff --git a/frontend/src/hooks/api/useAuth.js b/frontend/src/hooks/api/useAuth.js index d7a928f..4bf402f 100644 --- a/frontend/src/hooks/api/useAuth.js +++ b/frontend/src/hooks/api/useAuth.js @@ -44,5 +44,16 @@ export function useAuth() { }); }; - return { login, userRegister }; + const logout = () => { + return instance + .get(`${API_URL}/user/logout`) + .then((response) => { + console.log(response); + setUserType(null); + setToken(null); + }) + .catch((e) => console.log(e)); + }; + + return { login, logout, userRegister }; } diff --git a/frontend/src/pages/QuizsetDetailPage/QuizsetDetailPage.jsx b/frontend/src/pages/QuizsetDetailPage/QuizsetDetailPage.jsx index 73e664f..bbb86d8 100644 --- a/frontend/src/pages/QuizsetDetailPage/QuizsetDetailPage.jsx +++ b/frontend/src/pages/QuizsetDetailPage/QuizsetDetailPage.jsx @@ -1,15 +1,12 @@ import { useQuizsetDetail } from '../../hooks/api/useQuizsetDetail'; import { useParams } from 'react-router-dom'; // import useBoundStore from '../../store'; +import { QuizsetDetail } from '../../components/QuizsetDetail'; export default function QuizsetListPage() { const { lectureId } = useParams(); const { data } = useQuizsetDetail(lectureId); const quizset = data?.data ?? []; console.log(quizset); - return ( -
-
디테일일{lectureId}
-
- ); + return ; } diff --git a/frontend/src/pages/QuizsetWritePage/QuizsetWritePage.jsx b/frontend/src/pages/QuizsetWritePage/QuizsetWritePage.jsx index 4ac6f45..d2eced1 100644 --- a/frontend/src/pages/QuizsetWritePage/QuizsetWritePage.jsx +++ b/frontend/src/pages/QuizsetWritePage/QuizsetWritePage.jsx @@ -4,31 +4,29 @@ import { useQuizsetWrite } from '../../hooks/api/useQuizsetWrite'; export default function QuizsetWritePage() { // TODO: lecture에서 이미지 전송 성공 후 해당 방법으로 이미지 파일 입력 const { quizsetWrite } = useQuizsetWrite(); - const handleSubmit = async (e, title, quizzes) => { + const handleSubmit = async (e, title, quizzes, imageFile = null) => { e.preventDefault(); - console.log(title, quizzes); const quizsetObject = { title, quizzes, }; + console.log(quizsetObject); + console.log(imageFile); const formData = new FormData(); formData.append('quizSetCreateRequest', new Blob([JSON.stringify(quizsetObject)], { type: 'application/json' })); - + if (imageFile) { + formData.append('image', imageFile); + } const response = await quizsetWrite(formData); console.log(response); }; return ( -
-
퀴즈 쓰기
- -
- - -
-
+ ); }