From a9192beae3e87e937e8fce37bb9158595410fd44 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sun, 3 May 2026 18:42:29 +0530 Subject: [PATCH] feat(announcements): rename "Announcements" to "What's New" across the application; add AnnouncementsDialog component for displaying updates; update empty state messaging; remove unused AnnouncementsSidebar component. --- .../app/(home)/announcements/layout.tsx | 6 +- .../app/(home)/announcements/page.tsx | 2 +- .../atoms/settings/settings-dialog.atoms.ts | 2 + .../announcements/AnnouncementsDialog.tsx | 50 +++++++++++ .../announcements/AnnouncementsEmptyState.tsx | 4 +- .../components/homepage/footer-new.tsx | 2 +- .../providers/FreeLayoutDataProvider.tsx | 46 +++++----- .../layout/ui/icon-rail/IconRail.tsx | 6 ++ .../layout/ui/shell/LayoutShell.tsx | 44 +++------- .../ui/sidebar/AnnouncementsSidebar.tsx | 84 ------------------- .../layout/ui/sidebar/MobileSidebar.tsx | 13 +++ .../layout/ui/sidebar/NavSection.tsx | 7 +- .../components/layout/ui/sidebar/Sidebar.tsx | 41 ++++++++- .../layout/ui/sidebar/SidebarUserProfile.tsx | 37 ++++++++ .../components/layout/ui/sidebar/index.ts | 1 - .../lib/announcements/announcements-data.ts | 4 +- 16 files changed, 189 insertions(+), 160 deletions(-) create mode 100644 surfsense_web/components/announcements/AnnouncementsDialog.tsx delete mode 100644 surfsense_web/components/layout/ui/sidebar/AnnouncementsSidebar.tsx diff --git a/surfsense_web/app/(home)/announcements/layout.tsx b/surfsense_web/app/(home)/announcements/layout.tsx index 072db2c3f..cff15d3a1 100644 --- a/surfsense_web/app/(home)/announcements/layout.tsx +++ b/surfsense_web/app/(home)/announcements/layout.tsx @@ -2,20 +2,20 @@ import type { Metadata } from "next"; import type { ReactNode } from "react"; export const metadata: Metadata = { - title: "Announcements | SurfSense", + title: "What's New | SurfSense", description: "Latest product updates, feature releases, and news from SurfSense.", alternates: { canonical: "https://surfsense.com/announcements", }, openGraph: { - title: "Announcements | SurfSense", + title: "What's New | SurfSense", description: "Latest product updates, feature releases, and news from SurfSense.", url: "https://surfsense.com/announcements", type: "website", }, twitter: { card: "summary_large_image", - title: "Announcements | SurfSense", + title: "What's New | SurfSense", description: "Latest product updates, feature releases, and news from SurfSense.", }, }; diff --git a/surfsense_web/app/(home)/announcements/page.tsx b/surfsense_web/app/(home)/announcements/page.tsx index 966c09f77..f287e43d1 100644 --- a/surfsense_web/app/(home)/announcements/page.tsx +++ b/surfsense_web/app/(home)/announcements/page.tsx @@ -24,7 +24,7 @@ export default function AnnouncementsPage() {

- Announcements + What's New

diff --git a/surfsense_web/atoms/settings/settings-dialog.atoms.ts b/surfsense_web/atoms/settings/settings-dialog.atoms.ts index 3b49f1f06..480c41204 100644 --- a/surfsense_web/atoms/settings/settings-dialog.atoms.ts +++ b/surfsense_web/atoms/settings/settings-dialog.atoms.ts @@ -21,3 +21,5 @@ export const userSettingsDialogAtom = atom({ }); export const teamDialogAtom = atom(false); + +export const announcementsDialogAtom = atom(false); diff --git a/surfsense_web/components/announcements/AnnouncementsDialog.tsx b/surfsense_web/components/announcements/AnnouncementsDialog.tsx new file mode 100644 index 000000000..4d3aaeb73 --- /dev/null +++ b/surfsense_web/components/announcements/AnnouncementsDialog.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useAtom } from "jotai"; +import { useEffect } from "react"; +import { announcementsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; +import { AnnouncementCard } from "@/components/announcements/AnnouncementCard"; +import { AnnouncementsEmptyState } from "@/components/announcements/AnnouncementsEmptyState"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { Separator } from "@/components/ui/separator"; +import { useAnnouncements } from "@/hooks/use-announcements"; + +export function AnnouncementsDialog() { + const [open, setOpen] = useAtom(announcementsDialogAtom); + const { announcements, markAllRead } = useAnnouncements(); + + // Auto-mark all visible announcements as read when the dialog opens + useEffect(() => { + if (open) { + markAllRead(); + } + }, [open, markAllRead]); + + return ( + + + What's New + +
+
+

What's New

+ +
+
+
+ {announcements.length === 0 ? ( + + ) : ( +
+ {announcements.map((announcement) => ( + + ))} +
+ )} +
+
+
+
+
+ ); +} diff --git a/surfsense_web/components/announcements/AnnouncementsEmptyState.tsx b/surfsense_web/components/announcements/AnnouncementsEmptyState.tsx index 329a284db..7aa4564b6 100644 --- a/surfsense_web/components/announcements/AnnouncementsEmptyState.tsx +++ b/surfsense_web/components/announcements/AnnouncementsEmptyState.tsx @@ -6,9 +6,9 @@ export function AnnouncementsEmptyState() {
-

No announcements

+

Nothing new yet

- You're all caught up! New announcements will appear here. + You're all caught up! New updates will appear here.

); diff --git a/surfsense_web/components/homepage/footer-new.tsx b/surfsense_web/components/homepage/footer-new.tsx index 53eb833e9..5943cc8ec 100644 --- a/surfsense_web/components/homepage/footer-new.tsx +++ b/surfsense_web/components/homepage/footer-new.tsx @@ -38,7 +38,7 @@ export function FooterNew() { href: "/contact", }, { - title: "Announcements", + title: "What's New", href: "/announcements", }, ]; diff --git a/surfsense_web/components/layout/providers/FreeLayoutDataProvider.tsx b/surfsense_web/components/layout/providers/FreeLayoutDataProvider.tsx index 8b362f45b..a2695078a 100644 --- a/surfsense_web/components/layout/providers/FreeLayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/FreeLayoutDataProvider.tsx @@ -1,6 +1,6 @@ "use client"; -import { Inbox, Megaphone, SquareLibrary } from "lucide-react"; +import { Inbox, SquareLibrary } from "lucide-react"; import { useRouter } from "next/navigation"; import type { ReactNode } from "react"; import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; @@ -55,28 +55,24 @@ export function FreeLayoutDataProvider({ children }: FreeLayoutDataProviderProps const navItems: NavItem[] = useMemo( () => - [ - { - title: "Inbox", - url: "#inbox", - icon: Inbox, - isActive: false, - }, - isMobile - ? { - title: "Documents", - url: "#documents", - icon: SquareLibrary, - isActive: false, - } - : null, - { - title: "Announcements", - url: "#announcements", - icon: Megaphone, - isActive: false, - }, - ].filter((item): item is NavItem => item !== null), + ( + [ + { + title: "Inbox", + url: "#inbox", + icon: Inbox, + isActive: false, + }, + isMobile + ? { + title: "Documents", + url: "#documents", + icon: SquareLibrary, + isActive: false, + } + : null, + ] as (NavItem | null)[] + ).filter((item): item is NavItem => item !== null), [isMobile] ); @@ -90,11 +86,12 @@ export function FreeLayoutDataProvider({ children }: FreeLayoutDataProviderProps (item: NavItem) => { if (item.title === "Inbox") gate("use the inbox"); else if (item.title === "Documents") setIsDocsSidebarOpen((v) => !v); - else if (item.title === "Announcements") gate("view announcements"); }, [gate] ); + const handleAnnouncements = useCallback(() => gate("see what's new"), [gate]); + const handleSearchSpaceSelect = useCallback( (_id: number) => gate("switch search spaces"), [gate] @@ -127,6 +124,7 @@ export function FreeLayoutDataProvider({ children }: FreeLayoutDataProviderProps onSettings={gatedAction("search space settings")} onManageMembers={gatedAction("team management")} onUserSettings={gatedAction("account settings")} + onAnnouncements={handleAnnouncements} onLogout={() => router.push("/register")} pageUsage={pageUsage} isChatPage diff --git a/surfsense_web/components/layout/ui/icon-rail/IconRail.tsx b/surfsense_web/components/layout/ui/icon-rail/IconRail.tsx index c4b127c63..fdc4930d6 100644 --- a/surfsense_web/components/layout/ui/icon-rail/IconRail.tsx +++ b/surfsense_web/components/layout/ui/icon-rail/IconRail.tsx @@ -23,6 +23,8 @@ interface IconRailProps { onNavItemClick?: (item: NavItem) => void; user: User; onUserSettings?: () => void; + onAnnouncements?: () => void; + announcementUnreadCount?: number; onLogout?: () => void; theme?: string; setTheme?: (theme: "light" | "dark" | "system") => void; @@ -42,6 +44,8 @@ export function IconRail({ onNavItemClick, user, onUserSettings, + onAnnouncements, + announcementUnreadCount = 0, onLogout, theme, setTheme, @@ -138,6 +142,8 @@ export function IconRail({ Promise; } -export type ActiveSlideoutPanel = "inbox" | "shared" | "private" | "announcements" | null; +export type ActiveSlideoutPanel = "inbox" | "shared" | "private" | null; // Inbox-related props — per-tab data sources with independent loading/pagination interface InboxProps { @@ -88,6 +87,8 @@ interface LayoutShellProps { onSettings?: () => void; onManageMembers?: () => void; onUserSettings?: () => void; + onAnnouncements?: () => void; + announcementUnreadCount?: number; onLogout?: () => void; pageUsage?: PageUsage; theme?: string; @@ -189,6 +190,8 @@ export function LayoutShell({ onSettings, onManageMembers, onUserSettings, + onAnnouncements, + announcementUnreadCount = 0, onLogout, pageUsage, theme, @@ -237,9 +240,7 @@ export function LayoutShell({ ? "Shared Chats" : activeSlideoutPanel === "private" ? "Private Chats" - : activeSlideoutPanel === "announcements" - ? "Announcements" - : "Panel"; + : "Panel"; // Mobile layout if (isMobile) { @@ -277,6 +278,8 @@ export function LayoutShell({ onSettings={onSettings} onManageMembers={onManageMembers} onUserSettings={onUserSettings} + onAnnouncements={onAnnouncements} + announcementUnreadCount={announcementUnreadCount} onLogout={onLogout} pageUsage={pageUsage} theme={theme} @@ -313,21 +316,6 @@ export function LayoutShell({ /> )} - {activeSlideoutPanel === "announcements" && ( - - closeSlideout(open)} - onCloseMobileSidebar={() => setMobileMenuOpen(false)} - /> - - )} {activeSlideoutPanel === "shared" && allSharedChatsPanel && ( )} - {activeSlideoutPanel === "announcements" && ( - - closeSlideout(open)} /> - - )} {activeSlideoutPanel === "shared" && allSharedChatsPanel && ( void; - onCloseMobileSidebar?: () => void; -} - -interface AnnouncementsSidebarProps extends AnnouncementsSidebarContentProps { - open: boolean; -} - -export function AnnouncementsSidebarContent({ - onOpenChange, - onCloseMobileSidebar, -}: AnnouncementsSidebarContentProps) { - const isMobile = !useMediaQuery("(min-width: 640px)"); - const { announcements, markAllRead } = useAnnouncements(); - - useEffect(() => { - markAllRead(); - }, [markAllRead]); - - return ( -
-
-
-
- {isMobile && ( - - )} -

Announcements

-
-
-
- -
- {announcements.length === 0 ? ( - - ) : ( -
- {announcements.map((announcement) => ( - - ))} -
- )} -
-
- ); -} - -export function AnnouncementsSidebar({ - open, - onOpenChange, - onCloseMobileSidebar, -}: AnnouncementsSidebarProps) { - return ( - - - - ); -} diff --git a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx index def40f24f..ebb12bae8 100644 --- a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx @@ -34,6 +34,8 @@ interface MobileSidebarProps { onSettings?: () => void; onManageMembers?: () => void; onUserSettings?: () => void; + onAnnouncements?: () => void; + announcementUnreadCount?: number; onLogout?: () => void; pageUsage?: PageUsage; theme?: string; @@ -77,6 +79,8 @@ export function MobileSidebar({ onSettings, onManageMembers, onUserSettings, + onAnnouncements, + announcementUnreadCount = 0, onLogout, pageUsage, theme, @@ -193,6 +197,15 @@ export function MobileSidebar({ } : undefined } + onAnnouncements={ + onAnnouncements + ? () => { + onOpenChange(false); + onAnnouncements(); + } + : undefined + } + announcementUnreadCount={announcementUnreadCount} onLogout={onLogout} pageUsage={pageUsage} theme={theme} diff --git a/surfsense_web/components/layout/ui/sidebar/NavSection.tsx b/surfsense_web/components/layout/ui/sidebar/NavSection.tsx index 658067f3f..3110382d0 100644 --- a/surfsense_web/components/layout/ui/sidebar/NavSection.tsx +++ b/surfsense_web/components/layout/ui/sidebar/NavSection.tsx @@ -139,10 +139,6 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti return (
{items.map((item) => { - const joyrideAttr = - item.title === "Inbox" || item.title.toLowerCase().includes("inbox") - ? { "data-joyride": "inbox-sidebar" as const } - : {}; const { tooltip } = getStatusInfo(item.statusIndicator); return ( @@ -159,12 +155,11 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti } trailingContent={} tooltipContent={tooltip} - buttonProps={joyrideAttr} /> ); })} diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index c5990b11b..946079333 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -4,7 +4,7 @@ import { CreditCard, SquarePen, Zap } from "lucide-react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { useTranslations } from "next-intl"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Skeleton } from "@/components/ui/skeleton"; @@ -53,6 +53,8 @@ interface SidebarProps { onSettings?: () => void; onManageMembers?: () => void; onUserSettings?: () => void; + onAnnouncements?: () => void; + announcementUnreadCount?: number; onLogout?: () => void; pageUsage?: PageUsage; theme?: string; @@ -87,6 +89,8 @@ export function Sidebar({ onSettings, onManageMembers, onUserSettings, + onAnnouncements, + announcementUnreadCount = 0, onLogout, pageUsage, theme, @@ -101,6 +105,14 @@ export function Sidebar({ const t = useTranslations("sidebar"); const [openDropdownChatId, setOpenDropdownChatId] = useState(null); + // Inbox is rendered explicitly right below New Chat. Pull it out of the + // nav items list so it doesn't also appear in the bottom NavSection. + const inboxItem = useMemo(() => navItems.find((item) => item.url === "#inbox"), [navItems]); + const footerNavItems = useMemo( + () => navItems.filter((item) => item.url !== "#inbox"), + [navItems] + ); + return (
)} - {/* New chat button */} + {/* New chat button + Inbox */}
+ {inboxItem && ( + onNavItemClick?.(inboxItem)} + isCollapsed={isCollapsed} + isActive={inboxItem.isActive} + badge={inboxItem.badge} + buttonProps={ + { + "data-joyride": "inbox-sidebar", + } as React.ButtonHTMLAttributes + } + /> + )}
{/* Chat sections - fills available space */} @@ -271,8 +298,12 @@ export function Sidebar({ {/* Footer */}
{/* Platform navigation */} - {navItems.length > 0 && ( - + {footerNavItems.length > 0 && ( + )} @@ -281,6 +312,8 @@ export function Sidebar({ void; + onAnnouncements?: () => void; + announcementUnreadCount?: number; onLogout?: () => void; isCollapsed?: boolean; theme?: string; setTheme?: (theme: "light" | "dark" | "system") => void; } +function formatAnnouncementCount(count: number): string { + if (count <= 999) { + return count.toString(); + } + const thousands = Math.floor(count / 1000); + return `${thousands}k+`; +} + /** * Generates a consistent color based on email */ @@ -152,6 +163,8 @@ function UserAvatar({ export function SidebarUserProfile({ user, onUserSettings, + onAnnouncements, + announcementUnreadCount = 0, onLogout, isCollapsed = false, theme, @@ -228,6 +241,18 @@ export function SidebarUserProfile({ {t("user_settings")} + {onAnnouncements && ( + + + What's New + {announcementUnreadCount > 0 && ( + + {formatAnnouncementCount(announcementUnreadCount)} + + )} + + )} + {setTheme && ( @@ -382,6 +407,18 @@ export function SidebarUserProfile({ {t("user_settings")} + {onAnnouncements && ( + + + What's New + {announcementUnreadCount > 0 && ( + + {formatAnnouncementCount(announcementUnreadCount)} + + )} + + )} + {setTheme && ( diff --git a/surfsense_web/components/layout/ui/sidebar/index.ts b/surfsense_web/components/layout/ui/sidebar/index.ts index 33023c9e8..d72f86c8a 100644 --- a/surfsense_web/components/layout/ui/sidebar/index.ts +++ b/surfsense_web/components/layout/ui/sidebar/index.ts @@ -1,6 +1,5 @@ export { AllPrivateChatsSidebar, AllPrivateChatsSidebarContent } from "./AllPrivateChatsSidebar"; export { AllSharedChatsSidebar, AllSharedChatsSidebarContent } from "./AllSharedChatsSidebar"; -export { AnnouncementsSidebar, AnnouncementsSidebarContent } from "./AnnouncementsSidebar"; export { ChatListItem } from "./ChatListItem"; export { DocumentsSidebar } from "./DocumentsSidebar"; export { InboxSidebar, InboxSidebarContent } from "./InboxSidebar"; diff --git a/surfsense_web/lib/announcements/announcements-data.ts b/surfsense_web/lib/announcements/announcements-data.ts index bde8b3f43..ce44ec539 100644 --- a/surfsense_web/lib/announcements/announcements-data.ts +++ b/surfsense_web/lib/announcements/announcements-data.ts @@ -15,8 +15,8 @@ import type { Announcement } from "@/contracts/types/announcement.types"; export const announcements: Announcement[] = [ { id: "announcement-1", - title: "Introducing Announcements", - description: "All major announcements will be posted here.", + title: "Introducing What's New", + description: "All major product updates will be posted here.", category: "feature", date: "2026-02-17T00:00:00Z", startTime: "2026-02-17T00:00:00Z",