mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-20 23:21:06 +02:00
refactor: enhance announcements functionality to include expired announcements in hooks and components
This commit is contained in:
parent
7bc02ce55b
commit
7af4bc0b49
5 changed files with 55 additions and 32 deletions
|
|
@ -10,7 +10,7 @@ import { useAnnouncements } from "@/hooks/use-announcements";
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export default function AnnouncementsPage() {
|
export default function AnnouncementsPage() {
|
||||||
const { announcements, markAllRead } = useAnnouncements();
|
const { announcements, markAllRead } = useAnnouncements({ includeExpired: true });
|
||||||
|
|
||||||
// Auto-mark all visible announcements as read when the page is opened
|
// Auto-mark all visible announcements as read when the page is opened
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { useAnnouncements } from "@/hooks/use-announcements";
|
||||||
|
|
||||||
export function AnnouncementsDialog() {
|
export function AnnouncementsDialog() {
|
||||||
const [open, setOpen] = useAtom(announcementsDialogAtom);
|
const [open, setOpen] = useAtom(announcementsDialogAtom);
|
||||||
const { announcements, markAllRead } = useAnnouncements();
|
const { announcements, markAllRead } = useAnnouncements({ includeExpired: true });
|
||||||
|
|
||||||
// Auto-mark all visible announcements as read when the dialog opens
|
// Auto-mark all visible announcements as read when the dialog opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from "@/lib/announcements/announcements-storage";
|
} from "@/lib/announcements/announcements-storage";
|
||||||
import {
|
import {
|
||||||
getActiveAnnouncements,
|
getActiveAnnouncements,
|
||||||
|
getPublishedAnnouncements,
|
||||||
msUntilNextTransition,
|
msUntilNextTransition,
|
||||||
} from "@/lib/announcements/announcements-utils";
|
} from "@/lib/announcements/announcements-utils";
|
||||||
import { isAuthenticated } from "@/lib/auth-utils";
|
import { isAuthenticated } from "@/lib/auth-utils";
|
||||||
|
|
@ -56,10 +57,16 @@ export interface AnnouncementWithState extends Announcement {
|
||||||
interface UseAnnouncementsOptions {
|
interface UseAnnouncementsOptions {
|
||||||
/** Filter by category */
|
/** Filter by category */
|
||||||
category?: AnnouncementCategory;
|
category?: AnnouncementCategory;
|
||||||
|
/**
|
||||||
|
* Include announcements whose visibility window has expired (archive
|
||||||
|
* views). Defaults to false so spotlights, toasts, and unread badges
|
||||||
|
* only consider currently-active announcements.
|
||||||
|
*/
|
||||||
|
includeExpired?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAnnouncements(options: UseAnnouncementsOptions = {}) {
|
export function useAnnouncements(options: UseAnnouncementsOptions = {}) {
|
||||||
const { category } = options;
|
const { category, includeExpired = false } = options;
|
||||||
|
|
||||||
// Subscribe to state changes (re-renders when localStorage state is bumped)
|
// Subscribe to state changes (re-renders when localStorage state is bumped)
|
||||||
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||||
|
|
@ -71,12 +78,13 @@ export function useAnnouncements(options: UseAnnouncementsOptions = {}) {
|
||||||
const enriched: AnnouncementWithState[] = useMemo(() => {
|
const enriched: AnnouncementWithState[] = useMemo(() => {
|
||||||
const authed = isAuthenticated();
|
const authed = isAuthenticated();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let items: AnnouncementWithState[] = getActiveAnnouncements(announcements, authed, now).map(
|
const visible = includeExpired
|
||||||
(a) => ({
|
? getPublishedAnnouncements(announcements, authed, now)
|
||||||
...a,
|
: getActiveAnnouncements(announcements, authed, now);
|
||||||
isRead: isAnnouncementRead(a.id),
|
let items: AnnouncementWithState[] = visible.map((a) => ({
|
||||||
})
|
...a,
|
||||||
);
|
isRead: isAnnouncementRead(a.id),
|
||||||
|
}));
|
||||||
|
|
||||||
if (category) {
|
if (category) {
|
||||||
items = items.filter((a) => a.category === category);
|
items = items.filter((a) => a.category === category);
|
||||||
|
|
@ -86,7 +94,7 @@ export function useAnnouncements(options: UseAnnouncementsOptions = {}) {
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [category, stateVersion, tick]);
|
}, [category, includeExpired, stateVersion, tick]);
|
||||||
|
|
||||||
// Schedule a re-render when the next announcement starts or expires
|
// Schedule a re-render when the next announcement starts or expires
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -54,28 +54,17 @@ export const announcements: Announcement[] = [
|
||||||
url: "/changelog",
|
url: "/changelog",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
id: "announcement-1",
|
// id: "announcement-1",
|
||||||
title: "Introducing What's New",
|
// title: "Introducing What's New",
|
||||||
description: "All major product updates will be posted here.",
|
// description: "All major product updates will be posted here.",
|
||||||
category: "feature",
|
// category: "feature",
|
||||||
date: "2026-02-17T00:00:00Z",
|
// date: "2026-02-17T00:00:00Z",
|
||||||
startTime: "2026-02-17T00:00:00Z",
|
// startTime: "2026-02-17T00:00:00Z",
|
||||||
endTime: "2026-02-20T00:00:00Z",
|
// endTime: "2026-02-20T00:00:00Z",
|
||||||
audience: "all",
|
// audience: "all",
|
||||||
isImportant: false,
|
// 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,
|
|
||||||
},
|
|
||||||
// {
|
// {
|
||||||
// id: "2026-02-10-podcast-improvements",
|
// id: "2026-02-10-podcast-improvements",
|
||||||
// title: "Podcast Generation Improvements",
|
// title: "Podcast Generation Improvements",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,32 @@ export function announcementMatchesAudience(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when the announcement has been published (startTime has
|
||||||
|
* passed), regardless of whether its visibility window has expired.
|
||||||
|
* Used for archive views like the announcements page.
|
||||||
|
*/
|
||||||
|
export function isAnnouncementPublished(announcement: Announcement, now = new Date()): boolean {
|
||||||
|
const start = new Date(announcement.startTime).getTime();
|
||||||
|
if (Number.isNaN(start)) return false;
|
||||||
|
return now.getTime() >= start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter announcements to all published ones (including expired) targeted
|
||||||
|
* at the given audience. Powers archive views; use getActiveAnnouncements
|
||||||
|
* for toasts, spotlights, and unread badges.
|
||||||
|
*/
|
||||||
|
export function getPublishedAnnouncements(
|
||||||
|
announcements: Announcement[],
|
||||||
|
isAuthenticated: boolean,
|
||||||
|
now = new Date()
|
||||||
|
): Announcement[] {
|
||||||
|
return announcements.filter(
|
||||||
|
(a) => isAnnouncementPublished(a, now) && announcementMatchesAudience(a, isAuthenticated)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter announcements to only those that are currently active and
|
* Filter announcements to only those that are currently active and
|
||||||
* targeted at the given audience.
|
* targeted at the given audience.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue