Skip to content

Commit

Permalink
[FE] - 퀴즈 진행 페이지 소켓 연결 (#97)
Browse files Browse the repository at this point in the history
* feat: quiz-session socket 연결

* fix: socket transports 방식 websocket으로 고정

* feat: User Type 식별 및 퀴즈 시작 소켓 연결

* fix: 퀴즈 로딩 화면 UI 수정

* fix: quizLoading UI 변경

* feat: spin keyframe 추가

* feat: quiz loading UI 추가

* feat: quiz end UI 추가

* feat: quiz end 상태 추가
  • Loading branch information
chan-byeong authored Nov 21, 2024
1 parent 5ed80a8 commit 39eb220
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 59 deletions.
23 changes: 16 additions & 7 deletions packages/client/src/pages/quiz-session/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';

import { getQuizSocket } from '@/shared/utils/socket';
import QuizBackground from './ui/QuizBackground';
import QuizBox from './ui/QuizBox';
import QuizEnd from './ui/QuizEnd';
import QuizHeader from './ui/QuizHeader';
import QuizLoading from './ui/QuizLoading';
import { toastController } from '@/features/toast/model/toastController';

export default function QuizSession() {
const { pinCode } = useParams();
const socket = getQuizSocket();
const toast = toastController();
const [isLoading, setIsLoading] = useState(true);
const [isQuizEnd, setIsQuizEnd] = useState(false);
const [reactionStats, setReactionStats] = useState({
easy: 0,
hard: 0,
});
const [quiz, setQuiz] = useState(null);
const [quiz, setQuiz] = useState<QuizData>({
id: '',
content: '',
choices: [],
});

const totalReactions = reactionStats.easy + reactionStats.hard;
const easyPercentage = totalReactions ? (reactionStats.easy / totalReactions) * 100 : 50;

useEffect(() => {
const quizPromise = new Promise((resolve, reject) => {
const handleShowQuiz = (data: any) => {
const handleShowQuiz = (data: QuizData) => {
setQuiz(data);
setIsLoading(true);
setIsQuizEnd(false);
resolve(data);
};

socket.on('show quiz', handleShowQuiz);

const timer = setTimeout(() => {
Expand All @@ -52,12 +61,11 @@ export default function QuizSession() {
setIsLoading(false);
});

socket.emit('timeout', (response: any) => {
console.log(response);
socket.on('timer end', () => {
setIsQuizEnd(true);
});
}, []);

console.log(quiz);
return (
<>
{isLoading ? (
Expand All @@ -66,9 +74,10 @@ export default function QuizSession() {
<div>
<QuizHeader />
<QuizBackground easyPercentage={easyPercentage} />
<QuizBox reactionStats={reactionStats} setReactionStats={setReactionStats} />
<QuizBox reactionStats={reactionStats} setReactionStats={setReactionStats} quiz={quiz} />
</div>
)}
{isQuizEnd && <QuizEnd />}
</>
);
}
12 changes: 12 additions & 0 deletions packages/client/src/pages/quiz-session/type.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface Choice {
id: number;
quizId: number;
content: string;
isCorrect: boolean;
position: number;
}
interface QuizData {
id: string;
content: string;
choices: Choice[];
}
27 changes: 9 additions & 18 deletions packages/client/src/pages/quiz-session/ui/QuizBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ interface ReactionData {
interface QuizBoxProps {
reactionStats: ReactionData;
setReactionStats: Dispatch<SetStateAction<ReactionData>>;
quiz: QuizData;
}

export default function QuizBox({ reactionStats, setReactionStats }: QuizBoxProps) {
export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizBoxProps) {
const [selectedAnswer, setSelectedAnswer] = useState<number[]>([]);
const [hasSubmitted, setHasSubmitted] = useState(false);
const easyButtonRef = useRef<HTMLButtonElement>(null);
const hardButtonRef = useRef<HTMLButtonElement>(null);
const socket = getQuizSocket();

console.log(quiz);
const handleSelectAnswer = (idx: number) => {
setSelectedAnswer((prev) => {
if (prev.includes(idx)) {
Expand Down Expand Up @@ -80,32 +81,22 @@ export default function QuizBox({ reactionStats, setReactionStats }: QuizBoxProp
</div>
{/* 문제 */}
<div className="mb-8">
<h2 className="text-xl font-bold mb-4">
Python의 기본 자료형에 대한 설명으로 올바른 것은?
</h2>
<p className="text-gray-600">
다음 중 Python의 기본 자료형(Data Type)에 대한 설명으로 가장 적절한 것을 고르시오.
</p>
<h2 className="text-xl font-bold mb-4">{quiz?.content}</h2>
</div>
{/* 선택지 */}
<div className="space-y-4">
{[
'문자열(string)은 변경 가능한(mutable) 자료형이다.',
'튜플(tuple)은 변경 불가능한(immutable) 자료형이다.',
'리스트(list)는 변경 불가능한(immutable) 자료형이다.',
'딕셔너리(dictionary)는 정렬된 자료형이다.',
].map((answer, idx) => (
{quiz?.choices.map((choice, idx) => (
<button
key={idx}
onClick={() => handleSelectAnswer(idx)}
key={choice.id}
onClick={() => handleSelectAnswer(choice.id)}
className={`w-full p-4 text-left rounded-xl border transition-all ${
selectedAnswer.includes(idx)
selectedAnswer.includes(choice.id)
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-200'
}`}
>
<span className="font-medium mr-3">{String.fromCharCode(65 + idx)}.</span>
{answer}
{choice.content}
</button>
))}
</div>
Expand Down
28 changes: 28 additions & 0 deletions packages/client/src/pages/quiz-session/ui/QuizEnd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default function QuizLoading() {
return (
<div className="min-h-screen bg-blue-100 p-4">
<div className="max-w-2xl mx-auto mt-12 p-16 ">
<div className="text-center mb-8">
<span className="text-4xl font-bold">🏆 Leader Board </span>
</div>

<div className="flex justify-center items-end gap-20 mb-12 border-2 bg-gradient-to-b from-blue-50 to-white rounded-2xl p-4">
<div className="flex flex-col items-center gap-2">
<div className="h-24 w-12 bg-gradient-to-t from-blue-200 to-blue-100 rounded-base" />
</div>
<div className="flex flex-col items-center gap-2">
<div className="h-32 w-12 bg-gradient-to-t from-blue-300 to-blue-200 rounded-base" />
</div>
<div className="flex flex-col items-center gap-2">
<div className="h-20 w-12 bg-gradient-to-t from-blue-100 to-blue-50 rounded-base" />
</div>
</div>

<div className="flex flex-col justify-center items-center gap-8">
<span className="text-bold-xl">나는 몇 등?</span>
<span className="font-semibold text-4xl text-blue-600">#10</span>
</div>
</div>
</div>
);
}
61 changes: 39 additions & 22 deletions packages/client/src/pages/quiz-session/ui/QuizLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,48 @@
export default function QuizLoading() {
const QuizLoading = () => {
return (
<div className="min-h-screen bg-blue-100 p-4">
<div className="max-w-4xl mx-auto mt-12 ">
<div className="text-center mb-8">
<span className="text-bold-xl">Leader Board</span>
</div>
<div className="min-h-screen w-full flex flex-col items-center justify-center bg-gradient-to-b from-slate-50 to-white">
<div className="relative p-12 rounded-3xl bg-white/30 backdrop-blur-lg shadow-[0_8px_30px_rgb(0,0,0,0.05)] border border-white/20">
<div className="relative flex flex-col items-center">
<div className="relative w-32 h-32">
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-20 h-20 rounded-full border-2 border-transparent border-t-blue-500 border-l-blue-500 animate-spin" />
</div>

<div className="flex justify-center items-end gap-20 mb-12 border-2 border-white rounded-2xl p-4">
<div className="flex flex-col items-center gap-2">
<div className="h-24 w-12 bg-white rounded-base" />
<span>2</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="h-32 w-12 bg-white rounded-base" />
<span>1</span>
<div className="absolute inset-0 flex items-center justify-center animate-spin-slow">
<div className="absolute w-2 h-2 rounded-full bg-blue-400 top-0" />
<div className="absolute w-2 h-2 rounded-full bg-indigo-400 right-0" />
<div className="absolute w-2 h-2 rounded-full bg-violet-400 bottom-0" />
<div className="absolute w-2 h-2 rounded-full bg-purple-400 left-0" />
</div>

<div className="absolute inset-0 flex items-center justify-center">
<div className="w-3 h-3 rounded-full bg-blue-500 animate-pulse" />
</div>
</div>
<div className="flex flex-col items-center gap-2">
<div className="h-20 w-12 bg-white rounded-base" />
<span>3</span>

<div className="mt-8 text-center">
<h2 className="text-xl font-medium text-slate-700 tracking-wide">
다음 퀴즈로 이동 중 입니다.
</h2>

<div className="flex items-center justify-center gap-1.5 mt-3">
{[0, 1, 2].map((i) => (
<div
key={i}
className="w-1.5 h-1.5 rounded-full bg-gradient-to-r from-blue-500 to-violet-500 animate-bounce"
style={{ animationDelay: `${i * 150}ms` }}
/>
))}
</div>
</div>
</div>

<div className="flex flex-col justify-center items-center gap-8">
<span className="text-bold-xl">나는 몇 등?</span>
<span className="font-semibold text-4xl text-primary">#10</span>
<p className="mt-4 text-sm text-slate-500 font-medium animate-pulse">
잠시만 기다려주세요...
</p>
</div>
</div>
</div>
);
}
};

export default QuizLoading;
31 changes: 19 additions & 12 deletions packages/client/src/pages/quiz-wait/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ export default function QuizWait() {
const socket = getQuizSocket();
const navigate = useNavigate();
const toast = toastController();
const [userType, setUserType] = useState<string>('');

useEffect(() => {
socket.on('nickname', (response) => {
setGuests([...response]);
});
socket.emit('nickname', { pinCode });

socket.on('start quiz', (response) => {
console.log('start quiz', response);
navigate(`/quiz/session/${pinCode}/1`);
});
}, []);

useLayoutEffect(() => {
Expand Down Expand Up @@ -60,8 +65,8 @@ export default function QuizWait() {
};

const handleQuizStart = () => {
socket.emit('master entry', { classId: '123', sid: getCookie('sid') });
navigate('/quiz/session');
socket.emit('start quiz', { sid: getCookie('sid'), pinCode });
navigate(`/quiz/session/host/${pinCode}/1`);
};

return (
Expand Down Expand Up @@ -109,15 +114,17 @@ export default function QuizWait() {
))}
</div>
</div>
<div className="flex justify-end min-w-full">
<CustomButton
type="full"
color="primary"
label="퀴즈 시작하기"
size="md"
onClick={handleQuizStart}
/>
</div>
{userType === 'master' && (
<div className="flex justify-end min-w-full">
<CustomButton
type="full"
color="primary"
label="퀴즈 시작하기"
size="md"
onClick={handleQuizStart}
/>
</div>
)}
</div>
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions packages/client/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default {
'progress-20s': 'progress 20s linear forwards',
'progress-30s': 'progress 30s linear forwards',
'slide-out': 'slide-out 0.5s ease-in forwards',
'spin-slow': 'spin-slow 6s linear infinite',
},
keyframes: {
progress: {
Expand Down Expand Up @@ -75,6 +76,10 @@ export default {
opacity: 0,
},
},
'spin-slow': {
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' },
},
},
},
},
Expand Down

0 comments on commit 39eb220

Please sign in to comment.