Skip to content
Snippets Groups Projects
Commit 6614b071 authored by yeonnnnjs's avatar yeonnnnjs
Browse files

feat : Add paging

parent f20e4809
No related branches found
No related tags found
No related merge requests found
Showing
with 153 additions and 91 deletions
...@@ -3,7 +3,6 @@ import env from '../Components/Common/dotenv'; ...@@ -3,7 +3,6 @@ import env from '../Components/Common/dotenv';
const API_User_URL = env.REACT_APP_API_BASE_URL + "/user"; const API_User_URL = env.REACT_APP_API_BASE_URL + "/user";
const accessToken = localStorage.getItem("accessToken"); const accessToken = localStorage.getItem("accessToken");
const UserService = { const UserService = {
getFriendList: async () => { getFriendList: async () => {
try { try {
......
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useSocket } from "../../Contexts/SocketContext"; import { useSocket } from "../../Contexts/SocketContext";
import chatService from "../../API/ChatService" import chatService from "../../API/ChatService";
import userService from "../../API/UserService" import userService from "../../API/UserService";
import Message from "./Message"; import Message from "./Message";
function ChatScreen({ roomId, userInfo }) { function ChatScreen({ roomId, userInfo }) {
...@@ -10,18 +10,30 @@ function ChatScreen({ roomId, userInfo }) { ...@@ -10,18 +10,30 @@ function ChatScreen({ roomId, userInfo }) {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [userId, setUserId] = useState(); const [userId, setUserId] = useState();
const [startId, setStartId] = useState(null); const [startId, setStartId] = useState(null);
const [isNearBottom, setIsNearBottom] = useState(true);
const [scrollPosition, setScrollPosition] = useState(null);
useEffect(() => { useEffect(() => {
fetchUserInfo(); fetchUserInfo();
fetchMesssages(); fetchMessages();
sendMessageWhenReady(socket, { type: "joinRoom", data: { roomId } }); sendMessageWhenReady(socket, { type: "joinRoom", data: { roomId } });
socket.addEventListener("message", handleSocketMessage); socket.addEventListener("message", handleSocketMessage);
return () => { return () => {
socket.removeEventListener("message", handleSocketMessage); socket.removeEventListener("message", handleSocketMessage);
}; };
}, []); }, []);
useEffect(() => {
if(scrollPosition == 0) {
fetchMessages();
window.scrollTo({
top: document.documentElement.scrollHeight-10,
});
}
}, [scrollPosition]);
function sendMessageWhenReady(client, message) { function sendMessageWhenReady(client, message) {
if (client.readyState === WebSocket.OPEN) { if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message)); client.send(JSON.stringify(message));
...@@ -30,10 +42,18 @@ function ChatScreen({ roomId, userInfo }) { ...@@ -30,10 +42,18 @@ function ChatScreen({ roomId, userInfo }) {
} }
} }
const fetchMesssages = async () => { const fetchMessages = async () => {
const response = await chatService.getMessages(roomId, startId); const response = await chatService.getMessages(roomId, startId);
setStartId(response.nextId); setStartId(response.nextId);
setMessages(response.messages); setMessages((prevMessages) => {
let newMessages = [];
if (startId) {
newMessages = response.messages.concat(prevMessages);
} else {
newMessages = response.messages;
}
return newMessages;
});
}; };
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
...@@ -41,10 +61,6 @@ function ChatScreen({ roomId, userInfo }) { ...@@ -41,10 +61,6 @@ function ChatScreen({ roomId, userInfo }) {
setUserId(response.id); setUserId(response.id);
}; };
// useEffect(() => {
// console.log(messages);
// }, [messages]);
const handleSocketMessage = (event) => { const handleSocketMessage = (event) => {
const receivedMessage = JSON.parse(event.data); const receivedMessage = JSON.parse(event.data);
...@@ -69,6 +85,7 @@ function ChatScreen({ roomId, userInfo }) { ...@@ -69,6 +85,7 @@ function ChatScreen({ roomId, userInfo }) {
data: { userName: userId, roomId, message }, data: { userName: userId, roomId, message },
}); });
setMessage(""); setMessage("");
handleScrollToBottom(true);
}; };
const getUserInfo = (id) => { const getUserInfo = (id) => {
...@@ -79,18 +96,57 @@ function ChatScreen({ roomId, userInfo }) { ...@@ -79,18 +96,57 @@ function ChatScreen({ roomId, userInfo }) {
} }
}; };
const handleScroll = () => {
setScrollPosition(window.scrollY);
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
const num = scrollTop + clientHeight >= scrollHeight - 100;
setIsNearBottom(num);
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
useEffect(() => {
handleNewMessage();
}, [messages]);
const handleScrollToBottom = () => {
window.scrollTo({
top: document.documentElement.scrollHeight,
});
};
const handleNewMessage = () => {
if(isNearBottom) {
window.scrollTo({
top: document.documentElement.scrollHeight,
});
}
};
return ( return (
<main className="main-screen main-chat"> <main>
<div className="main-screen main-chat">
{!isNearBottom && (
<button className="scroll-to-bottom-btn" onClick={handleScrollToBottom}>
이동
</button>
)}
{Array.isArray(messages) && {Array.isArray(messages) &&
messages.map((msg) => ( messages.map((msg) => (
<Message <Message
key={msg.id}
message={msg} message={msg}
userId={userId} userId={userId}
senderInfo={getUserInfo(msg.sender)} senderInfo={getUserInfo(msg.sender)}
/> />
))} ))}
<div className="reply"> </div>
<div className="reply__column"> <footer className="reply">
<input <input
type="text" type="text"
placeholder="Write a message..." placeholder="Write a message..."
...@@ -102,8 +158,7 @@ function ChatScreen({ roomId, userInfo }) { ...@@ -102,8 +158,7 @@ function ChatScreen({ roomId, userInfo }) {
} }
}} }}
/> />
</div> </footer>
</div>
</main> </main>
); );
} }
......
import React from "react"; import React from "react";
import "../../css/components/message.css"; import "../../css/components/message.css";
import env from "../Common/dotenv";
function Message({ message, userId, senderInfo }) { function Message({ message, userId, senderInfo }) {
const isOwnMessage = message.sender === userId; const isOwnMessage = message.sender === userId;
...@@ -8,20 +9,20 @@ function Message({ message, userId, senderInfo }) { ...@@ -8,20 +9,20 @@ function Message({ message, userId, senderInfo }) {
}`; }`;
return ( return (
<div className={messageRowClass}> <div className={messageRowClass}>
{!isOwnMessage && ( {!isOwnMessage ? (
<img <img
src={ src={
senderInfo.profileImage == null || senderInfo.profileImage == "" senderInfo.profileImage == null || senderInfo.profileImage == ""
? "http://localhost:8040/uploads/default-profile.png" ? `${env.REACT_APP_IMAGE_BASE_URL}/uploads/default-profile.png`
: senderInfo.profileImage : senderInfo.profileImage
} }
alt={senderInfo.name} alt={senderInfo.name}
/> />
)} ):""}
<div className="message-row__content"> <div className="message-row__content">
{!isOwnMessage && ( {!isOwnMessage ? (
<span className="message__author">{senderInfo.name}</span> <span className="message__author">{senderInfo.name}</span>
)} ):""}
<div className="message__info"> <div className="message__info">
<span className="message__bubble">{message.content}</span> <span className="message__bubble">{message.content}</span>
<span className="message__time">{message.timestamp}</span> <span className="message__time">{message.timestamp}</span>
......
import env from "../Common/dotenv"
function UserMessage({ function UserMessage({
avatar, avatar,
name, name,
...@@ -14,7 +16,7 @@ function UserMessage({ ...@@ -14,7 +16,7 @@ function UserMessage({
<img <img
src={ src={
avatar == null || avatar == "" avatar == null || avatar == ""
? "http://localhost:8040/uploads/default-profile.png" ? `${env.REACT_APP_IMAGE_BASE_URL}/uploads/default-profile.png`
: avatar : avatar
} }
className="user-component__avatar user-component__avatar--xl" className="user-component__avatar user-component__avatar--xl"
......
...@@ -3,7 +3,6 @@ import { useState } from "react"; ...@@ -3,7 +3,6 @@ import { useState } from "react";
import { EmailButton } from '../Auth/Signin/EmailButton.js'; import { EmailButton } from '../Auth/Signin/EmailButton.js';
import authService from "../../API/AuthService.js"; import authService from "../../API/AuthService.js";
function CommonForm({ onSubmit, buttonText, fields, showVerificationButton, setWarningMessage }) { function CommonForm({ onSubmit, buttonText, fields, showVerificationButton, setWarningMessage }) {
const [emailMessage, setEmailMessage] = useState("전송"); const [emailMessage, setEmailMessage] = useState("전송");
const [emailValue, setEmailValue] = useState(""); const [emailValue, setEmailValue] = useState("");
......
export const loadEnv = { export const loadEnv = {
REACT_APP_API_BASE_URL: "http://localhost:8080/api", // include /api prefix REACT_APP_API_BASE_URL: "http://localhost:8080/api", // include /api prefix
REACT_APP_IMAGE_BASE_URL: "http://localhost:8040" REACT_APP_IMAGE_BASE_URL: "http://localhost:8040",
REACT_APP_SOCKET_BASE_URL: "ws://localhost:3001"
}; };
export default loadEnv; export default loadEnv;
\ No newline at end of file
...@@ -4,6 +4,7 @@ import UserService from "../../API/UserService"; ...@@ -4,6 +4,7 @@ import UserService from "../../API/UserService";
import ChatService from "../../API/ChatService"; import ChatService from "../../API/ChatService";
import AuthService from "../../API/AuthService"; import AuthService from "../../API/AuthService";
import Comment from "../../assets/speech-bubble.png"; import Comment from "../../assets/speech-bubble.png";
import env from "../Common/dotenv"
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
...@@ -86,7 +87,7 @@ function FriendsList() { ...@@ -86,7 +87,7 @@ function FriendsList() {
key={friend.id} key={friend.id}
avatar={ avatar={
friend.profileImage == null || friend.profileImage == "" friend.profileImage == null || friend.profileImage == ""
? "http://localhost:8040/uploads/default-profile.png" ? `${env.REACT_APP_IMAGE_BASE_URL}/uploads/default-profile.png`
: friend.profileImage : friend.profileImage
} }
name={friend.name} name={friend.name}
......
import { useRef, useEffect, useState } from "react"; import { useRef, useEffect, useState } from "react";
import UserService from "../../API/UserService"; import UserService from "../../API/UserService";
const baseURL = "http://localhost:8040"; import env from "../Common/dotenv"
function ProfileUpdate({ onClose, origin_image, origin_comment, name }) { function ProfileUpdate({ onClose, origin_image, origin_comment, name }) {
const [image, setImage] = useState(null); const [image, setImage] = useState(null);
...@@ -20,7 +20,7 @@ function ProfileUpdate({ onClose, origin_image, origin_comment, name }) { ...@@ -20,7 +20,7 @@ function ProfileUpdate({ onClose, origin_image, origin_comment, name }) {
formData.append("image", file); formData.append("image", file);
try { try {
const response = await fetch(`${baseURL}/api/upload`, { const response = await fetch(`${env.REACT_APP_IMAGE_BASE_URL}/api/upload`, {
method: "POST", method: "POST",
body: formData, body: formData,
}); });
...@@ -30,7 +30,7 @@ function ProfileUpdate({ onClose, origin_image, origin_comment, name }) { ...@@ -30,7 +30,7 @@ function ProfileUpdate({ onClose, origin_image, origin_comment, name }) {
} }
const data = await response.json(); const data = await response.json();
setImageUrl(`${baseURL}${data.imageUrl}`); // 서버로부터 받은 이미지 URL setImageUrl(`${env.REACT_APP_IMAGE_BASE_URL}${data.imageUrl}`); // 서버로부터 받은 이미지 URL
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
......
import React from "react"; import React from "react";
import env from "../Common/dotenv"
import "../../css/components/userProfile.css"; import "../../css/components/userProfile.css";
function UserComponent({ avatar, name, subtitle, additionalContent }) { function UserComponent({ avatar, name, subtitle, additionalContent }) {
...@@ -14,7 +15,7 @@ function UserComponent({ avatar, name, subtitle, additionalContent }) { ...@@ -14,7 +15,7 @@ function UserComponent({ avatar, name, subtitle, additionalContent }) {
<img <img
src={ src={
!avatar !avatar
? "http://localhost:8040/uploads/default-profile.png" ? `${env.REACT_APP_IMAGE_BASE_URL}/uploads/default-profile.png`
: avatar : avatar
} }
className="user-component__avatar" className="user-component__avatar"
......
...@@ -5,7 +5,7 @@ import AuthService from "../../API/AuthService"; ...@@ -5,7 +5,7 @@ import AuthService from "../../API/AuthService";
import Add from "../../assets/add-friend-icon.png"; import Add from "../../assets/add-friend-icon.png";
import { useContext, useEffect, useState } from "react"; import { useEffect, useState } from "react";
function UserList() { function UserList() {
const [showAddedFriends, setShowAddedFriends] = useState(false); const [showAddedFriends, setShowAddedFriends] = useState(false);
......
...@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react"; ...@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import UserService from "../../API/UserService"; import UserService from "../../API/UserService";
import AuthService from "../../API/AuthService"; import AuthService from "../../API/AuthService";
import ProfileComponent from "./ProfileComponent"; import ProfileComponent from "./ProfileComponent";
import env from "../Common/dotenv"
function UserProfile() { function UserProfile() {
const accessToken = localStorage.getItem("accessToken"); const accessToken = localStorage.getItem("accessToken");
...@@ -17,7 +18,7 @@ function UserProfile() { ...@@ -17,7 +18,7 @@ function UserProfile() {
if (response) { if (response) {
setSubtitle(response.comment); setSubtitle(response.comment);
if (response.profileImage == null || response.profileImage == "") { if (response.profileImage == null || response.profileImage == "") {
setUserProfile("http://localhost:8040/uploads/default-profile.png"); setUserProfile(`${env.REACT_APP_IMAGE_BASE_URL}/uploads/default-profile.png`);
} else { } else {
setUserProfile(response.profileImage); setUserProfile(response.profileImage);
} }
......
import React, { createContext, useContext, useEffect } from "react"; import React, { createContext, useContext, useEffect } from "react";
import env from "../Components/Common/dotenv"
const SocketContext = createContext(); const SocketContext = createContext();
const socket = new WebSocket("ws://localhost:3001"); const socket = new WebSocket(`${env.REACT_APP_SOCKET_BASE_URL}`);
export function useSocket() { export function useSocket() {
return useContext(SocketContext); return useContext(SocketContext);
......
...@@ -19,7 +19,7 @@ function Chat() { ...@@ -19,7 +19,7 @@ function Chat() {
return ( return (
<WebSocketProvider> <WebSocketProvider>
<div id="chat-screen"> <div>
<ChatHeader roomId={roomId} /> <ChatHeader roomId={roomId} />
{userInfo ? <ChatScreen roomId={roomId} userInfo={userInfo} /> : ""} {userInfo ? <ChatScreen roomId={roomId} userInfo={userInfo} /> : ""}
</div> </div>
......
...@@ -107,6 +107,7 @@ ...@@ -107,6 +107,7 @@
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 3%;
background-color: white; background-color: white;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
......
.alt-header { .alt-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3%;
z-index: 100;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 1rem; padding: 2vh;
background-color: #0054a6; /* 아주대학교 색상인 파란색으로 변경 */ background-color: #0054a6; /* 아주대학교 색상인 파란색으로 변경 */
color: #fff; color: #fff;
} }
......
.main-screen.main-chat { .main-screen.main-chat {
/* background-color: #f9f9f9; */ /* background-color: #f9f9f9; */
padding: 20px; padding-left: 3vw;
padding-right: 3vw;
overflow-y: auto; overflow-y: auto;
margin-top: 7vh;
margin-bottom: 7vh;
} }
.message { .message {
margin-bottom: 20px; margin-bottom: 20px;
padding: 10px; padding: 10px;
...@@ -16,15 +20,6 @@ ...@@ -16,15 +20,6 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
.reply {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
background-color: #fff;
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);
}
.reply__column { .reply__column {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -97,10 +92,29 @@ ...@@ -97,10 +92,29 @@
background-color: #fee500; background-color: #fee500;
} }
.scroll-to-bottom-btn {
position: fixed;
bottom: 5%;
left: 50%;
transform: translateX(-50%);
background-color: #0054a6;
color: #fff;
border: none;
border-radius: 25%;
padding: 1%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
margin-bottom: 1%;
}
.reply { .reply {
/* display: flex; */ display: flex;
justify-content: space-between; justify-content: start;
padding: 10px; padding: 1vh;
height: 3vh;
background-color: #fff; background-color: #fff;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
......
...@@ -6,26 +6,6 @@ body, h1, p, input, button, span { ...@@ -6,26 +6,6 @@ body, h1, p, input, button, span {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.status-bar {
background-color: #0054a6;
color: white;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.status-bar__column span {
display: flex;
align-items: center;
}
.status-bar__column span:not(:last-child) {
margin-right: 10px;
}
input::placeholder { input::placeholder {
color: #bbb; color: #bbb;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment