Skip to content

Commit 0dfdb0d

Browse files
authored
✨ feat: 이미지 게임 추가 (#10)
1 parent ec4dd4a commit 0dfdb0d

File tree

16 files changed

+917
-78
lines changed

16 files changed

+917
-78
lines changed

package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"apexcharts": "^4.3.0",
2020
"axios": "^1.7.8",
2121
"bootstrap": "^5.3.3",
22+
"browser-image-compression": "^2.0.2",
2223
"clsx": "^2.1.1",
2324
"column-resizer": "^1.4.0",
2425
"express": "^4.21.2",

src/layout/game/GameLayout.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
stompSendMessageAtom,
1616
systemNoticeAtom,
1717
quizStartedAtom,
18+
quizTypeAtom,
1819
} from '../../state/atoms';
1920
import useStompClient from '../../hooks/useStompClient';
2021
import { useCallback, useEffect, useRef, useLayoutEffect } from 'react';
@@ -51,6 +52,7 @@ const GameLayout = () => {
5152
const setSystemNotice = useSetRecoilState(systemNoticeAtom);
5253
const setSendMessage = useSetRecoilState(stompSendMessageAtom);
5354
const setLoginUser = useSetRecoilState(loginUserAtom);
55+
const setQuizType = useSetRecoilState(quizTypeAtom);
5456
const navigate = useNavigate();
5557

5658
const isQuizStarted = useRecoilValue(quizStartedAtom);
@@ -107,6 +109,7 @@ const GameLayout = () => {
107109
break;
108110
case 'GAME_START':
109111
setQuestions(payload.message.questions);
112+
setQuizType(payload.message.quizType);
110113
navigate('play');
111114
break;
112115
case 'QUESTION_START':
@@ -151,6 +154,7 @@ const GameLayout = () => {
151154
setRankUpdate,
152155
setGameResult,
153156
navigate,
157+
setQuizType,
154158
],
155159
);
156160

src/layout/game/components/QuizInfoCard.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import {Clock, List, MessageCircleQuestion} from "lucide-react"
22

3+
// 상대 경로 앞에 슬래시를 보장해 Public 기준에서 로드되도록 보정
4+
const ensureLeadingSlash = (url) => {
5+
if (!url) return url;
6+
if (/^https?:\/\//i.test(url)) return url; // 절대 URL은 그대로 사용
7+
return url.startsWith('/') ? url : `/${url}`;
8+
}
9+
310
function QuizInfoCard({ gameSetting }) {
411
return (
512
<div className="bg-white rounded-2xl shadow-lg p-8 mb-6">
@@ -15,7 +22,7 @@ function QuizInfoCard({ gameSetting }) {
1522
<div className="relative">
1623
<img
1724
className="w-full h-64 object-cover rounded-xl"
18-
src={gameSetting?.quiz.thumbnailUrl}
25+
src={ensureLeadingSlash(gameSetting?.quiz.thumbnailUrl)}
1926
alt="F1 race car on track with checkered flag"
2027
/>
2128
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent rounded-xl"></div>
Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11

2-
function QuizQuestion({ questionContent }) {
2+
// 상대 경로 앞에 슬래시를 보장해 Public 기준에서 로드되도록 보정
3+
const ensureLeadingSlash = (url) => {
4+
if (!url) return url;
5+
if (/^https?:\/\//i.test(url)) return url; // 절대 URL은 그대로 사용
6+
return url.startsWith('/') ? url : `/${url}`;
7+
}
8+
9+
function QuizQuestion({ questionContent, quizType }) {
10+
if (quizType === 'IMAGE' && questionContent) {
11+
return (
12+
<div className="bg-white rounded-2xl shadow-lg p-8 mb-8 flex-1 flex items-center justify-center">
13+
<div className="text-center">
14+
<img
15+
src={ensureLeadingSlash(questionContent)}
16+
alt="곧 퀴즈가 시작됩니다!"
17+
className="max-w-full max-h-96 object-contain rounded-lg shadow-md"
18+
style={{ maxHeight: '400px' }}
19+
/>
20+
</div>
21+
</div>
22+
);
23+
}
24+
325
return (
4-
<div className="bg-white rounded-2xl shadow-lg p-8 mb-8 flex-1 flex items-center justify-center">
5-
<div className="text-center">
6-
<h2 className="text-4xl font-bold text-gray-900">{questionContent ?? '곧 퀴즈가 시작됩니다!'}</h2>
26+
<div className="bg-white rounded-2xl shadow-lg p-8 mb-8 flex-1 flex items-center justify-center">
27+
<div className="text-center">
28+
<h2 className="text-4xl font-bold text-gray-900">{questionContent ?? '곧 퀴즈가 시작됩니다!'}</h2>
29+
</div>
730
</div>
8-
</div>
9-
)
31+
);
1032
}
1133

1234
export default QuizQuestion

src/pages/game/GamePlay.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
questionsAtom,
1212
questionStartAtom,
1313
quizStartedAtom, // 새로 추가한 아톰
14+
quizTypeAtom,
1415
} from '../../state/atoms';
1516
import WinnerModal from '../../layout/game/components/WinnerModal';
1617

@@ -29,6 +30,7 @@ function GamePlay() {
2930
const [gameResult, setGameResult] = useRecoilState(gameResultAtom);
3031
const [visibleQuestion, setVisibleQuestion] = useState(false);
3132
const [showWinnerModal, setShowWinnerModal] = useState(false);
33+
const [quizType] = useRecoilState(quizTypeAtom);
3234

3335
// useSetRecoilState를 사용하여 quizStartedAtom을 업데이트하는 함수를 가져옵니다.
3436
const setQuizStartedState = useSetRecoilState(quizStartedAtom);
@@ -147,6 +149,7 @@ function GamePlay() {
147149
questions &&
148150
questions[currentQuestion.round - 1].question
149151
}
152+
quizType={quizType}
150153
/>
151154
)}
152155
</div>

src/pages/game/QuizSelectModal.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { X, Search, ChevronLeft, ChevronRight, Clock, FileText, Loader2 } from "
44
import {useApiQuery} from "../../hooks/useApiQuery";
55
import axios from "axios";
66

7+
// 상대 경로 앞에 슬래시를 보장해 Public 기준에서 로드되도록 보정
8+
const ensureLeadingSlash = (url) => {
9+
if (!url) return url;
10+
if (/^https?:\/\//i.test(url)) return url; // 절대 URL은 그대로 사용
11+
return url.startsWith('/') ? url : `/${url}`;
12+
}
13+
714
function QuizSelectModal({ isOpen, onClose, onSelect }) {
815
const [searchTerm, setSearchTerm] = useState("")
916
const [currentPage, setCurrentPage] = useState(1)
@@ -188,11 +195,11 @@ function QuizSelectModal({ isOpen, onClose, onSelect }) {
188195
>
189196
<div className="aspect-video bg-gray-200 relative">
190197
<img
191-
src={quiz.thumbnailUrl || "/placeholder.svg?height=120&width=200&text=No+Image"}
198+
src={ensureLeadingSlash(quiz.thumbnailUrl) || "/placeholder.svg?height=120&width=200&text=No+Image"}
192199
alt={quiz.title}
193200
className="w-full h-full object-cover"
194201
onError={(e) => {
195-
e.target.src = "/placeholder.svg?height=120&width=200&text=No+Image"
202+
e.target.src = ensureLeadingSlash("/placeholder.svg?height=120&width=200&text=No+Image")
196203
}}
197204
/>
198205
{selectedQuizId === quiz.quizId && (

0 commit comments

Comments
 (0)