Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom'
import { useDispatch } from 'react-redux'
import { Box, Button, Card, CardContent, CircularProgress, Collapse, Container, Grid, Typography } from '@mui/material'
import {
ChevronRight as ChevronRightIcon,
Expand Down Expand Up @@ -43,6 +44,7 @@ import { useSettings } from './contexts/SettingsContext'
import { useAuth } from './contexts/AuthContext'
import LoginPage from './pages/Login'
import SignUpPage from './pages/SignUp'
import { fetchMyProfile } from "./domains/profile/store/profileSlice";


function ProtectedRoute({ children }) {
Expand Down Expand Up @@ -1141,8 +1143,17 @@ function NotFound() {
}

function App() {
const dispatch = useDispatch()
const { isAuthenticated } = useAuth()
const { activeRoom, closeChatRoom } = useChat()

useEffect(() => {
if (isAuthenticated) {
// redux로 프로필 정보 API(/users/profile/me) 호출
dispatch(fetchMyProfile())
}
}, [isAuthenticated, dispatch])

const handleRefreshRooms = () => {
// Refresh rooms list after leaving a room
}
Expand Down
4 changes: 2 additions & 2 deletions src/aws-config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const awsConfig = {
Auth: {
Cognito: {
userPoolId: 'ap-northeast-2_ezDwzFCzR',
userPoolClientId: '4ns077jcr1pkue2vvisr6qdpu5',
userPoolId: import.meta.env.VITE_COGNITO_POOL_ID,
userPoolClientId: import.meta.env.VITE_COGNITO_CLIENT_ID,

loginWith: {
email: true,
Expand Down
11 changes: 6 additions & 5 deletions src/domains/freetalk/hooks/useChatWebSocket.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useCallback, useEffect, useRef, useState} from 'react'
import {chatWebSocketService} from '../services/chatWebSocketService'
import {chatRoomService} from '../../chat/services/chatService'
import { useCallback, useEffect, useRef, useState } from 'react'
import { chatWebSocketService } from '../services/chatWebSocketService'
import { chatRoomService } from '../../chat/services/chatService'

/**
* Chat WebSocket 훅
Expand All @@ -26,10 +26,10 @@ export function useChatWebSocket(roomId, userId) {
* WebSocket 연결
*/
const connect = useCallback(async (forceNewToken = false) => {
console.log('[useChatWebSocket] Attempting to connect...', {roomId, userId, forceNewToken})
console.log('[useChatWebSocket] Attempting to connect...', { roomId, userId, forceNewToken })

if (!roomId || !userId) {
console.error('[useChatWebSocket] roomId and userId are required', {roomId, userId})
console.error('[useChatWebSocket] roomId and userId are required', { roomId, userId })
return
}

Expand Down Expand Up @@ -74,6 +74,7 @@ export function useChatWebSocket(roomId, userId) {
id: messageId,
content: data.content,
userId: data.userId,
nickname: data.nickname,
messageType: data.messageType || 'TEXT',
createdAt: data.createdAt || new Date().toISOString(),
isOwn: data.userId === userId,
Expand Down
101 changes: 51 additions & 50 deletions src/domains/freetalk/pages/ChatRoomPage.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useCallback, useEffect, useRef, useState} from 'react'
import {useNavigate, useParams} from 'react-router-dom'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
Alert,
AppBar,
Expand All @@ -21,31 +21,31 @@ import {
Send as SendIcon,
VolumeUp as VolumeUpIcon,
} from '@mui/icons-material'
import {chatRoomService, messageService, voiceService} from '../../chat/services/chatService'
import {useAuth} from '../../../contexts/AuthContext'
import {useChatWebSocket} from '../hooks/useChatWebSocket'
import {useThemeMode} from '../../../contexts/ThemeContext'
import { chatRoomService, messageService, voiceService } from '../../chat/services/chatService'
import { useAuth } from '../../../contexts/AuthContext'
import { useChatWebSocket } from '../hooks/useChatWebSocket'
import { useThemeMode } from '../../../contexts/ThemeContext'
import CommandAutocomplete from '../components/CommandAutocomplete'
import PollCard from '../components/PollCard'
import SystemCommandMessage from '../components/SystemCommandMessage'
import {parseCommand, MessageType} from '../types/chatCommandTypes'
import { parseCommand, MessageType } from '../types/chatCommandTypes'

const levelColors = {
beginner: {bg: '#e8f5e9', color: '#2e7d32', label: '초급'},
intermediate: {bg: '#fff3e0', color: '#ef6c00', label: '중급'},
advanced: {bg: '#fce4ec', color: '#c2185b', label: '고급'},
beginner: { bg: '#e8f5e9', color: '#2e7d32', label: '초급' },
intermediate: { bg: '#fff3e0', color: '#ef6c00', label: '중급' },
advanced: { bg: '#fce4ec', color: '#c2185b', label: '고급' },
}

const ChatRoomPage = () => {
const {roomId} = useParams()
const { roomId } = useParams()
const navigate = useNavigate()
const {user} = useAuth()
const {mode} = useThemeMode()
const { user } = useAuth()
const { mode } = useThemeMode()
const isDark = mode === 'dark'
const currentUserId = user?.userId || user?.username || user?.sub

// 디버깅: 사용자 정보 확인
console.log('[ChatRoomPage] User info:', {user, currentUserId, roomId})
console.log('[ChatRoomPage] User info:', { user, currentUserId, roomId })

const messagesEndRef = useRef(null)
const messagesContainerRef = useRef(null)
Expand Down Expand Up @@ -90,11 +90,12 @@ const ChatRoomPage = () => {
// 기존 메시지 목록 조회 (초기 로드용)
const fetchMessages = useCallback(async () => {
try {
const response = await messageService.getList(roomId, {limit: 50})
const response = await messageService.getList(roomId, { limit: 50 })
const transformedMessages = (response.messages || []).map((msg, index) => ({
id: msg.messageId || `msg-${index}-${Date.now()}`,
content: msg.content,
userId: msg.userId,
nickname: msg.nickname,
messageType: msg.messageType,
createdAt: new Date(msg.createdAt),
isOwn: msg.userId === currentUserId,
Expand Down Expand Up @@ -122,12 +123,12 @@ const ChatRoomPage = () => {

// WebSocket 연결 (별도 effect)
useEffect(() => {
console.log('[ChatRoomPage] WebSocket effect triggered:', {roomId, currentUserId, isConnected})
console.log('[ChatRoomPage] WebSocket effect triggered:', { roomId, currentUserId, isConnected })
if (currentUserId && roomId) {
console.log('[ChatRoomPage] Connecting WebSocket...', {roomId, currentUserId})
console.log('[ChatRoomPage] Connecting WebSocket...', { roomId, currentUserId })
wsConnect()
} else {
console.log('[ChatRoomPage] Missing required values:', {roomId, currentUserId})
console.log('[ChatRoomPage] Missing required values:', { roomId, currentUserId })
}

return () => {
Expand All @@ -138,7 +139,7 @@ const ChatRoomPage = () => {

// 스크롤 맨 아래로
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({behavior: 'smooth'})
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}

useEffect(() => {
Expand All @@ -161,7 +162,7 @@ const ChatRoomPage = () => {
setShowCommandAutocomplete(false)

// 명령어 파싱
const {isCommand, command, args} = parseCommand(messageContent)
const { isCommand, command, args } = parseCommand(messageContent)

// /leave 명령어는 클라이언트에서 직접 처리
if (isCommand && command === '/leave') {
Expand Down Expand Up @@ -293,23 +294,23 @@ const ChatRoomPage = () => {

if (loading) {
return (
<Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh'}}>
<CircularProgress/>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<CircularProgress />
</Box>
)
}

return (
<Box sx={{display: 'flex', flexDirection: 'column', height: '100vh', bgcolor: isDark ? '#1e293b' : '#b2c7d9'}}>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh', bgcolor: isDark ? '#1e293b' : '#b2c7d9' }}>
{/* 헤더 */}
<AppBar position="static" color="default" elevation={1}>
<Toolbar>
<IconButton edge="start" onClick={() => navigate('/freetalk/people')} sx={{mr: 1}}>
<ArrowBackIcon/>
<IconButton edge="start" onClick={() => navigate('/freetalk/people')} sx={{ mr: 1 }}>
<ArrowBackIcon />
</IconButton>
<Box sx={{flex: 1}}>
<Box sx={{display: 'flex', alignItems: 'center', gap: 1}}>
<Typography variant="h6" component="div" sx={{fontWeight: 600}}>
<Box sx={{ flex: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" component="div" sx={{ fontWeight: 600 }}>
{room?.name || '채팅방'}
</Typography>
{/* 연결 상태 표시 */}
Expand All @@ -320,7 +321,7 @@ const ChatRoomPage = () => {
}}
/>
</Box>
<Box sx={{display: 'flex', alignItems: 'center', gap: 1}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{room?.level && (
<Chip
label={levelColors[room.level]?.label}
Expand All @@ -342,17 +343,17 @@ const ChatRoomPage = () => {
</Box>
</Box>
<IconButton onClick={handleRefresh} title="새로고침">
<RefreshIcon/>
<RefreshIcon />
</IconButton>
<IconButton onClick={handleLeaveRoom} color="error" title="나가기">
<ExitToAppIcon/>
<ExitToAppIcon />
</IconButton>
</Toolbar>
</AppBar>

{/* 에러 메시지 */}
{error && (
<Alert severity="error" onClose={handleCloseError} sx={{m: 1}}>
<Alert severity="error" onClose={handleCloseError} sx={{ m: 1 }}>
{error}
</Alert>
)}
Expand All @@ -370,7 +371,7 @@ const ChatRoomPage = () => {
}}
>
{messages.length === 0 ? (
<Box sx={{textAlign: 'center', py: 4}}>
<Box sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="body2" color="text.secondary">
아직 메시지가 없습니다. 첫 메시지를 보내보세요!
</Typography>
Expand All @@ -380,7 +381,7 @@ const ChatRoomPage = () => {
// 투표 메시지
if (message.messageType === MessageType.POLL_CREATE) {
return (
<Box key={message.id} sx={{width: '100%', display: 'flex', justifyContent: 'center', py: 1}}>
<Box key={message.id} sx={{ width: '100%', display: 'flex', justifyContent: 'center', py: 1 }}>
<PollCard
poll={message.data}
currentUserId={currentUserId}
Expand All @@ -394,7 +395,7 @@ const ChatRoomPage = () => {
// 시스템 명령어 메시지
if (message.messageType === MessageType.SYSTEM_COMMAND) {
return (
<SystemCommandMessage key={message.id} data={message.data}/>
<SystemCommandMessage key={message.id} data={message.data} />
)
}

Expand All @@ -411,7 +412,7 @@ const ChatRoomPage = () => {
>
{/* 시스템 메시지 */}
{message.isSystem ? (
<Box sx={{width: '100%', textAlign: 'center', py: 0.5}}>
<Box sx={{ width: '100%', textAlign: 'center', py: 0.5 }}>
<Typography variant="caption" color="text.secondary">
{message.content}
</Typography>
Expand All @@ -420,8 +421,8 @@ const ChatRoomPage = () => {
<>
{/* 아바타 (상대방만) */}
{!message.isOwn && (
<Avatar sx={{width: 36, height: 36, bgcolor: 'primary.main'}}>
{message.userId?.charAt(0)?.toUpperCase() || 'U'}
<Avatar sx={{ width: 36, height: 36, bgcolor: 'primary.main' }}>
{(message.nickname || message.userId)?.charAt(0)?.toUpperCase() || 'U'}
</Avatar>
)}

Expand All @@ -435,13 +436,13 @@ const ChatRoomPage = () => {
>
{/* 사용자 이름 (상대방만) */}
{!message.isOwn && (
<Typography variant="caption" sx={{mb: 0.5, ml: 1}}>
{message.userId}
<Typography variant="caption" sx={{ mb: 0.5, ml: 1 }}>
{message.nickname || message.userId}
</Typography>
)}

{/* 메시지 버블 */}
<Box sx={{display: 'flex', alignItems: 'flex-end', gap: 0.5}}>
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 0.5 }}>
{message.isOwn && (
<Typography variant="caption" color="text.secondary">
{formatTime(message.createdAt)}
Expand All @@ -463,23 +464,23 @@ const ChatRoomPage = () => {
opacity: message.isPending ? 0.7 : 1,
}}
>
<Typography variant="body2" sx={{whiteSpace: 'pre-wrap'}}>
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
{message.content}
</Typography>
</Paper>

{!message.isOwn && (
<Box sx={{display: 'flex', alignItems: 'center', gap: 0.5}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<IconButton
size="small"
onClick={() => handlePlayTTS(message.id)}
disabled={playingTTS === message.id}
sx={{p: 0.5}}
sx={{ p: 0.5 }}
>
{playingTTS === message.id ? (
<CircularProgress size={16}/>
<CircularProgress size={16} />
) : (
<VolumeUpIcon fontSize="small"/>
<VolumeUpIcon fontSize="small" />
)}
</IconButton>
<Typography variant="caption" color="text.secondary">
Expand All @@ -495,7 +496,7 @@ const ChatRoomPage = () => {
)
})
)}
<div ref={messagesEndRef}/>
<div ref={messagesEndRef} />
</Box>

{/* 입력 영역 */}
Expand Down Expand Up @@ -539,11 +540,11 @@ const ChatRoomPage = () => {
sx={{
bgcolor: 'primary.main',
color: 'white',
'&:hover': {bgcolor: 'primary.dark'},
'&:disabled': {bgcolor: 'grey.300'},
'&:hover': { bgcolor: 'primary.dark' },
'&:disabled': { bgcolor: 'grey.300' },
}}
>
<SendIcon/>
<SendIcon />
</IconButton>
</Paper>
</Box>
Expand Down
35 changes: 35 additions & 0 deletions src/domains/profile/hooks/useProfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
fetchMyProfile,
updateProfile,
uploadProfileImage,
clearError
} from '../store/profileSlice'

export const useProfile = () => {
const dispatch = useDispatch()
const { profile, loading, error, updateLoading, imageUploading } = useSelector(
(state) => state.profile
)

useEffect(() => {
if (!profile && !loading && !error) {
dispatch(fetchMyProfile())
}
}, [dispatch, profile, loading, error])

return {
profile,
loading,
error,
updateLoading,
imageUploading,
updateProfile: (data) => dispatch(updateProfile(data)).unwrap(),
uploadImage: (file) => dispatch(uploadProfileImage(file)).unwrap(),
clearError: () => dispatch(clearError()),
refetch: () => dispatch(fetchMyProfile())
}
}

export default useProfile
Loading