mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
293506217e
Server-side notice registry with per-user condition evaluation (firstLogin, existingUserBeforeVersion, addonEnabled, dateWindow, role, custom). Notices are sorted by priority then severity, filtered against dismissals stored in a new user_notice_dismissals table, and served via GET /api/system-notices/active + POST /api/system-notices/:id/dismiss. Client renders notices through a host component that partitions by display type (modal / banner / toast). The modal renderer supports multi-page pagination with directional slide transitions, keyboard navigation, and correct dismiss-all semantics on CTA / X / ESC. Dismissals are optimistic with a single background retry. Includes 3.0.0 upgrade notices (v3-photos, v3-journey, v3-features), onboarding welcome modal, and full i18n coverage across 15 languages. The /journey route is addon-gated on both client and server. Also includes: unit + integration test suites, registry integrity test that validates action CTA IDs against client source, and technical documentation in docs/system-notices.md.
32 lines
1.1 KiB
TypeScript
32 lines
1.1 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { useSystemNoticeStore } from '../../store/systemNoticeStore.js';
|
|
import { ModalRenderer } from './SystemNoticeModal.js';
|
|
import { BannerRenderer, ToastRenderer } from './SystemNoticeBanner.js';
|
|
|
|
export function SystemNoticeHost() {
|
|
const { notices, loaded } = useSystemNoticeStore();
|
|
|
|
// Notices are fetched by authStore after login (see App.tsx / authStore modification).
|
|
// Cold-session fetch (page reload with valid session) is triggered here:
|
|
useEffect(() => {
|
|
// Only fetch if not already loaded (authStore may have already triggered)
|
|
if (!loaded) {
|
|
useSystemNoticeStore.getState().fetch();
|
|
}
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
if (!loaded) return null;
|
|
|
|
const modals = notices.filter(n => n.display === 'modal');
|
|
const banners = notices.filter(n => n.display === 'banner');
|
|
const toasts = notices.filter(n => n.display === 'toast');
|
|
|
|
return (
|
|
<>
|
|
<BannerRenderer notices={banners} />
|
|
<ModalRenderer notices={modals} />
|
|
<ToastRenderer notices={toasts} />
|
|
</>
|
|
);
|
|
}
|