feat: Quizset 생성 및 조회, 로그아웃 기능 임시 추가
This commit is contained in:
parent
561f91481c
commit
fe7cf10f03
@ -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: <QuizsetWritePage />,
|
||||
},
|
||||
{
|
||||
path: ':quizsetId',
|
||||
element: <QuizsetDetailPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -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 (
|
||||
<header className={styles.header}>
|
||||
<nav className={styles.nav}>
|
||||
@ -28,12 +37,21 @@ export default function Header() {
|
||||
</li>
|
||||
</ul>
|
||||
<ul className={styles.group}>
|
||||
<li>
|
||||
<Link to={'user/my'}>마이페이지</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={'/auth/login'}>로그인</Link>
|
||||
</li>
|
||||
{userType && (
|
||||
<>
|
||||
<li>
|
||||
<Link to={'user/my'}>마이페이지</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link onClick={handleClick}>로그아웃</Link>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{!userType && (
|
||||
<li>
|
||||
<Link to={'/auth/login'}>로그인</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
@ -31,6 +31,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
|
||||
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<label>질문</label>
|
||||
<input
|
||||
type="text"
|
||||
value={question}
|
||||
@ -40,6 +41,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
|
||||
}}
|
||||
placeholder="질문 내용을 입력하세요"
|
||||
/>
|
||||
<label>정답</label>
|
||||
<input
|
||||
type="text"
|
||||
value={answer}
|
||||
@ -52,20 +54,25 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
|
||||
<div>
|
||||
<span>Tip: 선택지를 넣지 않는다면 단답형 문제가 됩니다</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddChoice}
|
||||
>
|
||||
선택지 추가하기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePopChoice}
|
||||
>
|
||||
선택지 줄이기
|
||||
</button>
|
||||
<div className={styles.buttonsWrapper}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddChoice}
|
||||
className={styles.button}
|
||||
>
|
||||
선택지 추가하기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePopChoice}
|
||||
className={styles.removeButton}
|
||||
>
|
||||
선택지 줄이기
|
||||
</button>
|
||||
</div>
|
||||
{choices.map?.((choice, idx) => (
|
||||
<div key={idx}>
|
||||
<label>선택지 {choice.num} : </label>
|
||||
<input
|
||||
type="text"
|
||||
value={choice.content}
|
||||
|
@ -1,8 +1,47 @@
|
||||
.card {
|
||||
border: 1px solid black;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.buttonsWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.removeButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import QuizCard from './QuizCard';
|
||||
import styles from './QuizsetForm.module.css';
|
||||
import EditIcon from '/src/assets/icons/edit.svg?react';
|
||||
import BackIcon from '/src/assets/icons/back.svg?react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function QuizsetForm({ onSubmit }) {
|
||||
export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
|
||||
// TODO: 디자인 만들기 및 스타일 적용
|
||||
const [title, setTitle] = useState('');
|
||||
const [quizzes, setQuizzes] = useState([]);
|
||||
const [imageFile, setImageFile] = useState(null);
|
||||
|
||||
const handleAddQuiz = () => {
|
||||
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 (
|
||||
<form
|
||||
className={styles.form}
|
||||
onSubmit={(e) => onSubmit(e, title, quizzes)}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="퀴즈셋 제목을 입력해주세요"
|
||||
/>
|
||||
{quizzes.map((quiz, index) => (
|
||||
<QuizCard
|
||||
key={index}
|
||||
quiz={quiz}
|
||||
index={index}
|
||||
updateQuiz={updateQuiz}
|
||||
<div className={styles.quizsetForm}>
|
||||
<header className={styles.header}>
|
||||
<Link
|
||||
to={to}
|
||||
className={styles.goBack}
|
||||
>
|
||||
<BackIcon />
|
||||
<span>{headerTitle}</span>
|
||||
</Link>
|
||||
<div className={styles.title}>{topic}</div>
|
||||
</header>
|
||||
<form
|
||||
className={styles.form}
|
||||
onSubmit={(e) => onSubmit(e, title, quizzes, imageFile)}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="퀴즈셋 제목을 입력해주세요"
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddQuiz}
|
||||
>
|
||||
퀴즈 추가하기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => onSubmit(e, title, quizzes)}
|
||||
>
|
||||
퀴즈셋 저장하기
|
||||
</button>
|
||||
</form>
|
||||
{quizzes.map((quiz, index) => (
|
||||
<QuizCard
|
||||
key={index}
|
||||
quiz={quiz}
|
||||
index={index}
|
||||
updateQuiz={updateQuiz}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddQuiz}
|
||||
className={styles.button}
|
||||
>
|
||||
퀴즈 추가하기
|
||||
</button>
|
||||
<label>퀴즈 이미지</label>
|
||||
<input
|
||||
type="file"
|
||||
accept=".png, .jpg, .jpeg"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.button}
|
||||
>
|
||||
<EditIcon />
|
||||
<div>제출</div>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
24
frontend/src/components/QuizsetDetail/QuizsetDetail.jsx
Normal file
24
frontend/src/components/QuizsetDetail/QuizsetDetail.jsx
Normal file
@ -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 (
|
||||
<div className={styles.quizsetDetail}>
|
||||
<header className={styles.header}>
|
||||
<Link
|
||||
to={'..'}
|
||||
className={styles.goBack}
|
||||
>
|
||||
<BackIcon />
|
||||
<span>{topic}</span>
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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;
|
||||
}
|
1
frontend/src/components/QuizsetDetail/index.js
Normal file
1
frontend/src/components/QuizsetDetail/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default as QuizsetDetail } from './QuizsetDetail';
|
@ -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 };
|
||||
}
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<div>디테일일{lectureId}</div>
|
||||
</div>
|
||||
);
|
||||
return <QuizsetDetail title={quizset.title} />;
|
||||
}
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<div>퀴즈 쓰기</div>
|
||||
<QuizsetForm onSubmit={handleSubmit} />
|
||||
<div>
|
||||
<label>퀴즈 이미지</label>
|
||||
<input
|
||||
type="file"
|
||||
accept=".png, .jpg, .jpeg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<QuizsetForm
|
||||
onSubmit={handleSubmit}
|
||||
headerTitle={'퀴즈 목록'}
|
||||
topic={'퀴즈 작성'}
|
||||
to={'..'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user