From 42596fba163a57366b8905a380423e091703b1a1 Mon Sep 17 00:00:00 2001 From: benya Date: Sun, 8 Mar 2026 20:35:57 +0300 Subject: [PATCH] feat(status): improve last-seen labels in web private chats --- docs/core-checklist-status.md | 2 +- web/src/components/ChatInfoPanel.tsx | 20 ++++++++++++++++++++ web/src/components/ChatList.tsx | 20 ++++++++++++++++++++ web/src/pages/ChatsPage.tsx | 20 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/docs/core-checklist-status.md b/docs/core-checklist-status.md index a7158fe..5c4727f 100644 --- a/docs/core-checklist-status.md +++ b/docs/core-checklist-status.md @@ -9,7 +9,7 @@ Legend: 1. Account - `PARTIAL` (email auth, JWT, refresh, logout, reset; sessions exist, full UX still improving) 2. User Profile - `DONE` (username, name, avatar, bio, update) -3. User Status - `PARTIAL` (online/last seen/offline; "recently" heuristic limited) +3. User Status - `PARTIAL` (online/last seen/offline; web now formats `just now/today/yesterday/recently`, backend-side presence heuristics still limited) 4. Contacts - `PARTIAL` (list/search/add/remove/block/unblock; `add by email` flow covered by integration tests including `success/not found/blocked conflict`; UX moved to menu) 5. Chat List - `DONE` (all/pinned/archive/sort/unread; saved-messages delete behavior covered: clear history without deleting chat) 6. Chat Types - `DONE` (private/group/channel) diff --git a/web/src/components/ChatInfoPanel.tsx b/web/src/components/ChatInfoPanel.tsx index 8a2449f..9b36001 100644 --- a/web/src/components/ChatInfoPanel.tsx +++ b/web/src/components/ChatInfoPanel.tsx @@ -878,6 +878,26 @@ function formatLastSeen(value: string): string { if (Number.isNaN(date.getTime())) { return "recently"; } + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + if (diffMs < 0) { + return "recently"; + } + const minute = 60 * 1000; + const hour = 60 * minute; + const day = 24 * hour; + if (diffMs < 2 * minute) { + return "just now"; + } + if (diffMs < day) { + return `today at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; + } + if (diffMs < 2 * day) { + return `yesterday at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; + } + if (diffMs < 7 * day) { + return "recently"; + } return date.toLocaleString(undefined, { day: "2-digit", month: "short", diff --git a/web/src/components/ChatList.tsx b/web/src/components/ChatList.tsx index 5858169..2500e86 100644 --- a/web/src/components/ChatList.tsx +++ b/web/src/components/ChatList.tsx @@ -895,6 +895,26 @@ function formatLastSeen(value: string): string { if (Number.isNaN(date.getTime())) { return "recently"; } + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + if (diffMs < 0) { + return "recently"; + } + const minute = 60 * 1000; + const hour = 60 * minute; + const day = 24 * hour; + if (diffMs < 2 * minute) { + return "just now"; + } + if (diffMs < day) { + return `today at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; + } + if (diffMs < 2 * day) { + return `yesterday at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; + } + if (diffMs < 7 * day) { + return "recently"; + } return date.toLocaleString(undefined, { day: "2-digit", month: "short", diff --git a/web/src/pages/ChatsPage.tsx b/web/src/pages/ChatsPage.tsx index e900fa4..a269b15 100644 --- a/web/src/pages/ChatsPage.tsx +++ b/web/src/pages/ChatsPage.tsx @@ -317,6 +317,26 @@ function formatLastSeen(value: string): string { if (Number.isNaN(date.getTime())) { return "recently"; } + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + if (diffMs < 0) { + return "recently"; + } + const minute = 60 * 1000; + const hour = 60 * minute; + const day = 24 * hour; + if (diffMs < 2 * minute) { + return "just now"; + } + if (diffMs < day) { + return `today at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; + } + if (diffMs < 2 * day) { + return `yesterday at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; + } + if (diffMs < 7 * day) { + return "recently"; + } return date.toLocaleString(undefined, { day: "2-digit", month: "short",