fix(customer): unwrap /customer/* from RequireFeature gate

RequireFeature calls useFeatureFlags(), which throws unless mounted
inside FeatureFlagsProvider — and that provider only wraps
AdminLayout. So unauthenticated visitors hitting /customer/login
crashed into the React error boundary with 'Oops! Something went
wrong'.

The customerPortal flag continues to hide every admin-side surface
(sidebar entry, /admin/customers routes, CustomerAccountPicker on
event forms), which is what the flag is actually for. The
customer-side tree stays reachable so existing customers can still
log in even if the admin flips the flag off temporarily.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Luca
2026-05-11 01:30:19 +02:00
co-authored by Claude Opus 4.6
parent f048011324
commit da08a5828a
+33 -28
View File
@@ -175,36 +175,41 @@ function App() {
<Route path="/invite/:token" element={<AcceptInvitePage />} /> <Route path="/invite/:token" element={<AcceptInvitePage />} />
{/* Customer surface (#354). Strictly separate provider / {/* Customer surface (#354). Strictly separate provider /
cookie / API surface from /admin/*. Gated by the cookie / API surface from /admin/*. The customerPortal
customerPortal feature flag — when off, all feature flag hides the *admin-side* surfaces (sidebar
/customer/* URLs redirect to /admin/dashboard. */} entry, /admin/customers routes, CustomerAccountPicker)
<Route element={<RequireFeature flag="customerPortal" fallback="/admin/login" />}> via RequireFeature. The customer-side /customer/*
<Route path="/customer/*" element={ tree stays publicly reachable so existing customers
<CustomerAuthProvider> can still log in even if the admin temporarily flips
<Routes> the flag off — and because RequireFeature reads from
{/* Public surfaces: login, accept-invite, reset — FeatureFlagsProvider (admin-only context), gating
no CustomerLayout (their own branded shells). */} these routes here would crash unauthenticated
<Route path="login" element={<CustomerLoginPage />} /> visitors with an unmounted-provider error. */}
<Route path="invite/:token" element={<CustomerAcceptInvitePage />} /> <Route path="/customer/*" element={
<Route path="reset-password/:token" element={<CustomerResetPasswordPage />} /> <CustomerAuthProvider>
<Routes>
{/* Public surfaces: login, accept-invite, reset —
no CustomerLayout (their own branded shells). */}
<Route path="login" element={<CustomerLoginPage />} />
<Route path="invite/:token" element={<CustomerAcceptInvitePage />} />
<Route path="reset-password/:token" element={<CustomerResetPasswordPage />} />
{/* Authenticated surfaces share the sidebar layout {/* Authenticated surfaces share the sidebar layout
(Outlet pattern, mirrors AdminLayout). The (Outlet pattern, mirrors AdminLayout). The
CustomerLayout itself enforces auth — bouncing CustomerLayout itself enforces auth — bouncing
unauthenticated visitors to /customer/login. */} unauthenticated visitors to /customer/login. */}
<Route element={<CustomerLayout />}> <Route element={<CustomerLayout />}>
<Route path="dashboard" element={<CustomerDashboardPage />} /> <Route path="dashboard" element={<CustomerDashboardPage />} />
<Route path="calendar" element={<CustomerCalendarPage />} /> <Route path="calendar" element={<CustomerCalendarPage />} />
<Route path="quotes" element={<CustomerQuotesPage />} /> <Route path="quotes" element={<CustomerQuotesPage />} />
<Route path="bills" element={<CustomerBillsPage />} /> <Route path="bills" element={<CustomerBillsPage />} />
<Route path="profile" element={<CustomerProfilePage />} /> <Route path="profile" element={<CustomerProfilePage />} />
</Route> </Route>
<Route index element={<Navigate to="/customer/dashboard" replace />} /> <Route index element={<Navigate to="/customer/dashboard" replace />} />
</Routes> </Routes>
</CustomerAuthProvider> </CustomerAuthProvider>
} /> } />
</Route>
{/* Public legal pages */} {/* Public legal pages */}
<Route path="/impressum" element={<LegalPage />} /> <Route path="/impressum" element={<LegalPage />} />