Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…ouQuiz into develop
  • Loading branch information
chan-byeong committed Nov 28, 2024
2 parents f2d038c + 13577f7 commit 175d9a8
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 124 deletions.
27 changes: 21 additions & 6 deletions packages/client/src/pages/quiz-wait/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import LoadingSpinner from '@/shared/assets/icons/loading-alt-loop.svg?react';
import { apiClient } from '@/shared/api';
import UserGridView from './ui/UserGridView';

interface Guest {
export interface Guest {
nickname: string;
character: number;
message: string;
position: number;
}

export default function QuizWait() {
const { pinCode } = useParams();
const [userType, setUserType] = useState<string>('');
const [guests, setGuests] = useState<Guest[]>([]);
const [myPosition, setMyPosition] = useState<number>(0);

const guestLink = `${import.meta.env.VITE_CLIENT_URL}/nickname/${pinCode}`;
const socket = getQuizSocket();
Expand All @@ -28,9 +28,20 @@ export default function QuizWait() {
const navigate = useNavigate();

useEffect(() => {
socket.on('nickname', (response) => {
setGuests([...response]);
});
const handleMyPosition = (response: any) => {
const { participantList, myPosition } = response;
setGuests(participantList);
setMyPosition(myPosition);
};

socket.on('my position', handleMyPosition);

const handleNickname = (response: any) => {
const { participantList } = response;
setGuests(participantList);
};

socket.on('nickname', handleNickname);

const fetchUserType = async () => {
const response = await apiClient.get(`/games/${pinCode}/sid/${getCookie('sid')}`);
Expand All @@ -43,6 +54,10 @@ export default function QuizWait() {
});

fetchUserType();

return () => {
socket.off('nickname', handleNickname);
};
}, []);

const handleCopyLink = () => {
Expand Down Expand Up @@ -90,7 +105,7 @@ export default function QuizWait() {
{guests.length === 0 ? 'no' : guests.length} participants
</p>
</div>
<UserGridView guests={guests} />
<UserGridView guests={guests} myPosition={myPosition} />
</div>
{userType === 'master' && (
<div className="flex justify-end min-w-full">
Expand Down
121 changes: 121 additions & 0 deletions packages/client/src/pages/quiz-wait/ui/UserGridItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useRef, useEffect, useState } from 'react';

import { Guest } from '../index';
import { getQuizSocket } from '@/shared/utils/socket';
import { useParams } from 'react-router-dom';

interface UserGridItemProps {
participant: Guest;
isMine: boolean;
otherMessage: string | undefined;
}

const characterNames = ['강아지', '고양이', '돼지', '토끼', '펭귄', '햄스터'];

const randomColor = [
'bg-gradient-to-br from-blue-400/80 to-blue-500/80 text-white',
'bg-gradient-to-br from-pink-300/80 to-pink-400/80',
'bg-gradient-to-br from-yellow-300/80 to-yellow-400/80',
];

export default function UserGridItem({ participant, isMine, otherMessage }: UserGridItemProps) {
// 내 메시지, 상대방들 메시지
const [message, setMessage] = useState('');
const [isFocused, setIsFocused] = useState(false);
const socket = getQuizSocket();
const { pinCode } = useParams();

const inputRef = useRef<HTMLInputElement>(null);

const handleMessageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setMessage(e.target.value);
socket.emit('message', { pinCode, message: e.target.value, position: participant.position });
};

const handleEnterKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
//메세지 전송
setIsFocused(false);

setTimeout(() => {
socket.emit('message', {
pinCode,
message: '',
position: participant.position,
});
setMessage('');
}, 1500);
}
};

useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === '/') {
setIsFocused(true);
setTimeout(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, 100);
}
};

window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, []);

return (
<div key={participant.position} className="relative w-full h-24 flex flex-col items-center">
{isMine && isFocused && (
<>
<input
ref={inputRef}
value={message}
onChange={handleMessageChange}
onKeyDown={handleEnterKeyDown}
className="absolute -top-7 rounded-md shadow-sm outline-none px-2 w-40 h-6 text-sm"
/>
<div
className="absolute inset-0 -top-1 left-12 w-0 h-0
border-l-[8px] border-l-transparent
border-t-[10px] border-t-white
border-r-[8px] border-r-transparent
drop-shadow-[0_0.7px_0.7px_rgba(0,0,0,0.1)]
"
/>
</>
)}
{!isFocused && message && (
<div
className={`flex items-center absolute -top-7 left-1/2 transform -translate-x-1/2 ${randomColor[participant.position % 3]} px-2 h-6 rounded-lg shadow-sm text-sm font-bold whitespace-nowrap`}
>
{message}
</div>
)}
<div
className={`relative w-20 h-20 rounded-full aspect-square flex items-center justify-center shadow-sm hover:shadow transition-shadow ${
isMine ? 'bg-blue-500' : 'bg-white'
}`}
>
<img
src={`/src/shared/assets/characters/${characterNames[participant.character]}.png`}
alt={`${characterNames[participant.character]}character`}
className="w-20 h-20 rounded-full"
/>
{!isMine && otherMessage && (
<div
className={`flex items-center absolute -top-7 left-1/2 transform -translate-x-1/2 ${randomColor[participant.position % 3]} px-2 h-6 rounded-lg shadow-sm text-sm font-semibold whitespace-nowrap`}
>
{otherMessage}
</div>
)}
</div>
<div className="flex justify-center items-center gap-2 w-full mt-2">
<div className="w-[10px] h-[10px] rounded-full bg-green-500 animate-blink" />
<div className="text-sm text-center truncate font-bold">{participant.nickname}</div>
</div>
</div>
);
}
61 changes: 27 additions & 34 deletions packages/client/src/pages/quiz-wait/ui/UserGridView.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';

interface Guest {
nickname: string;
character: number;
message: string;
position: number;
}
import { Guest } from '../index';
import UserGridItem from './UserGridItem';
import { getQuizSocket } from '@/shared/utils/socket';

interface UserGridViewProps {
guests: Guest[];
myPosition: number;
}

const characterNames = ['강아지', '고양이', '돼지', '토끼', '펭귄', '햄스터'];
export default function UserGridView({ guests, myPosition }: UserGridViewProps) {
const [otherMessage, setOtherMessage] = useState<Map<number, string>>(new Map<number, string>());

const socket = getQuizSocket();

export default function UserGridView({ guests }: UserGridViewProps) {
const [message, setMessage] = useState('');
useEffect(() => {
socket.on('message', (response) => {
setOtherMessage((prevMap) => {
const newMap = new Map(prevMap);
newMap.set(response.position, response.message);
return newMap;
});
});
}, []);

return (
<div className="w-full bg-blue-50 rounded-xl shadow-md">
<div className="grid grid-cols-6 gap-4 p-8">
{Array.from({ length: 30 }).map((_, index) => {
<div className="grid grid-cols-7 gap-16 p-8">
{Array.from({ length: 28 }).map((_, index) => {
if (index >= guests.length) {
return (
<div key={index} className="flex flex-col items-center">
<div className="w-20 h-20 rounded-full bg-gray-200"></div>
<div key={index} className={`flex flex-col items-center`}>
<div className="w-20 h-20 bg-gray-200 rounded-full"></div>
<div className="w-16 h-4 bg-gray-200 rounded-md mt-2"></div>
</div>
);
}
const participant = guests[index];
return (
<div className="w-full flex flex-col items-center">
<div className="relative w-20 h-20 aspect-square bg-white rounded-full flex items-center justify-center shadow-sm hover:shadow transition-shadow">
<img
src={`/src/shared/assets/characters/${characterNames[participant.character]}.png`}
alt={`${characterNames[participant.character]}character`}
className="w-20 h-20 rounded-full"
/>
{participant.message && (
<input
className="absolute -top-12 left-1/2 transform -translate-x-1/2 bg-white p-2 rounded-lg shadow-sm text-sm whitespace-nowrap"
onChange={(e) => setMessage(e.target.value)}
value={message}
/>
)}
</div>
<div className="flex justify-center items-center gap-2 w-full mt-2">
<div className="w-[10px] h-[10px] rounded-full bg-green-500 animate-blink" />
<div className="text-sm text-center truncate">{participant.nickname}</div>
</div>
</div>
<UserGridItem
participant={participant}
isMine={myPosition === participant.position}
otherMessage={otherMessage.get(index)}
/>
);
})}
</div>
Expand Down
Binary file modified packages/client/src/shared/assets/characters/햄스터.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 0 additions & 18 deletions packages/server/src/app.controller.ts

This file was deleted.

16 changes: 6 additions & 10 deletions packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MysqlConfigModule } from './config/database/mysql/configuration.module';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { UserModule } from './module/user/user.module';
import { QuizModule } from './module/quiz/quiz.module';
import { GameGateway } from './module/game/game.gateway';
import { RedisService } from './config/database/redis/redis.service';
import { RedisModule } from '@nestjs-modules/ioredis'; // 추가
import { GameService } from './module/game/games/game.service';
import { GameController } from './module/game/games/game.controller';
import { RedisModule } from '@nestjs-modules/ioredis';
import { GameModule } from './module/game/game.module';
import { RedisModule as RedisModuleTEST } from './config/database/redis/redis.module'; //

@Module({
imports: [
UserModule,
QuizModule,
GameModule,
RedisModuleTEST,
MysqlConfigModule,
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
Expand All @@ -37,8 +36,5 @@ import { GameController } from './module/game/games/game.controller';
url: process.env.REDIS_URL,
}),
],
controllers: [AppController, GameController],
providers: [AppService, GameGateway, RedisService, GameService],
exports: [RedisService],
})
export class AppModule {}
8 changes: 0 additions & 8 deletions packages/server/src/app.service.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ConfigModule } from '@nestjs/config';
import Joi from 'joi';
import databaseConfig from './configuration';
import { MysqlConfigService } from './configuration.service';
import { join } from 'path';

@Module({
imports: [
Expand Down
8 changes: 8 additions & 0 deletions packages/server/src/config/database/redis/redis.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { RedisService } from './redis.service';

@Module({
providers: [RedisService],
exports: [RedisService],
})
export class RedisModule {}
18 changes: 0 additions & 18 deletions packages/server/src/module/game/game.gateway.spec.ts

This file was deleted.

14 changes: 14 additions & 0 deletions packages/server/src/module/game/game.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { GameController } from './games/game.controller';
import { GameService } from './games/game.service';
import { GameGateway } from './games/game.gateway';
import { QuizModule } from '../quiz/quiz.module';
import { RedisModule } from '../../config/database/redis/redis.module';

@Module({
imports: [QuizModule, RedisModule],
controllers: [GameController],
providers: [GameService, GameGateway],
exports: [GameService, GameGateway],
})
export class GameModule {}
Loading

0 comments on commit 175d9a8

Please sign in to comment.