From 776d3f5d5d6d072a99602bd9eaf854abfc615687 Mon Sep 17 00:00:00 2001
From: Anas
Date: Thu, 17 Jul 2025 17:21:15 +0900
Subject: [PATCH 1/9] =?UTF-8?q?=EA=B2=8C=EC=8A=A4=ED=8A=B8=20=EB=A1=9C?=
=?UTF-8?q?=EA=B7=B8=EC=9D=B8=20UI=20&=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/canvas/CanvasUIPC.tsx | 16 +-----
src/components/game/GameCanvas.tsx | 21 +++++--
src/components/game/GameReadyModal.tsx | 23 +++++---
.../{modal => game}/GameResultModal.tsx | 0
.../{modal => game}/QuestionModal.tsx | 0
src/components/modal/LoginModalContent.tsx | 29 ++++------
src/services/authService.ts | 23 ++++++++
src/utils/getRandomName.ts | 57 +++++++++++++++++++
tailwind.config.js | 15 +++++
9 files changed, 139 insertions(+), 45 deletions(-)
rename src/components/{modal => game}/GameResultModal.tsx (100%)
rename src/components/{modal => game}/QuestionModal.tsx (100%)
create mode 100644 src/utils/getRandomName.ts
create mode 100644 tailwind.config.js
diff --git a/src/components/canvas/CanvasUIPC.tsx b/src/components/canvas/CanvasUIPC.tsx
index 3abcf74..61ac547 100644
--- a/src/components/canvas/CanvasUIPC.tsx
+++ b/src/components/canvas/CanvasUIPC.tsx
@@ -191,21 +191,9 @@ export default function CanvasUIPC({
{/* 항상 보이는 메뉴 토글 버튼 (햄버거 아이콘) */}
{/* isMenuOpen이 true일 때만 드롭다운 메뉴가 보입니다. */}
diff --git a/src/components/game/GameCanvas.tsx b/src/components/game/GameCanvas.tsx
index 5f50f7d..bc3cad8 100644
--- a/src/components/game/GameCanvas.tsx
+++ b/src/components/game/GameCanvas.tsx
@@ -15,9 +15,9 @@ import useSound from 'use-sound';
import { useGameSocketIntegration } from '../gameSocketIntegration';
import { useNavigate } from 'react-router-dom';
import GameTimer from './GameTimer'; // GameTimer import 추가
-import GameResultModal from '../modal/GameResultModal'; // 게임 결과 모달 import
+import GameResultModal from './GameResultModal'; // 게임 결과 모달 import
import DeathModal from '../modal/DeathModal'; // 사망 모달 import
-import QuestionModal from '../modal/QuestionModal'; // 문제 모달 import
+import QuestionModal from './QuestionModal'; // 문제 모달 import
import ExitModal from '../modal/ExitModal'; // 나가기 모달 import
import {
@@ -745,14 +745,14 @@ function GameCanvas({
// 시간 초과시 자동으로 false 결과 전송
startCooldown(1);
setLives((prev) => Math.max(0, prev - 1));
-
+
sendGameResult({
x: currentPixel.x,
y: currentPixel.y,
color: currentPixel.color,
result: false,
});
-
+
setShowQuestionModal(false);
setShowResult(false);
setCurrentPixel(null);
@@ -762,7 +762,14 @@ function GameCanvas({
return () => {
clearInterval(timerId);
};
- }, [showQuestionModal, questionTimeLeft, currentPixel, startCooldown, setLives, sendGameResult]);
+ }, [
+ showQuestionModal,
+ questionTimeLeft,
+ currentPixel,
+ startCooldown,
+ setLives,
+ sendGameResult,
+ ]);
// 게임 데이터 및 캔버스 초기화
const { getSynchronizedServerTime } = useTimeSyncStore();
@@ -805,7 +812,9 @@ function GameCanvas({
// 게임 총 시간 계산 및 설정
const endTime = new Date(gameData.endedAt).getTime();
- const calculatedTotalGameDuration = Math.floor((endTime - startTime) / 1000);
+ const calculatedTotalGameDuration = Math.floor(
+ (endTime - startTime) / 1000
+ );
setTotalGameDuration(calculatedTotalGameDuration);
setGameTime(calculatedTotalGameDuration);
diff --git a/src/components/game/GameReadyModal.tsx b/src/components/game/GameReadyModal.tsx
index b4fa9c4..3e21dc3 100644
--- a/src/components/game/GameReadyModal.tsx
+++ b/src/components/game/GameReadyModal.tsx
@@ -4,7 +4,6 @@ import type { WaitingRoomData } from '../../api/GameAPI';
import { useTimeSyncStore } from '../../store/timeSyncStore';
import { useToastStore } from '../../store/toastStore';
-
interface GameReadyModalProps {
isOpen: boolean;
onClose: (data?: WaitingRoomData) => void;
@@ -13,7 +12,13 @@ interface GameReadyModalProps {
remainingTime?: number;
}
-const GameReadyModal = ({ isOpen, onClose, canvasId, color, remainingTime }: GameReadyModalProps) => {
+const GameReadyModal = ({
+ isOpen,
+ onClose,
+ canvasId,
+ color,
+ remainingTime,
+}: GameReadyModalProps) => {
const navigate = useNavigate();
const { showToast } = useToastStore();
const { getSynchronizedServerTime } = useTimeSyncStore();
@@ -25,12 +30,12 @@ const GameReadyModal = ({ isOpen, onClose, canvasId, color, remainingTime }: Gam
if (!isOpen) {
return;
}
-
+
// 부모 컴포넌트에서 전달받은 remainingTime 사용
if (remainingTime !== undefined) {
setTimeUntilStart(remainingTime);
setLoading(false);
-
+
// 시간이 0이하인 경우 모달 닫기
if (remainingTime <= 0) {
setTimeout(() => onClose(), 1000); // 1초 후 모달 닫기
@@ -48,7 +53,7 @@ const GameReadyModal = ({ isOpen, onClose, canvasId, color, remainingTime }: Gam
const timer = setInterval(() => {
// useTimeSyncStore를 사용하여 더 정확한 시간 계산
if (remainingTime === undefined) {
- setTimeUntilStart(prev => {
+ setTimeUntilStart((prev) => {
const newValue = prev !== null ? prev - 1 : 0;
if (newValue <= 0) {
clearInterval(timer);
@@ -159,9 +164,11 @@ const GameReadyModal = ({ isOpen, onClose, canvasId, color, remainingTime }: Gam
>
- {remainingTime !== undefined && remainingTime > 0 ? `${remainingTime}` :
- timeUntilStart !== null && timeUntilStart > 0 ? `${timeUntilStart}` :
- '시작!'}
+ {remainingTime !== undefined && remainingTime > 0
+ ? `${remainingTime}`
+ : timeUntilStart !== null && timeUntilStart > 0
+ ? `${timeUntilStart}`
+ : '--'}
diff --git a/src/components/modal/GameResultModal.tsx b/src/components/game/GameResultModal.tsx
similarity index 100%
rename from src/components/modal/GameResultModal.tsx
rename to src/components/game/GameResultModal.tsx
diff --git a/src/components/modal/QuestionModal.tsx b/src/components/game/QuestionModal.tsx
similarity index 100%
rename from src/components/modal/QuestionModal.tsx
rename to src/components/game/QuestionModal.tsx
diff --git a/src/components/modal/LoginModalContent.tsx b/src/components/modal/LoginModalContent.tsx
index f4d9554..9854f7f 100644
--- a/src/components/modal/LoginModalContent.tsx
+++ b/src/components/modal/LoginModalContent.tsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import OAuthButton from './OAuthButton';
-import { authService } from '../../services/authService';
+import { authService, guestLogin } from '../../services/authService';
+import { generateRandomNickname } from '../../utils/getRandomName';
type LoginModalContentProps = {
onClose?: () => void;
@@ -10,9 +11,10 @@ export default function LoginModalContent({ onClose }: LoginModalContentProps) {
// 로그인 폼 자체의 상태를 스스로 관리합니다.
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
+ const [randomName, setRandomname] = useState(generateRandomNickname());
- const handleLogin = ({}) => {
- // 실제 로그인 로직을
+ const handleLogin = () => {
+ guestLogin(randomName);
onClose?.();
};
@@ -32,33 +34,26 @@ export default function LoginModalContent({ onClose }: LoginModalContentProps) {
{/* 이메일 및 비밀번호 입력 */}
- {/*
{/* 로그인 버튼 */}
- {/*
+
-
*/}
+
{/* 소셜 로그인 */}
{/*
구글 계정으로 로그인
*/}
diff --git a/src/services/authService.ts b/src/services/authService.ts
index 91d5ee0..91eb240 100644
--- a/src/services/authService.ts
+++ b/src/services/authService.ts
@@ -113,3 +113,26 @@ export const authService = {
}
},
};
+
+// --- 비회원 로그인 ---
+export const guestLogin = async (nickname: string) => {
+ try {
+ const response = await apiClient.post('/users/signup', {
+ userName: nickname,
+ });
+ const authHeader = response.headers['authorization'];
+ const accessToken = authHeader?.split(' ')[1];
+
+ const decodedToken = jwtDecode
(accessToken);
+ const user = {
+ userId: decodedToken.sub.userId,
+ nickname: decodedToken.sub.nickName,
+ };
+
+ useAuthStore.getState().setAuth(accessToken, user);
+ toast.success(`${nickname}님 환영합니다!`);
+ } catch (error) {
+ console.error('Login failed', error);
+ toast.error('로그인에 실패했습니다.');
+ }
+};
diff --git a/src/utils/getRandomName.ts b/src/utils/getRandomName.ts
new file mode 100644
index 0000000..0721859
--- /dev/null
+++ b/src/utils/getRandomName.ts
@@ -0,0 +1,57 @@
+export const generateRandomNickname = (): string => {
+ const names = [
+ 'JavaScript',
+ 'Python',
+ 'Java',
+ 'C++',
+ 'C#',
+ 'Ruby',
+ 'Go',
+ 'Swift',
+ 'Kotlin',
+ 'TypeScript',
+ 'PHP',
+ 'Rust',
+ 'Scala',
+ 'Perl',
+ 'Haskell',
+ 'Lua',
+ 'Dart',
+ 'SQL',
+ 'MATLAB',
+ 'Assembly',
+ 'Flutter',
+ 'React',
+ 'Nest',
+ 'Next',
+ 'Zustand',
+ 'nodeJS',
+ 'Umlang',
+ 'R',
+ 'HTML',
+ 'CSS',
+ 'Scratch',
+ ];
+
+ const getRandomItem = (arr: string[]): string =>
+ arr[Math.floor(Math.random() * arr.length)];
+
+ const generateRandomNumber = (min: number, max: number): string =>
+ Math.floor(min + Math.random() * (max - min + 1)).toString();
+
+ const selectNamePart = (): string => getRandomItem(names);
+
+ const namePart = selectNamePart();
+
+ const remainingLength = Math.max(0, 8 - namePart.length);
+
+ const numberPart =
+ remainingLength > 0
+ ? generateRandomNumber(
+ Math.pow(10, remainingLength - 1),
+ Math.pow(10, remainingLength) - 1
+ )
+ : '';
+
+ return `${namePart}${numberPart}`;
+};
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..29d7665
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,15 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ 'press-start': ['"Press Start 2P"', 'cursive'],
+ },
+ },
+ },
+ plugins: [],
+}
From 8ffff74fb7eb62f3530ed58450996db5a9b26335 Mon Sep 17 00:00:00 2001
From: Anas
Date: Thu, 17 Jul 2025 17:51:22 +0900
Subject: [PATCH 2/9] =?UTF-8?q?=EA=B2=8C=EC=9E=84=20=EB=AA=A8=EB=B0=94?=
=?UTF-8?q?=EC=9D=BC=20UI=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20useViewport?=
=?UTF-8?q?=20=ED=9B=85=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/game/GameCanvas.tsx | 10 ++-
src/components/game/GameTimer.tsx | 20 +++--
src/components/toast/NotificationToast.tsx | 90 ++++++++++++++--------
src/hooks/useViewport.ts | 13 ++++
4 files changed, 95 insertions(+), 38 deletions(-)
create mode 100644 src/hooks/useViewport.ts
diff --git a/src/components/game/GameCanvas.tsx b/src/components/game/GameCanvas.tsx
index bc3cad8..16f0375 100644
--- a/src/components/game/GameCanvas.tsx
+++ b/src/components/game/GameCanvas.tsx
@@ -28,6 +28,7 @@ import {
VIEWPORT_BACKGROUND_COLOR,
} from '../canvas/canvasConstants';
import GameReadyModal from './GameReadyModal';
+import { useViewport } from '../../hooks/useViewport';
// 게임 문제 타입 정의
interface GameQuestion {
@@ -110,6 +111,9 @@ function GameCanvas({
} | null>(null);
const flashingPixelRef = useRef<{ x: number; y: number } | null>(null);
+ const { width } = useViewport();
+ const isMobile = width <= 768;
+
// 상태 관리
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
const [hasError, setHasError] = useState(false);
@@ -1041,7 +1045,11 @@ function GameCanvas({
{isGameStarted && (
<>
{/* 나가기 버튼 및 생명 표시 */}
-
+