-
+ const onSubmit = () => {
+ if (!email || !password) {
+ alert('이메일과 비밀번호를 입력해주세요.');
+ return;
+ }
+ handleLogin(email, password);
+ };
+
+ return (
+
+
로그인
+
+ setEmail(e.target.value)}
+ className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
+ />
+
+
+ setPassword(e.target.value)}
+ className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
+ />
+
+
+
+
+ 비밀번호를 잊으셨나요?
+
+
-
-);
+ );
+};
diff --git a/FE/src/pages/MainPage.tsx b/FE/src/pages/MainPage.tsx
index f580f2e..d23e8d5 100644
--- a/FE/src/pages/MainPage.tsx
+++ b/FE/src/pages/MainPage.tsx
@@ -19,7 +19,9 @@ export const MainPage = () => {
-
+
diff --git a/FE/src/pages/PinPage.tsx b/FE/src/pages/PinPage.tsx
new file mode 100644
index 0000000..2fb7cd5
--- /dev/null
+++ b/FE/src/pages/PinPage.tsx
@@ -0,0 +1,74 @@
+import { socketService } from '@/api/socket';
+import { HeaderBar } from '@/components/HeaderBar';
+import { TextInput } from '@/components/TextInput';
+import { getRandomNickname } from '@/utils/nickname';
+import { useEffect, useState } from 'react';
+
+export const PinPage = () => {
+ const [nickname, setNickname] = useState('');
+ const [pin, setPin] = useState('');
+ const [errors, setErrors] = useState({ nickname: '', pin: '' });
+
+ useEffect(() => {
+ setNickname(getRandomNickname());
+ }, []);
+
+ const handleJoin = () => {
+ const newErrors = { nickname: '', pin: '' };
+ let hasError = false;
+
+ if (!nickname.trim()) {
+ newErrors.nickname = '닉네임을 입력해주세요';
+ hasError = true;
+ }
+
+ if (!pin.trim()) {
+ newErrors.pin = '핀번호를 입력해주세요';
+ hasError = true;
+ }
+
+ setErrors(newErrors);
+
+ if (hasError) return;
+
+ socketService.joinRoom(pin, nickname);
+ };
+
+ return (
+ <>
+
+
+
+
방 들어가기
+
+ {
+ setNickname(e.target.value);
+ if (errors.nickname) setErrors((prev) => ({ ...prev, nickname: '' }));
+ }}
+ error={errors.nickname}
+ />
+
+ {
+ setPin(e.target.value);
+ if (errors.pin) setErrors((prev) => ({ ...prev, pin: '' }));
+ }}
+ error={errors.pin}
+ />
+
+
+
+
+ >
+ );
+};
diff --git a/FE/src/pages/QuizSetupPage.tsx b/FE/src/pages/QuizSetupPage.tsx
index fe608ba..210536e 100644
--- a/FE/src/pages/QuizSetupPage.tsx
+++ b/FE/src/pages/QuizSetupPage.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import {
TextField,
Button,
@@ -9,6 +9,9 @@ import {
SelectChangeEvent
} from '@mui/material';
import { HeaderBar } from '@/components/HeaderBar';
+import { TextInput } from '@/components/TextInput';
+import { createQuizSet } from '@/api/rest/quizApi';
+import { CreateQuizSetPayload } from '@/api/rest/quizTypes';
/*
{
title: string, // 퀴즈셋의 제목
@@ -43,29 +46,44 @@ type QuizData = {
};
export const QuizSetupPage: React.FC = () => {
- const [title, setTitle] = useState
('');
- const [category, setCategory] = useState('');
- const [quizSet, setQuizSet] = useState([
- { quiz: '', limitTime: 0, choices: [{ content: '', order: 1, isAnswer: false }] }
- ]);
+ const [title, setTitle] = useState('');
+ const [titleError, setTitleError] = useState('');
+ const [category, setCategory] = useState('');
+ const [quizSet, setQuizSet] = useState([]);
+ const [quizErrorIndex, setQuizErrorIndex] = useState(null);
+ const [choiceErrorIndex, setChoiceErrorIndex] = useState(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ // 빌드가 되기 위해 변수 사용
+ console.log(isSubmitting);
+
+ const handleTitleChange: React.ChangeEventHandler = (e) => {
+ setTitle(e.target.value);
+ setTitleError('');
+ };
- const handleTitleChange = (e: React.ChangeEvent) => setTitle(e.target.value);
const handleCategoryChange = (e: SelectChangeEvent) => {
setCategory(e.target.value);
};
+ // 퀴즈 이름 변경
const handleQuizChange = (index: number, value: string) => {
const updatedQuizSet = [...quizSet];
updatedQuizSet[index].quiz = value;
setQuizSet(updatedQuizSet);
+ if (index === quizErrorIndex) setQuizErrorIndex(null);
};
+ // 제한 시간 변경
const handleLimitTimeChange = (index: number, value: string) => {
const updatedQuizSet = [...quizSet];
- updatedQuizSet[index].limitTime = parseInt(value, 10);
+ let newLimitTime = parseInt(value, 10);
+ if (newLimitTime > 99) newLimitTime %= 10;
+ updatedQuizSet[index].limitTime = Math.min(60, Math.max(1, newLimitTime));
setQuizSet(updatedQuizSet);
};
+ // 질문지 변경
const handleChoiceChange = (
quizIndex: number,
choiceIndex: number,
@@ -74,33 +92,112 @@ export const QuizSetupPage: React.FC = () => {
) => {
const updatedQuizSet = [...quizSet];
if (field === 'isAnswer') {
- (updatedQuizSet[quizIndex].choices[choiceIndex][field] as boolean) = value === 'true';
+ // 정답인지를 수정한 경우
+ updatedQuizSet[quizIndex].choices = updatedQuizSet[quizIndex].choices.map((c, i) => ({
+ ...c,
+ isAnswer: choiceIndex === i
+ }));
} else {
+ // 질문지 인풋을 수정한 경우
(updatedQuizSet[quizIndex].choices[choiceIndex][field] as string) = value;
+ if (
+ choiceErrorIndex &&
+ quizIndex === choiceErrorIndex[0] &&
+ choiceIndex === choiceErrorIndex[1]
+ )
+ setChoiceErrorIndex(null);
}
setQuizSet(updatedQuizSet);
};
- const addQuiz = () => {
+
+ // 퀴즈 추가
+ const addQuiz = useCallback(() => {
setQuizSet([
...quizSet,
- { quiz: '', limitTime: 0, choices: [{ content: '', order: 1, isAnswer: false }] }
+ { quiz: '', limitTime: 10, choices: [{ content: '', order: 1, isAnswer: true }] }
]);
+ }, [quizSet]);
+
+ const removeQuiz = (quizIndex: number) => {
+ setQuizSet(quizSet.filter((_, i) => i !== quizIndex));
};
+ //선택지 삭제
+ const removeChoice = (quizIndex: number, choiceIndex: number) => {
+ const updatedQuizSet = [...quizSet];
+ updatedQuizSet[quizIndex].choices = updatedQuizSet[quizIndex].choices.filter(
+ (_, i) => i !== choiceIndex
+ );
+ setQuizSet(updatedQuizSet);
+ };
+
+ //선택지 추가
const addChoice = (quizIndex: number) => {
+ if (quizSet[quizIndex].choices.length > 5) return;
const updatedQuizSet = [...quizSet];
const newChoiceOrder = updatedQuizSet[quizIndex].choices.length + 1;
updatedQuizSet[quizIndex].choices.push({ content: '', order: newChoiceOrder, isAnswer: false });
setQuizSet(updatedQuizSet);
};
- const handleSubmit = () => {
+ const handleSubmit = async () => {
+ // 제목 비어있는지 검사
+ if (!title.trim()) {
+ setTitleError('제목을 입력해주세요');
+ return;
+ }
+
+ // 퀴즈 이름 비어있는지 검사
+ const emptyQuizIndex = quizSet.findIndex((quiz) => !quiz.quiz.trim());
+ if (emptyQuizIndex >= 0) {
+ setQuizErrorIndex(emptyQuizIndex);
+ return;
+ }
+
+ //선택지 비어있는지 검사
+ const emptyQuizChoiceIndex = quizSet.findIndex((quiz) =>
+ quiz.choices.find((choice) => !choice.content.trim())
+ );
+ if (emptyQuizChoiceIndex >= 0) {
+ const emptyChoiceIndex = quizSet[emptyQuizChoiceIndex].choices.findIndex(
+ (choice) => !choice.content.trim()
+ );
+ setChoiceErrorIndex([emptyQuizChoiceIndex, emptyChoiceIndex]);
+ return;
+ }
+
const quizData: QuizData = { title, category, quizSet };
+ const payload: CreateQuizSetPayload = {
+ title: quizData.title,
+ category: quizData.category,
+ quizList: quizData.quizSet // 이름 변경
+ };
console.log('Quiz Data:', quizData);
- // POST 요청
- // fetch "/api/quizset"
+
+ try {
+ setIsSubmitting(true); // 로딩 시작
+ const response = await createQuizSet(payload);
+ if (response) {
+ alert('퀴즈셋이 성공적으로 생성되었습니다!');
+ // 성공적으로 생성되면 상태 초기화
+ setTitle('');
+ setCategory('');
+ setQuizSet([]);
+ } else {
+ alert('퀴즈셋 생성에 실패했습니다. 다시 시도해주세요.');
+ }
+ } catch (error) {
+ console.error('Error submitting quiz data:', error);
+ alert('퀴즈셋 생성 중 오류가 발생했습니다.');
+ } finally {
+ setIsSubmitting(false); // 로딩 종료
+ }
};
+ useEffect(() => {
+ if (quizSet.length === 0) addQuiz();
+ }, [quizSet, addQuiz]);
+
return (
<>
@@ -109,14 +206,7 @@ export const QuizSetupPage: React.FC = () => {
퀴즈셋 생성하기
-
+
diff --git a/FE/src/store/useChatStore.ts b/FE/src/store/useChatStore.ts
index 82e771a..0c67d32 100644
--- a/FE/src/store/useChatStore.ts
+++ b/FE/src/store/useChatStore.ts
@@ -20,6 +20,6 @@ export const useChatStore = create