feat: Quizset 생성 및 조회, 로그아웃 기능 임시 추가

This commit is contained in:
정기영 2024-08-02 11:30:47 +09:00
parent 561f91481c
commit fe7cf10f03
12 changed files with 307 additions and 74 deletions

View File

@ -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 />,
},
],
},
],

View File

@ -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>

View File

@ -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}

View File

@ -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;
}

View File

@ -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>
);
}

View File

@ -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;
}

View 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>
);
}

View File

@ -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;
}

View File

@ -0,0 +1 @@
export { default as QuizsetDetail } from './QuizsetDetail';

View File

@ -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 };
}

View File

@ -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} />;
}

View File

@ -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={'..'}
/>
);
}