feat: added announcements

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-02-12 16:12:45 -08:00
parent 0e96e4492b
commit e9979dfa7d
11 changed files with 833 additions and 3 deletions

View file

@ -0,0 +1,65 @@
import type { Announcement } from "@/contracts/types/announcement.types";
/**
* Static announcements data.
*
* To add a new announcement, append an entry to this array.
* Set `isImportant: true` to trigger a toast notification for the user.
*
* This file can be replaced with an API call in the future.
*/
export const announcements: Announcement[] = [
{
id: "2026-02-12-announcement-syste",
title: "Introducing Announcements",
description:
"Stay up to date with the latest SurfSense news! Important announcements will appear as toast notifications so you never miss critical updates. Visit the Announcements page from the sidebar to browse all past announcements.",
category: "feature",
date: "2026-02-12T00:00:00Z",
isImportant: true,
link: {
label: "Learn more",
url: "/changelog",
},
},
// {
// id: "2026-02-10-podcast-improvements",
// title: "Podcast Generation Improvements",
// description:
// "We've improved podcast generation with faster processing, better audio quality, and support for longer documents. Try it out in any search space.",
// category: "update",
// date: "2026-02-10T00:00:00Z",
// isImportant: false,
// },
// {
// id: "2026-02-08-scheduled-maintenance",
// title: "Scheduled Maintenance — Feb 15",
// description:
// "SurfSense will undergo scheduled maintenance on February 15, 2026 from 2:00 AM to 4:00 AM UTC. During this window, the service may be temporarily unavailable. We apologize for any inconvenience.",
// category: "maintenance",
// date: "2026-02-08T00:00:00Z",
// isImportant: true,
// },
// {
// id: "2026-02-05-new-connectors",
// title: "New Connectors Available",
// description:
// "We've added support for new connectors including Linear, Jira, and Confluence. Connect your project management tools and start chatting with your data.",
// category: "feature",
// date: "2026-02-05T00:00:00Z",
// isImportant: false,
// link: {
// label: "View connectors",
// url: "#connectors",
// },
// },
// {
// id: "2026-01-28-team-collaboration",
// title: "Enhanced Team Collaboration",
// description:
// "Shared search spaces now support real-time mentions, comment threads, and role-based access control. Invite your team and collaborate more effectively.",
// category: "feature",
// date: "2026-01-28T00:00:00Z",
// isImportant: false,
// },
];

View file

@ -0,0 +1,107 @@
import type { AnnouncementUserState } from "@/contracts/types/announcement.types";
const STORAGE_KEY = "surfsense_announcements_state";
const defaultState: AnnouncementUserState = {
readIds: [],
toastedIds: [],
dismissedIds: [],
};
/**
* Get the current announcement user state from localStorage
*/
export function getAnnouncementState(): AnnouncementUserState {
if (typeof window === "undefined") return defaultState;
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return defaultState;
const parsed = JSON.parse(raw) as Partial<AnnouncementUserState>;
return {
readIds: Array.isArray(parsed.readIds) ? parsed.readIds : [],
toastedIds: Array.isArray(parsed.toastedIds) ? parsed.toastedIds : [],
dismissedIds: Array.isArray(parsed.dismissedIds) ? parsed.dismissedIds : [],
};
} catch {
return defaultState;
}
}
/**
* Save announcement user state to localStorage
*/
function saveAnnouncementState(state: AnnouncementUserState): void {
if (typeof window === "undefined") return;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch {
// Silently fail if localStorage is full or unavailable
}
}
/**
* Mark an announcement as read
*/
export function markAnnouncementRead(id: string): void {
const state = getAnnouncementState();
if (!state.readIds.includes(id)) {
state.readIds.push(id);
saveAnnouncementState(state);
}
}
/**
* Mark all announcements as read
*/
export function markAllAnnouncementsRead(ids: string[]): void {
const state = getAnnouncementState();
const newIds = ids.filter((id) => !state.readIds.includes(id));
if (newIds.length > 0) {
state.readIds.push(...newIds);
saveAnnouncementState(state);
}
}
/**
* Dismiss an announcement (hide it from the list)
*/
export function dismissAnnouncement(id: string): void {
const state = getAnnouncementState();
if (!state.dismissedIds.includes(id)) {
state.dismissedIds.push(id);
saveAnnouncementState(state);
}
}
/**
* Mark an important announcement as already toasted (shown as toast)
*/
export function markAnnouncementToasted(id: string): void {
const state = getAnnouncementState();
if (!state.toastedIds.includes(id)) {
state.toastedIds.push(id);
saveAnnouncementState(state);
}
}
/**
* Check if an announcement has been read
*/
export function isAnnouncementRead(id: string): boolean {
return getAnnouncementState().readIds.includes(id);
}
/**
* Check if an announcement has been toasted
*/
export function isAnnouncementToasted(id: string): boolean {
return getAnnouncementState().toastedIds.includes(id);
}
/**
* Check if an announcement has been dismissed
*/
export function isAnnouncementDismissed(id: string): boolean {
return getAnnouncementState().dismissedIds.includes(id);
}