Skip to content

Commit fc84706

Browse files
authored
Merge pull request #176 from dnd-side-project/refactor/#175/chat-context
[refactor] 채팅 소켓 context 구조로 리팩토링
2 parents 75e7b9a + 3495b45 commit fc84706

File tree

10 files changed

+164
-137
lines changed

10 files changed

+164
-137
lines changed

src/app/providers/ChatProvider.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createContext, useContext, type ReactNode } from 'react'
2+
3+
import { useChatSocket } from '@/features/chat/model/sendMessage'
4+
5+
interface ChatProviderProps {
6+
roomId: string
7+
children: ReactNode
8+
}
9+
10+
export const ChatContext = createContext<ReturnType<typeof useChatSocket> | null>(null)
11+
12+
export const ChatProvider = ({ roomId, children }: ChatProviderProps) => {
13+
const chat = useChatSocket(roomId)
14+
15+
return <ChatContext.Provider value={chat}>{children}</ChatContext.Provider>
16+
}
17+
18+
export const useChat = () => {
19+
const ctx = useContext(ChatContext)
20+
if (!ctx) throw new Error('useChat 사용 오류: ChatProvider 내부에서 호출하세요')
21+
return ctx
22+
}

src/app/providers/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { ChatProvider } from '@/app/providers/ChatProvider'
2+
13
import { default as PlayerProvider } from './PlayerProvider'
24
import QueryProvider from './QueryProvider'
35
import ThemeProvider from './ThemeProvider'
46
import { ToastProvider, useToast } from './ToastProvider'
57

6-
export { QueryProvider, ThemeProvider, PlayerProvider, ToastProvider, useToast }
8+
export { QueryProvider, ThemeProvider, PlayerProvider, ToastProvider, useToast, ChatProvider }

src/features/chat/api/chat.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ChatCountResponse,
33
ChatHistoryParams,
44
ChatHistoryResponse,
5+
ListenerNumResponse,
56
} from '@/features/chat/types/chat'
67
import { api } from '@/shared/api/httpClient'
78

@@ -18,3 +19,7 @@ export const deleteChatMessage = async (roomId: string, messageId: string) => {
1819
export const getChatCount = (roomId: string) => {
1920
return api.get<ChatCountResponse>(`/chat/rooms/${roomId}/count/chat`)
2021
}
22+
23+
export const getListenerCount = (roomId: string) => {
24+
return api.get<ListenerNumResponse>(`/chat/rooms/${roomId}/count/member`)
25+
}

src/features/chat/model/sendMessage.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,7 @@ import { Client, type Message } from '@stomp/stompjs'
44
import SockJS from 'sockjs-client/dist/sockjs'
55

66
import { useAuthStore } from '@/features/auth/store/authStore'
7-
8-
export interface ChatMessage {
9-
senderId: string
10-
messageId: string
11-
username: string | null
12-
content: string
13-
sentAt: string
14-
profileImage: string | null
15-
systemMessage?: boolean
16-
roomId: string
17-
}
7+
import { getListenerCount, type ChatMessage } from '@/features/chat'
188

199
export const useChatSocket = (roomId: string) => {
2010
const clientRef = useRef<Client | null>(null)
@@ -26,6 +16,18 @@ export const useChatSocket = (roomId: string) => {
2616

2717
const authToken = accessToken || anonymousToken
2818

19+
const fetchInitialCount = useCallback(async () => {
20+
if (!roomId) return
21+
22+
try {
23+
const res = await getListenerCount(roomId)
24+
25+
setParticipantCount(res.count)
26+
} catch (e) {
27+
console.error('초기 인원 조회 실패 : ', e)
28+
}
29+
}, [roomId])
30+
2931
useEffect(() => {
3032
if (!roomId || !authToken) return
3133
if (clientRef.current?.active) return // 이미 활성화된 연결 방지
@@ -39,7 +41,7 @@ export const useChatSocket = (roomId: string) => {
3941
debug: (str) => console.log('[STOMP]', str),
4042
})
4143

42-
client.onConnect = () => {
44+
client.onConnect = async () => {
4345
setConnected(true)
4446

4547
// 메시지 구독
@@ -51,8 +53,11 @@ export const useChatSocket = (roomId: string) => {
5153
// 참여자 수
5254
client.subscribe(`/chat/topic/rooms/${roomId}/count`, (message: Message) => {
5355
const { count } = JSON.parse(message.body)
56+
5457
setParticipantCount(count)
5558
})
59+
60+
await fetchInitialCount()
5661
}
5762

5863
client.activate()
@@ -61,7 +66,7 @@ export const useChatSocket = (roomId: string) => {
6166
return () => {
6267
client.deactivate()
6368
}
64-
}, [roomId, authToken])
69+
}, [roomId, authToken, fetchInitialCount])
6570

6671
const sendMessage = useCallback(
6772
(content: string) => {

src/features/chat/types/chat.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ export interface ChatHistoryParams {
33
limit?: number
44
}
55

6-
export interface ChatHistoryMessage {
7-
roomId: string
8-
messageId: string
6+
export interface ChatMessage {
97
senderId: string
8+
messageId: string
109
username: string | null
1110
content: string
1211
sentAt: string
13-
systemMessage?: boolean
1412
profileImage: string | null
13+
systemMessage?: boolean
14+
roomId: string
1515
}
1616

1717
export interface ChatHistoryResponse {
18-
messages: ChatHistoryMessage[]
18+
messages: ChatMessage[]
1919
nextCursor?: string
2020
}
2121

2222
export interface ChatCountResponse {
2323
totalCount: number
2424
}
25+
26+
export interface ListenerNumResponse {
27+
roomId: string
28+
count: number
29+
}

src/pages/discover/index.tsx

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom'
33

44
import styled from 'styled-components'
55

6+
import { ChatProvider } from '@/app/providers/ChatProvider'
67
import { usePlaylist } from '@/app/providers/PlayerProvider'
78
import {
89
usePlaylistConfirmMutation,
@@ -105,35 +106,34 @@ const DiscoverPage = () => {
105106

106107
return (
107108
<Page>
108-
<SwipeCarousel
109-
data={playlistsData}
110-
onSelectIndexChange={handleSelectPlaylist}
111-
axis="y"
112-
basePath="/discover"
113-
>
114-
{playlistsData.map((data) => {
115-
const isCurrentActive = currentPlaylist?.playlistId === data.playlistId
116-
117-
return (
118-
<Slide key={data.playlistId}>
119-
<PlaylistLayout
120-
isActive={isCurrentActive}
121-
currentPlaylist={currentPlaylist}
122-
currentTrackIndex={currentTrackIndex}
123-
currentTime={currentTime}
124-
isPlaying={isPlaying}
125-
onPlayPause={() => (isPlaying ? pause() : play())}
126-
onNext={nextTrack}
127-
onPrev={prevTrack}
128-
onSelectTrack={(trackIndex, time) => {
129-
if (currentPlaylist) setPlaylist(currentPlaylist, trackIndex, time)
130-
}}
131-
playerRef={playerRef}
132-
/>
133-
</Slide>
134-
)
135-
})}
136-
</SwipeCarousel>
109+
<ChatProvider roomId={currentPlaylist ? String(currentPlaylist.playlistId) : ''}>
110+
<SwipeCarousel
111+
data={playlistsData}
112+
onSelectIndexChange={handleSelectPlaylist}
113+
axis="y"
114+
basePath="/discover"
115+
>
116+
{playlistsData.map((data) => {
117+
return (
118+
<Slide key={data.playlistId}>
119+
<PlaylistLayout
120+
currentPlaylist={currentPlaylist}
121+
currentTrackIndex={currentTrackIndex}
122+
currentTime={currentTime}
123+
isPlaying={isPlaying}
124+
onPlayPause={() => (isPlaying ? pause() : play())}
125+
onNext={nextTrack}
126+
onPrev={prevTrack}
127+
onSelectTrack={(trackIndex, time) => {
128+
if (currentPlaylist) setPlaylist(currentPlaylist, trackIndex, time)
129+
}}
130+
playerRef={playerRef}
131+
/>
132+
</Slide>
133+
)
134+
})}
135+
</SwipeCarousel>
136+
</ChatProvider>
137137
</Page>
138138
)
139139
}

src/pages/mycd/index.tsx

Lines changed: 68 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom'
33

44
import styled from 'styled-components'
55

6+
import { ChatProvider } from '@/app/providers/ChatProvider'
67
import { usePlaylist } from '@/app/providers/PlayerProvider'
78
import { NoLike } from '@/assets/icons'
89
import { MemberCharacter } from '@/assets/images'
910
import { usePlaylistDetail } from '@/entities/playlist'
1011
import { useMyCdActions, useMyCdList, useMyLikedCdList } from '@/entities/playlist/model/useMyCd'
1112
import { useAuthStore } from '@/features/auth/store/authStore'
12-
import { useChatSocket } from '@/features/chat/model/sendMessage'
1313
import { HeaderTab, PlaylistCarousel } from '@/pages/mycd/ui'
1414
import type { MyCdTab } from '@/pages/mycd/ui/HeaderTab'
1515
import { useDevice } from '@/shared/lib/useDevice'
@@ -136,11 +136,6 @@ const MyCdPage = () => {
136136
}
137137
}, [playlistDetail, userInfo, setPlaylist, currentPlaylist])
138138

139-
const isActive = currentPlaylist?.playlistId === playlistDetail?.playlistId
140-
const { participantCount: listenersNum } = useChatSocket(
141-
isActive && centerItem.playlistId ? String(centerItem.playlistId) : ''
142-
)
143-
144139
const handleProgressClick = useCallback(
145140
(trackIndex: number, seconds: number) => {
146141
if (!currentPlaylist) return
@@ -190,73 +185,75 @@ const MyCdPage = () => {
190185
}
191186

192187
return (
193-
<div>
194-
{currentPlaylist && (
195-
<>
196-
<HeaderTab selectedTab={selectedTab} onSelect={handleTabSelect} />
197-
<Container>
198-
<LiveInfo isOnAir={listenersNum > 0} listenerCount={listenersNum} />
199-
{selectedTab === 'MY' && (
200-
<Button
201-
size="S"
202-
state="primary"
203-
onClick={() =>
204-
navigate(`/mypage/customize`, {
205-
state: { cdId: currentPlaylist?.playlistId },
206-
})
207-
}
208-
>
209-
편집
210-
</Button>
211-
)}
212-
</Container>
213-
<CenterWrapper>
214-
<PlaylistCarousel
215-
data={playlistData ?? []}
216-
onCenterChange={handleCenterChange}
217-
currentPlaylistId={currentPlaylist?.playlistId}
218-
isPlaying={isPlaying}
219-
/>
220-
<ActionBar
221-
playlistId={centerItem.playlistId ?? 0}
222-
creatorId={currentPlaylist.creatorId}
223-
stickers={playlistDetail?.cdResponse?.cdItems || []}
224-
type="MY"
225-
selectedTab={selectedTab}
226-
/>
227-
<Title $isMobile={isMobile}> {centerItem.playlistName}</Title>
228-
{selectedTab === 'LIKE' && playlistDetail?.creatorNickname && (
229-
<Creator>{playlistDetail.creatorNickname}</Creator>
230-
)}
231-
232-
<BottomWrapper>
233-
<ProgressBar
234-
trackLengths={currentPlaylist.songs.map((t) => t.youtubeLength) || []}
235-
currentIndex={currentTrackIndex}
236-
onClick={handleProgressClick}
237-
/>
238-
239-
<ControlBar
240-
isPlaying={isPlaying}
241-
onTogglePlay={() => {
242-
if (isMobile && !isPlaying) {
243-
unmuteOnce()
188+
<ChatProvider roomId={currentPlaylist ? String(currentPlaylist.playlistId) : ''}>
189+
<div>
190+
{currentPlaylist && (
191+
<>
192+
<HeaderTab selectedTab={selectedTab} onSelect={handleTabSelect} />
193+
<Container>
194+
<LiveInfo />
195+
{selectedTab === 'MY' && (
196+
<Button
197+
size="S"
198+
state="primary"
199+
onClick={() =>
200+
navigate(`/mypage/customize`, {
201+
state: { cdId: currentPlaylist?.playlistId },
202+
})
244203
}
245-
246-
if (isPlaying) {
247-
pause()
248-
} else {
249-
play()
250-
}
251-
}}
252-
onNext={nextTrack}
253-
onPrev={prevTrack}
204+
>
205+
편집
206+
</Button>
207+
)}
208+
</Container>
209+
<CenterWrapper>
210+
<PlaylistCarousel
211+
data={playlistData ?? []}
212+
onCenterChange={handleCenterChange}
213+
currentPlaylistId={currentPlaylist?.playlistId}
214+
isPlaying={isPlaying}
215+
/>
216+
<ActionBar
217+
playlistId={centerItem.playlistId ?? 0}
218+
creatorId={currentPlaylist.creatorId}
219+
stickers={playlistDetail?.cdResponse?.cdItems || []}
220+
type="MY"
221+
selectedTab={selectedTab}
254222
/>
255-
</BottomWrapper>
256-
</CenterWrapper>
257-
</>
258-
)}
259-
</div>
223+
<Title $isMobile={isMobile}> {centerItem.playlistName}</Title>
224+
{selectedTab === 'LIKE' && playlistDetail?.creatorNickname && (
225+
<Creator>{playlistDetail.creatorNickname}</Creator>
226+
)}
227+
228+
<BottomWrapper>
229+
<ProgressBar
230+
trackLengths={currentPlaylist.songs.map((t) => t.youtubeLength) || []}
231+
currentIndex={currentTrackIndex}
232+
onClick={handleProgressClick}
233+
/>
234+
235+
<ControlBar
236+
isPlaying={isPlaying}
237+
onTogglePlay={() => {
238+
if (isMobile && !isPlaying) {
239+
unmuteOnce()
240+
}
241+
242+
if (isPlaying) {
243+
pause()
244+
} else {
245+
play()
246+
}
247+
}}
248+
onNext={nextTrack}
249+
onPrev={prevTrack}
250+
/>
251+
</BottomWrapper>
252+
</CenterWrapper>
253+
</>
254+
)}
255+
</div>
256+
</ChatProvider>
260257
)
261258
}
262259

0 commit comments

Comments
 (0)