Skip to content

Commit

Permalink
Merge pull request #18 from Help-M-Ssaem/feat/#11
Browse files Browse the repository at this point in the history
[Design] 1:1 채팅방 페이지, Chatting Input, Message 컴포넌트
  • Loading branch information
uiop5809 authored Aug 9, 2024
2 parents 674acb5 + dbffebf commit dacffe4
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 76 deletions.
205 changes: 134 additions & 71 deletions src/app/chatting/page.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,141 @@
'use client'

import ChattingInput from '@/components/chatting/ChattingInput'
import ChattingProfile from '@/components/chatting/ChattingProfile'
import Profile from '@/components/common/Profile'
import { ChattingProfileI } from '@/model/Chatting'
import { User } from '@/model/User'
import React, { useEffect, useRef, useState } from 'react'

const Chatting = () => {
// const chatRoomId = 1
// const member = 1
// const [messages, setMessages] = useState<string[]>([])
// const [input, setInput] = useState<string>('')
// const [isConnected, setIsConnected] = useState<boolean>(false)
// const socketRef = useRef<WebSocket | null>(null)
// useEffect(() => {
// const socket = new WebSocket(
// `wss://lc3cc1cnma.execute-api.ap-northeast-2.amazonaws.com/mssaem?chatRoomId=${chatRoomId}&member=${member}`,
// )
// socket.onopen = () => {
// console.log('WebSocket Opened')
// setIsConnected(true)
// }
// socket.onmessage = (event: MessageEvent<string>) => {
// console.log('WebSocket Message:', event.data)
// setMessages((prev) => [...prev, event.data])
// }
// socket.onerror = (error: Event) => {
// console.error('WebSocket Error:', error)
// }
// socket.onclose = () => {
// console.log('WebSocket is closed')
// setIsConnected(false)
// }
// socketRef.current = socket
// return () => {
// socket.close()
// }
// }, [chatRoomId, member])
// const sendMessage = () => {
// if (socketRef.current && input.trim() !== '') {
// const message = {
// action: 'sendMessage',
// chatRoomId: 1,
// message: input,
// }
// socketRef.current.send(JSON.stringify(message))
// setInput('')
// }
// }
// const disconnect = () => {
// if (socketRef.current) {
// socketRef.current.close()
// socketRef.current = null
// setIsConnected(false)
// }
// }
// return (
// <div>
// <h1>Chatting</h1>
// <div>
// {messages.map((msg, index) => (
// <div key={index}>{msg}</div>
// ))}
// </div>
// <input
// type="text"
// value={input}
// onChange={(e) => setInput(e.target.value)}
// onKeyPress={(e) => {
// if (e.key === 'Enter') {
// sendMessage()
// }
// }}
// />
// {/* <button onClick={sendMessage}>Send</button>
// {isConnected && <button onClick={disconnect}>Disconnect</button>} */}
// </div>
// )
const [chatRooms] = useState<number[]>([1, 2, 3])
const [currentChatRoomId, setCurrentChatRoomId] = useState<number>(1)
const [messages, setMessages] = useState<{ [key: number]: string[] }>({})
const [input, setInput] = useState<string>('')
const [isConnected, setIsConnected] = useState<boolean>(true)
const socketRef = useRef<WebSocket | null>(null)

const chattProfile: ChattingProfileI = {
nickName: '박빵이',
mbti: 'ENFP',
badge: 'ENFP',
profileImgUrl: '/images/common/default.svg',
recent: '5',
lastMessage: '안녕하세요',
}

const user: User = {
profileImgUrl: '/images/common/default.svg',
nickName: '유보라',
mbti: 'ENFP',
badge: '엠비티어른',
}

useEffect(() => {
const socket = new WebSocket(
`wss://lc3cc1cnma.execute-api.ap-northeast-2.amazonaws.com/mssaem?chatRoomId=${currentChatRoomId}&member=1`,
)
socketRef.current = socket
socket.onopen = () => {
setIsConnected(true)
}
socket.onmessage = (event) => {
const receivedMessage = JSON.parse(event.data)
setMessages((prevMessages) => ({
...prevMessages,
[currentChatRoomId]: [
...(prevMessages[currentChatRoomId] || []),
receivedMessage.message,
],
}))
}
return () => {
socket.close()
}
}, [currentChatRoomId])

const sendMessage = () => {
if (socketRef.current && input.trim() !== '') {
const message = {
action: 'sendMessage',
chatRoomId: currentChatRoomId,
message: input,
}
socketRef.current.send(JSON.stringify(message))
setMessages((prevMessages) => ({
...prevMessages,
[currentChatRoomId]: [
...(prevMessages[currentChatRoomId] || []),
input,
],
}))
setInput('')
}
}

const disconnect = () => {
if (socketRef.current) {
socketRef.current.close()
socketRef.current = null
setIsConnected(false)
}
}

const selectChatRoom = (chatRoomId: number) => {
if (isConnected) {
disconnect()
}
setCurrentChatRoomId(chatRoomId)
}

return (
<div className="w-full-vw ml-half-vw bg-main3 py-10">
<div className="flex h-screen-40 border-7.5 mx-2% sm:mx-6% md:mx-13% bg-white rounded-7.5 shadow-custom-light ">
{/* Left Sidebar for 채팅 목록 */}
<div className="border-r flex flex-col overflow-y-scroll scrollbar-hide">
<div className="flex items-center p-10 border-b text-title3 font-bold h-27.5">
채팅 목록
</div>
<ul className="">
{chatRooms.map((roomId) => (
<li
key={roomId}
className="border-b last:border-none box-border p-4"
>
<ChattingProfile
chattingProfile={chattProfile}
onClick={() => selectChatRoom(roomId)}
/>
</li>
))}
</ul>
</div>

{/* Right Content for 채팅 내역 */}
<div className="flex flex-col flex-1 bg-white rounded-7.5">
<div className="flex items-center border-b p-4 h-27.5">
<Profile user={user} />
</div>

<div className="flex-1 overflow-y-auto box-border">
{messages[currentChatRoomId]?.map((msg, index) => (
<div key={index} className="my-2 p-2 box-border">
{msg}
</div>
)) || <p className="p-4">No messages yet.</p>}
</div>

return <div>test</div>
<div className="flex items-center p-6">
<ChattingInput
value={input}
onChange={(e) => setInput(e.target.value)}
onClick={sendMessage}
/>
</div>
</div>
</div>
</div>
)
}

export default Chatting
16 changes: 16 additions & 0 deletions src/components/chatting/ChattingInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Meta, StoryFn } from '@storybook/react'
import ChattingInput, {
ChattingInputProps,
} from '@/components/chatting/ChattingInput'

export default {
title: 'Chatting/ChattingInput',
component: ChattingInput,
} as Meta<ChattingInputProps>

const Template: StoryFn<ChattingInputProps> = (args: ChattingInputProps) => (
<ChattingInput {...args} />
)

export const Primary = Template.bind({})
Primary.args = {}
32 changes: 32 additions & 0 deletions src/components/chatting/ChattingInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client'

import Button from '../common/Button'

export interface ChattingInputProps {
value: string
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onClick: () => void
}

const ChattingInput = ({ value, onChange, onClick }: ChattingInputProps) => {
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
onClick()
}
}

return (
<div className="flex w-full gap-3.75">
<input
type="text"
className="w-full text-gray2 text-headline font-semibold px-4 py-2 border border-main rounded-7.5 focus:outline-none focus:border-main"
value={value}
onChange={onChange}
onKeyPress={handleKeyPress}
/>
<Button text="등록" color="PURPLE" size="small" onClick={onClick} />
</div>
)
}

export default ChattingInput
31 changes: 31 additions & 0 deletions src/components/chatting/ChattingMessage.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Meta, StoryFn } from '@storybook/react'
import ChattingMessage, {
ChattingMessageProps,
} from '@/components/chatting/ChattingMessage'

export default {
title: 'Chatting/ChattingMessage',
component: ChattingMessage,
} as Meta<ChattingMessageProps>

const Template: StoryFn<ChattingMessageProps> = (
args: ChattingMessageProps,
) => <ChattingMessage {...args} />

export const SentMessage = Template.bind({})
SentMessage.args = {
message: {
content: '안녕하세요 도와주세요 어쩌고 저쩌고 머시라 머시라',
sendAt: '오후 11:32',
},
isReceived: false,
}

export const ReceivedMessage = Template.bind({})
ReceivedMessage.args = {
message: {
content: '안녕하세요 도와주세요 어쩌고 저쩌고 머시라 머시라',
sendAt: '오후 11:32',
},
isReceived: true,
}
40 changes: 40 additions & 0 deletions src/components/chatting/ChattingMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ChattingMessageI } from '@/model/Chatting'
import Image from 'next/image'

export interface ChattingMessageProps {
message: ChattingMessageI
isReceived: boolean
}

const ChattingMessage = ({ message, isReceived }: ChattingMessageProps) => {
const { content, sendAt } = message

return (
<div
className={`flex items-end mb-4 ${isReceived ? 'justify-start' : 'justify-end'}`}
>
<div
className={`flex items-center gap-2.5 ${isReceived ? 'flex-row-reverse' : 'flex-row'}`}
>
<div className="text-caption text-gray2 whitespace-nowrap">
{sendAt}
</div>
<div
className={`text-headine text-gray2 font-semibold rounded-7.5 px-5 py-4 max-w-90 ${isReceived ? 'bg-main4' : 'bg-white border border-main'}`}
>
{content}
</div>
{isReceived && (
<Image
src="/images/common/default.svg"
width={56}
height={56}
alt="profile"
/>
)}
</div>
</div>
)
}

export default ChattingMessage
8 changes: 6 additions & 2 deletions src/components/chatting/ChattingProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import Button from '../common/Button'

export interface ChattingProfileProps {
chattingProfile: ChattingProfileI
onClick: () => void
}

const ChattingProfile = ({ chattingProfile }: ChattingProfileProps) => {
const ChattingProfile = ({
chattingProfile,
onClick,
}: ChattingProfileProps) => {
const { nickName, mbti, badge, profileImgUrl, recent, lastMessage } =
chattingProfile

return (
<div className="flex items-center gap-4.5">
<div className="flex items-center gap-4.5 cursor-pointer" onClick={onClick}>
<div className="w-14 h-14 relative rounded-full overflow-hidden">
<Image
src={profileImgUrl}
Expand Down
11 changes: 8 additions & 3 deletions src/model/Chatting.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { MBTI } from '@/components/common/Button'
import { MBTI, Color } from '@/components/common/Button'

interface ChattingProfileI {
nickName: string
mbti: MBTI
badge?: string
badge?: Color
profileImgUrl: string
recent?: string
lastMessage: string
}

export default ChattingProfileI
interface ChattingMessageI {
content: string
sendAt: string
}

export type { ChattingProfileI, ChattingMessageI }
Loading

0 comments on commit dacffe4

Please sign in to comment.