mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-23 19:05:16 +02:00
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.
This commit is contained in:
parent
3cdfe8b5b6
commit
a9192beae3
16 changed files with 189 additions and 160 deletions
|
|
@ -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({
|
|||
<SidebarUserProfile
|
||||
user={user}
|
||||
onUserSettings={onUserSettings}
|
||||
onAnnouncements={onAnnouncements}
|
||||
announcementUnreadCount={announcementUnreadCount}
|
||||
onLogout={onLogout}
|
||||
isCollapsed
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import { RightPanel, RightPanelExpandButton } from "../right-panel/RightPanel";
|
|||
import {
|
||||
AllPrivateChatsSidebarContent,
|
||||
AllSharedChatsSidebarContent,
|
||||
AnnouncementsSidebarContent,
|
||||
DocumentsSidebar,
|
||||
InboxSidebarContent,
|
||||
MobileSidebar,
|
||||
|
|
@ -54,7 +53,7 @@ interface TabDataSource {
|
|||
markAllAsRead: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
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({
|
|||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeSlideoutPanel === "announcements" && (
|
||||
<motion.div
|
||||
key="announcements"
|
||||
className="h-full flex flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
>
|
||||
<AnnouncementsSidebarContent
|
||||
onOpenChange={(open) => closeSlideout(open)}
|
||||
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeSlideoutPanel === "shared" && allSharedChatsPanel && (
|
||||
<motion.div
|
||||
key="shared"
|
||||
|
|
@ -397,6 +385,8 @@ export function LayoutShell({
|
|||
onNavItemClick={onNavItemClick}
|
||||
user={user}
|
||||
onUserSettings={onUserSettings}
|
||||
onAnnouncements={onAnnouncements}
|
||||
announcementUnreadCount={announcementUnreadCount}
|
||||
onLogout={onLogout}
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
|
|
@ -433,6 +423,8 @@ export function LayoutShell({
|
|||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
onUserSettings={onUserSettings}
|
||||
onAnnouncements={onAnnouncements}
|
||||
announcementUnreadCount={announcementUnreadCount}
|
||||
onLogout={onLogout}
|
||||
pageUsage={pageUsage}
|
||||
theme={theme}
|
||||
|
|
@ -479,18 +471,6 @@ export function LayoutShell({
|
|||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeSlideoutPanel === "announcements" && (
|
||||
<motion.div
|
||||
key="announcements"
|
||||
className="h-full flex flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
>
|
||||
<AnnouncementsSidebarContent onOpenChange={(open) => closeSlideout(open)} />
|
||||
</motion.div>
|
||||
)}
|
||||
{activeSlideoutPanel === "shared" && allSharedChatsPanel && (
|
||||
<motion.div
|
||||
key="shared"
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { AnnouncementCard } from "@/components/announcements/AnnouncementCard";
|
||||
import { AnnouncementsEmptyState } from "@/components/announcements/AnnouncementsEmptyState";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAnnouncements } from "@/hooks/use-announcements";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||
|
||||
export interface AnnouncementsSidebarContentProps {
|
||||
onOpenChange: (open: boolean) => 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 (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="shrink-0 p-3 pb-1.5 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{isMobile && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full"
|
||||
onClick={() => {
|
||||
onOpenChange(false);
|
||||
onCloseMobileSidebar?.();
|
||||
}}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
)}
|
||||
<h2 className="text-md font-semibold">Announcements</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-3">
|
||||
{announcements.length === 0 ? (
|
||||
<AnnouncementsEmptyState />
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
{announcements.map((announcement) => (
|
||||
<AnnouncementCard key={announcement.id} announcement={announcement} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AnnouncementsSidebar({
|
||||
open,
|
||||
onOpenChange,
|
||||
onCloseMobileSidebar,
|
||||
}: AnnouncementsSidebarProps) {
|
||||
return (
|
||||
<SidebarSlideOutPanel open={open} onOpenChange={onOpenChange} ariaLabel="Announcements">
|
||||
<AnnouncementsSidebarContent
|
||||
onOpenChange={onOpenChange}
|
||||
onCloseMobileSidebar={onCloseMobileSidebar}
|
||||
/>
|
||||
</SidebarSlideOutPanel>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -139,10 +139,6 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
|
|||
return (
|
||||
<div className={cn("flex flex-col gap-0.5 py-2", isCollapsed && "items-center")}>
|
||||
{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
|
|||
<StatusIcon
|
||||
status={item.statusIndicator}
|
||||
FallbackIcon={item.icon}
|
||||
className="h-3.5 w-3.5"
|
||||
className="h-3.5 w-3.5"
|
||||
/>
|
||||
}
|
||||
trailingContent={<StatusPill status={item.statusIndicator} />}
|
||||
tooltipContent={tooltip}
|
||||
buttonProps={joyrideAttr}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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<number | null>(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 (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -138,7 +150,7 @@ export function Sidebar({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* New chat button */}
|
||||
{/* New chat button + Inbox */}
|
||||
<div className={cn("flex flex-col gap-0.5 py-1.5", isCollapsed && "items-center")}>
|
||||
<SidebarButton
|
||||
icon={SquarePen}
|
||||
|
|
@ -146,6 +158,21 @@ export function Sidebar({
|
|||
onClick={onNewChat}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
{inboxItem && (
|
||||
<SidebarButton
|
||||
icon={inboxItem.icon}
|
||||
label={inboxItem.title}
|
||||
onClick={() => onNavItemClick?.(inboxItem)}
|
||||
isCollapsed={isCollapsed}
|
||||
isActive={inboxItem.isActive}
|
||||
badge={inboxItem.badge}
|
||||
buttonProps={
|
||||
{
|
||||
"data-joyride": "inbox-sidebar",
|
||||
} as React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Chat sections - fills available space */}
|
||||
|
|
@ -271,8 +298,12 @@ export function Sidebar({
|
|||
{/* Footer */}
|
||||
<div className="mt-auto border-t border-border/60">
|
||||
{/* Platform navigation */}
|
||||
{navItems.length > 0 && (
|
||||
<NavSection items={navItems} onItemClick={onNavItemClick} isCollapsed={isCollapsed} />
|
||||
{footerNavItems.length > 0 && (
|
||||
<NavSection
|
||||
items={footerNavItems}
|
||||
onItemClick={onNavItemClick}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SidebarUsageFooter pageUsage={pageUsage} isCollapsed={isCollapsed} />
|
||||
|
|
@ -281,6 +312,8 @@ export function Sidebar({
|
|||
<SidebarUserProfile
|
||||
user={user}
|
||||
onUserSettings={onUserSettings}
|
||||
onAnnouncements={onAnnouncements}
|
||||
announcementUnreadCount={announcementUnreadCount}
|
||||
onLogout={onLogout}
|
||||
isCollapsed={isCollapsed}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Info,
|
||||
Languages,
|
||||
LogOut,
|
||||
Megaphone,
|
||||
Monitor,
|
||||
Moon,
|
||||
Sun,
|
||||
|
|
@ -60,12 +61,22 @@ const LEARN_MORE_LINKS = [
|
|||
interface SidebarUserProfileProps {
|
||||
user: User;
|
||||
onUserSettings?: () => 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")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
{onAnnouncements && (
|
||||
<DropdownMenuItem onClick={onAnnouncements}>
|
||||
<Megaphone className="h-4 w-4" />
|
||||
<span className="flex-1">What's New</span>
|
||||
{announcementUnreadCount > 0 && (
|
||||
<span className="inline-flex items-center justify-center min-w-4 h-4 px-1 rounded-full bg-red-500 text-white text-[10px] font-medium">
|
||||
{formatAnnouncementCount(announcementUnreadCount)}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{setTheme && (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
|
|
@ -382,6 +407,18 @@ export function SidebarUserProfile({
|
|||
{t("user_settings")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
{onAnnouncements && (
|
||||
<DropdownMenuItem onClick={onAnnouncements}>
|
||||
<Megaphone className="h-4 w-4" />
|
||||
<span className="flex-1">What's New</span>
|
||||
{announcementUnreadCount > 0 && (
|
||||
<span className="inline-flex items-center justify-center min-w-4 h-4 px-1 rounded-full bg-red-500 text-white text-[10px] font-medium">
|
||||
{formatAnnouncementCount(announcementUnreadCount)}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{setTheme && (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue