import { Button } from "@/src/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/src/components/ui/card"; import { useEffect, useState } from "react"; import { X } from "lucide-react"; import useLocalStorage from "../useLocalStorage"; import Link from "next/link"; import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture"; import ProductHuntBadgeLight from "../images/product_hunt_badge_light.svg"; import ProductHuntBadgeDark from "../images/product_hunt_badge_dark.svg"; import Image from "next/image"; const NOTIFICATION_TTL_MS = 14 * 24 * 60 * 60 * 1000; // two weeks type SidebarNotification = { id: string; // Add unique ID for each notification title: string; description: React.ReactNode; createdAt?: string; // optional, used to expire the notification link?: string; // defaults to "Learn more" if no linkContent and no linkTitle linkTitle?: string; linkContent?: React.ReactNode; }; const notifications: SidebarNotification[] = [ { id: "js-sdk-v4", title: "New JS/TS SDK v4", description: "With v4, the TypeScript SDK significantly improves DX, speed, and ecosystem integrations.", link: "https://langfuse.com/docs/observability/sdk/typescript/overview", linkTitle: "Learn more", createdAt: "2025-09-09", }, { id: "python-sdk-v3", title: "New Python SDK v3", description: "Python SDK V3 offers significant improvements in developer experience, performance, and integrations.", link: "https://langfuse.com/docs/sdk/python/sdk-v3#upgrade-from-v2", linkTitle: "Upgrade to v3", createdAt: "2025-06-27", }, { id: "lw3-5", title: "Launch Week #3: Day 5", description: "New OpenTelemetry based Python SDK.", link: "https://langfuse.com/changelog/2025-05-23-otel-based-python-sdk", linkTitle: "Learn more", createdAt: "2025-05-23", }, { id: "lw3-4", title: "Launch Week #3: Day 4", description: "Terraform Modules for AWS, Azure and GCP.", link: "https://langfuse.com/changelog/2025-05-22-terraform-modules", linkTitle: "Learn more", createdAt: "2025-05-22", }, { id: "lw3-3-producthunt", title: "Launch Week #3: Day 3", createdAt: "2025-05-21", description: ( We are launching Custom Dashboards on Product Hunt today.
Support the launch to help grow the community!
), link: "https://langfuse.com/ph", linkTitle: "Product Hunt", linkContent: ( <> Product Hunt Product Hunt ), }, { id: "lw3-2", title: "Launch Week #3: Day 2", description: "Saved table views let you reopen any filtered table view with one click.", link: "https://langfuse.com/blog/2025-05-19-launch-week-3", linkTitle: "Learn more", createdAt: "2025-05-20", }, { id: "lw3-1", title: "Launch Week #3: Day 1", description: "New full text search for trace and observation input/output.", link: "https://langfuse.com/blog/2025-05-19-launch-week-3", linkTitle: "Learn more", createdAt: "2025-05-19", }, { id: "github-star", title: "Star Langfuse", description: "See the latest releases and help grow the community on GitHub", link: "https://github.com/langfuse/langfuse", linkContent: ( // eslint-disable-next-line @next/next/no-img-element Langfuse GitHub stars ), }, ]; const STORAGE_KEY = "dismissed-sidebar-notifications"; export function SidebarNotifications() { const capture = usePostHogClientCapture(); const [currentNotificationIndex, setCurrentNotificationIndex] = useState< number | null >(null); const [dismissedNotifications, setDismissedNotifications] = useLocalStorage< string[] >(STORAGE_KEY, []); const isExpired = (notif: SidebarNotification) => { if (!notif.createdAt) return false; const created = new Date(notif.createdAt).getTime(); return Date.now() > created + NOTIFICATION_TTL_MS; }; // Find the oldest non-dismissed notification on mount or when dismissed list changes useEffect(() => { const firstAvailableIndex = notifications.findIndex( (notif) => !dismissedNotifications.includes(notif.id) && !isExpired(notif), ); setCurrentNotificationIndex( firstAvailableIndex === -1 ? null : firstAvailableIndex, ); }, [dismissedNotifications]); const dismissNotification = (id: string) => { setDismissedNotifications([...dismissedNotifications, id]); }; if (currentNotificationIndex === null) { return null; } const currentNotification = notifications[currentNotificationIndex]; return ( {currentNotification.title} {currentNotification.description} {currentNotification.link && (currentNotification.linkContent ? ( { capture("notification:click_link", { notification_id: currentNotification.id, }); }} > {currentNotification.linkContent} ) : ( ))} ); }