feat(12-07): gate /setup route on setupComplete (gap 5)
- Reverse-gate the /setup route: setupComplete===true → SetupPage alreadyLocked (Surface 8 'Setup already complete'); loading → no-flash placeholder; else wizard - Add App.test.tsx reverse-gate tests (already-complete surface + active wizard on /setup) - SetupPage mock now respects the alreadyLocked prop
This commit is contained in:
+31
-3
@@ -81,11 +81,21 @@ export default function App() {
|
||||
// Fetch current user once at the app shell level so AppNav has member data on
|
||||
// ALL routes. This is the same query key (['me']) used by CalendarShell, so
|
||||
// TanStack Query deduplicates the request — no double fetch.
|
||||
//
|
||||
// gap 6 (mechanism (ii) — ['me'] staleness, NOT a linking gap; see SUMMARY):
|
||||
// the first-login claim in upsertUser (auth/user.ts) preserves the same users.id,
|
||||
// so the wizard-stored CalDAV credential stays linked → needsProviderSetup is
|
||||
// correctly FALSE in the DB after the operator authenticates post-wizard. The bug
|
||||
// was purely client-cache: a ['me'] entry populated BEFORE the claim (e.g. an
|
||||
// earlier pre-auth visit) served a stale needsProviderSetup=true for up to 5
|
||||
// minutes, so the "Set up your calendar" banner kept showing. Set staleTime 0 on
|
||||
// the boot ['me'] query so the authenticated app shell always refetches member
|
||||
// status on entry — needsProviderSetup then reflects the just-claimed credential.
|
||||
const meQuery = useQuery({
|
||||
queryKey: ['me'],
|
||||
queryFn: fetchMe,
|
||||
retry: false,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
staleTime: 0,
|
||||
});
|
||||
|
||||
// isAdmin from /api/me — used for UX gating only (D-03). Server enforces 403.
|
||||
@@ -135,8 +145,26 @@ export default function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{/* /setup route — standalone wizard, no AppNav/BottomTabBar shell (UI-SPEC §Routing) */}
|
||||
<Route path="/setup" element={<SetupPage />} />
|
||||
{/* /setup route — standalone wizard, no AppNav/BottomTabBar shell (UI-SPEC §Routing).
|
||||
Reverse gate (gap 5, T-12-04): once setup is complete the wizard must NOT re-mount.
|
||||
- While setupQuery is loading → render the no-flash placeholder (no wizard before
|
||||
status resolves), mirroring the `*`-route loading gate below.
|
||||
- setupComplete === true → render SetupPage with alreadyLocked → Surface 8
|
||||
("Setup already complete"), keeping the operator on /setup with a terminal surface.
|
||||
- setupComplete === false (or undefined post-load) → active wizard, as before.
|
||||
The backend already 423s setup mutations; this is the matching frontend reverse-gate. */}
|
||||
<Route
|
||||
path="/setup"
|
||||
element={
|
||||
setupLoading ? (
|
||||
<div aria-hidden="true" />
|
||||
) : setupComplete === true ? (
|
||||
<SetupPage alreadyLocked={true} />
|
||||
) : (
|
||||
<SetupPage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* All other routes are gated on setup completion */}
|
||||
<Route
|
||||
|
||||
Reference in New Issue
Block a user