garage-erp/apps/dashboard/shared/components/onboarding-gate.tsx
ERP-System 97ad275588 feat(dashboard): add onboarding gate forcing company setup before app use
After login/activation, show a full-screen loader while company settings
resolve, then force the user onto /settings/company and block navigation to
any other route until the company info is filled in. The gate releases once the
company record has a name; fails open on API error so a transient failure can
never lock the user out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:58:37 +04:00

73 lines
2.5 KiB
TypeScript

"use client"
import { useEffect, useMemo } from "react"
import { usePathname, useRouter } from "next/navigation"
import { useQuery } from "@tanstack/react-query"
import { Loader2 } from "lucide-react"
import { useAuthApi } from "@/shared/useApi"
import { SETTINGS_ROUTES } from "@garage/api"
const COMPANY_SETUP_PATH = "/settings/company"
function FullScreenLoader({ label }: { label: string }) {
return (
<div className="bg-background fixed inset-0 z-50 flex flex-col items-center justify-center gap-3">
<Loader2 className="text-primary size-8 animate-spin" />
<p className="text-muted-foreground text-sm">{label}</p>
</div>
)
}
/**
* Onboarding gate.
*
* After login/activation the workspace's company information must be filled in
* before the rest of the app is usable. While the settings record loads we show
* a full-screen loader; if the company isn't set up yet we force the user onto
* the company-settings page and block navigation to any other route until it is.
*/
export function OnboardingGate({ children }: { children: React.ReactNode }) {
const api = useAuthApi()
const router = useRouter()
const pathname = usePathname()
const { data, isLoading, isError } = useQuery({
queryKey: [SETTINGS_ROUTES.INDEX],
queryFn: () => api.settings.fetch(),
staleTime: 5 * 60 * 1000,
retry: 1,
})
const isCompanyFilled = useMemo(() => {
const raw = (data as any)?.data
const record = Array.isArray(raw) ? raw[0] : raw
return Boolean(record?.id) && Boolean(String(record?.name ?? "").trim())
}, [data])
const onCompanyPage = pathname?.startsWith(COMPANY_SETUP_PATH) ?? false
// Onboarding is required once we know (loaded, no error) the company isn't set up.
// Fail open on error so a transient API failure can never lock the user out.
const mustOnboard = !isLoading && !isError && !isCompanyFilled
useEffect(() => {
if (mustOnboard && !onCompanyPage) {
router.replace(COMPANY_SETUP_PATH)
}
}, [mustOnboard, onCompanyPage, router])
// Initial load — determine onboarding status before showing anything.
if (isLoading) {
return <FullScreenLoader label="Loading your workspace…" />
}
// Needs onboarding and not yet on the company page → show loader while redirecting,
// so other pages never flash or become usable.
if (mustOnboard && !onCompanyPage) {
return <FullScreenLoader label="Let's set up your company first…" />
}
return <>{children}</>
}