diff --git a/backend/src/swagger.json b/backend/src/swagger.json
index 6d4608b..bee23f0 100644
--- a/backend/src/swagger.json
+++ b/backend/src/swagger.json
@@ -1,7 +1,7 @@
{
"openapi": "3.0.0",
"info": {
- "title": "Moji",
+ "title": "Chatify",
"version": "1.0.0",
"description": ""
},
diff --git a/frontend/src/components/auth/ProtectedRoute.tsx b/frontend/src/components/auth/ProtectedRoute.tsx
index 19e28ee..1af6506 100644
--- a/frontend/src/components/auth/ProtectedRoute.tsx
+++ b/frontend/src/components/auth/ProtectedRoute.tsx
@@ -41,7 +41,7 @@ const ProtectedRoute = () => {
);
diff --git a/frontend/src/components/chat/layout/ChatWelcomeScreeen.tsx b/frontend/src/components/chat/layout/ChatWelcomeScreeen.tsx
new file mode 100644
index 0000000..12df44a
--- /dev/null
+++ b/frontend/src/components/chat/layout/ChatWelcomeScreeen.tsx
@@ -0,0 +1,25 @@
+import { SidebarInset } from "@/components/ui/sidebar";
+import ChatWindowHeader from "./component/ChatWindowHeader";
+
+const ChatWelcomeScreen = () => {
+ return (
+
+
+
+
+
+ 💬
+
+
+ Chào mừng bạn đến với Chatify!
+
+
+ Chọn một cuộc hội thoại để bắt đầu chat!
+
+
+
+
+ );
+};
+
+export default ChatWelcomeScreen;
\ No newline at end of file
diff --git a/frontend/src/components/chat/layout/ChatWindowLayout.tsx b/frontend/src/components/chat/layout/ChatWindowLayout.tsx
index 29d3950..196dbbb 100644
--- a/frontend/src/components/chat/layout/ChatWindowLayout.tsx
+++ b/frontend/src/components/chat/layout/ChatWindowLayout.tsx
@@ -1,6 +1,36 @@
+import { useChatStore } from "@/stores/useChatStore";
+import ChatWelcomeScreeen from "./ChatWelcomeScreeen";
+import ChatWindowSkeleton from "./ChatWindowSkeleton";
+import { SidebarInset } from "@/components/ui/sidebar";
+import ChatWindowHeader from "./component/ChatWindowHeader";
+import ChatWindowBody from "./component/ChatWindowBody";
+import MessageInput from "./component/MessageInput";
+
const ChatWindowLayout = () => {
+ const { activeConversationId, conversations, messageLoading: loading, messages } = useChatStore();
+ const selectedConvo = conversations.find((c) => c._id === activeConversationId) ?? null
+
+ if (!selectedConvo) {
+ return
+ }
+
+ if (loading) {
+ return
+ }
return (
-
ChatWindowLayout
+
+
+ {/* Header */}
+
+
+ {/* Body */}
+
+
+
+
+ {/* Footer */}
+
+
)
};
diff --git a/frontend/src/components/chat/layout/ChatWindowSkeleton.tsx b/frontend/src/components/chat/layout/ChatWindowSkeleton.tsx
new file mode 100644
index 0000000..6757db0
--- /dev/null
+++ b/frontend/src/components/chat/layout/ChatWindowSkeleton.tsx
@@ -0,0 +1,8 @@
+
+const ChatWindowSkeleton = () => {
+ return (
+ ChatWindowSkeleton
+ )
+}
+
+export default ChatWindowSkeleton
\ No newline at end of file
diff --git a/frontend/src/components/chat/layout/component/ChatWindowBody.tsx b/frontend/src/components/chat/layout/component/ChatWindowBody.tsx
new file mode 100644
index 0000000..9203412
--- /dev/null
+++ b/frontend/src/components/chat/layout/component/ChatWindowBody.tsx
@@ -0,0 +1,8 @@
+
+const ChatWindowBody = () => {
+ return (
+ ChatWindowBody
+ )
+}
+
+export default ChatWindowBody
\ No newline at end of file
diff --git a/frontend/src/components/chat/layout/component/ChatWindowHeader.tsx b/frontend/src/components/chat/layout/component/ChatWindowHeader.tsx
new file mode 100644
index 0000000..2aa6fa4
--- /dev/null
+++ b/frontend/src/components/chat/layout/component/ChatWindowHeader.tsx
@@ -0,0 +1,8 @@
+
+const ChatWindowHeader = () => {
+ return (
+ ChatWindowHeader
+ )
+}
+
+export default ChatWindowHeader
\ No newline at end of file
diff --git a/frontend/src/components/chat/layout/component/MessageInput.tsx b/frontend/src/components/chat/layout/component/MessageInput.tsx
new file mode 100644
index 0000000..dc75881
--- /dev/null
+++ b/frontend/src/components/chat/layout/component/MessageInput.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+const MessageInput = () => {
+ return (
+ MessageInput
+ )
+}
+
+export default MessageInput
\ No newline at end of file
diff --git a/frontend/src/components/chat/sidebar/DirectMessageCard.tsx b/frontend/src/components/chat/sidebar/DirectMessageCard.tsx
index 8087ad9..f81fdf6 100644
--- a/frontend/src/components/chat/sidebar/DirectMessageCard.tsx
+++ b/frontend/src/components/chat/sidebar/DirectMessageCard.tsx
@@ -9,7 +9,7 @@ import UnreadCountBadge from "../shared/UnreadCountBadge";
const DirectMessageCard = ({convo} : {convo:Conversation}) => {
const {user} = useAuthStore();
- const {activeConversationId , setActiveConversation , messages} = useChatStore();
+ const {activeConversationId , setActiveConversation , messages, fetchMessage} = useChatStore();
if(!user) return null;
@@ -22,7 +22,7 @@ const DirectMessageCard = ({convo} : {convo:Conversation}) => {
const handleSelectConversation = async (id:string) => {
setActiveConversation(id);
if(!messages[id]){
- //fetch message
+ await fetchMessage();
}
}
return (
diff --git a/frontend/src/components/chat/sidebar/GropuMessageCard.tsx b/frontend/src/components/chat/sidebar/GropuMessageCard.tsx
index ca4f5fd..17a0fd2 100644
--- a/frontend/src/components/chat/sidebar/GropuMessageCard.tsx
+++ b/frontend/src/components/chat/sidebar/GropuMessageCard.tsx
@@ -7,7 +7,7 @@ import GroupChatAvatar from './GroupChatAvatar';
const GropuMessageCard = ({ convo }: { convo: Conversation }) => {
const { user } = useAuthStore();
- const { activeConversationId, setActiveConversation, messages } = useChatStore();
+ const { activeConversationId, setActiveConversation, messages , fetchMessage} = useChatStore();
if (!user) return null;
@@ -17,7 +17,7 @@ const GropuMessageCard = ({ convo }: { convo: Conversation }) => {
const handleSelectConversation = async (id: string) => {
setActiveConversation(id);
if (!messages[id]) {
- //fetch message
+ fetchMessage();
}
}
return (
diff --git a/frontend/src/pages/ChatAppPage.tsx b/frontend/src/pages/ChatAppPage.tsx
index 5f90161..7c39076 100644
--- a/frontend/src/pages/ChatAppPage.tsx
+++ b/frontend/src/pages/ChatAppPage.tsx
@@ -5,11 +5,10 @@ const ChatAppPage = () => {
return (
-
-
-
-
-
+
+
+
+
);
diff --git a/frontend/src/services/chatService.ts b/frontend/src/services/chatService.ts
index 8ab1569..d172157 100644
--- a/frontend/src/services/chatService.ts
+++ b/frontend/src/services/chatService.ts
@@ -1,9 +1,21 @@
import api from "@/lib/axios";
import type { ConversationResponse, Message } from "@/types/chat";
+interface FetchMessageProps {
+ messages: Message[];
+ cursor?:string
+}
+
+const pageLimit = 50;
+
export const chatService = {
async fetchConversations() : Promise{
const res = await api.get("/conversations");
return res.data;
+ },
+
+ async fetchMessage(id:string, cursor:string) : Promise {
+ const res = await api.get(`/conversation/${id}/message?limit=${pageLimit}&cursor=${cursor}`)
+ return {messages: res.data.m.messages, cursor: res.data.nextCursor}
}
}
\ No newline at end of file
diff --git a/frontend/src/stores/useChatStore.tsx b/frontend/src/stores/useChatStore.tsx
index 051c493..3f3a1da 100644
--- a/frontend/src/stores/useChatStore.tsx
+++ b/frontend/src/stores/useChatStore.tsx
@@ -2,6 +2,7 @@ import { chatService } from "@/services/chatService"
import type { ChatState } from "@/types/store"
import { create } from "zustand"
import { persist } from "zustand/middleware"
+import { useAuthStore } from "./useAuthStore"
export const useChatStore = create()(
persist(
@@ -9,7 +10,8 @@ export const useChatStore = create()(
conversations: [],
messages: {},
activeConversationId: null,
- loading: false,
+ convoLoading: false,
+ messageLoading: false,
setActiveConversation: (id) => set({ activeConversationId: id }),
@@ -18,18 +20,64 @@ export const useChatStore = create()(
conversations: [],
messages: {},
activeConversationId: null,
- loading: false,
+ convoLoading: false,
+ messageLoading: false
})
},
fetchConversatons: async () => {
try {
- set({loading:true});
- const {conversations} = await chatService.fetchConversations();
- set({conversations,loading:false})
+ set({ convoLoading: true });
+ const { conversations } = await chatService.fetchConversations();
+ set({ conversations, convoLoading: false })
} catch (error) {
- console.error("Lỗi xảy ra khi fetchConversation:",error)
- set({loading:false})
+ console.error("Lỗi xảy ra khi fetchConversation:", error)
+ set({ convoLoading: false })
+ }
+ },
+
+ fetchMessage: async (conversationId) => {
+ const { activeConversationId, messages } = get();
+ const { user } = useAuthStore.getState();
+
+ const convoId = conversationId ?? activeConversationId;
+
+ if (!convoId) return;
+
+ const current = messages?.[convoId];
+ const nextCursor = current?.nextCursor === undefined ? "" : current?.nextCursor;
+
+ if (nextCursor === null) return
+
+ set({ messageLoading: true })
+
+ try {
+ const { messages: fetched, cursor } = await chatService.fetchMessage(convoId, nextCursor);
+ const processed = fetched.map((m) => ({
+ ...m,
+ isOwn:m.senderId === user?._id
+ }))
+
+ set( (state) => {
+ const prev = state.messages[convoId]?.items ?? [];
+ const merged = prev.length > 0 ? [...processed, ...prev] : processed;
+
+ return {
+ messages:{
+ ...state.messages,
+ [convoId] : {
+ items:merged,
+ hasMore:!!cursor,
+ nextCursor:cursor ?? null,
+ }
+ }
+ }
+ })
+
+ } catch (error) {
+ console.error("Loi xay ra khi fetch Message:",error);
+ } finally{
+ set({messageLoading:false})
}
}
}), {
diff --git a/frontend/src/types/store.ts b/frontend/src/types/store.ts
index e26e256..9bcee1d 100644
--- a/frontend/src/types/store.ts
+++ b/frontend/src/types/store.ts
@@ -36,8 +36,10 @@ export interface ChatState{
nextCursor?: string | null
}>;
activeConversationId: string | null;
- loading:boolean;
+ convoLoading:boolean;
+ messageLoading:boolean;
reset: () => void;
setActiveConversation: (id:string | null) => void;
- fetchConversatons: () => Promise
+ fetchConversatons: () => Promise;
+ fetchMessage:(conversationId?:string) => Promise
}
\ No newline at end of file