fe: danh sach cuoc tro chuyen theo nhom, ca nhan - #5
Conversation
📝 WalkthroughWalkthroughChangesThe frontend adds typed chat and user contracts, persisted authentication, chat, and theme stores, conversation loading through Chat frontend
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (28)
frontend/src/App.tsxfrontend/src/components/auth/Logout.tsxfrontend/src/components/chat/layout/ChatWindowLayout.tsxfrontend/src/components/chat/modals/AddFriendModal.tsxfrontend/src/components/chat/modals/CreateNewChat.tsxfrontend/src/components/chat/modals/NewGroupChatModal.tsxfrontend/src/components/chat/shared/ChatCard.tsxfrontend/src/components/chat/shared/StatusBadge.tsxfrontend/src/components/chat/shared/UnreadCountBadge.tsxfrontend/src/components/chat/shared/UserAvatar.tsxfrontend/src/components/chat/sidebar/DirectMesageList.tsxfrontend/src/components/chat/sidebar/DirectMessageCard.tsxfrontend/src/components/chat/sidebar/GropuMessageCard.tsxfrontend/src/components/chat/sidebar/GroupChatAvatar.tsxfrontend/src/components/chat/sidebar/GroupChatList.tsxfrontend/src/components/sidebar/app-sidebar.tsxfrontend/src/components/sidebar/nav-user.tsxfrontend/src/components/ui/button.tsxfrontend/src/lib/utils.tsfrontend/src/pages/ChatAppPage.tsxfrontend/src/services/chatService.tsfrontend/src/stores/useAuthStore.tsfrontend/src/stores/useChatStore.tsxfrontend/src/stores/useThemeStore.tsxfrontend/src/types/chat.tsfrontend/src/types/store.tsfrontend/src/types/user.tsfrontend/vite.config.ts
| <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")} |
There was a problem hiding this comment.
🎯 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" || trueRepository: 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.
| <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.
| <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)} | ||
| > |
There was a problem hiding this comment.
🎯 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.
| <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.
| const handleSelectConversation = async (id:string) => { | ||
| setActiveConversation(id); | ||
| if(!messages[id]){ | ||
| //fetch message | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 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 forid.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.
| <p className={cn("text-sm truncate", unreadCounts > 0 ? "font-medium text-foreground" : "text-muted=foreground")}> | ||
| {lastMessage} | ||
| </p> |
There was a problem hiding this comment.
🎯 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' || trueRepository: 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.
| <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.
| 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} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
🎯 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 || trueRepository: 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:
- 1: https://react.dev/learn/rendering-lists
- 2: https://legacy.reactjs.org/docs/lists-and-keys.html
- 3: https://react.dev/learn
- 4: https://johnkavanagh.co.uk/articles/rendering-lists-and-why-react-keys-matter/
- 5: https://az.react.dev/learn/rendering-lists
- 6: https://legacy.reactjs.org/docs/reconciliation.html
- 7: https://www.developerway.com/posts/react-key-attribute
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.
| 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.
| <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 /> |
There was a problem hiding this comment.
🎯 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.
| <DropdownMenuItem className="cursor-pointer" variant="destructive" > | ||
| <Logout/> |
There was a problem hiding this comment.
🎯 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.
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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.
| clearState: () => { | ||
| set({ accessToken: null, user: null, loading: false }); | ||
| localStorage.clear() | ||
| useChatStore.getState().reset(); |
There was a problem hiding this comment.
🎯 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 onlyauth-storageandchat-storageafter 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.
| 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}) | ||
| } |
There was a problem hiding this comment.
🔒 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.
Summary by CodeRabbit