From 72755f31ca372040c38fde704c832647396baa78 Mon Sep 17 00:00:00 2001 From: ByeongChan Choi <77400298+chan-byeong@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:02:52 +0900 Subject: [PATCH] =?UTF-8?q?[FE=20-=20#109=20]=20Quiz-Session=20=EC=86=8C?= =?UTF-8?q?=EC=BC=93=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20(#110)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 상태 추가 * feat: master-session 이벤트 추가 * fix: show quiz 응답 수정 * temp: 소켓 테스트를 위한 소켓 응답 코드 추가 * fix: user type fetch function added * fix: Loading 페이지 Heigth 수정 * feat: Animation keyframe 추가 - floatDown : 아래로 내려오는 애니메이션 - swing : 좌우로 왔다갔다 하는 애니메이션 * fix: Quiz Session 소켓 로직 수정 * fix: QuizBox 소켓 로직 수정 및 Background 추가 * feat: tick props 추가 --- .../client/src/pages/quiz-session/index.tsx | 36 +++++++------ .../src/pages/quiz-session/ui/QuizBox.tsx | 50 +++++++++++++------ .../src/pages/quiz-session/ui/QuizHeader.tsx | 8 ++- .../src/pages/quiz-session/ui/QuizLoading.tsx | 2 +- packages/client/tailwind.config.js | 15 ++++++ 5 files changed, 73 insertions(+), 38 deletions(-) diff --git a/packages/client/src/pages/quiz-session/index.tsx b/packages/client/src/pages/quiz-session/index.tsx index 5c77424d..12906e95 100644 --- a/packages/client/src/pages/quiz-session/index.tsx +++ b/packages/client/src/pages/quiz-session/index.tsx @@ -1,5 +1,4 @@ import { useState, useEffect } from 'react'; -import { useParams } from 'react-router-dom'; import { getQuizSocket } from '@/shared/utils/socket'; import QuizBackground from './ui/QuizBackground'; @@ -10,23 +9,24 @@ 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 [tick, setTick] = useState({ currentTime: 0, elapsedTime: 0, remainingTime: 0 }); const [quiz, setQuiz] = useState({ id: '', content: '', choices: [], }); - const totalReactions = reactionStats.easy + reactionStats.hard; - const easyPercentage = totalReactions ? (reactionStats.easy / totalReactions) * 100 : 50; + const handleTick = (response: any) => { + setTick(response); + }; + + const handleTimeEnd = () => { + setIsQuizEnd(true); + }; useEffect(() => { const quizPromise = new Promise((resolve, reject) => { @@ -62,24 +62,22 @@ export default function QuizSession() { setIsLoading(false); }); - socket.on('timer end', () => { - setIsQuizEnd(true); - }); + socket.on('timer tick', handleTick); + socket.on('time end', handleTimeEnd); return () => { - socket.off('timer end', () => {}); + socket.off('time end', handleTimeEnd); + socket.off('timer tick', handleTick); }; }, []); return ( <> - {isLoading ? ( - - ) : ( -
- - - + {isLoading && !isQuizEnd && } + {!isLoading && !isQuizEnd && ( +
+ +
)} {isQuizEnd && } diff --git a/packages/client/src/pages/quiz-session/ui/QuizBox.tsx b/packages/client/src/pages/quiz-session/ui/QuizBox.tsx index 93107732..bcd7ed18 100644 --- a/packages/client/src/pages/quiz-session/ui/QuizBox.tsx +++ b/packages/client/src/pages/quiz-session/ui/QuizBox.tsx @@ -3,25 +3,46 @@ import { Dispatch, SetStateAction, useState, useRef, useEffect, useCallback } fr import { getQuizSocket } from '@/shared/utils/socket'; import { getCookie } from '@/shared/utils/cookie'; import { useParams } from 'react-router-dom'; +import AfterQuizSubmit from './AfterQuizSubmit'; +import QuizBackground from './QuizBackground'; interface ReactionData { easy: number; hard: number; } +export interface StatisticsData { + averageTime: number; + participantRate: number; + solveRate: number; + totalSubmit: number; +} + interface QuizBoxProps { - reactionStats: ReactionData; - setReactionStats: Dispatch>; quiz: QuizData; + tick: { currentTime: number; elapsedTime: number; remainingTime: number }; } -export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizBoxProps) { +export default function QuizBox({ quiz, tick }: QuizBoxProps) { const { pinCode } = useParams(); const [selectedAnswer, setSelectedAnswer] = useState([]); const [hasSubmitted, setHasSubmitted] = useState(false); + const [reactionStats, setReactionStats] = useState({ + easy: 0, + hard: 0, + }); + const [participantStatistics, setParticipantStatistics] = useState({ + averageTime: 0, + participantRate: 0, + solveRate: 0, + totalSubmit: 0, + }); const easyButtonRef = useRef(null); const hardButtonRef = useRef(null); const socket = getQuizSocket(); - const [tick, setTick] = useState({ currentTime: 0, elapsedTime: 0, remainingTime: 0 }); + + const totalReactions = reactionStats.easy + reactionStats.hard; + const easyPercentage = totalReactions ? (reactionStats.easy / totalReactions) * 100 : 50; + const handleSelectAnswer = (idx: number) => { setSelectedAnswer((prev) => { if (prev.includes(idx)) { @@ -38,7 +59,6 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB pinCode: pinCode, submitTime: tick.elapsedTime, }); - console.log(selectedAnswer); setHasSubmitted(true); }; @@ -65,27 +85,23 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB setReactionStats(data); }, []); + const handleParticipantStatistics = (response: StatisticsData) => { + setParticipantStatistics(response); + }; + useEffect(() => { socket.on('emoji', handleReactionUpdate); + socket.on('participant statistics', handleParticipantStatistics); - socket.on('timer tick', (response) => { - setTick(response); - }); - - socket.on('participant statistics', (response) => { - console.log('participant statistics', response); - }); - - socket.on('time end', (response) => { - console.log('time end', response); - }); return () => { socket.off('emoji', handleReactionUpdate); + socket.off('participant statistics', handleParticipantStatistics); }; }, []); return ( <> +
@@ -154,6 +170,8 @@ export default function QuizBox({ reactionStats, setReactionStats, quiz }: QuizB
+ + {} ); } diff --git a/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx b/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx index 83849401..d90efa56 100644 --- a/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx +++ b/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx @@ -2,7 +2,11 @@ import { useEffect, useState } from 'react'; import { getQuizSocket } from '@/shared/utils/socket'; -export default function QuizHeader() { +interface QuizHeaderProps { + tick: { currentTime: number; elapsedTime: number; remainingTime: number }; +} + +export default function QuizHeader({ tick }: QuizHeaderProps) { const socket = getQuizSocket(); const [submitStatus, setSubmitStatus] = useState<{ count: number; total: number }>({ count: 0, @@ -27,7 +31,7 @@ export default function QuizHeader() {
{submitStatus.count} / {submitStatus.total}명 제출
-
남은 시간
+
{tick.remainingTime}초 남음
); diff --git a/packages/client/src/pages/quiz-session/ui/QuizLoading.tsx b/packages/client/src/pages/quiz-session/ui/QuizLoading.tsx index d2c7cca6..c5240776 100644 --- a/packages/client/src/pages/quiz-session/ui/QuizLoading.tsx +++ b/packages/client/src/pages/quiz-session/ui/QuizLoading.tsx @@ -1,6 +1,6 @@ const QuizLoading = () => { return ( -
+
diff --git a/packages/client/tailwind.config.js b/packages/client/tailwind.config.js index 6b51cf6c..061e00e8 100644 --- a/packages/client/tailwind.config.js +++ b/packages/client/tailwind.config.js @@ -80,6 +80,21 @@ export default { from: { transform: 'rotate(0deg)' }, to: { transform: 'rotate(360deg)' }, }, + floatDown: { + '0%': { + transform: 'translateY(-100px)', + opacity: 0, + }, + '100%': { + transform: 'translateY(0)', + opacity: 1, + }, + }, + swing: { + '0%': { transform: 'rotate(0deg) translateY(0)' }, + '50%': { transform: 'rotate(30deg) translateY(-20px)' }, + '100%': { transform: 'rotate(0deg) translateY(0)' }, + }, }, }, },