mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-17 18:35:19 +02:00
refactor: made announcements time-bound and added audiences
- Added startTime and endTime properties to announcements for time-bound visibility. - Introduced audience targeting to control who sees announcements (all, users, web_visitors). - Updated related components and hooks to support new announcement features. - Removed unused state tracking for dismissed announcements to streamline functionality.
This commit is contained in:
parent
2c68e4ad69
commit
f777142017
7 changed files with 211 additions and 263 deletions
|
|
@ -4,6 +4,10 @@ import type { Announcement } from "@/contracts/types/announcement.types";
|
|||
* Static announcements data.
|
||||
*
|
||||
* To add a new announcement, append an entry to this array.
|
||||
* Each announcement requires `startTime` and `endTime` (ISO datetime strings)
|
||||
* to define its visibility window, and `audience` to control who sees it.
|
||||
* Current possible audiences are "all", "users", and "web_visitors".
|
||||
* Current possible categories are "feature", "update", "maintenance", and "info".
|
||||
* Set `isImportant: true` to trigger a toast notification for the user.
|
||||
*
|
||||
* This file can be replaced with an API call in the future.
|
||||
|
|
@ -15,11 +19,21 @@ export const announcements: Announcement[] = [
|
|||
description: "All major announcements will be posted here.",
|
||||
category: "feature",
|
||||
date: "2026-02-17T00:00:00Z",
|
||||
startTime: "2026-02-17T00:00:00Z",
|
||||
endTime: "2026-02-20T00:00:00Z",
|
||||
audience: "all",
|
||||
isImportant: false,
|
||||
},
|
||||
{
|
||||
id: "announcement-6",
|
||||
title: "Past Test Announcement",
|
||||
description: "This should be seen by nobody, because it's in the past.",
|
||||
category: "maintenance",
|
||||
date: "2026-02-17T00:00:00Z",
|
||||
startTime: "2026-02-15T23:23:00Z",
|
||||
endTime: "2026-02-16T00:00:00Z",
|
||||
audience: "users",
|
||||
isImportant: true,
|
||||
link: {
|
||||
label: "Check Here",
|
||||
url: "/announcements",
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: "2026-02-10-podcast-improvements",
|
||||
|
|
@ -28,6 +42,9 @@ export const announcements: Announcement[] = [
|
|||
// "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",
|
||||
// startTime: "2026-02-10T00:00:00Z",
|
||||
// endTime: "2026-03-10T00:00:00Z",
|
||||
// audience: "all",
|
||||
// isImportant: false,
|
||||
// },
|
||||
// {
|
||||
|
|
@ -37,6 +54,9 @@ export const announcements: Announcement[] = [
|
|||
// "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",
|
||||
// startTime: "2026-02-08T00:00:00Z",
|
||||
// endTime: "2026-02-16T00:00:00Z",
|
||||
// audience: "all",
|
||||
// isImportant: true,
|
||||
// },
|
||||
// {
|
||||
|
|
@ -46,6 +66,9 @@ export const announcements: Announcement[] = [
|
|||
// "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",
|
||||
// startTime: "2026-02-05T00:00:00Z",
|
||||
// endTime: "2026-03-05T00:00:00Z",
|
||||
// audience: "users",
|
||||
// isImportant: false,
|
||||
// link: {
|
||||
// label: "View connectors",
|
||||
|
|
@ -59,6 +82,9 @@ export const announcements: Announcement[] = [
|
|||
// "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",
|
||||
// startTime: "2026-01-28T00:00:00Z",
|
||||
// endTime: "2026-02-28T00:00:00Z",
|
||||
// audience: "users",
|
||||
// isImportant: false,
|
||||
// },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ const STORAGE_KEY = "surfsense_announcements_state";
|
|||
const defaultState: AnnouncementUserState = {
|
||||
readIds: [],
|
||||
toastedIds: [],
|
||||
dismissedIds: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current announcement user state from localStorage
|
||||
* Get the current announcement user state from localStorage.
|
||||
* Gracefully ignores legacy `dismissedIds` from older versions.
|
||||
*/
|
||||
export function getAnnouncementState(): AnnouncementUserState {
|
||||
if (typeof window === "undefined") return defaultState;
|
||||
|
|
@ -21,7 +21,6 @@ export function getAnnouncementState(): 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;
|
||||
|
|
@ -63,17 +62,6 @@ export function markAllAnnouncementsRead(ids: string[]): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
|
|
@ -98,10 +86,3 @@ export function isAnnouncementRead(id: string): boolean {
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
80
surfsense_web/lib/announcements/announcements-utils.ts
Normal file
80
surfsense_web/lib/announcements/announcements-utils.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import type { Announcement } from "@/contracts/types/announcement.types";
|
||||
|
||||
/**
|
||||
* Returns true when the current time falls within the announcement's
|
||||
* [startTime, endTime] window. Returns false for invalid windows
|
||||
* (endTime before startTime) or when now is outside the range.
|
||||
*/
|
||||
export function isAnnouncementActive(announcement: Announcement, now = new Date()): boolean {
|
||||
const start = new Date(announcement.startTime).getTime();
|
||||
const end = new Date(announcement.endTime).getTime();
|
||||
|
||||
if (Number.isNaN(start) || Number.isNaN(end) || end < start) return false;
|
||||
|
||||
const nowMs = now.getTime();
|
||||
return nowMs >= start && nowMs <= end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the announcement's audience matches the viewer context.
|
||||
* - `"all"` — visible to everyone
|
||||
* - `"users"` — visible only to authenticated users
|
||||
* - `"web_visitors"` — visible only to unauthenticated visitors
|
||||
*/
|
||||
export function announcementMatchesAudience(
|
||||
announcement: Announcement,
|
||||
isAuthenticated: boolean,
|
||||
): boolean {
|
||||
switch (announcement.audience) {
|
||||
case "all":
|
||||
return true;
|
||||
case "users":
|
||||
return isAuthenticated;
|
||||
case "web_visitors":
|
||||
return !isAuthenticated;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter announcements to only those that are currently active and
|
||||
* targeted at the given audience.
|
||||
*/
|
||||
export function getActiveAnnouncements(
|
||||
announcements: Announcement[],
|
||||
isAuthenticated: boolean,
|
||||
now = new Date(),
|
||||
): Announcement[] {
|
||||
return announcements.filter(
|
||||
(a) => isAnnouncementActive(a, now) && announcementMatchesAudience(a, isAuthenticated),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds until the next announcement either
|
||||
* starts or expires. Returns `null` when there are no upcoming transitions.
|
||||
* Useful for scheduling re-renders so the UI updates automatically.
|
||||
*/
|
||||
export function msUntilNextTransition(
|
||||
announcements: Announcement[],
|
||||
now = new Date(),
|
||||
): number | null {
|
||||
const nowMs = now.getTime();
|
||||
let nearest: number | null = null;
|
||||
|
||||
for (const a of announcements) {
|
||||
const start = new Date(a.startTime).getTime();
|
||||
const end = new Date(a.endTime).getTime();
|
||||
if (Number.isNaN(start) || Number.isNaN(end) || end < start) continue;
|
||||
|
||||
for (const edge of [start, end]) {
|
||||
if (edge > nowMs) {
|
||||
const diff = edge - nowMs;
|
||||
if (nearest === null || diff < nearest) nearest = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue