Merge branch 'frontend' into 'fe/useAcceptLecture'

# Conflicts:
#   frontend/src/Router.jsx
This commit is contained in:
조민우 2024-08-05 16:36:11 +09:00
commit 30e159273e
24 changed files with 460 additions and 163 deletions

View File

@ -5,9 +5,9 @@ import HomePage from './pages/HomePage';
import NotFoundPage from './pages/NotFoundPage';
import { lazy } from 'react';
import MyPageLayout from './components/Layout/MyPageLayout';
import { LiveLayout } from './components/Layout';
import LivePage from './pages/LivePage';
import ErrorPage from './pages/ErrorPage';
const LivePage = lazy(async () => await import('./pages/LivePage'));
const LectureLayout = lazy(async () => await import('./components/Layout/LectureLayout'));
const LearningLectureDetailPage = lazy(async () => await import('./pages/LearningLectureDetailPage'));
const NoticeListPage = lazy(async () => await import('./pages/NoticeListPage'));
@ -31,22 +31,21 @@ 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 LectureEnrollPage = lazy(async () => await import('./pages/LectureEnrollPage'));
const QuizsetEditPage = lazy(async () => await import('./pages/QuizsetEditPage'));
const router = createBrowserRouter([
{
path: '*',
element: <NotFoundPage />,
},
{
path: 'live/:roomId',
element: <LiveLayout />,
children: [
{
index: true,
element: <LivePage />,
},
],
element: <LivePage />,
},
{
path: '',
element: <PageLayout />,
errorElement: <NotFoundPage />,
errorElement: <ErrorPage />,
children: [
{
index: true,
@ -145,7 +144,16 @@ const router = createBrowserRouter([
},
{
path: ':quizsetId',
element: <QuizsetDetailPage />,
children: [
{
index: true,
element: <QuizsetDetailPage />,
},
{
path: 'edit',
element: <QuizsetEditPage />,
},
],
},
],
},

View File

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

View File

@ -4,7 +4,7 @@ import styles from './LectureForm.module.css';
import EditIcon from '/src/assets/icons/edit.svg?react';
import BackIcon from '/src/assets/icons/back.svg?react';
export default function LectureForm({ title, topic, to, initialValues = {}, onSubmit, onCreate = false }) {
export default function LectureForm({ title, topic, to = '..', initialValues = {}, onSubmit, onCreate = false }) {
// TODO: , useState
const titleRef = useRef('');
const descriptionRef = useRef('');

View File

@ -1,3 +1,4 @@
import styles from './LiveRoom.module.css';
import { isEqualTrackRef, isTrackReference } from '@livekit/components-core';
import {
CarouselLayout,
@ -11,14 +12,22 @@ import {
RoomAudioRenderer,
useCreateLayoutContext,
usePinnedTracks,
useRoomInfo,
useTracks,
useParticipants,
useLocalParticipant,
} from '@livekit/components-react';
import { RoomEvent, Track } from 'livekit-client';
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import ChatRoom from '../ChatRoom/ChatRoom';
export default function LiveRoom() {
const lastAutoFocusedScreenShareTrack = useRef(null);
const [role, setRole] = useState(null);
const room = useRoomInfo();
const participants = useParticipants();
const { localParticipant } = useLocalParticipant();
const tracks = useTracks(
[
@ -36,6 +45,16 @@ export default function LiveRoom() {
const focusTrack = usePinnedTracks(layoutContext)?.[0];
const carouselTracks = tracks.filter((track) => !isEqualTrackRef(track, focusTrack));
useEffect(() => {
try {
const role = JSON.parse(localParticipant.identity).role;
setRole(role);
} catch (_) {
return;
}
}, [localParticipant.identity]);
useEffect(() => {
if (
screenShareTracks.some((track) => track.publication.isSubscribed) &&
@ -69,31 +88,40 @@ export default function LiveRoom() {
]);
return (
<div className="lk-video-conference">
<LayoutContextProvider value={layoutContext}>
<div className="lk-video-conference-inner">
{!focusTrack ? (
<div className="lk-grid-layout-wrapper">
<GridLayout tracks={tracks}>
<ParticipantTile />
</GridLayout>
</div>
) : (
<div className="lk-focus-layout-wrapper">
<FocusLayoutContainer>
<CarouselLayout tracks={carouselTracks}>
<ParticipantTile />
</CarouselLayout>
{focusTrack && <FocusLayout trackRef={focusTrack} />}
</FocusLayoutContainer>
</div>
)}
<ControlBar controls={{ chat: false, leave: false }} />
<div className={styles.wrapper}>
<header className={styles.header}>
<h1 className={styles.title}>{room.name}</h1>
<div className={styles.roomInfo}>
<span>참가자</span>
<span>{participants.length}</span>
</div>
<ChatRoom />
</LayoutContextProvider>
<RoomAudioRenderer />
<ConnectionStateToast />
</header>
<div className="lk-video-conference">
<LayoutContextProvider value={layoutContext}>
<div className="lk-video-conference-inner">
{!focusTrack ? (
<div className="lk-grid-layout-wrapper">
<GridLayout tracks={tracks}>
<ParticipantTile />
</GridLayout>
</div>
) : (
<div className="lk-focus-layout-wrapper">
<FocusLayoutContainer>
<CarouselLayout tracks={carouselTracks}>
<ParticipantTile />
</CarouselLayout>
{focusTrack && <FocusLayout trackRef={focusTrack} />}
</FocusLayoutContainer>
</div>
)}
<ControlBar controls={{ chat: false, leave: false, screenShare: role === '강사' }} />
</div>
<ChatRoom />
</LayoutContextProvider>
<RoomAudioRenderer />
<ConnectionStateToast />
</div>
</div>
);
}

View File

@ -1,67 +1,32 @@
.main {
.wrapper {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
}
.videoWrapper {
flex: 0 0 auto;
display: flex;
overflow-x: auto;
height: 80px;
gap: 10px;
padding: 10px;
border-bottom: 1px solid var(--border-color);
box-sizing: border-box;
& > audio {
display: none;
}
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: var(--text-color-tertiary);
border-radius: 6px;
}
}
.mainContent {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
box-sizing: border-box;
& > video {
width: 100%;
height: calc(100vh - 208px);
object-fit: contain;
box-sizing: border-box;
}
}
.controlBar {
.header {
display: flex;
justify-content: space-between;
align-items: center;
height: 80px;
border-top: 1px solid var(--border-color);
box-sizing: border-box;
& > button {
background-color: var(--background-color-secondary);
color: var(--text-color-primary);
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: var(--background-color-tertiary);
}
}
width: 100%;
height: 48px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.title {
padding: 0 16px;
font-size: 14px;
line-height: 1.4;
font-weight: 500;
}
.roomInfo {
display: flex;
align-items: center;
gap: 4px;
padding: 0 16px;
font-size: 12px;
line-height: 1.4;
font-weight: 500;
}

View File

@ -1,13 +1,13 @@
import { useState, useRef } from 'react';
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 [errorSameMessage, setErrorSameMessage] = useState(false);
const currentPasswordRef = useRef('');
const newPasswordRef = useRef('');
const confirmPasswordRef = useRef('');
const userPassword = '1234';
const handleSubmit = (e) => {
e.preventDefault();
@ -15,13 +15,15 @@ export default function PasswordChangeForm() {
const newPassword = newPasswordRef.current.value;
const confirmPassword = confirmPasswordRef.current.value;
if (currentPassword === userPassword) {
setErrorSameMessage(false);
} else {
setErrorSameMessage(true);
}
if (newPassword === confirmPassword) {
setErrorConfirmMessage(false);
onSubmit(currentPassword, newPassword, confirmPassword);
if (onPwError) {
setErrorSameMessage(true);
} else {
setErrorSameMessage(false);
}
} else {
setErrorConfirmMessage(true);
}

View File

@ -1,15 +1,16 @@
import { useState } from 'react';
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 [answer, setAnswer] = useState(quiz.answer || '');
const [choices, setChoices] = useState(quiz.choices || []);
const [image, setImage] = useState(quiz.image || null);
const handleChoiceChange = (num, content) => {
const updatedChoices = choices.map((choice) => (choice.num === num ? { ...choice, content } : choice));
setChoices(updatedChoices);
updateQuiz(index, { question, answer, choices: updatedChoices });
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
};
const handleAddChoice = () => {
@ -17,7 +18,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
const newChoice = { num: choices.length + 1, content: '' };
const updatedChoices = [...choices, newChoice];
setChoices(updatedChoices);
updateQuiz(index, { question, answer, choices: updatedChoices });
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
}
};
@ -25,19 +26,29 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
if (choices.length > 0) {
const updatedChoices = choices.slice(0, -1);
setChoices(updatedChoices);
updateQuiz(index, { question, answer, choices: updatedChoices });
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
}
};
const handleFileChange = (e) => {
const file = e.target.files[0] ?? null;
setImage(file);
updateQuiz(quiz.id, { ...quiz, question, answer, choices, image: file });
};
return (
<div className={styles.card}>
<div className={styles.header}>
<span>퀴즈 생성 카드</span>
<span onClick={() => deleteQuiz(quiz.id)}>X</span> {/* id를 기반으로 삭제 */}
</div>
<label>질문</label>
<input
type="text"
value={question}
onChange={(e) => {
setQuestion(e.target.value);
updateQuiz(index, { question: e.target.value, answer, choices });
updateQuiz(quiz.id, { ...quiz, question: e.target.value, answer, choices, image });
}}
placeholder="질문 내용을 입력하세요"
/>
@ -47,7 +58,7 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
value={answer}
onChange={(e) => {
setAnswer(e.target.value);
updateQuiz(index, { question, answer: e.target.value, choices });
updateQuiz(quiz.id, { ...quiz, question, answer: e.target.value, choices, image });
}}
placeholder="정답을 입력하세요"
/>
@ -81,6 +92,12 @@ export default function QuizCard({ quiz, index, updateQuiz }) {
/>
</div>
))}
<label>퀴즈 이미지</label>
<input
type="file"
accept=".png, .jpg, .jpeg"
onChange={handleFileChange}
/>
</div>
);
}

View File

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

View File

@ -1,28 +1,39 @@
import { useState } from 'react';
import { useState, useEffect } 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({ headerTitle, topic, to, onSubmit }) {
// TODO:
export default function QuizsetForm({ headerTitle, topic, to, onSubmit, initialValue = null }) {
const [title, setTitle] = useState('');
const [quizzes, setQuizzes] = useState([]);
const [imageFile, setImageFile] = useState(null);
const [quizId, setQuizId] = useState(0);
useEffect(() => {
if (initialValue) {
setTitle(initialValue.title || '');
setQuizzes(initialValue.quizzes || []);
setQuizId(initialValue.quizzes ? initialValue.quizzes[initialValue.quizzes.length - 1].id + 1 : 0);
console.log(initialValue.quizzes.length);
}
}, [initialValue]);
const handleAddQuiz = () => {
setQuizzes([...quizzes, { question: '', answer: '', choices: [] }]);
console.log(quizzes);
setQuizzes([...quizzes, { id: quizId, question: '', answer: '', choices: [], image: null }]);
setQuizId(quizId + 1);
};
const updateQuiz = (index, updatedQuiz) => {
const updatedQuizzes = quizzes.map((quiz, i) => (i === index ? updatedQuiz : quiz));
const updateQuiz = (id, updatedQuiz) => {
console.log(quizzes);
const updatedQuizzes = quizzes.map((quiz) => (quiz.id === id ? updatedQuiz : quiz));
setQuizzes(updatedQuizzes);
};
const handleFileChange = (e) => {
const file = e.target.files?.[0];
setImageFile(file);
const deleteQuiz = (id) => {
console.log(quizzes);
setQuizzes(quizzes.filter((quiz) => quiz.id !== id));
};
return (
@ -39,7 +50,7 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
</header>
<form
className={styles.form}
onSubmit={(e) => onSubmit(e, title, quizzes, imageFile)}
onSubmit={(e) => onSubmit(e, title, quizzes)}
>
<input
type="text"
@ -47,12 +58,12 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
onChange={(e) => setTitle(e.target.value)}
placeholder="퀴즈셋 제목을 입력해주세요"
/>
{quizzes.map((quiz, index) => (
{quizzes.map((quiz) => (
<QuizCard
key={index}
key={quiz.id}
quiz={quiz}
index={index}
updateQuiz={updateQuiz}
deleteQuiz={deleteQuiz}
/>
))}
<button
@ -62,12 +73,6 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit }) {
>
퀴즈 추가하기
</button>
<label>퀴즈 이미지</label>
<input
type="file"
accept=".png, .jpg, .jpeg"
onChange={handleFileChange}
/>
<button
type="submit"
className={styles.button}

View File

@ -2,7 +2,7 @@ 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 }) {
export default function QuizsetDetail({ topic, title, quizzes = [], onDelete, onEdit }) {
return (
<div className={styles.quizsetDetail}>
<header className={styles.header}>
@ -17,6 +17,30 @@ export default function QuizsetDetail({ topic, title }) {
<h1 className={styles.title}>{title}</h1>
</div>
</header>
<div>
{quizzes.map((quiz, index) => (
<div key={index}>
<div>질문 : {quiz.question}</div>
<img
src={quiz.image}
alt="강의 이미지"
/>
<div>정답 : {quiz.answer}</div>
</div>
))}
</div>
<button
type="button"
onClick={onDelete}
>
퀴즈셋 삭제
</button>
<button
type="button"
onClick={onEdit}
>
퀴즈셋 수정
</button>
</div>
);
}

View File

@ -59,5 +59,23 @@ export function useAuth() {
});
};
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

@ -5,6 +5,6 @@ import { API_URL } from '../../constants';
export function useQuizsetDetail(id) {
return useSuspenseQuery({
queryKey: ['quizset', id],
queryFn: () => instance.get(`${API_URL}/quiz/${id}`),
queryFn: () => instance.get(`${API_URL}/quiz/teacher/${id}`),
});
}

View File

@ -0,0 +1,14 @@
import instance from '../../utils/axios/instance';
import { API_URL } from '../../constants';
export function useQuizsetEdit() {
const quizsetEdit = (formData) => {
return instance.put(`${API_URL}/quiz`, formData, {
headers: {
'Content-type': 'multipart/form-data',
},
});
};
return { quizsetEdit };
}

View File

@ -0,0 +1,45 @@
import styles from './ErrorPage.module.css';
import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Header } from '../../components/Header';
import { Footer } from '../../components/Footer';
export default function ErrorPage() {
const [time, setTime] = useState(5);
const navigate = useNavigate();
useEffect(() => {
const timer = setInterval(() => {
setTime((prev) => prev - 1);
}, 1000);
return () => clearInterval(timer);
}, []);
useEffect(() => {
if (time === 0) {
navigate('/');
}
}, [navigate, time]);
return (
<>
<Header />
<div className={styles.wrapper}>
<div className={styles.contents}>
<p className={styles.title}>에러가 발생했습니다.</p>
<p className={styles.msg}>
<span className={styles.seconds}>{time}</span> 후에 자동으로 홈으로 이동합니다.
</p>
<Link
to={'/'}
className={styles.link}
>
홈으로 가기
</Link>
</div>
<Footer />
</div>
</>
);
}

View File

@ -0,0 +1,49 @@
.wrapper {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
min-height: 100vh;
padding-top: 64px;
color: var(--text-color);
font-size: 24px;
line-height: 1.2;
font-weight: 700;
box-sizing: border-box;
}
.contents {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
}
.title {
margin: 32px;
font-size: 32px;
}
.msg {
padding: 0;
margin: 0;
}
.seconds {
color: var(--primary-color);
font-size: 24px;
line-height: 1.2;
font-weight: 700;
}
.link {
color: var(--primary-color);
font-size: 16px;
line-height: 1.4;
font-weight: 700;
}

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import instance from '../../utils/axios/instance';
import { API_URL, ROOM_URL } from '../../constants';
import useBoundStore from '../../store';
import '@livekit/components-styles';
import LoadingIndicator from '../../components/LoadingIndicator.jsx/LoadingIndicator';
export default function LivePage() {
const { roomId } = useParams();
@ -26,16 +27,16 @@ export default function LivePage() {
}
}, [generateToken, liveToken]);
return (
liveToken && (
<LiveKitRoom
token={liveToken}
serverUrl={ROOM_URL}
connect={true}
data-lk-theme="default"
>
<LiveRoom />
</LiveKitRoom>
)
return liveToken ? (
<LiveKitRoom
token={liveToken}
serverUrl={ROOM_URL}
connect={true}
data-lk-theme="default"
>
<LiveRoom />
</LiveKitRoom>
) : (
<LoadingIndicator fill />
);
}

View File

@ -1,11 +1,30 @@
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 { useQuizsetDelete } from '../../hooks/api/useQuizsetDelete';
export default function QuizsetListPage() {
const { lectureId } = useParams();
const { data } = useQuizsetDetail(lectureId);
const quizset = data?.data ?? [];
export default function QuizsetDetailPage() {
const navigate = useNavigate();
const { quizsetId } = useParams();
const { quizsetDelete } = useQuizsetDelete();
const { data } = useQuizsetDetail(quizsetId);
const quizset = data.data;
console.log(quizset);
return <QuizsetDetail title={quizset.title} />;
const handleEdit = () => {
navigate('edit', { state: { initialValue: quizset } });
};
const handleDelete = async () => {
await quizsetDelete(quizsetId);
navigate('..');
};
return (
<QuizsetDetail
topic={'퀴즈 목록'}
title={quizset.title}
quizzes={quizset.quizzes}
onDelete={handleDelete}
onEdit={handleEdit}
/>
);
}

View File

@ -0,0 +1,59 @@
import { QuizsetForm } from '../../components/QuizForm';
import { useQuizsetEdit } from '../../hooks/api/useQuizsetEdit';
import { useNavigate, useLocation } from 'react-router-dom';
import { useParams } from 'react-router-dom';
export default function QuizsetEditPage() {
const { quizsetId } = useParams();
const navigate = useNavigate();
const location = useLocation();
const initialValue = location.state.initialValue;
const { quizsetEdit } = useQuizsetEdit();
const handleSubmit = async (e, title, quizzes) => {
e.preventDefault();
console.log(quizzes);
const images = [];
const quizContents = [];
quizzes.forEach((quiz) => {
const { image, ...quizData } = quiz;
images.push(image);
quizContents.push(quizData);
});
const quizsetObject = {
id: quizsetId,
title,
quizzes: quizContents,
};
console.log(quizsetObject);
const formData = new FormData();
formData.append('quizSetUpdateRequest', new Blob([JSON.stringify(quizsetObject)], { type: 'application/json' }));
images.forEach((imageFile) => {
if (imageFile && !(typeof imageFile === 'string')) {
formData.append('images', imageFile);
} else {
formData.append('images', new Blob([''], { type: 'image/jpg' }));
}
});
formData.forEach((value, key) => {
console.log(`FormData - Key: ${key}, Value:`, value);
});
await quizsetEdit(formData);
navigate('..');
};
return (
<QuizsetForm
initialValue={initialValue}
onSubmit={handleSubmit}
headerTitle={initialValue.title}
topic={'퀴즈 수정'}
to={'..'}
/>
);
}

View File

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

View File

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

View File

@ -1,4 +1,6 @@
export const liveSlice = (set) => ({
liveToken: null,
setLiveToken: (liveToken) => set({ liveToken }),
participants: 0,
setParticipants: (participants) => set({ participants }),
});