"use client" /** * LoginPage — identifier first, then whatever the active flow says comes next. * * The identifier is always its own step, because it is what decides how the * person authenticates. Asking for a password up front means asking for one from * people who will never type it, and a password field that turns out to be * irrelevant is worse than a second step. * * Everything after it is data. The flow authored at `/builder/sign-in-flows` * supplies an ordered list of steps, so this component walks that list rather * than hard-coding a branch per variant: an auth step (password or SSO) and any * number of choice steps for questions the credentials cannot imply, such as * which app this workspace bought or which kind of person is signing in. See * `lib/login-flow.ts` for the vocabulary. * * Each step owns the page `h1`, so there is exactly one heading at a time and it * names the step you are on. Later steps keep the identifier visible: the whole * point of collecting it first is that the rest can say who is signing in. * * Demo behaviour: no auth server, so any non-empty password opens a session and * the SSO step contacts no provider. See `lib/auth-session.ts`. */ import * as React from "react" import { useNavigate, useSearchParams } from "react-router" import { Button } from "@/components/ui/button" import { Field, FieldLabel } from "@/components/ui/field" import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, } from "@/components/ui/input-group" import { Separator } from "@/components/ui/separator" import { AuthShell } from "@/components/auth/auth-shell" import { LoginChoiceStep } from "@/components/auth/login-choice-step" import { LoginSsoStep } from "@/components/auth/login-sso-step" import { signIn } from "@/lib/auth-session" import { applyLoginSession, flowSession, getActiveFlow, nextApplicableStep, type AuthStep, type ChoiceOption, type FlowRunContext, type FlowSession, type WorkspaceRole, } from "@/lib/login-flow" import { PRODUCTS_HOME_PATH, postAuthLandingPath, postChoiceLandingPath, safeInternalPath, type PostChoiceAllowance, } from "@/lib/post-auth-landing" import { productSlug, type Product } from "@exxatdesignux/product-framework" import { useDocumentTitle } from "@/hooks/use-document-title" const IDENTIFIER_FIELD_ID = "login-identifier" const PASSWORD_FIELD_ID = "login-password" const ERROR_ID = "login-error" function LoginError({ message }: { message: string }) { return ( ) } /** Step one. The identifier decides the second step, so it is asked alone. */ function IdentifierStep({ identifier, onIdentifierChange, error, onSubmit, inputRef, }: { identifier: string onIdentifierChange: (value: string) => void error: string | null onSubmit: (event: React.FormEvent) => void inputRef: React.RefObject }) { return ( <> {/* Sans, not the DS `font-heading` serif. The auth pages are the only place the product speaks before a workspace exists, and the production sign-in page sets that voice in the sans face. */}

Access all your Exxat Applications

Username or email