fix(maintenance): never block /admin/* with the maintenance screen

The previous wrapper gated /admin/* on a /auth/session check and only showed
the panel when an admin session was detected. Two failures:

1. The session check effect depended on `isAdminRoute` (a boolean), so the
   client-side login → dashboard navigation (both /admin/*) never re-ran it.
   hasAdminSession stayed stale-false from the logged-out /admin/login render,
   so a freshly logged-in admin landed on the maintenance screen anyway.
2. It also hid /admin/login itself (the catch-22).

Fix: the maintenance screen only blocks customer/gallery/public routes —
/admin/* is never blocked. The admin auth layer already handles access
(AdminLayout redirects a logged-out admin to /admin/login), so no session
probe is needed here. Removes the fragile /auth/session dependency entirely.

Backend skipPaths (/api/auth/admin/login + /api/auth/session) stays: login and
AdminAuthContext's token validation must still work during maintenance.
This commit is contained in:
Luca
2026-06-13 14:18:02 +02:00
parent 249313072b
commit fdde4696e7
+13 -40
View File
@@ -1,58 +1,31 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { MaintenanceMode } from './MaintenanceMode';
import { useMaintenanceMode } from '../contexts/MaintenanceContext';
import { setMaintenanceModeCallback, api } from '../config/api';
import { setMaintenanceModeCallback } from '../config/api';
interface MaintenanceWrapperProps {
children: React.ReactNode;
}
// Maintenance detection now lives in two places:
// Maintenance detection 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.
//
// The maintenance screen ONLY blocks customer/gallery/public routes. Admin
// routes (/admin/*) are never blocked: an admin must always be able to reach
// the panel to turn maintenance back off, and the admin auth layer already
// handles access (AdminLayout redirects a logged-out admin to /admin/login).
// Gating /admin/* here on an "is the admin logged in?" check is what caused the
// lockout — it hid the login page itself, and after login the check went stale
// (login → dashboard is a client-side nav within /admin, so it never re-ran),
// leaving a logged-in admin stuck on the maintenance screen.
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');
// The admin login page must ALWAYS render during maintenance — it's how an
// admin gets a session to bypass it. Without this exemption a logged-out
// admin sees the maintenance screen over the login form (catch-22: needs a
// session to get past maintenance, but the login page that grants one is
// hidden).
const isAdminLoginRoute = location.pathname.startsWith('/admin/login');
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) => {
@@ -60,7 +33,7 @@ export const MaintenanceWrapper: React.FC<MaintenanceWrapperProps> = ({ children
});
}, [setMaintenanceMode]);
if (isMaintenanceMode && !isAdminLoginRoute && (!isAdminRoute || !hasAdminSession)) {
if (isMaintenanceMode && !isAdminRoute) {
return <MaintenanceMode />;
}