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: (
<>
>
),
},
{
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
),
},
];
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}
) : (
))}
);
}