diff --git a/.pnp.cjs b/.pnp.cjs index eb78e062..5bf37711 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6842,6 +6842,7 @@ const RAW_RUNTIME_STATE = ["qrcode.react", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:4.1.0"],\ ["react", "npm:18.3.1"],\ ["react-dom", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:18.3.1"],\ + ["react-error-boundary", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:4.1.2"],\ ["react-router-dom", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:6.27.0"],\ ["recharts", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:2.13.3"],\ ["socket.io-client", "npm:4.8.1"],\ @@ -12324,6 +12325,29 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["react-error-boundary", [\ + ["npm:4.1.2", {\ + "packageLocation": "./.yarn/cache/react-error-boundary-npm-4.1.2-7591172537-0737e5259b.zip/node_modules/react-error-boundary/",\ + "packageDependencies": [\ + ["react-error-boundary", "npm:4.1.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:4.1.2", {\ + "packageLocation": "./.yarn/__virtual__/react-error-boundary-virtual-0d90b1d24e/0/cache/react-error-boundary-npm-4.1.2-7591172537-0737e5259b.zip/node_modules/react-error-boundary/",\ + "packageDependencies": [\ + ["react-error-boundary", "virtual:658502eb4296e93abedc18b6aa9b26978f434f08d98e21ebb0e725354b8bb54b62db9c4a1893e460c694ff7500ff5cbafa4457b0dfd26b5838868666c861e990#npm:4.1.2"],\ + ["@babel/runtime", "npm:7.26.0"],\ + ["@types/react", "npm:18.3.12"],\ + ["react", "npm:18.3.1"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["react-is", [\ ["npm:16.13.1", {\ "packageLocation": "./.yarn/cache/react-is-npm-16.13.1-a9b9382b4f-33977da7a5.zip/node_modules/react-is/",\ diff --git a/.yarn/cache/react-error-boundary-npm-4.1.2-7591172537-0737e5259b.zip b/.yarn/cache/react-error-boundary-npm-4.1.2-7591172537-0737e5259b.zip new file mode 100644 index 00000000..155e82dd Binary files /dev/null and b/.yarn/cache/react-error-boundary-npm-4.1.2-7591172537-0737e5259b.zip differ diff --git a/packages/client/package.json b/packages/client/package.json index 82da9a74..2c70fc82 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -20,6 +20,7 @@ "qrcode.react": "^4.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^4.1.2", "react-router-dom": "^6.27.0", "recharts": "^2.13.3", "socket.io-client": "^4.8.1" diff --git a/packages/client/src/pages/quiz-session/index.lazy.tsx b/packages/client/src/pages/quiz-session/index.lazy.tsx index 12f7f03e..de18c25e 100644 --- a/packages/client/src/pages/quiz-session/index.lazy.tsx +++ b/packages/client/src/pages/quiz-session/index.lazy.tsx @@ -21,9 +21,6 @@ export default function QuizSessionLazyPage() { quizOrder: parseInt(id as string), }); - console.log(quiz.currentQuizData.timeLimit); - console.log(quiz.currentQuizData.position); - useEffect(() => { const prevCurrentOrder = localStorage.getItem('currentOrder'); if (prevCurrentOrder !== null && parseInt(prevCurrentOrder) !== quiz.currentQuizData.position) { @@ -45,21 +42,23 @@ export default function QuizSessionLazyPage() { return ( <> {!isQuizEnd && ( -
- - +
+
+ + +
)} {isQuizEnd && ( diff --git a/packages/client/src/pages/quiz-session/ui/ProgressBar.tsx b/packages/client/src/pages/quiz-session/ui/ProgressBar.tsx index 51df29ef..7f57a800 100644 --- a/packages/client/src/pages/quiz-session/ui/ProgressBar.tsx +++ b/packages/client/src/pages/quiz-session/ui/ProgressBar.tsx @@ -1,3 +1,5 @@ +import { useEffect, useRef } from 'react'; + interface ProgressBarProps { /** 전체 시간 설정 */ time: number; @@ -10,7 +12,7 @@ interface ProgressBarProps { /** 애니메이션 종료 시 콜백 함수 */ handleAnimationEnd?: () => void; /** 현재 진행 시간 */ - currentTime?: number; + remainingTime: number; } const progressBarColors = { @@ -22,27 +24,42 @@ const progressBarColors = { }; const progressBarShapes = { - rounded: 'rounded-r-base', + rounded: 'rounded-base', square: 'rounded-none', }; const ProgressBar = ({ - time = 5, + time, type = 'success', barShape = 'rounded', pauseOnHover, handleAnimationEnd, - currentTime = 0, + remainingTime, }: ProgressBarProps) => { const progressBarColor = progressBarColors[type]; const progressBarShape = progressBarShapes[barShape]; + const progressBarRef = useRef(null); + + useEffect(() => { + const startTime = Date.now(); + const endTime = startTime + remainingTime * 1000; + + const updateWidth = () => { + const now = Date.now(); + const remaining = Math.max(endTime - now, 0); + const progress = (remaining / (time * 1000)) * 100; + + if (progressBarRef.current) { + progressBarRef.current.style.width = `${progress}%`; + } + + if (remaining > 0) { + requestAnimationFrame(updateWidth); + } + }; - // 현재 시간에 따른 진행률 계산 - const progress = Math.min(Math.max((currentTime / time) * 100, 0), 100); - // 남은 시간 비율 계산 - const remainingTimeRatio = 1 - currentTime / time; - // 애니메이션 지속 시간 계산 (초 단위) - const animationDuration = time * remainingTimeRatio; + updateWidth(); + }, [remainingTime, time]); return (
@@ -51,10 +68,7 @@ const ProgressBar = ({ className={`h-[6px] ${progressBarColor} ${progressBarShape} ${ pauseOnHover && 'group-hover:[animation-play-state:paused]' }`} - style={{ - width: `${progress}%`, - animation: `progress ${animationDuration}s linear forwards`, - }} + ref={progressBarRef} />
diff --git a/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx b/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx index 7a9fd6b9..426eb8f0 100644 --- a/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx +++ b/packages/client/src/pages/quiz-session/ui/QuizHeader.tsx @@ -13,7 +13,7 @@ import HamsterImage from '@/shared/assets/characters/햄스터.png'; import { Calendar, Clock, Users } from 'lucide-react'; import { useQueryClient } from '@tanstack/react-query'; -// import ProgressBar from './ProgressBar'; +import ProgressBar from './ProgressBar'; interface QuizHeaderProps { startTime: number; @@ -72,7 +72,7 @@ export default function QuizHeader({
-
+
{`${characterNames[character]}character`}

{nickname}

-
-
+
+
{remainingTime}초 남음
- {/* */} + />
diff --git a/packages/client/src/shared/boundary/AsyncBoundary.tsx b/packages/client/src/shared/boundary/AsyncBoundary.tsx index c6ad2318..5da6bb03 100644 --- a/packages/client/src/shared/boundary/AsyncBoundary.tsx +++ b/packages/client/src/shared/boundary/AsyncBoundary.tsx @@ -1,6 +1,11 @@ import { Suspense } from 'react'; import QuizLoading from '@/pages/quiz-session/ui/QuizLoading'; +import { ErrorBoundary } from 'react-error-boundary'; export default function AsyncBoundary({ children }: { children: React.ReactNode }) { - return }>{children}; + return ( + 안녕하세요?
}> + }>{children} + + ); } diff --git a/yarn.lock b/yarn.lock index c01b918c..3c558c58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4553,6 +4553,7 @@ __metadata: qrcode.react: "npm:^4.1.0" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" + react-error-boundary: "npm:^4.1.2" react-router-dom: "npm:^6.27.0" recharts: "npm:^2.13.3" socket.io-client: "npm:^4.8.1" @@ -9212,6 +9213,17 @@ __metadata: languageName: node linkType: hard +"react-error-boundary@npm:^4.1.2": + version: 4.1.2 + resolution: "react-error-boundary@npm:4.1.2" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + react: ">=16.13.1" + checksum: 10c0/0737e5259bed40ce14eb0823b3c7b152171921f2179e604f48f3913490cdc594d6c22d43d7abb4ffb1512c832850228db07aa69d3b941db324953a5e393cb399 + languageName: node + linkType: hard + "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1"