2024.05.28 채팅 UI & 팔로우 UI 코드 - FlowNary SNS 29일차
2024. 5. 28. 17:03ㆍReact
작업 전 첫 팔로우 화면
중간 수정 단계 1
중간 수정 단계 2
chatting < ChattingIndex.js
import React, { useState, useRef, useEffect, useContext } from 'react';
import { Avatar, Box, Stack, TextField, InputAdornment, Icon } from "@mui/material";
import IconButton from '@mui/material/IconButton';
import EastIcon from '@mui/icons-material/East';
import { GetWithExpiry } from 'api/LocalStorage';
import './components/chat.css';
import DashboardLayout from 'examples/LayoutContainers/DashboardLayout';
import DashboardNavbar from 'examples/Navbars/DashboardNavbar';
import { UserContext } from 'api/LocalStorage';
import { useLocation } from 'react-router-dom';
import { useWebSocket } from 'api/webSocketContext';
import { getDmList } from 'api/axiosGet';
import { getChat } from 'api/axiosGet';
import TouchRipple from '@mui/material/ButtonBase/TouchRipple';
export default function Chat() {
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState('');
const messageEndRef = useRef(null);
const inputFieldHeight = 65; // 입력 필드의 높이를 고정값
const { activeUser } = useContext(UserContext);
const { state } = useLocation() || null;
const { cid } = state != null ? state : { cid: -1 };
const { stompClient } = useWebSocket();
const [count, setCount] = useState(20);
const [list, setList] = useState([]);
const [chatroom, setChatroom] = useState(null);
const profile = GetWithExpiry("profile");
const handleMessageSend = () => {
if (inputMessage.trim() !== '' && stompClient && stompClient.connected) {
const newMessage = { text: inputMessage, sender: 'user' };
setInputMessage('');
stompClient.publish({
destination: '/app/chatroom',
body: JSON.stringify({
cid: cid,
uid: activeUser.uid,
dContents: inputMessage,
dFile: null,
nickname: activeUser.nickname,
profile: profile,
status: chatroom.status,
}),
})
}
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
// event.preventDefault(); // 기본 엔터 기능 비활성화
handleMessageSend(); // 메시지 전송 함수 호출
}
};
useEffect(() => {
if (activeUser.uid !== -1 && cid !== -1) {
const fetchMList = async () => {
const mlist = await getDmList(cid, count);
setMessages(mlist);
const chatr = await getChat(cid, activeUser.uid);
setChatroom(chatr);
}
fetchMList();
let chatconnect;
if (stompClient && stompClient.connected) {
console.log('chatting room connected');
stompClient.publish({
destination: '/app/page',
body: JSON.stringify({ userId: activeUser.uid, page: 'chatroom' + cid, action: 'enter' }),
});
chatconnect = stompClient.subscribe(`/user/chat/` + cid, (message) => {
const data = JSON.parse(message.body);
// console.log(data);
setMessages(prevMessages => {
const messageExists = prevMessages.some(msg => msg.did === data.did);
if (!messageExists) {
return [data, ...prevMessages];
}
return prevMessages;
});
});
}
return () => {
if (stompClient && stompClient.connected) {
stompClient.publish({
destination: '/app/page',
body: JSON.stringify({ userId: activeUser.uid, page: 'chatroom' + cid, action: 'leave' }),
});
console.log('chatting room disconnected');
}
}
}
}, [activeUser.uid, stompClient]);
useEffect(() => {
if (messageEndRef.current) {
messageEndRef.current.scrollIntoView({ behavior: "smooth", block: "end" });
}
}, [messages]);
const rippleRef = useRef(null);
const handleMouseDown = (event) => {
rippleRef.current.start(event);
};
const handleMouseUp = () => {
rippleRef.current.stop();
};
if (cid === -1) {
return (
<div>채팅방 정보가 없습니다.</div>
)
}
return (
<DashboardLayout>
<DashboardNavbar />
<IconButton sx={{ fontSize: '3rem', cursor: 'pointer' }}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}>
<Icon>arrow_back</Icon>
<TouchRipple ref={rippleRef} center />
</IconButton>
<Box
sx={{
marginTop: '-70px',
padding: '0px 15px 10px 10px',
minHeight: '200px',
// height: 'calc(180vh - 200px)',
width: '80%',
mx: 'auto',
overflowY: 'auto',
}}
>
<Stack sx={{ fontSize: 'x-large', fontWeight: 'bold', mx: 'auto' }}>
<div style={{ color: 'rgb(88, 67, 135)' }}>
{/* <Avatar alt="User" src={`https://res.cloudinary.com/${process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}/image/upload/${profile}`} />
{email} */}
{chatroom && chatroom.name}
<hr style={{ opacity: '0.4' }} />
</div>
</Stack>
{/* maxHeight를 사용 스크롤 활성 */}
<Stack sx={{ maxHeight: `calc(100vh - ${inputFieldHeight + 385}px)`, overflowY: 'auto', flexDirection: 'column-reverse' }}> {/* 메시지 영역의 최대 높이를 조정 */}
<br />
<div ref={messageEndRef} />
{messages && messages.map((message, index) => (
<Stack
key={index}
direction='row'
justifyContent={message.uid === activeUser.uid ? 'flex-end' : 'flex-start'}
>
{message.uid !== activeUser.uid &&
<Avatar sx={{ width: 50, height: 50 }}
>R</Avatar>}
<div style={{ marginTop: '20px' }} className={message.uid === activeUser.uid ? "message" : "othermessage"}>{message.dContents}</div>
{message.uid === activeUser.uid &&
<Avatar style={{ marginTop: '10px', marginLeft: '5px', marginBottom: '-10px' }}
sx={{ width: 50, height: 50, marginRight: '.75rem' }}
src={`https://res.cloudinary.com/${process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}/image/upload/${message.profile}`}
>U</Avatar>}
</Stack>
))}
</Stack>
<Stack
sx={{
position: 'fixed',
bottom: '10px',
width: { xs: '60%', sm: '70%', md: '80%' },
}}
>
<TextField //원래 이랬음
sx={{
marginBottom: '4em',
height: `${inputFieldHeight}px`, // 입력 필드의 높이 설정
width: '70.5%',
}}
fullWidth
placeholder="메시지를 보내세요!"
variant="outlined"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
InputProps={{
endAdornment: (
<InputAdornment position="end" >
<IconButton onClick={handleMessageSend}>
<EastIcon />
</IconButton>
</InputAdornment>
),
}}
/>
{/* {{chatGPT에게 물어본 코드}}
<TextField
sx={{
marginBottom: '4em',
height: `${inputFieldHeight}px`,
width: '70.5%',
}}
fullWidth
placeholder="메시지를 보내세요!"
variant="outlined"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
// e.preventDefault(); // 기본 엔터 기능 비활성화
handleMessageSend(); // 메시지 전송 함수 호출
}
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleMessageSend}>
<EastIcon />
</IconButton>
</InputAdornment>
),
}}
/> */}
</Stack>
</Box>
</DashboardLayout>
);
}
chatting < Chattinglist.js.js
import { ListItem, List, ListItemAvatar, Avatar } from '@mui/material';
import { useQuery } from '@tanstack/react-query';
import { UserContext } from 'api/LocalStorage';
import { getChatList } from 'api/axiosGet';
import { useWebSocket } from 'api/webSocketContext';
import DashboardLayout from 'examples/LayoutContainers/DashboardLayout';
import DashboardNavbar from 'examples/Navbars/DashboardNavbar';
import React, { useState, useEffect, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
export default function ChatList() {
const { activeUser } = useContext(UserContext);
const [list, setList] = useState([]);
const { stompClient } = useWebSocket();
const [count, setCount] = useState(20);
const navigate = useNavigate();
useEffect(() => {
if (activeUser.uid !== -1) {
const fetchChatList = async () => {
const chatlist = await getChatList(activeUser.uid, count, 0);
console.log(chatlist);
setList(chatlist);
}
fetchChatList();
let chatrefresh;
if (stompClient && stompClient.connected) {
console.log('chat websocket connected');
stompClient.publish({
destination: '/app/page',
body: JSON.stringify({ userId: activeUser.uid, page: 'chat', action: 'enter' }),
});
chatrefresh = stompClient.subscribe(`/topic/chatlist`, (message) => {
const data = JSON.parse(message.body);
console.log(data);
setList(prevList => {
const indexcid = prevList.findIndex(item => item.cid === data.cid);
// console.log(list);
console.log(indexcid);
if (indexcid !== -1) {
const chat = prevList[indexcid];
const newlist = [{
cid: data.cid,
status: chat.status,
statusTime: chat.statusTime,
userCount: chat.userCount,
name: chat.name,
lastMessage: data.lastMessage,
}
, ...prevList.slice(0, indexcid), ...prevList.slice(indexcid + 1)];
console.log(newlist);
return newlist;
}
return prevList;
})
});
}
return () => {
if (stompClient && stompClient.connected) {
stompClient.publish({
destination: '/app/page',
body: JSON.stringify({ userId: activeUser.uid, page: 'chat', action: 'leave' }),
});
console.log('chat websocket disconnected');
}
if (chatrefresh) {
chatrefresh.unsubscribe();
}
}
}
}, [activeUser.uid, count, stompClient]);
const handleChatClick = (cid) => {
navigate("/chatting", { state: { cid: cid } });
}
function formatDate(dateString) {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}년 ${month}월 ${day}일 ${hours}:${minutes}`;
}
return (
<DashboardLayout>
<DashboardNavbar />
<List>
{list && list.map((data, idx) => (
<ListItem key={idx} onClick={() => handleChatClick(data.cid)}>
<ListItemAvatar>
<Avatar
sx={{ bgcolor: 'red'[500] }}
aria-label="recipe"
src={`https://res.cloudinary.com/${process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}/image/upload/Cloudinary-React/dddyq4tkk7gorhfm3twm`}
/>
</ListItemAvatar>
{/* {data.cid} <br /> */}
{data.name} <br />
{data.lastMessage} <br />
{formatDate(data.statusTime)} <br />
</ListItem>
))}
</List>
</DashboardLayout>
)
}
chatting < Chattingtemp.js
import React, { useState, useRef, useEffect, useContext } from 'react';
import { Avatar, Box, Stack, TextField, InputAdornment } from "@mui/material";
import IconButton from '@mui/material/IconButton';
import EastIcon from '@mui/icons-material/East';
import { GetWithExpiry } from 'api/LocalStorage';
import './components/chat.css';
import DashboardLayout from 'examples/LayoutContainers/DashboardLayout';
import DashboardNavbar from 'examples/Navbars/DashboardNavbar';
import { UserContext } from 'api/LocalStorage';
import { useLocation, useNavigate } from 'react-router-dom';
import { useWebSocket } from 'api/webSocketContext';
import { getDmList } from 'api/axiosGet';
import { getChat } from 'api/axiosGet';
import { getUserNickEmail } from 'api/axiosGet';
import { insertChat } from 'api/axiosPost';
export default function ChatTemp() {
const navigate = useNavigate();
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState('');
const messageEndRef = useRef(null);
const inputFieldHeight = 65; // 입력 필드의 높이를 고정값
const { activeUser } = useContext(UserContext);
const { state } = useLocation() || {};
const { uid1, uid2 } = state;
const { stompClient } = useWebSocket();
const [count, setCount] = useState(20);
const [list, setList] = useState([]);
const [chatroom, setChatroom] = useState(null);
const profile = GetWithExpiry("profile");
const [user, setUser] = useState(null);
const [name, setName] = useState('');
useEffect(() => {
const user2 = getUserNickEmail(uid2);
setUser(user2);
setName(user2.nickname + ' 채팅방');
}, [])
const handleMessageSend = () => {
if (inputMessage.trim() !== '' && stompClient && stompClient.connected) {
const cid = insertChat(name, uid1, uid2, inputMessage );
setInputMessage('');
if (cid >= 0) {
navigate("/chatting", {state: {cid: cid}});
}
}
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleMessageSend();
}
};
return (
<DashboardLayout>
<DashboardNavbar />
<Box
sx={{
top: '10%',
margin: '20px',
padding: '20px',
minHeight: '400px',
height: 'calc(100vh - 200px)',
width: '80%',
mx: 'auto',
overflowY: 'auto',
}}
>
<Stack sx={{ fontSize: 'xx-large', fontWeight: 'bold', mx: 'auto' }}>
<div style={{ color: 'rgb(88, 67, 135)' }}>
{/* <Avatar alt="User" src={`https://res.cloudinary.com/${process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}/image/upload/${profile}`} />
{email} */}
{name}와의 채팅방
<hr style={{ opacity: '0.4', marginTop: 20 }} />
</div>
</Stack>
{/* maxHeight를 사용 스크롤 활성 */}
<Stack sx={{ maxHeight: `calc(100vh - ${inputFieldHeight + 385}px)`, overflowY: 'auto', flexDirection: 'column-reverse' }}> {/* 메시지 영역의 최대 높이를 조정 */}
<br />
</Stack>
<Stack
sx={{
position: 'fixed',
bottom: '5px',
width: { xs: '60%', sm: '70%', md: '80%' },
}}
>
<TextField
sx={{
marginBottom: '1.5em',
height: `${inputFieldHeight}px`, // 입력 필드의 높이 설정
width: '70.5%',
}}
fullWidth
placeholder="메시지를 입력하세요..."
variant="outlined"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
InputProps={{
endAdornment: (
<InputAdornment position="end" >
<IconButton onClick={handleMessageSend}>
<EastIcon />
</IconButton>
</InputAdornment>
),
}}
/>
</Stack>
</Box>
</DashboardLayout>
);
}
chatting < components < chat.css
.message {
font-size: 1.1rem;
/* 글자 크기 설정*/
background-color: rgb(153, 95, 180);
/* 배경색 설정 */
border-radius: 50px;
/* 테두리를 둥글게 만듭니다. */
padding: 0dvh 1dvh 0dvh 1.2dvh;
/* 내용과 테두리 사이의 간격 설정 */
margin: 0 0 10.5px 5px;
/* 채팅 버블 간의 간격 설정 */
max-width: 100%;
/* 채팅 버블의 최대 너비 설정 */
max-height: 60%;
color: rgb(255, 255, 255);
}
.othermessage {
font-size: 1.1rem;
/* 글자 크기 설정*/
background-color: rgb(191, 169, 219);
/* 배경색 설정 */
border-radius: 50px;
/* 테두리를 둥글게 만듭니다. */
padding: 0dvh 1.2dvh 0dvh 1dvh;
/* 내용과 테두리 사이의 간격 설정 */
margin: 0 0 10.5px 5px;
/* 채팅 버블 간의 간격 설정 */
max-width: 100%;
/* 채팅 버블의 최대 너비 설정 */
max-height: 50px;
color: rgb(255, 255, 255);
}
chatting < components < ChattingModal.jsx
// 기본
import React, { useState } from 'react';
import { Box, Modal, Grid, ListItem, List, Divider, ListItemAvatar, Avatar, ListItemText, ListSubheader } from '@mui/material';
// 아이콘
import MessageIcon from '@mui/icons-material/Message';
import ClearIcon from '@mui/icons-material/Clear';
import ForumIcon from '@mui/icons-material/Forum';
// Components/css 연결
import '../notice.css';
import Chat from './Chat';
export default function ChattingModal() {
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<>
{/* Aside에 표시될 DM부분 표현 */}
<button onClick={handleOpen} className='asideStyle'>
<Grid container>
<Grid item xs={12} lg={6} sx={{ display: { xs: 'flex', lg: 'flex' }, pl: 3 }}>
<MessageIcon className='iconStyle' />
</Grid>
<Grid item xs={0} lg={6} sx={{ color:'rgb(58, 0, 85)', display: { xs: 'none', lg: 'flex' }, pr: 3, justifyContent: 'flex-end' }}>
DM
</Grid>
</Grid>
</button>
{/* DM 부분 Modal */}
<Modal open={open} onClose={handleClose} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description">
<Box className='styleBox2'>
{/* 메시지 목록 리스트 */}
<List sx={{ width: '20%', boxShadow: '0 0 6px rgba(0, 0, 0, 0.5)', padding: 3, justifyContent: 'center', alignItems: 'center', }}
subheader={
<>
<Grid item sx={{ display: { xs: 'none', lg: 'flex' }, justifyContent: 'center', alignItems: 'center', }} >
<ListSubheader component="div" id="nested-list-subheader" sx={{ fontSize: 25, }}>
메시지
</ListSubheader>
</Grid>
<Grid item sx={{ display: { xs: 'flex', lg: 'none' }, justifyContent: 'center', alignItems: 'center', }} >
<ListSubheader component="div" id="nested-list-subheader" sx={{ textAlign: 'center', fontSize: 25 }}>
<ForumIcon />
</ListSubheader>
</Grid>
</>
}>
<ListItem alignItems="flex-start" sx={{ marginBottom: '10%' }} >
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<Grid item sx={{ display: { xs: 'none', lg: 'flex' } }} >
<ListItemText
primary='곽주영'
secondary='2024-04-25'
/>
</Grid>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem alignItems="flex-start" sx={{ marginBottom: '10%' }}>
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<Grid item sx={{ display: { xs: 'none', lg: 'flex' } }} >
<ListItemText
primary='곽주영'
secondary='2024-04-25'
/>
</Grid>
</ListItem>
<Divider variant="inset" component="li" />
<ListItem alignItems="flex-start" sx={{ marginBottom: '10%' }}>
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<Grid item sx={{ display: { xs: 'none', lg: 'flex' } }} >
<ListItemText
primary='곽주영'
secondary='2024-04-25'
/>
</Grid>
</ListItem>
<Divider variant="inset" component="li" />
</List>
{/* Chatting을 구현한 Components 추가*/}
<Chat />
{/* 닫기 버튼 */}
<div className='board_div_style_2'>
<ClearIcon onClick={handleClose} sx={{ cursor: 'pointer', fontSize: '26px', backgroundColor: 'rgb(162, 152, 182)', borderRadius: '100%', margin: '3px' }} />
</div>
</Box>
</Modal >
</>
);
}
오늘 수정하는 부분은 팔로우UI 와 채팅 UI 다!
오늘 반드시 끝내고 간다!!
내일은 에디터에 완전 몰입하기!
그리고 전체 평가도 해야하고 주간보고서도 작성해야하기 때문에
&&&& 스토리보드 만들어야 하고 각 페이지마다...ㅎㅎㅎ;;;;
거기에 PPT 도 수정된 핵심기능들을 정리해서 다시 만들어야한다 ㅎㅎ
내 손목이여~!
수고를 부탁합니다!!!
그래도 오랜만에 코드랑 노니까 재밌다~ ㅋㅋㅋㅋ
몇 일동안 코드만지니까 말을 적게 해서 목도 좀 쉬는 기분 ㅎㅎ
가끔은 이럴 때도 필요한 것 같다 ^__________^ 브이~파이팅!
'React' 카테고리의 다른 글
2024.06.03 맘대로 해봐라! 3차 프로젝트 FlowNary SNS 31일차 (0) | 2024.06.03 |
---|---|
2024.05.31 주간보고서 미팅 - FlowNary SNS 30일차 (0) | 2024.05.31 |
20240527 맘대로 해봐라! 3차 프로젝트 FlowNary SNS 28일차 (0) | 2024.05.28 |
20240526 연속 주말 작업! (0) | 2024.05.27 |
20240525 주말작업! (0) | 2024.05.25 |