Skip to content

fe: danh sach cuoc tro chuyen theo nhom, ca nhan - #5

Merged
Hieukobtcode merged 1 commit into
mainfrom
sidebar
Aug 7, 2026
Merged

fe: danh sach cuoc tro chuyen theo nhom, ca nhan#5
Hieukobtcode merged 1 commit into
mainfrom
sidebar

Conversation

@Hieukobtcode

@Hieukobtcode Hieukobtcode commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added chat conversation lists with direct and group chat cards.
    • Added avatars, online status indicators, unread-count badges, timestamps, and group participant displays.
    • Added chat creation, group creation, and friend actions to the sidebar.
    • Added conversation loading and persistent chat state.
    • Added persistent dark-mode settings with live theme switching.
  • UI Improvements
    • Enhanced logout styling and sidebar user menu.
    • Added localized message and activity time formatting.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The frontend adds typed chat and user contracts, persisted authentication, chat, and theme stores, conversation loading through /conversations, reusable chat UI components, and authenticated sidebar integration.

Chat frontend

Layer / File(s) Summary
Chat contracts and persisted state
frontend/src/types/*, frontend/src/services/chatService.ts, frontend/src/stores/*
Adds chat data contracts, persisted stores, authentication cleanup, theme state, and conversation fetching.
Reusable chat components and formatting
frontend/src/components/chat/*, frontend/src/lib/utils.ts, frontend/src/pages/ChatAppPage.tsx, frontend/vite.config.ts
Adds shared chat cards, avatars, badges, date formatters, layout and modal components, and updates the chat import path and alias configuration.
Conversation sidebar integration
frontend/src/components/sidebar/*, frontend/src/components/auth/Logout.tsx, frontend/src/components/ui/button.tsx, frontend/src/App.tsx
Adds direct and group conversation lists, connects sidebar authentication and theme controls, updates logout styling, and synchronizes the document theme.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppSidebar
  participant useChatStore
  participant chatService
  AppSidebar->>useChatStore: request conversations
  useChatStore->>chatService: fetchConversations()
  chatService->>useChatStore: return ConversationResponse
  useChatStore->>AppSidebar: render conversation lists
Loading

Possibly related PRs

  • Hieukobtcode/Chatify#2: Shares authentication integration across App.tsx, useAuthStore, and user-facing components.
  • Hieukobtcode/Chatify#3: Provides related friend and conversation backend contracts used by the chat frontend.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the added group and direct conversation lists, which are central changes in the pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sidebar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Hieukobtcode Hieukobtcode changed the title fe: danh sach cuoc tro chuyen them nhom, ca nhan fe: danh sach cuoc tro chuyen theo nhom, ca nhan Aug 7, 2026
@Hieukobtcode
Hieukobtcode merged commit df1d1cc into main Aug 7, 2026
1 check was pending

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/chat/shared/ChatCard.tsx`:
- Around line 17-20: The conversation Card in ChatCard should be
keyboard-accessible while preserving onSelect(convoId) behavior. Replace it with
a native button where appropriate, or add button semantics, focusability, and
Enter/Space keyboard handling to the Card; ensure keyboard activation triggers
the same selection as clicking.
- Around line 17-18: Add the `group` utility class to the `Card` className in
`ChatCard` so the existing `MoreHorizontal` `group-hover:opacity-100` behavior
is anchored to its direct ancestor, preserving the other classes unchanged.

In `@frontend/src/components/chat/sidebar/DirectMessageCard.tsx`:
- Around line 22-27: Update handleSelectConversation in
frontend/src/components/chat/sidebar/DirectMessageCard.tsx at lines 22-27 to
call the shared store action that fetches and caches messages for id when
messages[id] is absent. Apply the same shared action in
frontend/src/components/chat/sidebar/GropuMessageCard.tsx at lines 17-22 for
group conversation selections, while preserving the existing active-conversation
updates.
- Around line 47-49: Update the unreadCounts conditional class in
DirectMessageCard so the read-message branch uses the valid muted foreground
Tailwind utility instead of the misspelled class, while preserving the existing
unread styling.

In `@frontend/src/components/chat/sidebar/GroupChatAvatar.tsx`:
- Around line 15-24: Update the UserAvatar element inside the participant loop
to use member._id as its React key instead of the constant key={1}, preserving
stable identity for each rendered member.
- Line 27: Update the className on the GroupChatAvatar container to use the
Tailwind v4 direct-child data-slot selector for elements with
data-slot="avatar", ensuring ring-2 is applied only to those direct children
while preserving the existing layout and ring-background classes.

In `@frontend/src/components/sidebar/app-sidebar.tsx`:
- Around line 71-85: Update the SidebarGroupAction instances for “Tạo nhóm” and
“Kết bạn” in app-sidebar to invoke the corresponding NewGroupChatModal and
AddFriendModal workflows when clicked. Add the required trigger or
action-handler wiring inside each modal while preserving their existing content
and sidebar layout.
- Around line 37-50: Replace the SidebarMenuButton wrapping the Chatify header
and theme controls with a non-interactive container, preserving the existing
layout and styling. Keep Switch, identified by checked={isDark} and
onCheckedChange={toggleTheme}, as the only interactive control in this header.

In `@frontend/src/components/sidebar/nav-user.tsx`:
- Around line 90-91: Update the logout action around DropdownMenuItem and Logout
so it renders only one interactive control. Move the sign-out handler onto
DropdownMenuItem, or configure Logout to render non-button content in this
context, while preserving the existing logout behavior and menu keyboard
navigation.

In `@frontend/src/lib/utils.ts`:
- Around line 8-19: Update formatOnlineTime to clamp the elapsed difference at
zero before calculating diffMins, diffHours, diffDays, diffMonths, and
diffYears, so future timestamps format as 0m rather than negative relative
values.

In `@frontend/src/stores/useAuthStore.ts`:
- Around line 17-20: Replace broad localStorage.clear() calls in useAuthStore.ts
at lines 17-20 and 45-50 with scoped removal of only auth-storage and
chat-storage. Preserve the existing in-memory reset behavior in clearState and
the session-start flow, while leaving unrelated persisted state such as
theme-storage intact.

In `@frontend/src/stores/useChatStore.tsx`:
- Around line 25-33: Update fetchConversatons and reset so an in-flight
conversation request cannot repopulate state after logout or account switching:
either abort the active request during reset or track a session generation and
ignore responses from earlier generations before applying conversations.
Preserve loading cleanup while ensuring stale responses never write the previous
session’s data.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c0a733d2-e621-4a44-b76e-6f315e50c438

📥 Commits

Reviewing files that changed from the base of the PR and between 70aee25 and d03f193.

📒 Files selected for processing (28)
  • frontend/src/App.tsx
  • frontend/src/components/auth/Logout.tsx
  • frontend/src/components/chat/layout/ChatWindowLayout.tsx
  • frontend/src/components/chat/modals/AddFriendModal.tsx
  • frontend/src/components/chat/modals/CreateNewChat.tsx
  • frontend/src/components/chat/modals/NewGroupChatModal.tsx
  • frontend/src/components/chat/shared/ChatCard.tsx
  • frontend/src/components/chat/shared/StatusBadge.tsx
  • frontend/src/components/chat/shared/UnreadCountBadge.tsx
  • frontend/src/components/chat/shared/UserAvatar.tsx
  • frontend/src/components/chat/sidebar/DirectMesageList.tsx
  • frontend/src/components/chat/sidebar/DirectMessageCard.tsx
  • frontend/src/components/chat/sidebar/GropuMessageCard.tsx
  • frontend/src/components/chat/sidebar/GroupChatAvatar.tsx
  • frontend/src/components/chat/sidebar/GroupChatList.tsx
  • frontend/src/components/sidebar/app-sidebar.tsx
  • frontend/src/components/sidebar/nav-user.tsx
  • frontend/src/components/ui/button.tsx
  • frontend/src/lib/utils.ts
  • frontend/src/pages/ChatAppPage.tsx
  • frontend/src/services/chatService.ts
  • frontend/src/stores/useAuthStore.ts
  • frontend/src/stores/useChatStore.tsx
  • frontend/src/stores/useThemeStore.tsx
  • frontend/src/types/chat.ts
  • frontend/src/types/store.ts
  • frontend/src/types/user.ts
  • frontend/vite.config.ts

Comment on lines +17 to +18
<Card key={convoId}
className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching ChatCard:"
fd -a 'ChatCard\.tsx$' . || true

file="frontend/src/components/chat/shared/ChatCard.tsx"
if [ -f "$file" ]; then
  echo "---- outline ----"
  ast-grep outline "$file" || true
  echo "---- relevant contents ----"
  wc -l "$file"
  cat -n "$ file" || true
fi

echo "---- all ChatCard files ----"
while IFS= read -r f; do
  echo "--- $f"
  rg -n "group-hover|group-|class.*MoreHorizontal|MoreHorizontal|className" "$f" || true
done < <(fd 'ChatCard\.tsx$' .)

Repository: Hieukobtcode/Chatify

Length of output: 769


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="frontend/src/components/chat/shared/ChatCard.tsx"
echo "---- contents ----"
cat -n "$file"

echo "---- group/MoreHorizontal references in matched file ----"
rg -n "group|group-hover|MoreHorizontal|opacity-100" "$file" || true

Repository: Hieukobtcode/Chatify

Length of output: 2552


Add group to Card.

group-hover:opacity-100 in MoreHorizontal needs a direct ancestor with the group class. Add group to the Card className so the hover can make the icon visible.

Proposed fix
- className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
+ className={cn("group border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Card key={convoId}
className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
<Card key={convoId}
className={cn("group border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/chat/shared/ChatCard.tsx` around lines 17 - 18, Add
the `group` utility class to the `Card` className in `ChatCard` so the existing
`MoreHorizontal` `group-hover:opacity-100` behavior is anchored to its direct
ancestor, preserving the other classes unchanged.

Comment on lines +17 to +20
<Card key={convoId}
className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
onClick={() => onSelect(convoId)}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add keyboard activation for conversation selection.

Card is not keyboard-accessible. Keyboard users cannot invoke onSelect.

Use a native button, or add button semantics, focusability, and Enter/Space key handling.

Proposed fix
 <Card key={convoId}
+    role="button"
+    tabIndex={0}
     className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
     onClick={() => onSelect(convoId)}
+    onKeyDown={(event) => {
+        if (event.key === "Enter" || event.key === " ") {
+            event.preventDefault();
+            onSelect(convoId);
+        }
+    }}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Card key={convoId}
className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
onClick={() => onSelect(convoId)}
>
<Card key={convoId}
role="button"
tabIndex={0}
className={cn("border-none p-3 cursor-pointer transaction-smooth glass hover:bg-muted/30", isActive && "ring-2 ring-primary/50 bg-gradient-to-tr from-primary-glow/10 to-primary-foreground")}
onClick={() => onSelect(convoId)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
onSelect(convoId);
}
}}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/chat/shared/ChatCard.tsx` around lines 17 - 20, The
conversation Card in ChatCard should be keyboard-accessible while preserving
onSelect(convoId) behavior. Replace it with a native button where appropriate,
or add button semantics, focusability, and Enter/Space keyboard handling to the
Card; ensure keyboard activation triggers the same selection as clicking.

Comment on lines +22 to +27
const handleSelectConversation = async (id:string) => {
setActiveConversation(id);
if(!messages[id]){
//fetch message
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Load messages when a conversation becomes active.

Both handlers only set activeConversationId. messages starts empty, and the sign-in flow fetches conversations only. The empty branches therefore leave newly selected conversations without message data.

  • frontend/src/components/chat/sidebar/DirectMessageCard.tsx#L22-L27: call a shared store action that fetches and caches messages for id.
  • frontend/src/components/chat/sidebar/GropuMessageCard.tsx#L17-L22: call the same shared action for group conversations.
📍 Affects 2 files
  • frontend/src/components/chat/sidebar/DirectMessageCard.tsx#L22-L27 (this comment)
  • frontend/src/components/chat/sidebar/GropuMessageCard.tsx#L17-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/chat/sidebar/DirectMessageCard.tsx` around lines 22 -
27, Update handleSelectConversation in
frontend/src/components/chat/sidebar/DirectMessageCard.tsx at lines 22-27 to
call the shared store action that fetches and caches messages for id when
messages[id] is absent. Apply the same shared action in
frontend/src/components/chat/sidebar/GropuMessageCard.tsx at lines 17-22 for
group conversation selections, while preserving the existing active-conversation
updates.

Comment on lines +47 to +49
<p className={cn("text-sm truncate", unreadCounts > 0 ? "font-medium text-foreground" : "text-muted=foreground")}>
{lastMessage}
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'DirectMessageCard\.tsx$' . || true

echo "== inspect relevant file/excerpts =="
file="$(fd 'DirectMessageCard\.tsx$' . | head -n 1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '1,120p' "$file" | cat -n
fi

echo "== search for text-muted-foreground usage =="
rg -n 'text-muted(=|-?)foreground|className=\{cn\(' "$file" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: Hieukobtcode/Chatify

Length of output: 17962


Fix the muted-text utility.

text-muted=foreground is not a valid Tailwind utility, so the unread message subtitle is not styled with the muted foreground color.

Proposed fix
- <p className={cn("text-sm truncate", unreadCounts > 0 ? "font-medium text-foreground" : "text-muted=foreground")}>
+ <p className={cn("text-sm truncate", unreadCounts > 0 ? "font-medium text-foreground" : "text-muted-foreground")}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p className={cn("text-sm truncate", unreadCounts > 0 ? "font-medium text-foreground" : "text-muted=foreground")}>
{lastMessage}
</p>
<p className={cn("text-sm truncate", unreadCounts > 0 ? "font-medium text-foreground" : "text-muted-foreground")}>
{lastMessage}
</p>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/chat/sidebar/DirectMessageCard.tsx` around lines 47 -
49, Update the unreadCounts conditional class in DirectMessageCard so the
read-message branch uses the valid muted foreground Tailwind utility instead of
the misspelled class, while preserving the existing unread styling.

Comment on lines +15 to +24
for (let i = 0; i < limit; i++) {
const member = participants[i];
avatars.push(
<UserAvatar
key={1}
type={type}
name={member.displayName}
avatarUrl={member.avatarUrl ?? undefined}
/>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file:"
fd -a 'GroupChatAvatar\.tsx$' . || true

echo
echo "Inspect target file:"
file=$(fd 'GroupChatAvatar\.tsx$' . | head -n1)
if [ -n "${file:-}" ]; then
  wc -l "$file"
  cat -n "$file"
fi

echo
echo "Search Participant type and _id usages:"
rg -n "interface\s+Participant|type\s+Participant|_id|displayName|avatarUrl|UserAvatar" frontend/src -S || true

Repository: Hieukobtcode/Chatify

Length of output: 6370


🌐 Web query:

React documentation keys in lists keys should be unique string or number

💡 Result:

In React, the key attribute used for list items must be either a string or a number [1][2][3]. This prop is a special attribute that provides a unique identifier for items within an array, allowing React to efficiently track which elements have changed, been added, or been removed during re-renders [1][2][3]. Key requirements and best practices: 1. Uniqueness: Keys must be unique among their immediate siblings [1][2][4]. They do not need to be globally unique; it is acceptable to reuse the same key for elements in different arrays [1][2][5]. 2. Stability: Keys should be stable and predictable [6][4]. Avoid generating keys during the render process (e.g., using Math.random), as this will cause items to be unnecessarily recreated, leading to potential performance degradation and loss of component state [6][5][7]. 3. Data-driven: The best practice is to use a unique identifier from your data (such as a database ID) as the key [1][2][3]. 4. Avoiding Index Keys: While you can use the array index as a key as a last resort, it is generally discouraged if the list order can change, items can be inserted or removed, or the items have internal state [6][7][4]. Using an unstable index as a key in these scenarios can lead to incorrect component behavior or performance issues [6][4]. Note that the key prop is not passed to the component itself; it serves only as a hint for React's reconciliation process [2][5]. If your component requires the ID value, you should pass it explicitly as a separate prop [2][5].

Citations:


Use member._id for the React key.

Each avatar currently has key={1}, so React cannot identify changed/removed/inserted members by a stable identity. Use member._id instead.

Proposed fix
-                key={1}
+                key={member._id}

[low_effort and_high_reward]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (let i = 0; i < limit; i++) {
const member = participants[i];
avatars.push(
<UserAvatar
key={1}
type={type}
name={member.displayName}
avatarUrl={member.avatarUrl ?? undefined}
/>
);
for (let i = 0; i < limit; i++) {
const member = participants[i];
avatars.push(
<UserAvatar
key={member._id}
type={type}
name={member.displayName}
avatarUrl={member.avatarUrl ?? undefined}
/>
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/chat/sidebar/GroupChatAvatar.tsx` around lines 15 -
24, Update the UserAvatar element inside the participant loop to use member._id
as its React key instead of the constant key={1}, preserving stable identity for
each rendered member.

Comment on lines +71 to +85
<SidebarGroupAction title="Tạo nhóm" className="cursor-pointer">
<NewGroupChatModal />
</SidebarGroupAction>
<SidebarGroupContent>
<GroupChatList />
</SidebarGroupContent>
</SidebarGroup>

{/* Dirrect Message */}
<SidebarGroup>
<SidebarGroupLabel className="uppercase">
Bạn bè
</SidebarGroupLabel>
<SidebarGroupAction title="Kết bạn" className="cursor-pointer">
<AddFriendModal />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Implement the advertised group and friend actions.

NewGroupChatModal and AddFriendModal currently render static text only. Neither SidebarGroupAction has an action handler. Clicking “Tạo nhóm” or “Kết bạn” does not open a modal or start a workflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/sidebar/app-sidebar.tsx` around lines 71 - 85, Update
the SidebarGroupAction instances for “Tạo nhóm” and “Kết bạn” in app-sidebar to
invoke the corresponding NewGroupChatModal and AddFriendModal workflows when
clicked. Add the required trigger or action-handler wiring inside each modal
while preserving their existing content and sidebar layout.

Comment on lines +90 to +91
<DropdownMenuItem className="cursor-pointer" variant="destructive" >
<Logout/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use one interactive control for logout.

DropdownMenuItem is already the menu action. Logout renders another button inside it. This creates two focus targets for one action and disrupts menu keyboard navigation. Move the sign-out handler to DropdownMenuItem, or make Logout render non-button content for this use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/sidebar/nav-user.tsx` around lines 90 - 91, Update
the logout action around DropdownMenuItem and Logout so it renders only one
interactive control. Move the sign-out handler onto DropdownMenuItem, or
configure Logout to render non-button content in this context, while preserving
the existing logout behavior and menu keyboard navigation.

Comment thread frontend/src/lib/utils.ts
Comment on lines +8 to +19
export const formatOnlineTime = (date: Date) => {
const now = new Date();
const diffMs = now.getTime() - date.getTime();

const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const diffMonths = Math.floor(diffDays / 30);
const diffYears = Math.floor(diffDays / 365);

if (diffMins < 60) {
return `${diffMins}m`; // 5m, 45m

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp future timestamps before formatting.

Client and server clock skew can make date later than now. The current code then renders invalid relative values such as -1m.

Proposed fix
-  const diffMs = now.getTime() - date.getTime();
+  const diffMs = Math.max(0, now.getTime() - date.getTime());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const formatOnlineTime = (date: Date) => {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const diffMonths = Math.floor(diffDays / 30);
const diffYears = Math.floor(diffDays / 365);
if (diffMins < 60) {
return `${diffMins}m`; // 5m, 45m
export const formatOnlineTime = (date: Date) => {
const now = new Date();
const diffMs = Math.max(0, now.getTime() - date.getTime());
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const diffMonths = Math.floor(diffDays / 30);
const diffYears = Math.floor(diffDays / 365);
if (diffMins < 60) {
return `${diffMins}m`; // 5m, 45m
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/utils.ts` around lines 8 - 19, Update formatOnlineTime to
clamp the elapsed difference at zero before calculating diffMins, diffHours,
diffDays, diffMonths, and diffYears, so future timestamps format as 0m rather
than negative relative values.

Comment on lines +17 to +20
clearState: () => {
set({ accessToken: null, user: null, loading: false });
localStorage.clear()
useChatStore.getState().reset();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restrict storage cleanup to authentication-owned keys.

Both calls use localStorage.clear(), which deletes theme-storage and any other same-origin persisted state.

  • frontend/src/stores/useAuthStore.ts#L17-L20: remove only auth-storage and chat-storage after resetting in-memory state.
  • frontend/src/stores/useAuthStore.ts#L45-L50: use the same scoped cleanup before starting a new session.
📍 Affects 1 file
  • frontend/src/stores/useAuthStore.ts#L17-L20 (this comment)
  • frontend/src/stores/useAuthStore.ts#L45-L50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/stores/useAuthStore.ts` around lines 17 - 20, Replace broad
localStorage.clear() calls in useAuthStore.ts at lines 17-20 and 45-50 with
scoped removal of only auth-storage and chat-storage. Preserve the existing
in-memory reset behavior in clearState and the session-start flow, while leaving
unrelated persisted state such as theme-storage intact.

Comment on lines +25 to +33
fetchConversatons: async () => {
try {
set({loading:true});
const {conversations} = await chatService.fetchConversations();
set({conversations,loading:false})
} catch (error) {
console.error("Lỗi xảy ra khi fetchConversation:",error)
set({loading:false})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Prevent stale conversation responses from restoring cleared data.

If logout or account switching occurs while this request is pending, the old response can complete after reset() and write the previous user’s conversations into the new session. Abort active requests during reset, or use a session generation value and ignore responses from an earlier session.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/stores/useChatStore.tsx` around lines 25 - 33, Update
fetchConversatons and reset so an in-flight conversation request cannot
repopulate state after logout or account switching: either abort the active
request during reset or track a session generation and ignore responses from
earlier generations before applying conversations. Preserve loading cleanup
while ensuring stale responses never write the previous session’s data.

@coderabbitai coderabbitai Bot mentioned this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant