Skip to content

Commit 7472e15

Browse files
authored
Merge pull request #70 from hamlsy/release/roadview-rank
Release/roadview rank
2 parents d7ecdde + ce5f0f8 commit 7472e15

45 files changed

Lines changed: 3382 additions & 555 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.development

4 Bytes
Binary file not shown.

.github/workflows/deploy.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,14 @@ jobs:
121121
122122
echo "Deployment completed successfully!"
123123
echo "Application URL: https://$BUCKET.s3-website.ap-northeast-2.amazonaws.com$DEPLOY_PATH"
124-
124+
125+
- name: CloudFront Invalidation
126+
env:
127+
CLOUD_FRONT_ID: ${{ secrets.AWS_CLOUDFRONT_ID}}
128+
run: |
129+
aws cloudfront create-invalidation \
130+
--distribution-id $CLOUD_FRONT_ID --paths "/*"
131+
125132
- name: Notify deployment
126133
if: always()
127134
run: |

src/features/game/multiplayer/chat/components/Lobby/ChatWindow.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="chat-window">
33
<div class="chat-header">
4-
<h3 class="chat-title">로비 채팅</h3>
4+
<h3 class="chat-title">채팅방</h3>
55
<!-- <div class="online-users">
66
<span class="online-indicator"></span>
77
<span>{{ onlineUsers }}명 접속중</span>

src/features/game/multiplayer/chat/components/Room/ChatMessage.vue

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,19 @@ const props = defineProps({
2626
required: true
2727
},
2828
currentUserId: {
29-
type: String,
29+
type: [String, Number],
3030
required: true
3131
}
3232
});
3333
3434
// Computed properties
3535
const isOwnMessage = computed(() => {
36-
return props.message.senderId === props.currentUserId;
36+
const sender = props.message?.senderId;
37+
if (sender === undefined || sender === null) {
38+
return false;
39+
}
40+
41+
return String(sender) === String(props.currentUserId);
3742
});
3843
3944
const isSystemMessage = computed(() => {
@@ -60,47 +65,64 @@ const formattedTime = computed(() => {
6065
max-width: 80%;
6166
align-self: flex-start;
6267
margin-bottom: 0.75rem;
68+
display: flex;
69+
flex-direction: column;
70+
gap: 0.25rem;
6371
}
6472
6573
.chat-message.own-message {
6674
align-self: flex-end;
75+
text-align: right;
6776
}
6877
6978
.message-sender {
7079
font-size: 0.8rem;
7180
color: #6b7280;
72-
margin-bottom: 0.25rem;
81+
margin-bottom: 0.1rem;
82+
}
83+
84+
.chat-message.own-message .message-sender {
85+
display: none;
7386
}
7487
7588
.message-content {
89+
display: inline-flex;
90+
align-items: flex-start;
7691
background: #f3f4f6;
7792
padding: 0.75rem 1rem;
78-
border-radius: 16px 16px 16px 0;
79-
color: black;
80-
font-size: 0.9rem;
93+
border-radius: 16px 16px 16px 4px;
94+
color: #111827;
95+
font-size: 0.92rem;
96+
line-height: 1.45;
8197
word-break: break-word;
8298
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
99+
max-width: 100%;
83100
}
84101
85102
.chat-message.own-message .message-content {
86-
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
87-
color: #1e40af;
88-
border-radius: 16px 16px 0 16px;
103+
margin-left: auto;
104+
background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
105+
color: white;
106+
border-radius: 16px 16px 4px 16px;
107+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.25);
89108
}
90109
91110
.message-time {
92111
font-size: 0.7rem;
93112
color: #9ca3af;
94-
margin-top: 0.25rem;
95113
text-align: right;
96114
}
97115
98-
/* Add subtle hover effect */
116+
.chat-message.own-message .message-time {
117+
text-align: left;
118+
margin-left: auto;
119+
color: rgba(255, 255, 255, 0.75);
120+
}
121+
99122
.chat-message:hover .message-content {
100-
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
123+
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
101124
}
102125
103-
/* Add subtle transition */
104126
.chat-message .message-content {
105127
transition: all 0.2s ease;
106128
}

src/features/game/multiplayer/lobby/components/CreateRoomModal.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ export default {
154154
data() {
155155
return {
156156
roomName: "",
157-
gameMode: "로드뷰",
158-
region: "전국",
157+
gameMode: "roadview",
159158
maxPlayers: 4,
160159
gameType: "solo",
160+
timeLimit: 180,
161161
password: "",
162162
gameSettings: {
163163
isPrivate: false,
@@ -182,6 +182,7 @@ export default {
182182
title: this.roomName,
183183
password: this.password || null,
184184
gameModeKey: this.gameMode,
185+
timeLimit: this.timeLimit,
185186
playerMatchTypeKey: playerMatchTypeKey,
186187
maxPlayers: this.maxPlayers,
187188
privateRoom: this.gameSettings.isPrivate,

src/features/game/multiplayer/lobby/services/useGlobalLobbyWebSocketService.js

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ import {
2222

2323
// 글로벌 로비 구독 정보
2424
const globalLobbySubscriptions = ref(new Map());
25+
const LOBBY_TOPIC = '/topic/chat/lobby';
2526

2627
/**
2728
* 글로벌 로비 WebSocket 서비스를 초기화하고 제공하는 컴포저블 함수
2829
*
2930
* @returns {Object} 글로벌 로비 WebSocket 서비스 관련 함수와 데이터를 포함하는 객체
3031
*/
3132
export function useGlobalLobbyWebSocketService() {
33+
const isTeardownInProgress = ref(false);
34+
const isLobbyActive = ref(false);
35+
3236
// 인증 컴포저블에서 사용자 정보 가져오기
3337
const { user: authUser, isAuthenticated } = useAuth();
3438

@@ -38,44 +42,75 @@ export function useGlobalLobbyWebSocketService() {
3842
*
3943
* @param {String} [endpoint='/ws'] - WebSocket 서버의 엔드포인트 URL
4044
*/
41-
const connectWebSocket = (endpoint = '/api/ws') => {
42-
// 이미 연결된 경우에는 글로벌 로비 채널만 구독
43-
if (webSocketManager.isConnected.value) {
44-
subscribeToGlobalLobbyChat();
45-
return;
46-
}
47-
48-
// 연결 성공 시 호출될 콜백 함수
49-
const onConnectCallback = () => {
50-
// 로비 전용 구독 설정 (게임 채팅, 플레이어 상태, 게임 상태 구독 제외)
45+
const connectWebSocket = (endpoint = '/ws') => {
46+
const bootstrapLobby = () => {
5147
webSocketManager.setupLobbySubscriptions();
52-
5348
subscribeToGlobalLobbyChat();
5449
joinGlobalLobby();
55-
// 연결 성공 메시지 표시
50+
isLobbyActive.value = true;
5651
createSystemMessage('채팅 서버에 연결되었습니다.', 'lobby');
5752
};
58-
59-
// WebSocketManager를 통해 연결
60-
webSocketManager.connect(endpoint, onConnectCallback);
53+
54+
if (webSocketManager.isConnected.value) {
55+
bootstrapLobby();
56+
return;
57+
}
58+
59+
const onConnectCallback = () => {
60+
bootstrapLobby();
61+
};
62+
63+
try {
64+
webSocketManager.connect(endpoint, onConnectCallback);
65+
} catch (error) {
66+
console.error('글로벌 로비 WebSocket 연결 중 오류:', error);
67+
}
6168
};
6269

6370
/**
6471
* 글로벌 로비 관련 WebSocket 구독을 해제합니다.
6572
* 컴포넌트가 언마운트되기 전에 호출되어야 합니다.
6673
*/
67-
const disconnectWebSocket = () => {
68-
// 글로벌 로비 구독 해제
69-
globalLobbySubscriptions.value.forEach((_, topic) => {
74+
const unsubscribeFromGlobalLobbyChat = () => {
75+
const topics = Array.from(globalLobbySubscriptions.value.keys());
76+
topics.forEach((topic) => {
7077
try {
7178
webSocketManager.unsubscribe(topic);
7279
} catch (error) {
7380
console.error(`글로벌 로비 구독 해제 중 오류 (${topic}):`, error);
81+
} finally {
82+
globalLobbySubscriptions.value.delete(topic);
7483
}
7584
});
76-
77-
// 구독 목록 초기화
78-
globalLobbySubscriptions.value.clear();
85+
};
86+
87+
const disconnectWebSocket = async ({ force = false } = {}) => {
88+
if (isTeardownInProgress.value) {
89+
return;
90+
}
91+
92+
isTeardownInProgress.value = true;
93+
94+
try {
95+
unsubscribeFromGlobalLobbyChat();
96+
97+
if (isLobbyActive.value) {
98+
leaveGlobalLobby();
99+
}
100+
101+
if (force) {
102+
if (typeof webSocketManager.deactivate === 'function') {
103+
await webSocketManager.deactivate();
104+
} else {
105+
webSocketManager.disconnect();
106+
}
107+
}
108+
} catch (error) {
109+
console.error('글로벌 로비 WebSocket 종료 처리 중 오류:', error);
110+
} finally {
111+
isLobbyActive.value = false;
112+
isTeardownInProgress.value = false;
113+
}
79114
};
80115

81116
/**
@@ -90,10 +125,8 @@ export function useGlobalLobbyWebSocketService() {
90125
}
91126

92127
try {
93-
94-
// API 명세서에 따른 로비 채팅 구독 경로: /topic/lobby
95-
const topic = '/topic/chat/lobby';
96-
128+
const topic = LOBBY_TOPIC;
129+
97130
// 이미 구독 중인지 확인
98131
if (globalLobbySubscriptions.value.has(topic)) {
99132
return;
@@ -202,8 +235,8 @@ export function useGlobalLobbyWebSocketService() {
202235
* beforeunload 이벤트 핸들러
203236
* 브라우저 창 닫기 시에만 로비 퇴장 메시지 전송
204237
*/
205-
const handleBeforeUnload = () => {
206-
leaveGlobalLobby();
238+
const handleBeforeUnload = async () => {
239+
await disconnectWebSocket({ force: true });
207240
};
208241

209242
// 컴포넌트 마운트 시 이벤트 리스너 등록
@@ -213,12 +246,12 @@ export function useGlobalLobbyWebSocketService() {
213246
});
214247

215248
// 컴포넌트 언마운트 시 정리 작업
216-
onBeforeUnmount(() => {
249+
onBeforeUnmount(async () => {
217250
// 이벤트 리스너 제거
218251
window.removeEventListener('beforeunload', handleBeforeUnload);
219252

220253
// 구독 해제 (연결은 유지)
221-
disconnectWebSocket();
254+
await disconnectWebSocket();
222255
});
223256

224257
// 반환할 객체

src/features/game/multiplayer/lobby/views/LobbyView.vue

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
</template>
114114

115115
<script setup>
116-
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
116+
import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue';
117117
import { useRouter } from 'vue-router';
118118
import { useAuth } from '@/core/composables/useAuth.js';
119119
import useGlobalLobbyWebSocketService from '../services/useGlobalLobbyWebSocketService';
@@ -225,16 +225,15 @@ const initializeData = async () => {
225225
226226
const connectToChat = async () => {
227227
try {
228-
// WebSocket 서비스 연결
229228
lobbyService.connectWebSocket();
230229
} catch (error) {
231230
console.error('채팅 서비스 연결 실패:', error);
232231
}
233232
};
234233
235-
const disconnectFromChat = () => {
234+
const disconnectFromChat = async () => {
236235
try {
237-
lobbyService.disconnectWebSocket();
236+
await lobbyService.disconnectWebSocket();
238237
} catch (error) {
239238
console.error('채팅 연결 해제 중 오류:', error);
240239
}
@@ -294,7 +293,6 @@ const createRoom = async (roomData) => {
294293
await router.push({
295294
name: 'RoomView',
296295
params: { roomId: newRoom.gameRoomId.toString() },
297-
// 방 정보를 state로 전달하여 RoomView에서 즉시 사용 가능
298296
state: {
299297
roomData: {
300298
id: newRoom.gameRoomId,
@@ -304,7 +302,7 @@ const createRoom = async (roomData) => {
304302
maxPlayers: newRoom.maxPlayers,
305303
isPrivate: newRoom.privateRoom || false,
306304
hostId: getCurrentUserId(),
307-
currentPlayerCount: 1 // 방장 혼자
305+
currentPlayerCount: 1
308306
}
309307
}
310308
});
@@ -326,26 +324,29 @@ const handleDisableDummyData = async () => {
326324
}
327325
};
328326
329-
// 개발 모드 토글 메서드
330327
const toggleDevMode = async () => {
331328
if (useDummyData.value) {
332-
// 개발 모드에서 API 모드로 전환
333329
console.log('🌐 API 모드로 전환');
334330
try {
335331
await disableDummyData();
336332
} catch (error) {
337333
console.error('❌ API 모드 전환 실패:', error);
338334
}
339335
} else {
340-
// API 모드에서 개발 모드로 전환
341336
console.log('🧪 개발 모드로 전환');
342-
clearError(); // 기존 에러 클리어
337+
clearError();
343338
enableDummyData(true);
344339
}
345340
};
346341
347342
// 라이프사이클 훅
348-
onMounted(() => {
343+
onMounted(async () => {
344+
// DOM이 완전히 렌더링된 후 페이지 상단으로 스크롤
345+
await nextTick();
346+
window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
347+
document.documentElement.scrollTop = 0;
348+
document.body.scrollTop = 0;
349+
349350
// 로그인 여부 확인
350351
const isLoggedIn = !!localStorage.getItem('accessToken');
351352
@@ -362,12 +363,12 @@ onMounted(() => {
362363
window.addEventListener('resize', checkMobileView);
363364
});
364365
365-
onBeforeUnmount(() => {
366+
onBeforeUnmount(async () => {
366367
// 정리 작업
367368
if (refreshInterval.value) {
368369
clearInterval(refreshInterval.value);
369370
}
370-
disconnectFromChat();
371+
await disconnectFromChat();
371372
window.removeEventListener('resize', checkMobileView);
372373
});
373374
</script>

0 commit comments

Comments
 (0)