1- import { Fragment } from 'react' ;
1+ import { Fragment , useState } from 'react' ;
22import { Link } from 'react-router-dom' ;
33import type { Advertisement } from '@/apis/advertisement/entity' ;
44import type { Room } from '@/apis/chat/entity' ;
55import BellOffIcon from '@/assets/svg/bell-off.svg' ;
6+ import ChevronLeftIcon from '@/assets/svg/chevron-left.svg' ;
67import PersonIcon from '@/assets/svg/person.svg' ;
8+ import BottomModal from '@/components/common/BottomModal' ;
9+ import Modal from '@/components/common/Modal' ;
710import BottomOverlaySpacer from '@/components/layout/BottomOverlaySpacer' ;
811import { useAdvertisements } from '@/utils/hooks/useAdvertisements' ;
12+ import { useLongPress } from '@/utils/hooks/useLongPress' ;
13+ import ChatRoomContextMenu from './components/ChatRoomContextMenu' ;
914import useChat from './hooks/useChat' ;
1015
1116const DEFAULT_LAST_MESSAGE = '동아리에 궁금한 점을 물어보세요' ;
@@ -70,15 +75,24 @@ function ChatRoomAvatar({ roomImageUrl }: Pick<Room, 'roomImageUrl'>) {
7075 ) ;
7176}
7277
73- function ChatRoomListItem ( { room } : { room : Room } ) {
78+ interface ChatRoomListItemProps {
79+ room : Room ;
80+ onLongPress : ( x : number , y : number , room : Room ) => void ;
81+ }
82+
83+ function ChatRoomListItem ( { room, onLongPress } : ChatRoomListItemProps ) {
7484 const isGroup = room . chatType === 'GROUP' ;
7585 const hasUnreadMessage = room . unreadCount > 0 ;
7686 const previewMessage = room . lastMessage ?. trim ( ) || DEFAULT_LAST_MESSAGE ;
87+ const longPress = useLongPress ( {
88+ onLongPress : ( x : number , y : number ) => onLongPress ( x , y , room ) ,
89+ } ) ;
7790
7891 return (
7992 < Link
93+ { ...longPress }
8094 to = { `${ room . roomId } ` }
81- className = "active:bg-indigo-5 flex items-center gap-3 bg-white px-5 py-3 transition-colors"
95+ className = "active:bg-indigo-5 flex touch-pan-y items-center gap-3 bg-white px-5 py-3 transition-colors select-none "
8296 >
8397 < ChatRoomAvatar roomImageUrl = { room . roomImageUrl } />
8498
@@ -187,15 +201,69 @@ function ChatAdvertisementListItemSkeleton() {
187201 ) ;
188202}
189203
204+ interface ContextMenuProps {
205+ x : number ;
206+ y : number ;
207+ room : Room ;
208+ }
209+
190210function ChatListPage ( ) {
191- const { chatRoomList } = useChat ( ) ;
211+ const { chatRoomList, updateRoomName , deleteChatRoom , toggleMute } = useChat ( ) ;
192212 const rooms = chatRoomList . rooms ;
193213 const advertisementCount = getAdvertisementCount ( rooms . length ) ;
194214 const { advertisements, isLoadingAdvertisements, trackAdvertisementClick } = useAdvertisements ( {
195215 advertisementCount,
196216 scope : 'chat-list' ,
197217 } ) ;
198218
219+ const [ contextMenu , setContextMenu ] = useState < ContextMenuProps | null > ( null ) ;
220+ const [ leaveRoom , setLeaveRoom ] = useState < Room | null > ( null ) ;
221+ const [ changeRoomName , setChangeRoomName ] = useState < Room | null > ( null ) ;
222+ const [ newRoomName , setNewRoomName ] = useState ( '' ) ;
223+
224+ const changeName = async ( ) => {
225+ if ( ! changeRoomName ) return ;
226+ const roomId = changeRoomName . roomId ;
227+ const normalizedName = newRoomName . trim ( ) ;
228+ try {
229+ await updateRoomName ( { chatRoomId : roomId , name : normalizedName } ) ;
230+ } catch ( error ) {
231+ console . error ( 'Error updating room name:' , error ) ;
232+ }
233+ setChangeRoomName ( null ) ;
234+ } ;
235+
236+ const contextMenuItems = ( room : Room ) => [
237+ {
238+ label : '채팅방 이름 변경' ,
239+ onClick : ( ) => {
240+ setChangeRoomName ( room ) ;
241+ setNewRoomName ( room . roomName ) ;
242+ } ,
243+ } ,
244+ {
245+ label : room . isMuted ? '알림 켜기' : '알림 끄기' ,
246+ onClick : ( ) => {
247+ toggleMute ( room . roomId ) ;
248+ setContextMenu ( null ) ;
249+ } ,
250+ } ,
251+ ...( room . chatType === 'DIRECT'
252+ ? [ { label : '채팅방 나가기' , onClick : ( ) => setLeaveRoom ( room ) , danger : true } ]
253+ : [ ] ) ,
254+ ] ;
255+
256+ const deleteChat = async ( ) => {
257+ if ( ! leaveRoom ) return ;
258+ const roomId = leaveRoom . roomId ;
259+ setLeaveRoom ( null ) ;
260+ try {
261+ await deleteChatRoom ( roomId ) ;
262+ } catch ( error ) {
263+ console . error ( 'Error leaving chat room:' , error ) ;
264+ }
265+ } ;
266+
199267 if ( rooms . length === 0 ) {
200268 return (
201269 < div className = "bg-indigo-0 flex min-h-full flex-col items-center justify-center px-6 py-3 text-center" >
@@ -215,7 +283,7 @@ function ChatListPage() {
215283
216284 return (
217285 < Fragment key = { room . roomId } >
218- < ChatRoomListItem room = { room } />
286+ < ChatRoomListItem room = { room } onLongPress = { ( x , y , room ) => setContextMenu ( { x , y , room } ) } />
219287 { advertisement && (
220288 < ChatAdvertisementListItem advertisement = { advertisement } onClick = { trackAdvertisementClick } />
221289 ) }
@@ -227,6 +295,61 @@ function ChatListPage() {
227295 } ) }
228296 < BottomOverlaySpacer gap = { 24 } />
229297 </ div >
298+ < Modal isOpen = { leaveRoom !== null } onClose = { ( ) => setLeaveRoom ( null ) } className = "h-[172px] w-[341px] rounded-2xl" >
299+ < div className = "px-6 py-6 text-center" >
300+ < p className = "text-text-700 mb-5 text-[16px] font-bold" > 채팅방 나가기</ p >
301+ < p className = "text-text-500 mt-2 text-[14px]" > { leaveRoom ?. roomName } 채팅방을 나가시겠어요?</ p >
302+ </ div >
303+ < div className = "flex gap-2" >
304+ < button
305+ type = "button"
306+ className = "ml-4 h-11 w-37 flex-1 cursor-pointer rounded-[10px] border border-[#69BFDF] py-4 text-[14px] font-bold text-[#69BFDF]"
307+ onClick = { ( ) => setLeaveRoom ( null ) }
308+ >
309+ 취소
310+ </ button >
311+ < button
312+ type = "button"
313+ className = "bg-primary-500 mr-4 flex-1 cursor-pointer rounded-[10px] py-4 text-[14px] font-medium text-white"
314+ onClick = { deleteChat }
315+ >
316+ 나가기
317+ </ button >
318+ </ div >
319+ </ Modal >
320+ < BottomModal isOpen = { changeRoomName !== null } onClose = { ( ) => setChangeRoomName ( null ) } className = "h-59" >
321+ < div className = "flex items-center px-4 py-4" >
322+ < button type = "button" aria-label = "닫기" onClick = { ( ) => setChangeRoomName ( null ) } >
323+ < ChevronLeftIcon />
324+ </ button >
325+ < div className = "px-30 text-center font-semibold" > 이름 변경</ div >
326+ </ div >
327+ < div className = "flex w-full flex-col items-center gap-6" >
328+ < input
329+ type = "text"
330+ value = { newRoomName }
331+ onChange = { ( e ) => setNewRoomName ( e . target . value ) }
332+ className = "text-text-700 mt-11 h-[50px] w-[343px] rounded-2xl border border-indigo-50 text-center"
333+ placeholder = "변경할 채팅방명을 입력해주세요."
334+ />
335+ < button
336+ type = "button"
337+ className = "bg-primary-500 w-[343px] flex-1 cursor-pointer rounded-[10px] py-4 text-[14px] font-medium text-white"
338+ onClick = { changeName }
339+ >
340+ 확인
341+ </ button >
342+ </ div >
343+ </ BottomModal >
344+ { contextMenu && (
345+ < ChatRoomContextMenu
346+ x = { contextMenu . x }
347+ y = { contextMenu . y }
348+ title = { contextMenu . room . roomName }
349+ items = { contextMenuItems ( contextMenu . room ) }
350+ onClose = { ( ) => setContextMenu ( null ) }
351+ />
352+ ) }
230353 </ div >
231354 ) ;
232355}
0 commit comments