feat(06-05): gate app render behind AuthSplash (no pre-auth flash)
- Create AuthSplash component: full-screen centered column, loading/redirecting/dead-end states - loading state: Loader2 spinner + 'Signing you in' heading + 'Taking you to the sign-in page…' body - redirecting state: spinner + customisable heading/body (defaults to cold-load copy) - dead-end state: tap-to-retry button (clearLoginRedirect + maybeRedirectToLogin) - role=status, aria-label, inline styles matching SkeletonCalendar layout approach - CalendarShell: early-return AuthSplash on meQuery.isLoading (no skeleton before auth) - CalendarShell: replace 'Sign-in required' role=alert block with AuthSplash state=redirecting - CalendarShell: isInitialLoading no longer includes meQuery.isLoading (handled by early return)
This commit is contained in:
@@ -46,6 +46,7 @@ import { hydrateEvents } from '../lib/hydrateEvents.js'
|
||||
import { maybeRedirectToLogin, clearLoginRedirect } from '../lib/loginRedirect.js'
|
||||
import { buildCalendarConfig, SX_FIRST_DAY_OF_WEEK } from '../lib/calendarConfig.js'
|
||||
import { useCalendarStore } from '../store/calendarStore.js'
|
||||
import { AuthSplash } from './AuthSplash.js'
|
||||
import { EventDetailPopover } from './EventDetailPopover.js'
|
||||
import { EventForm } from './EventForm.js'
|
||||
import { DeleteConfirmationDialog } from './DeleteConfirmationDialog.js'
|
||||
@@ -209,29 +210,32 @@ export function CalendarShell({ onOpenSettings }: { onOpenSettings?: () => void
|
||||
|
||||
// ── Render helpers ────────────────────────────────────────────────────────
|
||||
|
||||
// Determine which content to show in the calendar area
|
||||
const isInitialLoading = meQuery.isLoading || (eventsQuery.isLoading && !eventsQuery.data)
|
||||
// Determine which content to show in the calendar area.
|
||||
// Note: meQuery.isLoading is handled above by an early AuthSplash return — it
|
||||
// will always be false when we reach this line (meQuery.isSuccess is guaranteed).
|
||||
const isInitialLoading = eventsQuery.isLoading && !eventsQuery.data
|
||||
const isEventsError = eventsQuery.isError
|
||||
|
||||
const phone = isPhone()
|
||||
|
||||
// ── Sign-in required ───────────────────────────────────────────────────────
|
||||
// ── Auth splash (D-10) ────────────────────────────────────────────────────
|
||||
// Gate the calendar render on auth state so no calendar shell, skeleton, or
|
||||
// "Sign-in required" alert paints before Authelia (D-10 success criterion 5).
|
||||
//
|
||||
// loading: meQuery is still pending — show the neutral "Signing you in" splash.
|
||||
// No skeleton, no CalendarContent — nothing app-specific until authed.
|
||||
// isError: meQuery failed — the maybeRedirectToLogin() useEffect fires immediately
|
||||
// after this render. Show the "redirecting" splash while the browser navigates.
|
||||
// If the one-shot guard is exhausted (flag already set), maybeRedirectToLogin
|
||||
// returns false and this splash stays — the dead-end "Tap to try again" is
|
||||
// rendered by AuthSplash's dead-end state (wired in Task 3 via sessionExpired).
|
||||
if (meQuery.isLoading) {
|
||||
return <AuthSplash state="loading" />
|
||||
}
|
||||
|
||||
if (meQuery.isError) {
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
style={{
|
||||
color: 'var(--color-destructive)',
|
||||
padding: 'var(--space-4)',
|
||||
background: 'var(--color-surface-dim)',
|
||||
borderRadius: 'var(--space-2)',
|
||||
fontFamily: 'var(--font-family-base)',
|
||||
}}
|
||||
>
|
||||
Sign-in required
|
||||
</div>
|
||||
)
|
||||
// useEffect at line 196 calls maybeRedirectToLogin() — splash shows while redirect fires
|
||||
return <AuthSplash state="redirecting" />
|
||||
}
|
||||
|
||||
// ── Calendar content ───────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user