From e4a840aa077dbfa7a4e60279b83c9c6adffcff7a Mon Sep 17 00:00:00 2001 From: Carrano Date: Thu, 21 May 2026 11:34:04 +0200 Subject: [PATCH 1/2] feat(overview): add "New" badge on latest changelog entry Shows a "New" badge inside the latest changelog card when the entry was published within the last 7 days and the user hasn't clicked it yet. --- .prettierignore | 1 + .../section-changelog/section-changelog.tsx | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.prettierignore b/.prettierignore index fe779f6e0f2..a555741545f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,7 @@ # Symlinked skill directories (cause EISDIR errors in prettier) .cursor/skills .claude/skills +.claude/worktrees # Auto-generated TanStack Router file apps/console/src/routeTree.gen.ts \ No newline at end of file diff --git a/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx b/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx index 24cb984c59f..d6fced53513 100644 --- a/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx +++ b/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx @@ -1,9 +1,20 @@ -import { Heading, Icon, Section } from '@qovery/shared/ui' +import { Badge, Heading, Icon, Section } from '@qovery/shared/ui' import { dateFullFormat } from '@qovery/shared/util-dates' +import { useLocalStorage } from '@qovery/shared/util-hooks' import { useChangelogs } from '@qovery/shared/webflow/feature' +const SEEN_CHANGELOG_DATE_KEY = 'qovery-seen-changelog-date' +const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000 + export function SectionChangelog() { const { data: changelogs = [] } = useChangelogs() + const [seenChangelogDate, setSeenChangelogDate] = useLocalStorage(SEEN_CHANGELOG_DATE_KEY, null) + + const latestChangelog = changelogs[0] + const isWithinOneWeek = + latestChangelog !== undefined && Date.now() - new Date(latestChangelog.firstPublishedAt).getTime() < ONE_WEEK_MS + const hasNewChangelog = + isWithinOneWeek && (seenChangelogDate === null || latestChangelog.firstPublishedAt > seenChangelogDate) if (changelogs.length === 0) { return null @@ -15,16 +26,28 @@ export function SectionChangelog() { Changelog - {changelogs.map((changelog) => ( + {changelogs.map((changelog, index) => ( { + if (index === 0 && hasNewChangelog) { + setSeenChangelogDate(changelog.firstPublishedAt) + } + }} className="flex flex-col gap-2 rounded-lg border border-neutral p-4 text-neutral transition-colors hover:bg-surface-neutral-subtle" > -

{changelog.name}

+
+

{changelog.name}

+ {index === 0 && hasNewChangelog && ( + + New + + )} +
{dateFullFormat(changelog.firstPublishedAt, 'UTC', 'dd MMM, Y')} From 91f76bf8e0514f8925ff31e97f10d1a2cdccf1d0 Mon Sep 17 00:00:00 2001 From: Romain Billard Date: Thu, 21 May 2026 15:18:59 +0200 Subject: [PATCH 2/2] refactor: extract changelog badge logic --- .../section-changelog/section-changelog.tsx | 82 +++++++++++-------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx b/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx index d6fced53513..5f60a16a0cf 100644 --- a/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx +++ b/libs/domains/organizations/feature/src/lib/organization-overview/section-changelog/section-changelog.tsx @@ -6,53 +6,69 @@ import { useChangelogs } from '@qovery/shared/webflow/feature' const SEEN_CHANGELOG_DATE_KEY = 'qovery-seen-changelog-date' const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000 +function isRecentChangelog(firstPublishedAt: string): boolean { + return Date.now() - new Date(firstPublishedAt).getTime() < ONE_WEEK_MS +} + +function isUnseenChangelog(firstPublishedAt: string, seenDate: string | null): boolean { + return seenDate === null || firstPublishedAt > seenDate +} + +function shouldShowNewBadge(firstPublishedAt: string | undefined, seenDate: string | null): boolean { + if (firstPublishedAt === undefined) { + return false + } + + return isRecentChangelog(firstPublishedAt) && isUnseenChangelog(firstPublishedAt, seenDate) +} + export function SectionChangelog() { const { data: changelogs = [] } = useChangelogs() const [seenChangelogDate, setSeenChangelogDate] = useLocalStorage(SEEN_CHANGELOG_DATE_KEY, null) - const latestChangelog = changelogs[0] - const isWithinOneWeek = - latestChangelog !== undefined && Date.now() - new Date(latestChangelog.firstPublishedAt).getTime() < ONE_WEEK_MS - const hasNewChangelog = - isWithinOneWeek && (seenChangelogDate === null || latestChangelog.firstPublishedAt > seenChangelogDate) - if (changelogs.length === 0) { return null } + const showNewBadge = shouldShowNewBadge(changelogs[0].firstPublishedAt, seenChangelogDate) + return (
Changelog - {changelogs.map((changelog, index) => ( - { - if (index === 0 && hasNewChangelog) { - setSeenChangelogDate(changelog.firstPublishedAt) - } - }} - className="flex flex-col gap-2 rounded-lg border border-neutral p-4 text-neutral transition-colors hover:bg-surface-neutral-subtle" - > -
-

{changelog.name}

- {index === 0 && hasNewChangelog && ( - - New - - )} -
- - {dateFullFormat(changelog.firstPublishedAt, 'UTC', 'dd MMM, Y')} - -
- ))} + {changelogs.map((changelog, index) => { + const isLatest = index === 0 + + return ( + { + if (isLatest && showNewBadge) { + setSeenChangelogDate(changelog.firstPublishedAt) + } + }} + className="flex flex-col gap-2 rounded-lg border border-neutral p-4 text-neutral transition-colors hover:bg-surface-neutral-subtle" + > +
+

{changelog.name}

+ {isLatest && showNewBadge && ( + + New + + )} +
+ + {dateFullFormat(changelog.firstPublishedAt, 'UTC', 'dd MMM, Y')} + +
+ ) + })}
) }