Pre-dedup: 7 calls to /api/public/settings on a single /admin/login page load — 4 from raw-fetch consumers + 3 from React Query consumers using inconsistent queryKeys. Captured live in Chrome DevTools. Post-dedup: 1 call. Verified by tests/e2e/public-settings-dedup.spec.ts. Adds: - frontend/src/hooks/usePublicSettings.ts — single React Query hook, 60s staleTime, queryKey ['public-settings']. Vitest with mocked api proves multi-mount dedup. - Extended PublicSettings interface with seo_meta_* fields used by RobotsMetaTags and branding_logo_* fields used by GalleryView/AdminHeader. Migrates 19 call sites across 4 risk-ordered rounds: - Round A (high fan-in): GlobalThemeProvider, MaintenanceContext (with refetchInterval to preserve maintenance polling), MaintenanceWrapper (drops the now-redundant per-route ping; axios interceptor already handles 503), AdminHeader. - Round B (gallery/login): GalleryView, GalleryPage, ClientAccessPage, AdminLoginPage, MaintenanceMode. - Round C (decorative): RobotsMetaTags, DynamicFavicon, CMSContentBlock, ReCaptcha, useWatermarkSettings (rips out raw fetch + local state), LegalPage. - Round D (queryKey alignment): useLocalizedDate, UserPhotoUpload, CreateEventPage. EventDetailsPage Round D ships in the follow-up commit that adds presigned-download UI on the same page. App.tsx (AnalyticsBootstrap) and EventDetailsPage are deferred to later commits — both files mix #325 changes with backend feature work.
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { MaintenanceMode } from './MaintenanceMode';
|
|
import { useMaintenanceMode } from '../contexts/MaintenanceContext';
|
|
import { setMaintenanceModeCallback, api } from '../config/api';
|
|
|
|
interface MaintenanceWrapperProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
// Maintenance detection now lives in two places:
|
|
// 1. The axios interceptor in config/api.ts flips the flag on any 503 response.
|
|
// 2. MaintenanceContext polls /public/settings every 30s and reads the explicit
|
|
// maintenance_mode field (via the shared usePublicSettings hook).
|
|
// This wrapper only needs to gate the rendered tree on the resulting state.
|
|
export const MaintenanceWrapper: React.FC<MaintenanceWrapperProps> = ({ children }) => {
|
|
const location = useLocation();
|
|
const { isMaintenanceMode, setMaintenanceMode } = useMaintenanceMode();
|
|
const [hasAdminSession, setHasAdminSession] = useState(false);
|
|
|
|
const isAdminRoute = location.pathname.startsWith('/admin');
|
|
|
|
useEffect(() => {
|
|
let isMounted = true;
|
|
|
|
const checkAdminSession = async () => {
|
|
if (!isAdminRoute) {
|
|
setHasAdminSession(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await api.get<{ valid: boolean; type: string }>('/auth/session');
|
|
if (isMounted) {
|
|
setHasAdminSession(Boolean(response.data?.valid && response.data.type === 'admin'));
|
|
}
|
|
} catch {
|
|
if (isMounted) {
|
|
setHasAdminSession(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
checkAdminSession();
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, [isAdminRoute]);
|
|
|
|
useEffect(() => {
|
|
setMaintenanceModeCallback((enabled: boolean) => {
|
|
setMaintenanceMode(enabled);
|
|
});
|
|
}, [setMaintenanceMode]);
|
|
|
|
if (isMaintenanceMode && (!isAdminRoute || !hasAdminSession)) {
|
|
return <MaintenanceMode />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
};
|