Merge branch 'FE/useQuizsetWrite' into 'frontend'

[Front-End] feat: useQuizsetWrite 추가 외

See merge request s11-webmobile1-sub2/S11P12A701!32
This commit is contained in:
조현수 2024-08-05 10:21:26 +09:00
commit e3667f1b73
16 changed files with 210 additions and 94 deletions

View File

@ -1,11 +1,15 @@
import styles from './InfoEditForm.module.css'; import styles from './InfoEditForm.module.css';
import { useState } from 'react'; import { useState } from 'react';
export default function InfoEditForm() { export default function InfoEditForm({ onSubmit }) {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [useremail, setUseremail] = useState(''); const [useremail, setUseremail] = useState('');
return ( return (
<form className={styles.infoEditForm}> <form
onSubmit={(e) => onSubmit(e, username, useremail)}
className={styles.infoEditForm}
>
<p className={styles.textHeading}>이름 변경</p> <p className={styles.textHeading}>이름 변경</p>
<div className={styles.inputBox}> <div className={styles.inputBox}>
<label <label

View File

@ -1,13 +1,13 @@
import { useState, useRef } from 'react'; import { useState, useRef } from 'react';
import styles from './PasswordChangeForm.module.css'; import styles from './PasswordChangeForm.module.css';
export default function PasswordChangeForm() { export default function PasswordChangeForm({ onSubmit, onPwError = false }) {
// TODO: onPwError( )
const [errorConfirmMessage, setErrorConfirmMessage] = useState(false); const [errorConfirmMessage, setErrorConfirmMessage] = useState(false);
const [errorSameMessage, setErrorSameMessage] = useState(false); const [errorSameMessage, setErrorSameMessage] = useState(false);
const currentPasswordRef = useRef(''); const currentPasswordRef = useRef('');
const newPasswordRef = useRef(''); const newPasswordRef = useRef('');
const confirmPasswordRef = useRef(''); const confirmPasswordRef = useRef('');
const userPassword = '1234';
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
@ -15,13 +15,15 @@ export default function PasswordChangeForm() {
const newPassword = newPasswordRef.current.value; const newPassword = newPasswordRef.current.value;
const confirmPassword = confirmPasswordRef.current.value; const confirmPassword = confirmPasswordRef.current.value;
if (currentPassword === userPassword) {
setErrorSameMessage(false);
} else {
setErrorSameMessage(true);
}
if (newPassword === confirmPassword) { if (newPassword === confirmPassword) {
setErrorConfirmMessage(false); setErrorConfirmMessage(false);
onSubmit(currentPassword, newPassword, confirmPassword);
if (onPwError) {
setErrorSameMessage(true);
} else {
setErrorSameMessage(false);
}
} else { } else {
setErrorConfirmMessage(true); setErrorConfirmMessage(true);
} }

View File

@ -1,15 +1,18 @@
import { useState } from 'react'; import { useState } from 'react';
import styles from './QuizCard.module.css'; import styles from './QuizCard.module.css';
export default function QuizCard({ quiz, index, updateQuiz }) { export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
const [question, setQuestion] = useState(quiz.question || ''); const [question, setQuestion] = useState(quiz.question || '');
const [answer, setAnswer] = useState(quiz.answer || ''); const [answer, setAnswer] = useState(quiz.answer || '');
const [choices, setChoices] = useState(quiz.choices || []); const [choices, setChoices] = useState(quiz.choices || []);
const [imageFile, setImageFile] = useState(quiz.imageFile || null);
const handleChoiceChange = (num, content) => { const handleChoiceChange = (num, content) => {
const updatedChoices = choices.map((choice) => (choice.num === num ? { ...choice, content } : choice)); const updatedChoices = choices.map((choice) =>
choice.num === num ? { ...choice, content } : choice
);
setChoices(updatedChoices); setChoices(updatedChoices);
updateQuiz(index, { question, answer, choices: updatedChoices }); updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, imageFile });
}; };
const handleAddChoice = () => { const handleAddChoice = () => {
@ -17,7 +20,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
const newChoice = { num: choices.length + 1, content: '' }; const newChoice = { num: choices.length + 1, content: '' };
const updatedChoices = [...choices, newChoice]; const updatedChoices = [...choices, newChoice];
setChoices(updatedChoices); setChoices(updatedChoices);
updateQuiz(index, { question, answer, choices: updatedChoices }); updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, imageFile });
} }
}; };
@ -25,19 +28,29 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
if (choices.length > 0) { if (choices.length > 0) {
const updatedChoices = choices.slice(0, -1); const updatedChoices = choices.slice(0, -1);
setChoices(updatedChoices); setChoices(updatedChoices);
updateQuiz(index, { question, answer, choices: updatedChoices }); updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, imageFile });
} }
}; };
const handleFileChange = (e) => {
const file = e.target.files[0] ?? null;
setImageFile(file);
updateQuiz(quiz.id, { ...quiz, question, answer, choices, imageFile: file });
};
return ( return (
<div className={styles.card}> <div className={styles.card}>
<div className={styles.header}>
<span>퀴즈 생성 카드</span>
<span onClick={() => deleteQuiz(quiz.id)}>X</span> {/* id를 기반으로 삭제 */}
</div>
<label>질문</label> <label>질문</label>
<input <input
type="text" type="text"
value={question} value={question}
onChange={(e) => { onChange={(e) => {
setQuestion(e.target.value); setQuestion(e.target.value);
updateQuiz(index, { question: e.target.value, answer, choices }); updateQuiz(quiz.id, { ...quiz, question: e.target.value, answer, choices, imageFile });
}} }}
placeholder="질문 내용을 입력하세요" placeholder="질문 내용을 입력하세요"
/> />
@ -47,7 +60,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
value={answer} value={answer}
onChange={(e) => { onChange={(e) => {
setAnswer(e.target.value); setAnswer(e.target.value);
updateQuiz(index, { question, answer: e.target.value, choices }); updateQuiz(quiz.id, { ...quiz, question, answer: e.target.value, choices, imageFile });
}} }}
placeholder="정답을 입력하세요" placeholder="정답을 입력하세요"
/> />
@ -55,18 +68,10 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
<span>Tip: 선택지를 넣지 않는다면 단답형 문제가 됩니다</span> <span>Tip: 선택지를 넣지 않는다면 단답형 문제가 됩니다</span>
</div> </div>
<div className={styles.buttonsWrapper}> <div className={styles.buttonsWrapper}>
<button <button type="button" onClick={handleAddChoice} className={styles.button}>
type="button"
onClick={handleAddChoice}
className={styles.button}
>
선택지 추가하기 선택지 추가하기
</button> </button>
<button <button type="button" onClick={handlePopChoice} className={styles.removeButton}>
type="button"
onClick={handlePopChoice}
className={styles.removeButton}
>
선택지 줄이기 선택지 줄이기
</button> </button>
</div> </div>
@ -81,6 +86,8 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
/> />
</div> </div>
))} ))}
<label>퀴즈 이미지</label>
<input type="file" accept=".png, .jpg, .jpeg" onChange={handleFileChange} />
</div> </div>
); );
} }

View File

@ -8,6 +8,12 @@
gap: 8px; gap: 8px;
} }
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.buttonsWrapper { .buttonsWrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -6,32 +6,33 @@ import BackIcon from '/src/assets/icons/back.svg?react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) { export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
// TODO:
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [quizzes, setQuizzes] = useState([]); const [quizzes, setQuizzes] = useState([]);
const [imageFile, setImageFile] = useState(null); const [quizId, setQuizId] = useState(0);
const handleAddQuiz = () => { const handleAddQuiz = () => {
setQuizzes([...quizzes, { question: '', answer: '', choices: [] }]); setQuizzes([
...quizzes,
{ id: quizId, question: '', answer: '', choices: [], imageFile: null },
]);
setQuizId(quizId + 1);
}; };
const updateQuiz = (index, updatedQuiz) => { const updateQuiz = (id, updatedQuiz) => {
const updatedQuizzes = quizzes.map((quiz, i) => (i === index ? updatedQuiz : quiz)); const updatedQuizzes = quizzes.map((quiz) =>
quiz.id === id ? updatedQuiz : quiz
);
setQuizzes(updatedQuizzes); setQuizzes(updatedQuizzes);
}; };
const handleFileChange = (e) => { const deleteQuiz = (id) => {
const file = e.target.files?.[0]; setQuizzes(quizzes.filter((quiz) => quiz.id !== id));
setImageFile(file);
}; };
return ( return (
<div className={styles.quizsetForm}> <div className={styles.quizsetForm}>
<header className={styles.header}> <header className={styles.header}>
<Link <Link to={to} className={styles.goBack}>
to={to}
className={styles.goBack}
>
<BackIcon /> <BackIcon />
<span>{headerTitle}</span> <span>{headerTitle}</span>
</Link> </Link>
@ -39,7 +40,7 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
</header> </header>
<form <form
className={styles.form} className={styles.form}
onSubmit={(e) => onSubmit(e, title, quizzes, imageFile)} onSubmit={(e) => onSubmit(e, title, quizzes)}
> >
<input <input
type="text" type="text"
@ -47,12 +48,12 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
placeholder="퀴즈셋 제목을 입력해주세요" placeholder="퀴즈셋 제목을 입력해주세요"
/> />
{quizzes.map((quiz, index) => ( {quizzes.map((quiz) => (
<QuizCard <QuizCard
key={index} key={quiz.id}
quiz={quiz} quiz={quiz}
index={index}
updateQuiz={updateQuiz} updateQuiz={updateQuiz}
deleteQuiz={deleteQuiz}
/> />
))} ))}
<button <button
@ -62,16 +63,7 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
> >
퀴즈 추가하기 퀴즈 추가하기
</button> </button>
<label>퀴즈 이미지</label> <button type="submit" className={styles.button}>
<input
type="file"
accept=".png, .jpg, .jpeg"
onChange={handleFileChange}
/>
<button
type="submit"
className={styles.button}
>
<EditIcon /> <EditIcon />
<div>제출</div> <div>제출</div>
</button> </button>

View File

@ -2,7 +2,7 @@ import BackIcon from '/src/assets/icons/back.svg?react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styles from './QuizsetDetail.module.css'; import styles from './QuizsetDetail.module.css';
export default function QuizsetDetail({ topic, title }) { export default function QuizsetDetail({ topic, title, quizzes = [], onDelete }) {
return ( return (
<div className={styles.quizsetDetail}> <div className={styles.quizsetDetail}>
<header className={styles.header}> <header className={styles.header}>
@ -17,6 +17,24 @@ export default function QuizsetDetail({ topic, title }) {
<h1 className={styles.title}>{title}</h1> <h1 className={styles.title}>{title}</h1>
</div> </div>
</header> </header>
<div>
{quizzes.map((quiz, index) => (
<div key={index}>
<div>질문 : {quiz.question}</div>
<img
src={quiz.image}
alt="강의 이미지"
/>
<div>정답이 응답에 없네요</div>
</div>
))}
</div>
<button
type="button"
onClick={onDelete}
>
퀴즈셋 삭제
</button>
</div> </div>
); );
} }

View File

@ -55,5 +55,23 @@ export function useAuth() {
.catch((e) => console.log(e)); .catch((e) => console.log(e));
}; };
return { login, logout, userRegister }; const updateInfo = (name, email) => {
const infoBody = {
name,
email,
};
return instance.put(`${API_URL}/user/updateinfo`, infoBody);
};
const updatePassword = (currentPw, newPw, newPwCheck) => {
const passwordBody = {
currentPassword: currentPw,
newPassword: newPw,
newPasswordCheck: newPwCheck,
};
console.log(passwordBody);
return instance.put(`${API_URL}/user/updatepassword`, passwordBody);
};
return { login, logout, userRegister, updateInfo, updatePassword };
} }

View File

@ -0,0 +1,10 @@
import instance from '../../utils/axios/instance';
import { API_URL } from '../../constants';
export function useQuizsetDelete() {
const quizsetDelete = (quizsetId) => {
return instance.delete(`${API_URL}/quiz/teacher/${quizsetId}`);
};
return { quizsetDelete };
}

View File

@ -0,0 +1,10 @@
import instance from '../../utils/axios/instance';
import { API_URL } from '../../constants';
export function useQuizsetEdit() {
const quizsetEdit = (quizsetId, quizsetObject) => {
return instance.put(`${API_URL}/lecture/${quizsetId}`, quizsetObject);
};
return { quizsetEdit };
}

View File

@ -17,13 +17,12 @@ export default function LectureCreatePage() {
}; };
return ( return (
<div>
<h1>강의 생성</h1>
<LectureForm <LectureForm
title={'강의 생성'} title={'강의 홈'}
topic={'강의 생성'}
to={'..'}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onCreate={true} onCreate={true}
/> />
</div>
); );
} }

View File

@ -21,7 +21,6 @@ export default function LecutreEditPage() {
}; };
return ( return (
<div>
<LectureForm <LectureForm
initialValues={initialData} initialValues={initialData}
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -29,6 +28,5 @@ export default function LecutreEditPage() {
topic={'강의 수정'} topic={'강의 수정'}
to={'..'} to={'..'}
/> />
</div>
); );
} }

View File

@ -1,5 +1,15 @@
import { InfoEditForm } from '../../components/InfoEditForm'; import { InfoEditForm } from '../../components/InfoEditForm';
import { useAuth } from '../../hooks/api/useAuth';
export default function MyInfoChangePage() { export default function MyInfoChangePage() {
return <InfoEditForm />; const { updateInfo } = useAuth();
const handleSubmit = async (e, username, useremail) => {
e.preventDefault();
await updateInfo(username, useremail)
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return <InfoEditForm onSubmit={handleSubmit} />;
} }

View File

@ -1,5 +1,12 @@
import { PasswordChangeForm } from '../../components/PasswordChangeForm'; import { PasswordChangeForm } from '../../components/PasswordChangeForm';
import { useAuth } from '../../hooks/api/useAuth';
export default function PasswordChangePage() { export default function PasswordChangePage() {
return <PasswordChangeForm />; // TODO: 400
const { updatePassword } = useAuth();
const handleSubmit = async (currentPw, newPw, newPwCheck) => {
console.log(currentPw, newPw);
await updatePassword(currentPw, newPw, newPwCheck);
};
return <PasswordChangeForm onSubmit={handleSubmit} />;
} }

View File

@ -1,11 +1,25 @@
import { useQuizsetDetail } from '../../hooks/api/useQuizsetDetail'; import { useQuizsetDetail } from '../../hooks/api/useQuizsetDetail';
import { useParams } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { QuizsetDetail } from '../../components/QuizsetDetail'; import { QuizsetDetail } from '../../components/QuizsetDetail';
import { useQuizsetDelete } from '../../hooks/api/useQuizsetDelete';
export default function QuizsetListPage() { export default function QuizsetDetailPage() {
const { lectureId } = useParams(); const navigate = useNavigate();
const { data } = useQuizsetDetail(lectureId); const { quizsetId } = useParams();
const quizset = data?.data ?? []; const { quizsetDelete } = useQuizsetDelete();
const { data } = useQuizsetDetail(quizsetId);
const quizset = data.data;
console.log(quizset); console.log(quizset);
return <QuizsetDetail title={quizset.title} />; const handleDelete = async () => {
await quizsetDelete(quizsetId);
navigate('..');
};
return (
<QuizsetDetail
topic={'퀴즈 목록'}
title={quizset.title}
quizzes={quizset.quizzes}
onDelete={handleDelete}
/>
);
} }

View File

@ -1,26 +1,47 @@
import { QuizsetForm } from '../../components/QuizForm'; import { QuizsetForm } from '../../components/QuizForm';
import { useQuizsetWrite } from '../../hooks/api/useQuizsetWrite'; import { useQuizsetWrite } from '../../hooks/api/useQuizsetWrite';
import { useNavigate } from 'react-router-dom';
export default function QuizsetWritePage() { export default function QuizsetWritePage() {
// TODO: lecture const navigate = useNavigate();
const { quizsetWrite } = useQuizsetWrite(); const { quizsetWrite } = useQuizsetWrite();
const handleSubmit = async (e, title, quizzes, imageFile = null) => {
const handleSubmit = async (e, title, quizzes) => {
e.preventDefault(); e.preventDefault();
console.log(quizzes)
const images = [];
const quizContents = [];
quizzes.forEach((quiz) => {
const { imageFile, ...quizData } = quiz;
images.push(imageFile);
quizContents.push(quizData);
});
const quizsetObject = { const quizsetObject = {
title, title,
quizzes, quizzes: quizContents,
}; };
console.log(quizsetObject);
console.log(imageFile);
const formData = new FormData(); const formData = new FormData();
formData.append('quizSetCreateRequest', new Blob([JSON.stringify(quizsetObject)], { type: 'application/json' })); formData.append(
'quizSetCreateRequest',
new Blob([JSON.stringify(quizsetObject)], { type: 'application/json' })
);
images.forEach((imageFile) => {
if (imageFile) { if (imageFile) {
formData.append('image', imageFile); formData.append('images', imageFile);
} else {
formData.append('images', new Blob([''], { type: 'image/jpg' }));
} }
const response = await quizsetWrite(formData); });
console.log(response);
await quizsetWrite(formData);
navigate('..');
}; };
return ( return (
<QuizsetForm <QuizsetForm
onSubmit={handleSubmit} onSubmit={handleSubmit}

View File

@ -12,7 +12,7 @@ const instance = axios.create({
instance.interceptors.request.use((config) => { instance.interceptors.request.use((config) => {
const accessToken = useBoundStore.getState().token; const accessToken = useBoundStore.getState().token;
console.log(accessToken);
if (accessToken) { if (accessToken) {
config.headers.Authorization = `${accessToken}`; config.headers.Authorization = `${accessToken}`;
} }