@medalsocial/booking 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +2 -1
  3. package/dist/core/config.d.mts +14 -0
  4. package/dist/core/config.mjs +9 -4
  5. package/dist/core/config.mjs.map +1 -1
  6. package/dist/core/draft-store.d.mts +5 -0
  7. package/dist/core/draft-store.mjs +1 -0
  8. package/dist/core/draft-store.mjs.map +1 -1
  9. package/dist/core/index.d.mts +2 -1
  10. package/dist/core/index.mjs +2 -1
  11. package/dist/core/machine.d.mts +6 -0
  12. package/dist/core/machine.mjs +22 -3
  13. package/dist/core/machine.mjs.map +1 -1
  14. package/dist/core/range.d.mts +30 -0
  15. package/dist/core/range.mjs +32 -0
  16. package/dist/core/range.mjs.map +1 -0
  17. package/dist/next/catalogue.mjs +4 -5
  18. package/dist/next/catalogue.mjs.map +1 -1
  19. package/dist/next/handler.mjs +63 -5
  20. package/dist/next/handler.mjs.map +1 -1
  21. package/dist/next/medal.mjs +47 -15
  22. package/dist/next/medal.mjs.map +1 -1
  23. package/dist/next/routes/create.mjs +3 -6
  24. package/dist/next/routes/create.mjs.map +1 -1
  25. package/dist/next/routes/manage.mjs +5 -8
  26. package/dist/next/routes/manage.mjs.map +1 -1
  27. package/dist/next/routes/shared.mjs +57 -14
  28. package/dist/next/routes/shared.mjs.map +1 -1
  29. package/dist/react/BookingWizard.d.mts +7 -1
  30. package/dist/react/BookingWizard.mjs +15 -0
  31. package/dist/react/BookingWizard.mjs.map +1 -1
  32. package/dist/react/index.d.mts +2 -2
  33. package/dist/react/labels/screens.mjs +4 -0
  34. package/dist/react/labels/screens.mjs.map +1 -1
  35. package/dist/react/useBooking.d.mts +11 -1
  36. package/dist/react/useBooking.mjs +105 -29
  37. package/dist/react/useBooking.mjs.map +1 -1
  38. package/dist/react/wizard/index.d.mts +2 -2
  39. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"BookingWizard.mjs","names":["LoginSheet"],"sources":["../../src/react/BookingWizard.tsx"],"sourcesContent":["/**\n * `<BookingWizard>` — the booking flow, from «who is it for?» to the\n * confirmation, drawn with the `@medalsocial/meda/booking` screens over\n * `useBooking()`.\n *\n * The hook owns the machine, the fetches and the stores; this file owns the\n * page: which step is on screen (ONE at a time — the details step's nonce and\n * the submit flag depend on it), focus and scroll on every move, the restore\n * gate's pre-hydration script and skeleton, the header, and what each screen\n * is handed (prices, the weekend note, ages, the possessive, the calendar\n * file) out of the site's config.\n */\n\nimport {\n BookingButton,\n BookingRecap,\n Confirmation,\n DetailsScreen,\n LiveStatus,\n ServiceScreen,\n StylistScreen,\n SummaryBar,\n TakenToast,\n TimeScreen,\n TimeScreenSkeleton,\n WhoScreen,\n} from '@medalsocial/meda/booking';\nimport { ArrowLeft } from 'lucide-react';\nimport {\n type ReactNode,\n Suspense,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { fill, fillParts, labelText } from '../core/labels';\nimport { SELF_KEY, type WizardPerson, type WizardState, type WizardStep } from '../core/machine';\nimport type { BookingServiceDto } from '../core/types';\nimport { visitServicesOf } from '../core/visit';\nimport type { PortalActions } from './actions';\nimport type { BookingKit } from './kit';\nimport { LoginSheet } from './LoginSheet';\nimport type { BookingOverrides, ResolvedBooking } from './Provider';\nimport { useBookingKit } from './Provider';\nimport { screenLabels } from './screen-labels';\nimport {\n type BookingConfirmation,\n type BookingController,\n type BookingSeed,\n useBooking,\n} from './useBooking';\nimport {\n addedChildEntries,\n confirmationProps,\n detailsLines,\n detailsScreenBase,\n familyEntries,\n GUEST_ADULT_SEAT,\n guestChoices,\n partyPeople,\n partySizeWord,\n recapProps,\n serviceFitsFor,\n serviceScreenBase,\n stylistFace,\n weekendNoteFor,\n} from './wizard/adapters';\nimport { useWallClock } from './wizard/wall-clock';\nimport { forMedaScreen, wizardErrorText } from './wizard-error';\n\nexport interface BookingWizardProps extends BookingOverrides {\n /** What the page prefetched (the `/next` loader's seed). */\n seed: BookingSeed;\n /** This request's phone and address, over `config.contact` (the loader's `contact`). */\n contact?: { phone?: string | null; address?: string | null };\n /** The parent the page found a portal session for; `null` for a guest. */\n guardian?: BookingGuardianProp;\n /**\n * The app's server actions for the login sheet (Decision 8). Without them —\n * or with `config.portal.enabled` off — no login is offered.\n */\n actions?: Pick<PortalActions, 'startLogin' | 'startVipps'>;\n /** How many days the window covers. Default `config.window.rangeDays`. */\n rangeDays?: number;\n /**\n * The site's origin (`https://…`), so the manage links inside the calendar\n * file are absolute. Absent, they stay root-relative.\n */\n siteUrl?: string;\n /** A monitoring tap: the step the visitor is on, and a submission's outcome. */\n onEvent?: (event: BookingWizardEvent) => void;\n}\n\ntype BookingGuardianProp = Parameters<typeof useBooking>[0]['guardian'];\n\nexport type BookingWizardEvent =\n | { type: 'step'; step: WizardStep | 'confirmed' }\n | { type: 'submit_ok'; bookings: number }\n | { type: 'submit_error'; code: NonNullable<WizardState['error']> };\n\n/** The bundler's build mode, which it inlines; absent outside a bundler. */\ndeclare const process: { env: { NODE_ENV?: string } } | undefined;\n\n/** The wizard's root, for the inline restore gate to find before hydration. */\nconst WIZARD_ROOT_ID = 'booking-wizard';\n\n/** The time step's heading, which a lost slot sends the visitor back to. */\nconst TIME_HEADING_ID = 'booking-time-heading';\n\n/** The heading each screen focuses when the visitor arrives on it. */\nconst STEP_HEADINGS: Record<WizardStep | 'confirmed', string> = {\n who: 'booking-who-heading',\n service: 'booking-service-heading',\n when: 'booking-stylist-heading',\n details: 'booking-details-heading',\n confirmed: 'booking-confirmed-heading',\n};\n\nconst STEPS: readonly WizardStep[] = ['who', 'service', 'when', 'details'];\n\n/** Smooth, or instant for a visitor who asked for reduced motion. */\nfunction scrollToTop(target: HTMLElement | null): void {\n if (!target || typeof target.scrollIntoView !== 'function') return;\n const reduced =\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n target.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'start' });\n}\n\n/**\n * The wizard, behind its own Suspense boundary: `useSearchParams` throws to\n * the nearest one during a static prerender, and without ours that is the\n * root layout's — the whole page would ship empty until hydration.\n */\nexport function BookingWizard(props: BookingWizardProps) {\n return (\n <Suspense fallback={null}>\n <BookingWizardShell {...props} />\n </Suspense>\n );\n}\n\nfunction BookingWizardShell(props: BookingWizardProps) {\n const resolved = useBookingKit(props, props.contact);\n const loginOffered = props.actions !== undefined && resolved.kit.config.portal.enabled;\n const booking = useBooking({ ...props, loginOffered });\n const { kit } = booking;\n const { labels } = kit;\n\n // `account.required` with no way to log in: every «Bekreft» would be refused.\n const unreachableLogin = kit.config.account.required && !loginOffered;\n useEffect(() => {\n if (\n unreachableLogin &&\n typeof process !== 'undefined' &&\n process.env.NODE_ENV !== 'production'\n ) {\n console.warn('[booking] account.required without login actions: nobody can log in to book.');\n }\n }, [unreachableLogin]);\n const { state, restore, confirmed, onEvent } = { ...booking, onEvent: props.onEvent };\n\n /**\n * On every change of screen: the wizard's top into view and focus on the\n * new step's heading. Never on the first screen, and never for what the\n * restore gate reveals — that is the page arriving, not the visitor moving.\n */\n const rootRef = useRef<HTMLDivElement>(null);\n const screenKey: WizardStep | 'confirmed' = confirmed !== null ? 'confirmed' : state.step;\n const shownScreen = useRef<WizardStep | 'confirmed' | null>(null);\n const takenSlotTs = booking.slots.takenSlotTs;\n const timeStepReady = booking.slots.ready;\n const focusTimeWhenReady = useRef(false);\n useEffect(() => {\n if (restore.restoring) return;\n const previous = shownScreen.current;\n shownScreen.current = screenKey;\n if (previous === screenKey) return;\n onEvent?.({ type: 'step', step: screenKey });\n if (previous === null) return;\n // A lost slot sends the visitor back for the ALTERNATIVES, on the time half.\n const toTimes = screenKey === 'when' && takenSlotTs !== null;\n const headingId = toTimes ? TIME_HEADING_ID : STEP_HEADINGS[screenKey];\n const heading = document.getElementById(headingId);\n scrollToTop(toTimes ? heading : rootRef.current);\n heading?.focus({ preventScroll: true });\n // The heading focused now may be the skeleton's; focus follows the real one.\n focusTimeWhenReady.current = toTimes && !timeStepReady;\n }, [screenKey, restore.restoring, takenSlotTs, timeStepReady, onEvent]);\n\n /** After a login from the sheet the row that opened it is gone: focus the step's heading. */\n const focusAfterSignIn = useRef(false);\n const signedIn = booking.login.signedIn;\n useEffect(() => {\n if (signedIn === null || !focusAfterSignIn.current) return;\n focusAfterSignIn.current = false;\n document.getElementById(STEP_HEADINGS[state.step])?.focus({ preventScroll: true });\n }, [signedIn, state.step]);\n /**\n * A 401 put the gate back over the form: focus its heading (the details\n * step's id), where the notice beside it says why. Once per loss, and only\n * once the gate is actually on screen — a replay answers behind the skeleton.\n */\n const sessionLost = booking.login.sessionLost;\n const sessionLossFocused = useRef(false);\n useEffect(() => {\n if (!sessionLost) {\n sessionLossFocused.current = false;\n return;\n }\n if (sessionLossFocused.current || restore.restoring || state.step !== 'details') return;\n sessionLossFocused.current = true;\n const heading = document.getElementById(STEP_HEADINGS.details);\n scrollToTop(rootRef.current);\n heading?.focus({ preventScroll: true });\n }, [sessionLost, restore.restoring, state.step]);\n useEffect(() => {\n if (!timeStepReady || !focusTimeWhenReady.current) return;\n focusTimeWhenReady.current = false;\n const active = document.activeElement;\n if (active === null || active === document.body) {\n document.getElementById(TIME_HEADING_ID)?.focus({ preventScroll: true });\n }\n }, [timeStepReady]);\n\n // The monitoring tap for a submission's outcome.\n const lastError = useRef(state.error);\n useEffect(() => {\n if (state.error !== null && state.error !== lastError.current) {\n onEvent?.({ type: 'submit_error', code: state.error });\n }\n lastError.current = state.error;\n }, [state.error, onEvent]);\n const confirmedCount = confirmed?.bookings.length ?? 0;\n useEffect(() => {\n if (confirmedCount > 0) onEvent?.({ type: 'submit_ok', bookings: confirmedCount });\n }, [confirmedCount, onEvent]);\n\n // React owns the root from here on: drop the pre-hydration mark the inline\n // script may have set, and say so, so the script's fallback timer stands down.\n useLayoutEffect(() => {\n if (!restore.hydrated) return;\n rootRef.current?.removeAttribute('data-restoring');\n rootRef.current?.setAttribute('data-hydrated', '');\n }, [restore.hydrated]);\n\n // Steps animate in only once the visitor has moved between them.\n const [arrivalStep, setArrivalStep] = useState<WizardStep | null>(\n restore.restoring ? null : state.step\n );\n if (!restore.restoring && arrivalStep === null) setArrivalStep(state.step);\n const [stepsMovedOnce, setStepsMovedOnce] = useState(false);\n const movedNow = arrivalStep !== null && !restore.restoring && state.step !== arrivalStep;\n if (movedNow && !stepsMovedOnce) setStepsMovedOnce(true);\n const stepsMoved = stepsMovedOnce || movedNow;\n\n const restoreSkeleton = <RestoreSkeleton text={labelText(labels['wizard.restoring'])} />;\n /**\n * The root both screens share: the scroll target, and the element the\n * inline restore gate marks. The script and the hidden skeleton exist only\n * in the server's HTML and the hydration pass that adopts it.\n */\n const shell = (content: ReactNode) => (\n <div\n id={WIZARD_ROOT_ID}\n ref={rootRef}\n className={resolved.classNames.wizard?.root ?? 'group scroll-mt-28'}\n suppressHydrationWarning\n >\n {!restore.hydrated && (\n <>\n <script\n // A fixed script built from constants, run before hydration.\n // biome-ignore lint/security/noDangerouslySetInnerHtml: the restore gate must run before React does.\n dangerouslySetInnerHTML={{\n __html: kit.restoreGate.restoreGateScript(\n WIZARD_ROOT_ID,\n restore.linked,\n restore.resuming\n ),\n }}\n />\n <div className=\"hidden group-data-[restoring]:block\">{restoreSkeleton}</div>\n </>\n )}\n {restore.restoring && restore.hydrated ? (\n restoreSkeleton\n ) : (\n <div className=\"group-data-[restoring]:hidden\">{content}</div>\n )}\n </div>\n );\n\n if (confirmed !== null) {\n return shell(\n <ConfirmationStep booking={booking} resolved={resolved} siteUrl={props.siteUrl} />\n );\n }\n\n const onSignedIn = (who: Parameters<BookingController['login']['signIn']>[0]) => {\n focusAfterSignIn.current = true;\n booking.login.signIn(who);\n };\n // The multi-select service step carries its own total bar and refusal notice.\n const multiSelect = state.step === 'service' && kit.config.party.maxServicesPerPerson > 1;\n /**\n * `account.required`: every booking is a logged-in parent's. The login is\n * not offered along the way — it IS «Bekreft», for a parent not logged in.\n */\n const accountRequired = kit.config.account.required && loginOffered;\n const gated = accountRequired && state.step === 'details' && !booking.login.loggedIn;\n const loginRow =\n loginOffered && !accountRequired ? (\n <LoginRow\n booking={booking}\n actions={props.actions as NonNullable<BookingWizardProps['actions']>}\n overrides={props}\n onSignedIn={onSignedIn}\n />\n ) : null;\n\n return shell(\n <div className=\"space-y-8\">\n <WizardHeader\n kit={kit}\n state={state}\n submitting={booking.submitting}\n onGo={(step) => booking.dispatch({ type: 'goToStep', step })}\n />\n\n {/* The machine's error, on the steps that have nowhere else to put it. */}\n {state.error !== null &&\n state.step !== 'details' &&\n state.step !== 'when' &&\n !multiSelect && (\n <p\n role=\"alert\"\n className={\n resolved.classNames.wizard?.alert ??\n 'rounded-lg border border-destructive/40 bg-destructive/10 px-5 py-3 text-sm'\n }\n >\n {wizardErrorText(labels, state.error, kit.config.party.maxServicesPerPerson)}\n </p>\n )}\n\n {/* What is being booked, first: above the login offer and the form. */}\n {state.step === 'details' && !gated && kit.config.screens.recap && (\n <RecapStep booking={booking} resolved={resolved} />\n )}\n\n {state.step === 'details' && loginRow}\n\n {/* The gate takes a Vipps confirm code itself, in place of the form. */}\n {loginOffered && booking.login.vippsConfirm !== null && signedIn === null && !gated && (\n <LoginSheet\n {...overridesOf(props)}\n actions={props.actions as NonNullable<BookingWizardProps['actions']>}\n presentation=\"sheet\"\n trigger={false}\n vippsConfirm={booking.login.vippsConfirm}\n resumePath={booking.login.resumePath}\n onSignedIn={onSignedIn}\n />\n )}\n\n {/* One box per step, keyed by it, so a step arriving is a mount. */}\n <div\n key={state.step}\n data-booking-step={state.step}\n data-animate={stepsMoved ? '' : undefined}\n className=\"space-y-8\"\n >\n {state.step === 'who' && (\n <WhoStep booking={booking} resolved={resolved} loginRow={loginRow} />\n )}\n {state.step === 'service' && (\n <ServiceStep booking={booking} resolved={resolved} multiSelect={multiSelect} />\n )}\n {state.step === 'when' && <WhenStep booking={booking} resolved={resolved} />}\n {state.step === 'details' &&\n (gated ? (\n <AccountGate\n booking={booking}\n resolved={resolved}\n actions={props.actions as NonNullable<BookingWizardProps['actions']>}\n overrides={props}\n onSignedIn={onSignedIn}\n />\n ) : (\n <DetailsStep booking={booking} resolved={resolved} />\n ))}\n </div>\n\n {!multiSelect && (\n <SummaryBar\n {...(kit.config.screens.summaryDetail\n ? booking.derived.summaryParts\n : { line: booking.derived.summary })}\n canAdvance={booking.derived.canAdvance}\n step={state.step}\n onNext={booking.next}\n hideNextWhenDisabled={kit.config.screens.hideDisabledNext}\n labels={screenLabels(labels)}\n classNames={resolved.classNames.summary}\n />\n )}\n </div>\n );\n}\n\n/** The overrides a nested booking component inherits from the wizard's props. */\nfunction overridesOf(props: BookingOverrides): BookingOverrides {\n return {\n config: props.config,\n labels: props.labels,\n classNames: props.classNames,\n components: props.components,\n };\n}\n\ninterface StepProps {\n booking: BookingController;\n resolved: ResolvedBooking;\n}\n\nfunction WhoStep({ booking, resolved, loginRow }: StepProps & { loginRow: ReactNode }) {\n const { kit, state, people } = booking;\n const guests = useMemo(() => guestChoices(kit), [kit]);\n return (\n <WhoScreen\n labels={screenLabels(kit.labels)}\n format={kit.format}\n people={state.people}\n family={people.family === null ? null : familyEntries(kit, people.family, people.ageDayKey)}\n guestChoices={guests}\n addedChildren={addedChildEntries(kit, state.people, people.ageDayKey)}\n maxPeople={kit.wizard.maxPeople}\n selfKey={SELF_KEY}\n isGuestSeat={kit.wizard.isGuestSeat}\n onChoose={people.choosePeople}\n onAddChild={people.addChild}\n guestParty={\n kit.config.screens.guestParty\n ? { child: kit.wizard.guestChild, adult: GUEST_ADULT_SEAT }\n : undefined\n }\n loginRow={loginRow}\n currentYear={Number(kit.clock.dayKey(booking.slots.fromTs).slice(0, 4))}\n classNames={resolved.classNames.who}\n components={\n resolved.components.PersonCard ? { PersonCard: resolved.components.PersonCard } : undefined\n }\n />\n );\n}\n\nfunction ServiceStep({ booking, resolved, multiSelect }: StepProps & { multiSelect: boolean }) {\n const { kit, state, people, catalogue } = booking;\n const ageOf = (person: WizardPerson) => people.personAge(person, people.ageDayKey);\n const single = state.people.length === 1 ? state.people[0] : null;\n // «Samme som sist» stays a one-tap shortcut while the person's visit is\n // nothing, or just that service (a returning child is seated with it already\n // ticked). Once they have ticked something else, a one-tap pick would REPLACE\n // their visit — and, alone, jump to the time step — so the suggestion joins\n // the visit instead, and never unticks a service already in it.\n const pickFor = (index: number, service: BookingServiceDto, oneTap: () => void) => {\n const visit = state.choices[index] ? [state.choices[index], ...state.extras[index]] : [];\n if (!multiSelect || visit.length === 0 || (visit.length === 1 && visit[0].id === service.id)) {\n oneTap();\n } else if (!visit.some((ticked) => ticked.id === service.id)) {\n booking.toggleServiceFor(index, service);\n }\n };\n return (\n <ServiceScreen\n labels={screenLabels(kit.labels)}\n format={kit.format}\n services={catalogue.services}\n {...serviceScreenBase(kit)}\n serviceFits={serviceFitsFor(kit, state.people.map(ageOf), state.people)}\n initialCategory={catalogue.deepLinkCategory}\n onPick={(service) => pickFor(0, service, () => booking.pickService(service))}\n suggestion={single ? people.suggestionFor(single) : null}\n chosenId={state.choices[0]?.id ?? null}\n childName={single ? (single.name ?? null) : null}\n party={\n state.people.length > 1\n ? {\n people: partyPeople(kit, state.people, ageOf, people.suggestionFor),\n choices: state.choices,\n onPickFor: (index, service) =>\n pickFor(index, service, () => booking.pickServiceFor(index, service)),\n onRemove: (index) =>\n booking.dispatch({\n type: 'choosePeople',\n people: state.people.filter((_, position) => position !== index),\n }),\n }\n : undefined\n }\n selection={\n multiSelect\n ? {\n // Index = seated person; meda reads a missing list (nobody seated) as empty.\n lists: state.choices.map((first, index) =>\n first ? [first, ...state.extras[index]] : []\n ),\n onToggle: booking.toggleServiceFor,\n onContinue: booking.continueFromService,\n total:\n state.items.length === 0\n ? null\n : {\n minutes: kit.wizard.visitMinutes(state.items, state.partyMode),\n priceOre: booking.derived.total,\n },\n canContinue: booking.derived.canAdvance,\n notice:\n state.error === null\n ? null\n : labelText(\n wizardErrorText(\n kit.labels,\n state.error,\n kit.config.party.maxServicesPerPerson\n )\n ),\n labels: kit.labels,\n }\n : undefined\n }\n classNames={resolved.classNames.service}\n components={\n resolved.components.ServiceCard\n ? { ServiceCard: resolved.components.ServiceCard }\n : undefined\n }\n />\n );\n}\n\nfunction WhenStep({ booking, resolved }: StepProps) {\n const { kit, state, catalogue, slots, schedule } = booking;\n const { labels, format, wizard, config } = kit;\n const party = booking.derived.party;\n const items = state.items;\n const phone = config.contact.phone;\n const now = useWallClock(slots.fromTs);\n return (\n <>\n <StylistScreen\n labels={screenLabels(labels)}\n format={format}\n now={now}\n // Not deduplicated: a named stylist has to cover every service of every\n // line — a person's extras included, as their visit is one stylist's.\n serviceIds={items.flatMap((item) => visitServicesOf(item).map((service) => service.id))}\n resources={catalogue.resources}\n loading={!catalogue.resourcesKnown}\n nextAvailableLoading={slots.nextAvailableLoading}\n selectedResourceId={state.resourceId}\n nextAvailableTs={slots.nextAvailableTs}\n pendingName={catalogue.chosenStylistName}\n notice={catalogue.stylistNotice}\n onPick={booking.pickResource}\n firstAvailableFaces={config.screens.firstAvailableFaces}\n edgeFade={config.screens.stylistEdgeFade}\n party={\n party\n ? {\n mode: state.partyMode,\n size: items.length,\n sizeWord: partySizeWord(labels, items.length),\n minutes: {\n sequential: wizard.visitMinutes(items, 'sequential'),\n parallel: wizard.visitMinutes(items, 'parallel'),\n },\n onMode: (mode) => booking.dispatch({ type: 'setPartyMode', mode }),\n }\n : undefined\n }\n classNames={resolved.classNames.stylist}\n components={\n resolved.components.StylistCard\n ? { StylistCard: resolved.components.StylistCard }\n : undefined\n }\n />\n\n {slots.ready ? (\n <TimeScreen\n labels={screenLabels(labels)}\n format={format}\n dayparts={kit.dayparts}\n monthView\n slots={slots.single}\n days={slots.days}\n openDays={schedule.openDays}\n stylistName={catalogue.chosenStylistName}\n takenSlotTs={slots.takenSlotTs}\n now={now}\n phone={phone}\n weekendNote={(dayTs) => weekendNoteFor(kit, party ? items : items.slice(0, 1), dayTs)}\n onPick={booking.pickSlot}\n soonest={\n config.screens.soonest\n ? {\n resolveStylist: (resourceId) => stylistFace(catalogue.resources, resourceId),\n }\n : undefined\n }\n dayFullness={config.screens.dayFullness}\n party={\n party\n ? {\n items,\n mode: state.partyMode,\n slots: slots.party,\n chipEndTs: (slot) => wizard.visitEndTs(items, slot.startTs, 'sequential'),\n // Only a back-to-back family is offered the simultaneous rescue.\n alternativeFor:\n state.partyMode === 'sequential'\n ? (dayTs) =>\n kit.partySlots.partyAlternative({\n sequential: slots.party,\n parallel: slots.alternatives,\n dayTs,\n })\n : undefined,\n resolveStylistName: catalogue.resolveStylistName,\n onPick: booking.pickPartySlot,\n }\n : undefined\n }\n classNames={resolved.classNames.time}\n components={timeComponents(resolved)}\n />\n ) : slots.failed ? (\n <SlotsUnavailable kit={kit} onRetry={booking.refresh} />\n ) : (\n // The step's own shape, so nothing below it moves when it fills in.\n <TimeScreenSkeleton\n labels={screenLabels(labels)}\n days={slots.days.length}\n monthView\n surchargeRow={items.some((item) =>\n visitServicesOf(item).some((service) => service.weekendSurchargePct > 0)\n )}\n />\n )}\n\n {/* Once, outside the skeleton-or-step switch, so a re-read cannot remount it. */}\n <TakenToast labels={screenLabels(labels)} takenSlotTs={slots.takenSlotTs} />\n </>\n );\n}\n\n/**\n * The details step's recap (`config.screens.recap`): the day, hours, who and\n * total of the booking the form below submits, «edit» back to the time step,\n * and a swap to another stylist free at that minute.\n */\nfunction RecapStep({ booking, resolved }: StepProps) {\n const { kit, state, catalogue, slots } = booking;\n const recap = useMemo(\n () => recapProps(kit, state, catalogue.resources, slots.single),\n [kit, state, catalogue.resources, slots.single]\n );\n return (\n <BookingRecap\n lines={recap.lines}\n totalOre={booking.derived.total}\n format={kit.format}\n labels={screenLabels(kit.labels)}\n onEdit={() => booking.dispatch({ type: 'goToStep', step: 'when' })}\n alternatives={recap.alternatives}\n // Alternatives exist only for a timed visit, so there is always a start to keep.\n onSwap={(resourceId) => booking.pickSlot({ startTs: state.startTs as number, resourceId })}\n classNames={resolved.classNames.recap}\n />\n );\n}\n\nfunction timeComponents({ components }: ResolvedBooking) {\n if (!components.DayChip && !components.TimeChip) return undefined;\n return { DayChip: components.DayChip, TimeChip: components.TimeChip };\n}\n\nfunction DetailsStep({ booking, resolved }: StepProps) {\n const { kit, state } = booking;\n const guardian = booking.login.guardian;\n const lines = useMemo(() => detailsLines(kit, state), [kit, state]);\n return (\n <DetailsScreen\n state={forMedaScreen(state)}\n onChange={booking.dispatch}\n onSubmit={booking.submit}\n lines={lines}\n totalOre={booking.derived.total}\n {...detailsScreenBase(kit)}\n submitting={booking.submitting}\n submissionNonce={booking.submissionNonce}\n family={guardian?.family}\n guardianPhone={guardian?.phone ?? null}\n // The address they logged in with: the booking is theirs, by that address.\n emailReadOnly={kit.config.account.required && guardian !== null}\n format={kit.format}\n labels={screenLabels(kit.labels)}\n classNames={resolved.classNames.details}\n components={\n resolved.components.FamilyChip ? { FamilyChip: resolved.components.FamilyChip } : undefined\n }\n />\n );\n}\n\n/**\n * «Bekreft» under `account.required` for a parent not logged in: «Nesten\n * ferdig», a line, and the login — Vipps, or e-mail — in place of the form.\n * The heading carries the details step's id, so the focus a step change moves\n * lands here, and the form's heading takes it over once the parent is in.\n */\nfunction AccountGate({\n booking,\n resolved,\n actions,\n overrides,\n onSignedIn,\n}: StepProps & {\n actions: NonNullable<BookingWizardProps['actions']>;\n overrides: BookingOverrides;\n onSignedIn: (guardian: Parameters<BookingController['login']['signIn']>[0]) => void;\n}) {\n const { labels } = booking.kit;\n const { classNames } = resolved;\n // The details screen's own root and heading slots, after the same defaults.\n const slot = (base: string, extra: string | undefined) => [base, extra].filter(Boolean).join(' ');\n return (\n <section\n aria-labelledby={STEP_HEADINGS.details}\n className={slot('space-y-6', classNames.details?.root)}\n >\n <h2\n id={STEP_HEADINGS.details}\n tabIndex={-1}\n className={slot(\n 'font-sans text-2xl font-bold outline-none md:text-3xl',\n classNames.details?.heading\n )}\n >\n {labelText(labels['wizard.account.heading'])}\n </h2>\n <LiveStatus\n text={booking.login.sessionLost ? labelText(labels['wizard.account.sessionLost']) : null}\n className=\"text-sm font-medium empty:hidden\"\n />\n <p className=\"text-muted-foreground\">{labelText(labels['wizard.account.intro'])}</p>\n {/* What the login is for: the hour held, in the summary bar's own words and slot. */}\n <p\n className={slot(\n 'rounded-lg border border-border bg-card px-4 py-3 text-sm font-medium',\n classNames.summary?.line\n )}\n >\n {booking.derived.summary}\n </p>\n <LoginSheet\n {...overridesOf(overrides)}\n actions={actions}\n presentation=\"gate\"\n resumePath={booking.login.resumePath}\n vippsConfirm={booking.login.vippsConfirm}\n onSignedIn={onSignedIn}\n />\n </section>\n );\n}\n\nfunction ConfirmationStep({ booking, resolved, siteUrl }: StepProps & { siteUrl?: string }) {\n const { kit } = booking;\n // Read once, so «today» cannot become «tomorrow» halfway down the card.\n const [now] = useState(() => Date.now());\n const confirmation = booking.confirmed as BookingConfirmation;\n return (\n <Confirmation\n {...confirmationProps(kit, confirmation, siteUrl)}\n onStartOver={booking.startOver}\n now={now}\n format={kit.format}\n labels={screenLabels(kit.labels)}\n classNames={resolved.classNames.confirmation}\n components={\n resolved.components.PartyLine ? { PartyLine: resolved.components.PartyLine } : undefined\n }\n />\n );\n}\n\n/**\n * «Have an account? Log in» — or, once the parent is known, nothing at all\n * for one who arrived logged in, and one line saying so for one who logged in\n * from the sheet.\n */\nfunction LoginRow({\n booking,\n actions,\n overrides,\n onSignedIn,\n}: {\n booking: BookingController;\n actions: NonNullable<BookingWizardProps['actions']>;\n overrides: BookingOverrides;\n onSignedIn: (guardian: Parameters<BookingController['login']['signIn']>[0]) => void;\n}) {\n const { arrivedAs, signedIn, resumePath } = booking.login;\n const { labels } = booking.kit;\n if (signedIn === null && arrivedAs !== null) return null;\n // Named only by the login itself: an unreadable profile is «Logget inn»,\n // never the name of whoever the page arrived as.\n const who = signedIn?.guardian ?? null;\n const name =\n who === null\n ? null\n : [who.firstName, who.lastName].filter(Boolean).join(' ').trim() || who.email;\n const confirmation =\n signedIn === null\n ? null\n : name === null\n ? labelText(labels['wizard.signedIn'])\n : fill(labels['wizard.signedInAs'], { name });\n return (\n <div>\n {signedIn === null && (\n <LoginSheet\n {...overridesOf(overrides)}\n actions={actions}\n presentation=\"sheet\"\n resumePath={resumePath}\n onSignedIn={onSignedIn}\n />\n )}\n <LiveStatus text={confirmation} className=\"text-sm text-muted-foreground empty:hidden\" />\n </div>\n );\n}\n\n/** The neutral shell the restore gate shows, the header's and a step's shape. */\nfunction RestoreSkeleton({ text }: { text: string }) {\n return (\n <div data-testid=\"restore-skeleton\">\n {/* Outside the busy subtree: assistive tech may hold back what is inside it. */}\n <LiveStatus text={text} />\n <div aria-busy=\"true\" className=\"space-y-8\">\n <div aria-hidden=\"true\" className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"size-9 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"flex flex-col gap-1.5\">\n <span className=\"h-4 w-24 animate-pulse rounded bg-muted\" />\n <span className=\"h-3 w-36 animate-pulse rounded bg-muted\" />\n </span>\n </div>\n <span className=\"block h-1.5 w-full animate-pulse rounded-full bg-muted\" />\n </div>\n <div aria-hidden=\"true\" className=\"space-y-6\">\n <span className=\"block h-8 w-56 animate-pulse rounded bg-muted md:h-9\" />\n <div className=\"space-y-2\">\n {Array.from({ length: 4 }, (_, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: four identical placeholders.\n <span key={index} className=\"block h-24 animate-pulse rounded-lg bg-muted\" />\n ))}\n </div>\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Where the visitor is, the way back, and how much is left: a back arrow, the\n * title and «step N of 4», and one segment per step that IS the button to it —\n * tappable only where the machine's `canGoToStep` says so, and frozen while a\n * submission is in flight.\n */\nfunction WizardHeader({\n kit,\n state,\n submitting,\n onGo,\n}: {\n kit: BookingKit;\n state: WizardState;\n submitting: boolean;\n onGo: (step: WizardStep) => void;\n}) {\n const { labels, wizard } = kit;\n const stepLabel = (step: WizardStep) => labelText(labels[`wizard.step.${step}`]);\n const index = STEPS.indexOf(state.step);\n const previous = index > 0 ? STEPS[index - 1] : null;\n const canGoBack = previous !== null && !submitting && wizard.canGoToStep(state, previous);\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n {canGoBack ? (\n <button\n type=\"button\"\n aria-label={fill(labels['wizard.back'], { label: stepLabel(previous) })}\n onClick={() => onGo(previous)}\n className=\"flex size-9 shrink-0 items-center justify-center rounded-full border border-border text-foreground transition-colors hover:bg-muted\"\n >\n <ArrowLeft aria-hidden=\"true\" className=\"size-4\" />\n </button>\n ) : (\n // Held rather than dropped, so the title does not shuffle sideways.\n <span aria-hidden=\"true\" className=\"size-9 shrink-0\" />\n )}\n <p className=\"min-w-0\">\n <span className=\"block font-sans text-sm font-bold\">{labels['wizard.title']}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {fillParts(labels['wizard.progress'], {\n step: index + 1,\n total: STEPS.length,\n label: stepLabel(state.step),\n })}\n </span>\n </p>\n </div>\n\n <ol\n aria-label={labelText(labels['wizard.progressLabel'])}\n className=\"flex items-center gap-1.5\"\n >\n {STEPS.map((step, position) => {\n const isCurrent = step === state.step;\n const reachable = !submitting && !isCurrent && wizard.canGoToStep(state, step);\n return (\n <li key={step} className=\"flex-1\">\n <button\n type=\"button\"\n disabled={!reachable}\n aria-current={isCurrent ? 'step' : undefined}\n onClick={() => onGo(step)}\n className={`block h-1.5 w-full rounded-full transition-colors ${\n position <= index ? 'bg-primary' : 'bg-border'\n } ${reachable ? 'cursor-pointer' : 'cursor-default'}`}\n >\n <span className=\"sr-only\">{stepLabel(step)}</span>\n </button>\n </li>\n );\n })}\n </ol>\n </div>\n );\n}\n\n/**\n * The openings could not be read — this site's failure, not a full day —\n * with the telephone as the rescue and a retry as the cheaper first try.\n */\nfunction SlotsUnavailable({ kit, onRetry }: { kit: BookingKit; onRetry: () => void }) {\n const { labels, config, format } = kit;\n const phone = config.contact.phone;\n return (\n <div className=\"space-y-3\">\n <p role=\"alert\" className=\"rounded-lg border border-border bg-card px-5 py-4 text-sm\">\n {labels['wizard.slotsUnavailable.lead']}\n {phone ? (\n <a\n href={format.telHref(phone)}\n aria-label={fill(labels['wizard.slotsUnavailable.callAria'], { phone })}\n className=\"font-semibold text-primary underline underline-offset-4\"\n >\n {fillParts(labels['wizard.slotsUnavailable.call'], { phone })}\n </a>\n ) : (\n labels['wizard.slotsUnavailable.callPlain']\n )}\n {labels['wizard.slotsUnavailable.suffix']}\n </p>\n <BookingButtonOutline onClick={onRetry}>{labels['wizard.retry']}</BookingButtonOutline>\n </div>\n );\n}\n\nfunction BookingButtonOutline({ onClick, children }: { onClick: () => void; children: ReactNode }) {\n return (\n <BookingButton variant=\"outline\" onClick={onClick}>\n {children}\n </BookingButton>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0GA,MAAM,iBAAiB;;AAGvB,MAAM,kBAAkB;;AAGxB,MAAM,gBAA0D;CAC9D,KAAK;CACL,SAAS;CACT,MAAM;CACN,SAAS;CACT,WAAW;AACb;AAEA,MAAM,QAA+B;CAAC;CAAO;CAAW;CAAQ;AAAS;;AAGzE,SAAS,YAAY,QAAkC;CACrD,IAAI,CAAC,UAAU,OAAO,OAAO,mBAAmB,YAAY;CAC5D,MAAM,UACJ,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,CAAC,CAAC;CACxD,OAAO,eAAe;EAAE,UAAU,UAAU,SAAS;EAAU,OAAO;CAAQ,CAAC;AACjF;;;;;;AAOA,SAAgB,cAAc,OAA2B;CACvD,OACE,oBAAC,UAAD;EAAU,UAAU;EAClB,UAAA,oBAAC,oBAAD,EAAoB,GAAI,MAAQ,CAAA;CACxB,CAAA;AAEd;AAEA,SAAS,mBAAmB,OAA2B;CACrD,MAAM,WAAW,cAAc,OAAO,MAAM,OAAO;CACnD,MAAM,eAAe,MAAM,YAAY,KAAA,KAAa,SAAS,IAAI,OAAO,OAAO;CAC/E,MAAM,UAAU,WAAW;EAAE,GAAG;EAAO;CAAa,CAAC;CACrD,MAAM,EAAE,QAAQ;CAChB,MAAM,EAAE,WAAW;CAGnB,MAAM,mBAAmB,IAAI,OAAO,QAAQ,YAAY,CAAC;CACzD,gBAAgB;EACd,IACE,oBACA,OAAO,YAAY,eACnB,QAAQ,IAAI,aAAa,cAEzB,QAAQ,KAAK,8EAA8E;CAE/F,GAAG,CAAC,gBAAgB,CAAC;CACrB,MAAM,EAAE,OAAO,SAAS,WAAW,YAAY;EAAE,GAAG;EAAS,SAAS,MAAM;CAAQ;;;;;;CAOpF,MAAM,UAAU,OAAuB,IAAI;CAC3C,MAAM,YAAsC,cAAc,OAAO,cAAc,MAAM;CACrF,MAAM,cAAc,OAAwC,IAAI;CAChE,MAAM,cAAc,QAAQ,MAAM;CAClC,MAAM,gBAAgB,QAAQ,MAAM;CACpC,MAAM,qBAAqB,OAAO,KAAK;CACvC,gBAAgB;EACd,IAAI,QAAQ,WAAW;EACvB,MAAM,WAAW,YAAY;EAC7B,YAAY,UAAU;EACtB,IAAI,aAAa,WAAW;EAC5B,UAAU;GAAE,MAAM;GAAQ,MAAM;EAAU,CAAC;EAC3C,IAAI,aAAa,MAAM;EAEvB,MAAM,UAAU,cAAc,UAAU,gBAAgB;EACxD,MAAM,YAAY,UAAU,kBAAkB,cAAc;EAC5D,MAAM,UAAU,SAAS,eAAe,SAAS;EACjD,YAAY,UAAU,UAAU,QAAQ,OAAO;EAC/C,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC;EAEtC,mBAAmB,UAAU,WAAW,CAAC;CAC3C,GAAG;EAAC;EAAW,QAAQ;EAAW;EAAa;EAAe;CAAO,CAAC;;CAGtE,MAAM,mBAAmB,OAAO,KAAK;CACrC,MAAM,WAAW,QAAQ,MAAM;CAC/B,gBAAgB;EACd,IAAI,aAAa,QAAQ,CAAC,iBAAiB,SAAS;EACpD,iBAAiB,UAAU;EAC3B,SAAS,eAAe,cAAc,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,eAAe,KAAK,CAAC;CACnF,GAAG,CAAC,UAAU,MAAM,IAAI,CAAC;;;;;;CAMzB,MAAM,cAAc,QAAQ,MAAM;CAClC,MAAM,qBAAqB,OAAO,KAAK;CACvC,gBAAgB;EACd,IAAI,CAAC,aAAa;GAChB,mBAAmB,UAAU;GAC7B;EACF;EACA,IAAI,mBAAmB,WAAW,QAAQ,aAAa,MAAM,SAAS,WAAW;EACjF,mBAAmB,UAAU;EAC7B,MAAM,UAAU,SAAS,eAAe,cAAc,OAAO;EAC7D,YAAY,QAAQ,OAAO;EAC3B,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC;CACxC,GAAG;EAAC;EAAa,QAAQ;EAAW,MAAM;CAAI,CAAC;CAC/C,gBAAgB;EACd,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,SAAS;EACnD,mBAAmB,UAAU;EAC7B,MAAM,SAAS,SAAS;EACxB,IAAI,WAAW,QAAQ,WAAW,SAAS,MACzC,SAAS,eAAe,eAAe,CAAC,EAAE,MAAM,EAAE,eAAe,KAAK,CAAC;CAE3E,GAAG,CAAC,aAAa,CAAC;CAGlB,MAAM,YAAY,OAAO,MAAM,KAAK;CACpC,gBAAgB;EACd,IAAI,MAAM,UAAU,QAAQ,MAAM,UAAU,UAAU,SACpD,UAAU;GAAE,MAAM;GAAgB,MAAM,MAAM;EAAM,CAAC;EAEvD,UAAU,UAAU,MAAM;CAC5B,GAAG,CAAC,MAAM,OAAO,OAAO,CAAC;CACzB,MAAM,iBAAiB,WAAW,SAAS,UAAU;CACrD,gBAAgB;EACd,IAAI,iBAAiB,GAAG,UAAU;GAAE,MAAM;GAAa,UAAU;EAAe,CAAC;CACnF,GAAG,CAAC,gBAAgB,OAAO,CAAC;CAI5B,sBAAsB;EACpB,IAAI,CAAC,QAAQ,UAAU;EACvB,QAAQ,SAAS,gBAAgB,gBAAgB;EACjD,QAAQ,SAAS,aAAa,iBAAiB,EAAE;CACnD,GAAG,CAAC,QAAQ,QAAQ,CAAC;CAGrB,MAAM,CAAC,aAAa,kBAAkB,SACpC,QAAQ,YAAY,OAAO,MAAM,IACnC;CACA,IAAI,CAAC,QAAQ,aAAa,gBAAgB,MAAM,eAAe,MAAM,IAAI;CACzE,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK;CAC1D,MAAM,WAAW,gBAAgB,QAAQ,CAAC,QAAQ,aAAa,MAAM,SAAS;CAC9E,IAAI,YAAY,CAAC,gBAAgB,kBAAkB,IAAI;CACvD,MAAM,aAAa,kBAAkB;CAErC,MAAM,kBAAkB,oBAAC,iBAAD,EAAiB,MAAM,UAAU,OAAO,mBAAmB,EAAI,CAAA;;;;;;CAMvF,MAAM,SAAS,YACb,qBAAC,OAAD;EACE,IAAI;EACJ,KAAK;EACL,WAAW,SAAS,WAAW,QAAQ,QAAQ;EAC/C,0BAAA;EAJF,UAAA,CAMG,CAAC,QAAQ,YACR,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,UAAD,EAGE,yBAAyB,EACvB,QAAQ,IAAI,YAAY,kBACtB,gBACA,QAAQ,QACR,QAAQ,QACV,EACF,EACD,CAAA,GACD,oBAAC,OAAD;GAAK,WAAU;GAAuC,UAAA;EAAqB,CAAA,CAC3E,EAAA,CAAA,GAEH,QAAQ,aAAa,QAAQ,WAC5B,kBAEA,oBAAC,OAAD;GAAK,WAAU;GAAiC,UAAA;EAAa,CAAA,CAE5D;;CAGP,IAAI,cAAc,MAChB,OAAO,MACL,oBAAC,kBAAD;EAA2B;EAAmB;EAAU,SAAS,MAAM;CAAU,CAAA,CACnF;CAGF,MAAM,cAAc,QAA6D;EAC/E,iBAAiB,UAAU;EAC3B,QAAQ,MAAM,OAAO,GAAG;CAC1B;CAEA,MAAM,cAAc,MAAM,SAAS,aAAa,IAAI,OAAO,MAAM,uBAAuB;;;;;CAKxF,MAAM,kBAAkB,IAAI,OAAO,QAAQ,YAAY;CACvD,MAAM,QAAQ,mBAAmB,MAAM,SAAS,aAAa,CAAC,QAAQ,MAAM;CAC5E,MAAM,WACJ,gBAAgB,CAAC,kBACf,oBAAC,UAAD;EACW;EACT,SAAS,MAAM;EACf,WAAW;EACC;CACb,CAAA,IACC;CAEN,OAAO,MACL,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,oBAAC,cAAD;IACO;IACE;IACP,YAAY,QAAQ;IACpB,OAAO,SAAS,QAAQ,SAAS;KAAE,MAAM;KAAY;IAAK,CAAC;GAC5D,CAAA;GAGA,MAAM,UAAU,QACf,MAAM,SAAS,aACf,MAAM,SAAS,UACf,CAAC,eACC,oBAAC,KAAD;IACE,MAAK;IACL,WACE,SAAS,WAAW,QAAQ,SAC5B;IAGD,UAAA,gBAAgB,QAAQ,MAAM,OAAO,IAAI,OAAO,MAAM,oBAAoB;GAC1E,CAAA;GAIN,MAAM,SAAS,aAAa,CAAC,SAAS,IAAI,OAAO,QAAQ,SACxD,oBAAC,WAAD;IAAoB;IAAmB;GAAW,CAAA;GAGnD,MAAM,SAAS,aAAa;GAG5B,gBAAgB,QAAQ,MAAM,iBAAiB,QAAQ,aAAa,QAAQ,CAAC,SAC5E,oBAACA,cAAD;IACE,GAAI,YAAY,KAAK;IACrB,SAAS,MAAM;IACf,cAAa;IACb,SAAS;IACT,cAAc,QAAQ,MAAM;IAC5B,YAAY,QAAQ,MAAM;IACd;GACb,CAAA;GAIH,qBAAC,OAAD;IAEE,qBAAmB,MAAM;IACzB,gBAAc,aAAa,KAAK,KAAA;IAChC,WAAU;IAJZ,UAAA;KAMG,MAAM,SAAS,SACd,oBAAC,SAAD;MAAkB;MAAmB;MAAoB;KAAW,CAAA;KAErE,MAAM,SAAS,aACd,oBAAC,aAAD;MAAsB;MAAmB;MAAuB;KAAc,CAAA;KAE/E,MAAM,SAAS,UAAU,oBAAC,UAAD;MAAmB;MAAmB;KAAW,CAAA;KAC1E,MAAM,SAAS,cACb,QACC,oBAAC,aAAD;MACW;MACC;MACV,SAAS,MAAM;MACf,WAAW;MACC;KACb,CAAA,IAED,oBAAC,aAAD;MAAsB;MAAmB;KAAW,CAAA;IAErD;GAxBE,GAAA,MAAM,IAwBR;GAEJ,CAAC,eACA,oBAAC,YAAD;IACE,GAAK,IAAI,OAAO,QAAQ,gBACpB,QAAQ,QAAQ,eAChB,EAAE,MAAM,QAAQ,QAAQ,QAAQ;IACpC,YAAY,QAAQ,QAAQ;IAC5B,MAAM,MAAM;IACZ,QAAQ,QAAQ;IAChB,sBAAsB,IAAI,OAAO,QAAQ;IACzC,QAAQ,aAAa,MAAM;IAC3B,YAAY,SAAS,WAAW;GACjC,CAAA;EAEA;CACP,CAAA,CAAA;AACF;;AAGA,SAAS,YAAY,OAA2C;CAC9D,OAAO;EACL,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,YAAY,MAAM;CACpB;AACF;AAOA,SAAS,QAAQ,EAAE,SAAS,UAAU,YAAiD;CACrF,MAAM,EAAE,KAAK,OAAO,WAAW;CAC/B,MAAM,SAAS,cAAc,aAAa,GAAG,GAAG,CAAC,GAAG,CAAC;CACrD,OACE,oBAAC,WAAD;EACE,QAAQ,aAAa,IAAI,MAAM;EAC/B,QAAQ,IAAI;EACZ,QAAQ,MAAM;EACd,QAAQ,OAAO,WAAW,OAAO,OAAO,cAAc,KAAK,OAAO,QAAQ,OAAO,SAAS;EAC1F,cAAc;EACd,eAAe,kBAAkB,KAAK,MAAM,QAAQ,OAAO,SAAS;EACpE,WAAW,IAAI,OAAO;EACtB,SAAS;EACT,aAAa,IAAI,OAAO;EACxB,UAAU,OAAO;EACjB,YAAY,OAAO;EACnB,YACE,IAAI,OAAO,QAAQ,aACf;GAAE,OAAO,IAAI,OAAO;GAAY,OAAO;EAAiB,IACxD,KAAA;EAEI;EACV,aAAa,OAAO,IAAI,MAAM,OAAO,QAAQ,MAAM,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;EACtE,YAAY,SAAS,WAAW;EAChC,YACE,SAAS,WAAW,aAAa,EAAE,YAAY,SAAS,WAAW,WAAW,IAAI,KAAA;CAErF,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,SAAS,UAAU,eAAqD;CAC7F,MAAM,EAAE,KAAK,OAAO,QAAQ,cAAc;CAC1C,MAAM,SAAS,WAAyB,OAAO,UAAU,QAAQ,OAAO,SAAS;CACjF,MAAM,SAAS,MAAM,OAAO,WAAW,IAAI,MAAM,OAAO,KAAK;CAM7D,MAAM,WAAW,OAAe,SAA4B,WAAuB;EACjF,MAAM,QAAQ,MAAM,QAAQ,SAAS,CAAC,MAAM,QAAQ,QAAQ,GAAG,MAAM,OAAO,MAAM,IAAI,CAAC;EACvF,IAAI,CAAC,eAAe,MAAM,WAAW,KAAM,MAAM,WAAW,KAAK,MAAM,EAAE,CAAC,OAAO,QAAQ,IACvF,OAAO;OACF,IAAI,CAAC,MAAM,MAAM,WAAW,OAAO,OAAO,QAAQ,EAAE,GACzD,QAAQ,iBAAiB,OAAO,OAAO;CAE3C;CACA,OACE,oBAAC,eAAD;EACE,QAAQ,aAAa,IAAI,MAAM;EAC/B,QAAQ,IAAI;EACZ,UAAU,UAAU;EACpB,GAAI,kBAAkB,GAAG;EACzB,aAAa,eAAe,KAAK,MAAM,OAAO,IAAI,KAAK,GAAG,MAAM,MAAM;EACtE,iBAAiB,UAAU;EAC3B,SAAS,YAAY,QAAQ,GAAG,eAAe,QAAQ,YAAY,OAAO,CAAC;EAC3E,YAAY,SAAS,OAAO,cAAc,MAAM,IAAI;EACpD,UAAU,MAAM,QAAQ,EAAE,EAAE,MAAM;EAClC,WAAW,SAAU,OAAO,QAAQ,OAAQ;EAC5C,OACE,MAAM,OAAO,SAAS,IAClB;GACE,QAAQ,YAAY,KAAK,MAAM,QAAQ,OAAO,OAAO,aAAa;GAClE,SAAS,MAAM;GACf,YAAY,OAAO,YACjB,QAAQ,OAAO,eAAe,QAAQ,eAAe,OAAO,OAAO,CAAC;GACtE,WAAW,UACT,QAAQ,SAAS;IACf,MAAM;IACN,QAAQ,MAAM,OAAO,QAAQ,GAAG,aAAa,aAAa,KAAK;GACjE,CAAC;EACL,IACA,KAAA;EAEN,WACE,cACI;GAEE,OAAO,MAAM,QAAQ,KAAK,OAAO,UAC/B,QAAQ,CAAC,OAAO,GAAG,MAAM,OAAO,MAAM,IAAI,CAAC,CAC7C;GACA,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,OACE,MAAM,MAAM,WAAW,IACnB,OACA;IACE,SAAS,IAAI,OAAO,aAAa,MAAM,OAAO,MAAM,SAAS;IAC7D,UAAU,QAAQ,QAAQ;GAC5B;GACN,aAAa,QAAQ,QAAQ;GAC7B,QACE,MAAM,UAAU,OACZ,OACA,UACE,gBACE,IAAI,QACJ,MAAM,OACN,IAAI,OAAO,MAAM,oBACnB,CACF;GACN,QAAQ,IAAI;EACd,IACA,KAAA;EAEN,YAAY,SAAS,WAAW;EAChC,YACE,SAAS,WAAW,cAChB,EAAE,aAAa,SAAS,WAAW,YAAY,IAC/C,KAAA;CAEP,CAAA;AAEL;AAEA,SAAS,SAAS,EAAE,SAAS,YAAuB;CAClD,MAAM,EAAE,KAAK,OAAO,WAAW,OAAO,aAAa;CACnD,MAAM,EAAE,QAAQ,QAAQ,QAAQ,WAAW;CAC3C,MAAM,QAAQ,QAAQ,QAAQ;CAC9B,MAAM,QAAQ,MAAM;CACpB,MAAM,QAAQ,OAAO,QAAQ;CAC7B,MAAM,MAAM,aAAa,MAAM,MAAM;CACrC,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,eAAD;GACE,QAAQ,aAAa,MAAM;GACnB;GACH;GAGL,YAAY,MAAM,SAAS,SAAS,gBAAgB,IAAI,CAAC,CAAC,KAAK,YAAY,QAAQ,EAAE,CAAC;GACtF,WAAW,UAAU;GACrB,SAAS,CAAC,UAAU;GACpB,sBAAsB,MAAM;GAC5B,oBAAoB,MAAM;GAC1B,iBAAiB,MAAM;GACvB,aAAa,UAAU;GACvB,QAAQ,UAAU;GAClB,QAAQ,QAAQ;GAChB,qBAAqB,OAAO,QAAQ;GACpC,UAAU,OAAO,QAAQ;GACzB,OACE,QACI;IACE,MAAM,MAAM;IACZ,MAAM,MAAM;IACZ,UAAU,cAAc,QAAQ,MAAM,MAAM;IAC5C,SAAS;KACP,YAAY,OAAO,aAAa,OAAO,YAAY;KACnD,UAAU,OAAO,aAAa,OAAO,UAAU;IACjD;IACA,SAAS,SAAS,QAAQ,SAAS;KAAE,MAAM;KAAgB;IAAK,CAAC;GACnE,IACA,KAAA;GAEN,YAAY,SAAS,WAAW;GAChC,YACE,SAAS,WAAW,cAChB,EAAE,aAAa,SAAS,WAAW,YAAY,IAC/C,KAAA;EAEP,CAAA;EAEA,MAAM,QACL,oBAAC,YAAD;GACE,QAAQ,aAAa,MAAM;GACnB;GACR,UAAU,IAAI;GACd,WAAA;GACA,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,UAAU,SAAS;GACnB,aAAa,UAAU;GACvB,aAAa,MAAM;GACd;GACE;GACP,cAAc,UAAU,eAAe,KAAK,QAAQ,QAAQ,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK;GACpF,QAAQ,QAAQ;GAChB,SACE,OAAO,QAAQ,UACX,EACE,iBAAiB,eAAe,YAAY,UAAU,WAAW,UAAU,EAC7E,IACA,KAAA;GAEN,aAAa,OAAO,QAAQ;GAC5B,OACE,QACI;IACE;IACA,MAAM,MAAM;IACZ,OAAO,MAAM;IACb,YAAY,SAAS,OAAO,WAAW,OAAO,KAAK,SAAS,YAAY;IAExE,gBACE,MAAM,cAAc,gBACf,UACC,IAAI,WAAW,iBAAiB;KAC9B,YAAY,MAAM;KAClB,UAAU,MAAM;KAChB;IACF,CAAC,IACH,KAAA;IACN,oBAAoB,UAAU;IAC9B,QAAQ,QAAQ;GAClB,IACA,KAAA;GAEN,YAAY,SAAS,WAAW;GAChC,YAAY,eAAe,QAAQ;EACpC,CAAA,IACC,MAAM,SACR,oBAAC,kBAAD;GAAuB;GAAK,SAAS,QAAQ;EAAU,CAAA,IAGvD,oBAAC,oBAAD;GACE,QAAQ,aAAa,MAAM;GAC3B,MAAM,MAAM,KAAK;GACjB,WAAA;GACA,cAAc,MAAM,MAAM,SACxB,gBAAgB,IAAI,CAAC,CAAC,MAAM,YAAY,QAAQ,sBAAsB,CAAC,CACzE;EACD,CAAA;EAIH,oBAAC,YAAD;GAAY,QAAQ,aAAa,MAAM;GAAG,aAAa,MAAM;EAAc,CAAA;CAC3E,EAAA,CAAA;AAEN;;;;;;AAOA,SAAS,UAAU,EAAE,SAAS,YAAuB;CACnD,MAAM,EAAE,KAAK,OAAO,WAAW,UAAU;CACzC,MAAM,QAAQ,cACN,WAAW,KAAK,OAAO,UAAU,WAAW,MAAM,MAAM,GAC9D;EAAC;EAAK;EAAO,UAAU;EAAW,MAAM;CAAM,CAChD;CACA,OACE,oBAAC,cAAD;EACE,OAAO,MAAM;EACb,UAAU,QAAQ,QAAQ;EAC1B,QAAQ,IAAI;EACZ,QAAQ,aAAa,IAAI,MAAM;EAC/B,cAAc,QAAQ,SAAS;GAAE,MAAM;GAAY,MAAM;EAAO,CAAC;EACjE,cAAc,MAAM;EAEpB,SAAS,eAAe,QAAQ,SAAS;GAAE,SAAS,MAAM;GAAmB;EAAW,CAAC;EACzF,YAAY,SAAS,WAAW;CACjC,CAAA;AAEL;AAEA,SAAS,eAAe,EAAE,cAA+B;CACvD,IAAI,CAAC,WAAW,WAAW,CAAC,WAAW,UAAU,OAAO,KAAA;CACxD,OAAO;EAAE,SAAS,WAAW;EAAS,UAAU,WAAW;CAAS;AACtE;AAEA,SAAS,YAAY,EAAE,SAAS,YAAuB;CACrD,MAAM,EAAE,KAAK,UAAU;CACvB,MAAM,WAAW,QAAQ,MAAM;CAC/B,MAAM,QAAQ,cAAc,aAAa,KAAK,KAAK,GAAG,CAAC,KAAK,KAAK,CAAC;CAClE,OACE,oBAAC,eAAD;EACE,OAAO,cAAc,KAAK;EAC1B,UAAU,QAAQ;EAClB,UAAU,QAAQ;EACX;EACP,UAAU,QAAQ,QAAQ;EAC1B,GAAI,kBAAkB,GAAG;EACzB,YAAY,QAAQ;EACpB,iBAAiB,QAAQ;EACzB,QAAQ,UAAU;EAClB,eAAe,UAAU,SAAS;EAElC,eAAe,IAAI,OAAO,QAAQ,YAAY,aAAa;EAC3D,QAAQ,IAAI;EACZ,QAAQ,aAAa,IAAI,MAAM;EAC/B,YAAY,SAAS,WAAW;EAChC,YACE,SAAS,WAAW,aAAa,EAAE,YAAY,SAAS,WAAW,WAAW,IAAI,KAAA;CAErF,CAAA;AAEL;;;;;;;AAQA,SAAS,YAAY,EACnB,SACA,UACA,SACA,WACA,cAKC;CACD,MAAM,EAAE,WAAW,QAAQ;CAC3B,MAAM,EAAE,eAAe;CAEvB,MAAM,QAAQ,MAAc,UAA8B,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CAChG,OACE,qBAAC,WAAD;EACE,mBAAiB,cAAc;EAC/B,WAAW,KAAK,aAAa,WAAW,SAAS,IAAI;EAFvD,UAAA;GAIE,oBAAC,MAAD;IACE,IAAI,cAAc;IAClB,UAAU;IACV,WAAW,KACT,yDACA,WAAW,SAAS,OACtB;IAEC,UAAA,UAAU,OAAO,yBAAyB;GACzC,CAAA;GACJ,oBAAC,YAAD;IACE,MAAM,QAAQ,MAAM,cAAc,UAAU,OAAO,6BAA6B,IAAI;IACpF,WAAU;GACX,CAAA;GACD,oBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,UAAU,OAAO,uBAAuB;GAAK,CAAA;GAEnF,oBAAC,KAAD;IACE,WAAW,KACT,yEACA,WAAW,SAAS,IACtB;IAEC,UAAA,QAAQ,QAAQ;GAChB,CAAA;GACH,oBAACA,cAAD;IACE,GAAI,YAAY,SAAS;IAChB;IACT,cAAa;IACb,YAAY,QAAQ,MAAM;IAC1B,cAAc,QAAQ,MAAM;IAChB;GACb,CAAA;EACM;;AAEb;AAEA,SAAS,iBAAiB,EAAE,SAAS,UAAU,WAA6C;CAC1F,MAAM,EAAE,QAAQ;CAEhB,MAAM,CAAC,OAAO,eAAe,KAAK,IAAI,CAAC;CACvC,MAAM,eAAe,QAAQ;CAC7B,OACE,oBAAC,cAAD;EACE,GAAI,kBAAkB,KAAK,cAAc,OAAO;EAChD,aAAa,QAAQ;EAChB;EACL,QAAQ,IAAI;EACZ,QAAQ,aAAa,IAAI,MAAM;EAC/B,YAAY,SAAS,WAAW;EAChC,YACE,SAAS,WAAW,YAAY,EAAE,WAAW,SAAS,WAAW,UAAU,IAAI,KAAA;CAElF,CAAA;AAEL;;;;;;AAOA,SAAS,SAAS,EAChB,SACA,SACA,WACA,cAMC;CACD,MAAM,EAAE,WAAW,UAAU,eAAe,QAAQ;CACpD,MAAM,EAAE,WAAW,QAAQ;CAC3B,IAAI,aAAa,QAAQ,cAAc,MAAM,OAAO;CAGpD,MAAM,MAAM,UAAU,YAAY;CAClC,MAAM,OACJ,QAAQ,OACJ,OACA,CAAC,IAAI,WAAW,IAAI,QAAQ,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,KAAK,IAAI;CAC5E,MAAM,eACJ,aAAa,OACT,OACA,SAAS,OACP,UAAU,OAAO,kBAAkB,IACnC,KAAK,OAAO,sBAAsB,EAAE,KAAK,CAAC;CAClD,OACE,qBAAC,OAAD,EAAA,UAAA,CACG,aAAa,QACZ,oBAACA,cAAD;EACE,GAAI,YAAY,SAAS;EAChB;EACT,cAAa;EACD;EACA;CACb,CAAA,GAEH,oBAAC,YAAD;EAAY,MAAM;EAAc,WAAU;CAA8C,CAAA,CACrF,EAAA,CAAA;AAET;;AAGA,SAAS,gBAAgB,EAAE,QAA0B;CACnD,OACE,qBAAC,OAAD;EAAK,eAAY;EAAjB,UAAA,CAEE,oBAAC,YAAD,EAAkB,KAAO,CAAA,GACzB,qBAAC,OAAD;GAAK,aAAU;GAAO,WAAU;GAAhC,UAAA,CACE,qBAAC,OAAD;IAAK,eAAY;IAAO,WAAU;IAAlC,UAAA,CACE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,QAAD,EAAM,WAAU,sDAAuD,CAAA,GACvE,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,oBAAC,QAAD,EAAM,WAAU,0CAA2C,CAAA,GAC3D,oBAAC,QAAD,EAAM,WAAU,0CAA2C,CAAA,CACvD;KACH,CAAA,CAAA;IACL,CAAA,GAAA,oBAAC,QAAD,EAAM,WAAU,yDAA0D,CAAA,CACvE;GACL,CAAA,GAAA,qBAAC,OAAD;IAAK,eAAY;IAAO,WAAU;IAAlC,UAAA,CACE,oBAAC,QAAD,EAAM,WAAU,uDAAwD,CAAA,GACxE,oBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,UAE7B,oBAAC,QAAD,EAAkB,WAAU,+CAAgD,GAAjE,KAAiE,CAC7E;IACE,CAAA,CACF;GACF,CAAA,CAAA;EACF,CAAA,CAAA;;AAET;;;;;;;AAQA,SAAS,aAAa,EACpB,KACA,OACA,YACA,QAMC;CACD,MAAM,EAAE,QAAQ,WAAW;CAC3B,MAAM,aAAa,SAAqB,UAAU,OAAO,eAAe,OAAO;CAC/E,MAAM,QAAQ,MAAM,QAAQ,MAAM,IAAI;CACtC,MAAM,WAAW,QAAQ,IAAI,MAAM,QAAQ,KAAK;CAChD,MAAM,YAAY,aAAa,QAAQ,CAAC,cAAc,OAAO,YAAY,OAAO,QAAQ;CAExF,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACG,YACC,oBAAC,UAAD;IACE,MAAK;IACL,cAAY,KAAK,OAAO,gBAAgB,EAAE,OAAO,UAAU,QAAQ,EAAE,CAAC;IACtE,eAAe,KAAK,QAAQ;IAC5B,WAAU;IAEV,UAAA,oBAAC,WAAD;KAAW,eAAY;KAAO,WAAU;IAAU,CAAA;GAC5C,CAAA,IAGR,oBAAC,QAAD;IAAM,eAAY;IAAO,WAAU;GAAmB,CAAA,GAExD,qBAAC,KAAD;IAAG,WAAU;IAAb,UAAA,CACE,oBAAC,QAAD;KAAM,WAAU;KAAqC,UAAA,OAAO;IAAsB,CAAA,GAClF,oBAAC,QAAD;KAAM,WAAU;KACb,UAAA,UAAU,OAAO,oBAAoB;MACpC,MAAM,QAAQ;MACd,OAAO,MAAM;MACb,OAAO,UAAU,MAAM,IAAI;KAC7B,CAAC;IACG,CAAA,CACL;GACA,CAAA,CAAA;EAEL,CAAA,GAAA,oBAAC,MAAD;GACE,cAAY,UAAU,OAAO,uBAAuB;GACpD,WAAU;GAET,UAAA,MAAM,KAAK,MAAM,aAAa;IAC7B,MAAM,YAAY,SAAS,MAAM;IACjC,MAAM,YAAY,CAAC,cAAc,CAAC,aAAa,OAAO,YAAY,OAAO,IAAI;IAC7E,OACE,oBAAC,MAAD;KAAe,WAAU;KACvB,UAAA,oBAAC,UAAD;MACE,MAAK;MACL,UAAU,CAAC;MACX,gBAAc,YAAY,SAAS,KAAA;MACnC,eAAe,KAAK,IAAI;MACxB,WAAW,qDACT,YAAY,QAAQ,eAAe,YACpC,GAAG,YAAY,mBAAmB;MAEnC,UAAA,oBAAC,QAAD;OAAM,WAAU;OAAW,UAAA,UAAU,IAAI;MAAQ,CAAA;KAC3C,CAAA;IACN,GAZK,IAYL;GAER,CAAC;EACC,CAAA,CACD;;AAET;;;;;AAMA,SAAS,iBAAiB,EAAE,KAAK,WAAqD;CACpF,MAAM,EAAE,QAAQ,QAAQ,WAAW;CACnC,MAAM,QAAQ,OAAO,QAAQ;CAC7B,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,qBAAC,KAAD;GAAG,MAAK;GAAQ,WAAU;GAA1B,UAAA;IACG,OAAO;IACP,QACC,oBAAC,KAAD;KACE,MAAM,OAAO,QAAQ,KAAK;KAC1B,cAAY,KAAK,OAAO,qCAAqC,EAAE,MAAM,CAAC;KACtE,WAAU;KAET,UAAA,UAAU,OAAO,iCAAiC,EAAE,MAAM,CAAC;IAC3D,CAAA,IAEH,OAAO;IAER,OAAO;GACP;EACH,CAAA,GAAA,oBAAC,sBAAD;GAAsB,SAAS;GAAU,UAAA,OAAO;EAAsC,CAAA,CACnF;;AAET;AAEA,SAAS,qBAAqB,EAAE,SAAS,YAA0D;CACjG,OACE,oBAAC,eAAD;EAAe,SAAQ;EAAmB;EACvC;CACY,CAAA;AAEnB"}
1
+ {"version":3,"file":"BookingWizard.mjs","names":["LoginSheet"],"sources":["../../src/react/BookingWizard.tsx"],"sourcesContent":["/**\n * `<BookingWizard>` — the booking flow, from «who is it for?» to the\n * confirmation, drawn with the `@medalsocial/meda/booking` screens over\n * `useBooking()`.\n *\n * The hook owns the machine, the fetches and the stores; this file owns the\n * page: which step is on screen (ONE at a time — the details step's nonce and\n * the submit flag depend on it), focus and scroll on every move, the restore\n * gate's pre-hydration script and skeleton, the header, and what each screen\n * is handed (prices, the weekend note, ages, the possessive, the calendar\n * file) out of the site's config.\n */\n\nimport {\n BookingButton,\n BookingRecap,\n Confirmation,\n DetailsScreen,\n LiveStatus,\n ServiceScreen,\n StylistScreen,\n SummaryBar,\n TakenToast,\n TimeScreen,\n TimeScreenSkeleton,\n WhoScreen,\n} from '@medalsocial/meda/booking';\nimport { ArrowLeft } from 'lucide-react';\nimport {\n type ReactNode,\n Suspense,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { fill, fillParts, labelText } from '../core/labels';\nimport { SELF_KEY, type WizardPerson, type WizardState, type WizardStep } from '../core/machine';\nimport type { BookingServiceDto } from '../core/types';\nimport { visitServicesOf } from '../core/visit';\nimport type { PortalActions } from './actions';\nimport type { BookingKit } from './kit';\nimport { LoginSheet } from './LoginSheet';\nimport type { BookingOverrides, ResolvedBooking } from './Provider';\nimport { useBookingKit } from './Provider';\nimport { screenLabels } from './screen-labels';\nimport {\n type BookingConfirmation,\n type BookingController,\n type BookingReadEndpoint,\n type BookingSeed,\n useBooking,\n} from './useBooking';\nimport {\n addedChildEntries,\n confirmationProps,\n detailsLines,\n detailsScreenBase,\n familyEntries,\n GUEST_ADULT_SEAT,\n guestChoices,\n partyPeople,\n partySizeWord,\n recapProps,\n serviceFitsFor,\n serviceScreenBase,\n stylistFace,\n weekendNoteFor,\n} from './wizard/adapters';\nimport { useWallClock } from './wizard/wall-clock';\nimport { forMedaScreen, wizardErrorText } from './wizard-error';\n\nexport interface BookingWizardProps extends BookingOverrides {\n /** What the page prefetched (the `/next` loader's seed). */\n seed: BookingSeed;\n /** This request's phone and address, over `config.contact` (the loader's `contact`). */\n contact?: { phone?: string | null; address?: string | null };\n /** The parent the page found a portal session for; `null` for a guest. */\n guardian?: BookingGuardianProp;\n /**\n * The app's server actions for the login sheet (Decision 8). Without them —\n * or with `config.portal.enabled` off — no login is offered.\n */\n actions?: Pick<PortalActions, 'startLogin' | 'startVipps'>;\n /** How many days the window covers. Default `config.window.rangeDays`. */\n rangeDays?: number;\n /**\n * The site's origin (`https://…`), so the manage links inside the calendar\n * file are absolute. Absent, they stay root-relative.\n */\n siteUrl?: string;\n /** A monitoring tap: the step the visitor is on, and a submission's outcome. */\n onEvent?: (event: BookingWizardEvent) => void;\n}\n\ntype BookingGuardianProp = Parameters<typeof useBooking>[0]['guardian'];\n\nexport type BookingWizardEvent =\n | { type: 'step'; step: WizardStep | 'confirmed' }\n | { type: 'submit_ok'; bookings: number }\n | { type: 'submit_error'; code: NonNullable<WizardState['error']> }\n /** A read the time step waits on failed: `status` is `null` when nothing answered. */\n | { type: 'read_error'; endpoint: BookingReadEndpoint; status: number | null };\n\n/** The bundler's build mode, which it inlines; absent outside a bundler. */\ndeclare const process: { env: { NODE_ENV?: string } } | undefined;\n\n/** The wizard's root, for the inline restore gate to find before hydration. */\nconst WIZARD_ROOT_ID = 'booking-wizard';\n\n/** The time step's heading, which a lost slot sends the visitor back to. */\nconst TIME_HEADING_ID = 'booking-time-heading';\n\n/** The heading each screen focuses when the visitor arrives on it. */\nconst STEP_HEADINGS: Record<WizardStep | 'confirmed', string> = {\n who: 'booking-who-heading',\n service: 'booking-service-heading',\n when: 'booking-stylist-heading',\n details: 'booking-details-heading',\n confirmed: 'booking-confirmed-heading',\n};\n\nconst STEPS: readonly WizardStep[] = ['who', 'service', 'when', 'details'];\n\n/** Smooth, or instant for a visitor who asked for reduced motion. */\nfunction scrollToTop(target: HTMLElement | null): void {\n if (!target || typeof target.scrollIntoView !== 'function') return;\n const reduced =\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n target.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'start' });\n}\n\n/**\n * The wizard, behind its own Suspense boundary: `useSearchParams` throws to\n * the nearest one during a static prerender, and without ours that is the\n * root layout's — the whole page would ship empty until hydration.\n */\nexport function BookingWizard(props: BookingWizardProps) {\n return (\n <Suspense fallback={null}>\n <BookingWizardShell {...props} />\n </Suspense>\n );\n}\n\nfunction BookingWizardShell(props: BookingWizardProps) {\n const resolved = useBookingKit(props, props.contact);\n const loginOffered = props.actions !== undefined && resolved.kit.config.portal.enabled;\n const booking = useBooking({ ...props, loginOffered });\n const { kit } = booking;\n const { labels } = kit;\n\n // `account.required` with no way to log in: every «Bekreft» would be refused.\n const unreachableLogin = kit.config.account.required && !loginOffered;\n useEffect(() => {\n if (\n unreachableLogin &&\n typeof process !== 'undefined' &&\n process.env.NODE_ENV !== 'production'\n ) {\n console.warn('[booking] account.required without login actions: nobody can log in to book.');\n }\n }, [unreachableLogin]);\n const { state, restore, confirmed, onEvent } = { ...booking, onEvent: props.onEvent };\n\n /**\n * On every change of screen: the wizard's top into view and focus on the\n * new step's heading. Never on the first screen, and never for what the\n * restore gate reveals — that is the page arriving, not the visitor moving.\n */\n const rootRef = useRef<HTMLDivElement>(null);\n const screenKey: WizardStep | 'confirmed' = confirmed !== null ? 'confirmed' : state.step;\n const shownScreen = useRef<WizardStep | 'confirmed' | null>(null);\n const takenSlotTs = booking.slots.takenSlotTs;\n const timeStepReady = booking.slots.ready;\n const focusTimeWhenReady = useRef(false);\n useEffect(() => {\n if (restore.restoring) return;\n const previous = shownScreen.current;\n shownScreen.current = screenKey;\n if (previous === screenKey) return;\n onEvent?.({ type: 'step', step: screenKey });\n if (previous === null) return;\n // A lost slot sends the visitor back for the ALTERNATIVES, on the time half.\n const toTimes = screenKey === 'when' && takenSlotTs !== null;\n const headingId = toTimes ? TIME_HEADING_ID : STEP_HEADINGS[screenKey];\n const heading = document.getElementById(headingId);\n scrollToTop(toTimes ? heading : rootRef.current);\n heading?.focus({ preventScroll: true });\n // The heading focused now may be the skeleton's; focus follows the real one.\n focusTimeWhenReady.current = toTimes && !timeStepReady;\n }, [screenKey, restore.restoring, takenSlotTs, timeStepReady, onEvent]);\n\n /** After a login from the sheet the row that opened it is gone: focus the step's heading. */\n const focusAfterSignIn = useRef(false);\n const signedIn = booking.login.signedIn;\n useEffect(() => {\n if (signedIn === null || !focusAfterSignIn.current) return;\n focusAfterSignIn.current = false;\n document.getElementById(STEP_HEADINGS[state.step])?.focus({ preventScroll: true });\n }, [signedIn, state.step]);\n /**\n * A 401 put the gate back over the form: focus its heading (the details\n * step's id), where the notice beside it says why. Once per loss, and only\n * once the gate is actually on screen — a replay answers behind the skeleton.\n */\n const sessionLost = booking.login.sessionLost;\n const sessionLossFocused = useRef(false);\n useEffect(() => {\n if (!sessionLost) {\n sessionLossFocused.current = false;\n return;\n }\n if (sessionLossFocused.current || restore.restoring || state.step !== 'details') return;\n sessionLossFocused.current = true;\n const heading = document.getElementById(STEP_HEADINGS.details);\n scrollToTop(rootRef.current);\n heading?.focus({ preventScroll: true });\n }, [sessionLost, restore.restoring, state.step]);\n useEffect(() => {\n if (!timeStepReady || !focusTimeWhenReady.current) return;\n focusTimeWhenReady.current = false;\n const active = document.activeElement;\n if (active === null || active === document.body) {\n document.getElementById(TIME_HEADING_ID)?.focus({ preventScroll: true });\n }\n }, [timeStepReady]);\n\n // The monitoring tap for a submission's outcome.\n const lastError = useRef(state.error);\n useEffect(() => {\n if (state.error !== null && state.error !== lastError.current) {\n onEvent?.({ type: 'submit_error', code: state.error });\n }\n lastError.current = state.error;\n }, [state.error, onEvent]);\n // The monitoring tap for a failed read — what the visitor saw as «Vi får ikke\n // hentet ledige tider». Once per failure (`seq`), not per render.\n // Keyed on `seq` in a ref, like `lastError` above: the effect also re-runs when\n // an inline `onEvent` changes identity, which must not report the failure again.\n const { readErrors } = booking;\n const lastReadSeq = useRef(0);\n useEffect(() => {\n for (const failure of readErrors) {\n if (failure.seq <= lastReadSeq.current) continue;\n lastReadSeq.current = failure.seq;\n onEvent?.({ type: 'read_error', endpoint: failure.endpoint, status: failure.status });\n }\n }, [readErrors, onEvent]);\n const confirmedCount = confirmed?.bookings.length ?? 0;\n useEffect(() => {\n if (confirmedCount > 0) onEvent?.({ type: 'submit_ok', bookings: confirmedCount });\n }, [confirmedCount, onEvent]);\n\n // React owns the root from here on: drop the pre-hydration mark the inline\n // script may have set, and say so, so the script's fallback timer stands down.\n useLayoutEffect(() => {\n if (!restore.hydrated) return;\n rootRef.current?.removeAttribute('data-restoring');\n rootRef.current?.setAttribute('data-hydrated', '');\n }, [restore.hydrated]);\n\n // Steps animate in only once the visitor has moved between them.\n const [arrivalStep, setArrivalStep] = useState<WizardStep | null>(\n restore.restoring ? null : state.step\n );\n if (!restore.restoring && arrivalStep === null) setArrivalStep(state.step);\n const [stepsMovedOnce, setStepsMovedOnce] = useState(false);\n const movedNow = arrivalStep !== null && !restore.restoring && state.step !== arrivalStep;\n if (movedNow && !stepsMovedOnce) setStepsMovedOnce(true);\n const stepsMoved = stepsMovedOnce || movedNow;\n\n const restoreSkeleton = <RestoreSkeleton text={labelText(labels['wizard.restoring'])} />;\n /**\n * The root both screens share: the scroll target, and the element the\n * inline restore gate marks. The script and the hidden skeleton exist only\n * in the server's HTML and the hydration pass that adopts it.\n */\n const shell = (content: ReactNode) => (\n <div\n id={WIZARD_ROOT_ID}\n ref={rootRef}\n className={resolved.classNames.wizard?.root ?? 'group scroll-mt-28'}\n suppressHydrationWarning\n >\n {!restore.hydrated && (\n <>\n <script\n // A fixed script built from constants, run before hydration.\n // biome-ignore lint/security/noDangerouslySetInnerHtml: the restore gate must run before React does.\n dangerouslySetInnerHTML={{\n __html: kit.restoreGate.restoreGateScript(\n WIZARD_ROOT_ID,\n restore.linked,\n restore.resuming\n ),\n }}\n />\n <div className=\"hidden group-data-[restoring]:block\">{restoreSkeleton}</div>\n </>\n )}\n {restore.restoring && restore.hydrated ? (\n restoreSkeleton\n ) : (\n <div className=\"group-data-[restoring]:hidden\">{content}</div>\n )}\n </div>\n );\n\n if (confirmed !== null) {\n return shell(\n <ConfirmationStep booking={booking} resolved={resolved} siteUrl={props.siteUrl} />\n );\n }\n\n const onSignedIn = (who: Parameters<BookingController['login']['signIn']>[0]) => {\n focusAfterSignIn.current = true;\n booking.login.signIn(who);\n };\n // The multi-select service step carries its own total bar and refusal notice.\n const multiSelect = state.step === 'service' && kit.config.party.maxServicesPerPerson > 1;\n /**\n * `account.required`: every booking is a logged-in parent's. The login is\n * not offered along the way — it IS «Bekreft», for a parent not logged in.\n */\n const accountRequired = kit.config.account.required && loginOffered;\n const gated = accountRequired && state.step === 'details' && !booking.login.loggedIn;\n const loginRow =\n loginOffered && !accountRequired ? (\n <LoginRow\n booking={booking}\n actions={props.actions as NonNullable<BookingWizardProps['actions']>}\n overrides={props}\n onSignedIn={onSignedIn}\n />\n ) : null;\n\n return shell(\n <div className=\"space-y-8\">\n <WizardHeader\n kit={kit}\n state={state}\n submitting={booking.submitting}\n onGo={(step) => booking.dispatch({ type: 'goToStep', step })}\n />\n\n {/* The machine's error, on the steps that have nowhere else to put it. */}\n {state.error !== null &&\n state.step !== 'details' &&\n state.step !== 'when' &&\n !multiSelect && (\n <p\n role=\"alert\"\n className={\n resolved.classNames.wizard?.alert ??\n 'rounded-lg border border-destructive/40 bg-destructive/10 px-5 py-3 text-sm'\n }\n >\n {wizardErrorText(labels, state.error, kit.config.party.maxServicesPerPerson)}\n </p>\n )}\n\n {/* What is being booked, first: above the login offer and the form. */}\n {state.step === 'details' && !gated && kit.config.screens.recap && (\n <RecapStep booking={booking} resolved={resolved} />\n )}\n\n {state.step === 'details' && loginRow}\n\n {/* The gate takes a Vipps confirm code itself, in place of the form. */}\n {loginOffered && booking.login.vippsConfirm !== null && signedIn === null && !gated && (\n <LoginSheet\n {...overridesOf(props)}\n actions={props.actions as NonNullable<BookingWizardProps['actions']>}\n presentation=\"sheet\"\n trigger={false}\n vippsConfirm={booking.login.vippsConfirm}\n resumePath={booking.login.resumePath}\n onSignedIn={onSignedIn}\n />\n )}\n\n {/* One box per step, keyed by it, so a step arriving is a mount. */}\n <div\n key={state.step}\n data-booking-step={state.step}\n data-animate={stepsMoved ? '' : undefined}\n className=\"space-y-8\"\n >\n {state.step === 'who' && (\n <WhoStep booking={booking} resolved={resolved} loginRow={loginRow} />\n )}\n {state.step === 'service' && (\n <ServiceStep booking={booking} resolved={resolved} multiSelect={multiSelect} />\n )}\n {state.step === 'when' && <WhenStep booking={booking} resolved={resolved} />}\n {state.step === 'details' &&\n (gated ? (\n <AccountGate\n booking={booking}\n resolved={resolved}\n actions={props.actions as NonNullable<BookingWizardProps['actions']>}\n overrides={props}\n onSignedIn={onSignedIn}\n />\n ) : (\n <DetailsStep booking={booking} resolved={resolved} />\n ))}\n </div>\n\n {!multiSelect && (\n <SummaryBar\n {...(kit.config.screens.summaryDetail\n ? booking.derived.summaryParts\n : { line: booking.derived.summary })}\n canAdvance={booking.derived.canAdvance}\n step={state.step}\n onNext={booking.next}\n hideNextWhenDisabled={kit.config.screens.hideDisabledNext}\n labels={screenLabels(labels)}\n classNames={resolved.classNames.summary}\n />\n )}\n </div>\n );\n}\n\n/** The overrides a nested booking component inherits from the wizard's props. */\nfunction overridesOf(props: BookingOverrides): BookingOverrides {\n return {\n config: props.config,\n labels: props.labels,\n classNames: props.classNames,\n components: props.components,\n };\n}\n\ninterface StepProps {\n booking: BookingController;\n resolved: ResolvedBooking;\n}\n\nfunction WhoStep({ booking, resolved, loginRow }: StepProps & { loginRow: ReactNode }) {\n const { kit, state, people } = booking;\n const guests = useMemo(() => guestChoices(kit), [kit]);\n return (\n <WhoScreen\n labels={screenLabels(kit.labels)}\n format={kit.format}\n people={state.people}\n family={people.family === null ? null : familyEntries(kit, people.family, people.ageDayKey)}\n guestChoices={guests}\n addedChildren={addedChildEntries(kit, state.people, people.ageDayKey)}\n maxPeople={kit.wizard.maxPeople}\n selfKey={SELF_KEY}\n isGuestSeat={kit.wizard.isGuestSeat}\n onChoose={people.choosePeople}\n onAddChild={people.addChild}\n guestParty={\n kit.config.screens.guestParty\n ? { child: kit.wizard.guestChild, adult: GUEST_ADULT_SEAT }\n : undefined\n }\n loginRow={loginRow}\n currentYear={Number(kit.clock.dayKey(booking.slots.fromTs).slice(0, 4))}\n classNames={resolved.classNames.who}\n components={\n resolved.components.PersonCard ? { PersonCard: resolved.components.PersonCard } : undefined\n }\n />\n );\n}\n\nfunction ServiceStep({ booking, resolved, multiSelect }: StepProps & { multiSelect: boolean }) {\n const { kit, state, people, catalogue } = booking;\n const ageOf = (person: WizardPerson) => people.personAge(person, people.ageDayKey);\n const single = state.people.length === 1 ? state.people[0] : null;\n // «Samme som sist» stays a one-tap shortcut while the person's visit is\n // nothing, or just that service (a returning child is seated with it already\n // ticked). Once they have ticked something else, a one-tap pick would REPLACE\n // their visit — and, alone, jump to the time step — so the suggestion joins\n // the visit instead, and never unticks a service already in it.\n const pickFor = (index: number, service: BookingServiceDto, oneTap: () => void) => {\n const visit = state.choices[index] ? [state.choices[index], ...state.extras[index]] : [];\n if (!multiSelect || visit.length === 0 || (visit.length === 1 && visit[0].id === service.id)) {\n oneTap();\n } else if (!visit.some((ticked) => ticked.id === service.id)) {\n booking.toggleServiceFor(index, service);\n }\n };\n return (\n <ServiceScreen\n labels={screenLabels(kit.labels)}\n format={kit.format}\n services={catalogue.services}\n {...serviceScreenBase(kit)}\n serviceFits={serviceFitsFor(kit, state.people.map(ageOf), state.people)}\n initialCategory={catalogue.deepLinkCategory}\n onPick={(service) => pickFor(0, service, () => booking.pickService(service))}\n suggestion={single ? people.suggestionFor(single) : null}\n chosenId={state.choices[0]?.id ?? null}\n childName={single ? (single.name ?? null) : null}\n party={\n state.people.length > 1\n ? {\n people: partyPeople(kit, state.people, ageOf, people.suggestionFor),\n choices: state.choices,\n onPickFor: (index, service) =>\n pickFor(index, service, () => booking.pickServiceFor(index, service)),\n onRemove: (index) =>\n booking.dispatch({\n type: 'choosePeople',\n people: state.people.filter((_, position) => position !== index),\n }),\n }\n : undefined\n }\n selection={\n multiSelect\n ? {\n // Index = seated person; meda reads a missing list (nobody seated) as empty.\n lists: state.choices.map((first, index) =>\n first ? [first, ...state.extras[index]] : []\n ),\n onToggle: booking.toggleServiceFor,\n onContinue: booking.continueFromService,\n total:\n state.items.length === 0\n ? null\n : {\n minutes: kit.wizard.visitMinutes(state.items, state.partyMode),\n priceOre: booking.derived.total,\n },\n canContinue: booking.derived.canAdvance,\n notice:\n state.error === null\n ? null\n : labelText(\n wizardErrorText(\n kit.labels,\n state.error,\n kit.config.party.maxServicesPerPerson\n )\n ),\n labels: kit.labels,\n }\n : undefined\n }\n classNames={resolved.classNames.service}\n components={\n resolved.components.ServiceCard\n ? { ServiceCard: resolved.components.ServiceCard }\n : undefined\n }\n />\n );\n}\n\nfunction WhenStep({ booking, resolved }: StepProps) {\n const { kit, state, catalogue, slots, schedule } = booking;\n const { labels, format, wizard, config } = kit;\n const party = booking.derived.party;\n const items = state.items;\n const phone = config.contact.phone;\n const now = useWallClock(slots.fromTs);\n return (\n <>\n <StylistScreen\n labels={screenLabels(labels)}\n format={format}\n now={now}\n // Not deduplicated: a named stylist has to cover every service of every\n // line — a person's extras included, as their visit is one stylist's.\n serviceIds={items.flatMap((item) => visitServicesOf(item).map((service) => service.id))}\n resources={catalogue.resources}\n loading={!catalogue.resourcesKnown}\n nextAvailableLoading={slots.nextAvailableLoading}\n selectedResourceId={state.resourceId}\n nextAvailableTs={slots.nextAvailableTs}\n pendingName={catalogue.chosenStylistName}\n notice={catalogue.stylistNotice}\n onPick={booking.pickResource}\n firstAvailableFaces={config.screens.firstAvailableFaces}\n edgeFade={config.screens.stylistEdgeFade}\n row={config.screens.wideLayouts}\n party={\n party\n ? {\n mode: state.partyMode,\n size: items.length,\n sizeWord: partySizeWord(labels, items.length),\n minutes: {\n sequential: wizard.visitMinutes(items, 'sequential'),\n parallel: wizard.visitMinutes(items, 'parallel'),\n },\n onMode: (mode) => booking.dispatch({ type: 'setPartyMode', mode }),\n }\n : undefined\n }\n classNames={resolved.classNames.stylist}\n components={\n resolved.components.StylistCard\n ? { StylistCard: resolved.components.StylistCard }\n : undefined\n }\n />\n\n {slots.ready ? (\n <TimeScreen\n labels={screenLabels(labels)}\n format={format}\n dayparts={kit.dayparts}\n monthView\n slots={slots.single}\n days={slots.days}\n openDays={schedule.openDays}\n stylistName={catalogue.chosenStylistName}\n takenSlotTs={slots.takenSlotTs}\n now={now}\n phone={phone}\n weekendNote={(dayTs) => weekendNoteFor(kit, party ? items : items.slice(0, 1), dayTs)}\n onPick={booking.pickSlot}\n soonest={\n config.screens.soonest\n ? {\n resolveStylist: (resourceId) => stylistFace(catalogue.resources, resourceId),\n }\n : undefined\n }\n dayFullness={config.screens.dayFullness}\n layout={config.screens.wideLayouts ? 'adaptive' : 'stack'}\n party={\n party\n ? {\n items,\n mode: state.partyMode,\n slots: slots.party,\n chipEndTs: (slot) => wizard.visitEndTs(items, slot.startTs, 'sequential'),\n // Only a back-to-back family is offered the simultaneous rescue.\n alternativeFor:\n state.partyMode === 'sequential'\n ? (dayTs) =>\n kit.partySlots.partyAlternative({\n sequential: slots.party,\n parallel: slots.alternatives,\n dayTs,\n })\n : undefined,\n resolveStylistName: catalogue.resolveStylistName,\n onPick: booking.pickPartySlot,\n }\n : undefined\n }\n classNames={resolved.classNames.time}\n components={timeComponents(resolved)}\n />\n ) : slots.failed ? (\n <SlotsUnavailable kit={kit} onRetry={booking.refresh} />\n ) : (\n // The step's own shape, so nothing below it moves when it fills in.\n <TimeScreenSkeleton\n labels={screenLabels(labels)}\n days={slots.days.length}\n monthView\n surchargeRow={items.some((item) =>\n visitServicesOf(item).some((service) => service.weekendSurchargePct > 0)\n )}\n />\n )}\n\n {/* Once, outside the skeleton-or-step switch, so a re-read cannot remount it. */}\n <TakenToast labels={screenLabels(labels)} takenSlotTs={slots.takenSlotTs} />\n </>\n );\n}\n\n/**\n * The details step's recap (`config.screens.recap`): the day, hours, who and\n * total of the booking the form below submits, «edit» back to the time step,\n * and a swap to another stylist free at that minute.\n */\nfunction RecapStep({ booking, resolved }: StepProps) {\n const { kit, state, catalogue, slots } = booking;\n const recap = useMemo(\n () => recapProps(kit, state, catalogue.resources, slots.single),\n [kit, state, catalogue.resources, slots.single]\n );\n return (\n <BookingRecap\n lines={recap.lines}\n totalOre={booking.derived.total}\n format={kit.format}\n labels={screenLabels(kit.labels)}\n onEdit={() => booking.dispatch({ type: 'goToStep', step: 'when' })}\n alternatives={recap.alternatives}\n // Alternatives exist only for a timed visit, so there is always a start to keep.\n onSwap={(resourceId) => booking.pickSlot({ startTs: state.startTs as number, resourceId })}\n classNames={resolved.classNames.recap}\n />\n );\n}\n\nfunction timeComponents({ components }: ResolvedBooking) {\n if (!components.DayChip && !components.TimeChip) return undefined;\n return { DayChip: components.DayChip, TimeChip: components.TimeChip };\n}\n\nfunction DetailsStep({ booking, resolved }: StepProps) {\n const { kit, state } = booking;\n const guardian = booking.login.guardian;\n const lines = useMemo(() => detailsLines(kit, state), [kit, state]);\n return (\n <DetailsScreen\n state={forMedaScreen(state)}\n onChange={booking.dispatch}\n onSubmit={booking.submit}\n lines={lines}\n totalOre={booking.derived.total}\n {...detailsScreenBase(kit)}\n submitting={booking.submitting}\n submissionNonce={booking.submissionNonce}\n family={guardian?.family}\n guardianPhone={guardian?.phone ?? null}\n // The address they logged in with: the booking is theirs, by that address.\n emailReadOnly={kit.config.account.required && guardian !== null}\n format={kit.format}\n labels={screenLabels(kit.labels)}\n classNames={resolved.classNames.details}\n components={\n resolved.components.FamilyChip ? { FamilyChip: resolved.components.FamilyChip } : undefined\n }\n />\n );\n}\n\n/**\n * «Bekreft» under `account.required` for a parent not logged in: «Nesten\n * ferdig», a line, and the login — Vipps, or e-mail — in place of the form.\n * The heading carries the details step's id, so the focus a step change moves\n * lands here, and the form's heading takes it over once the parent is in.\n */\nfunction AccountGate({\n booking,\n resolved,\n actions,\n overrides,\n onSignedIn,\n}: StepProps & {\n actions: NonNullable<BookingWizardProps['actions']>;\n overrides: BookingOverrides;\n onSignedIn: (guardian: Parameters<BookingController['login']['signIn']>[0]) => void;\n}) {\n const { labels } = booking.kit;\n const { classNames } = resolved;\n // The details screen's own root and heading slots, after the same defaults.\n const slot = (base: string, extra: string | undefined) => [base, extra].filter(Boolean).join(' ');\n return (\n <section\n aria-labelledby={STEP_HEADINGS.details}\n className={slot('space-y-6', classNames.details?.root)}\n >\n <h2\n id={STEP_HEADINGS.details}\n tabIndex={-1}\n className={slot(\n 'font-sans text-2xl font-bold outline-none md:text-3xl',\n classNames.details?.heading\n )}\n >\n {labelText(labels['wizard.account.heading'])}\n </h2>\n <LiveStatus\n text={booking.login.sessionLost ? labelText(labels['wizard.account.sessionLost']) : null}\n className=\"text-sm font-medium empty:hidden\"\n />\n <p className=\"text-muted-foreground\">{labelText(labels['wizard.account.intro'])}</p>\n {/* What the login is for: the hour held, in the summary bar's own words and slot. */}\n <p\n className={slot(\n 'rounded-lg border border-border bg-card px-4 py-3 text-sm font-medium',\n classNames.summary?.line\n )}\n >\n {booking.derived.summary}\n </p>\n <LoginSheet\n {...overridesOf(overrides)}\n actions={actions}\n presentation=\"gate\"\n resumePath={booking.login.resumePath}\n vippsConfirm={booking.login.vippsConfirm}\n onSignedIn={onSignedIn}\n />\n </section>\n );\n}\n\nfunction ConfirmationStep({ booking, resolved, siteUrl }: StepProps & { siteUrl?: string }) {\n const { kit } = booking;\n // Read once, so «today» cannot become «tomorrow» halfway down the card.\n const [now] = useState(() => Date.now());\n const confirmation = booking.confirmed as BookingConfirmation;\n return (\n <Confirmation\n {...confirmationProps(kit, confirmation, siteUrl)}\n onStartOver={booking.startOver}\n now={now}\n format={kit.format}\n labels={screenLabels(kit.labels)}\n classNames={resolved.classNames.confirmation}\n components={\n resolved.components.PartyLine ? { PartyLine: resolved.components.PartyLine } : undefined\n }\n />\n );\n}\n\n/**\n * «Have an account? Log in» — or, once the parent is known, nothing at all\n * for one who arrived logged in, and one line saying so for one who logged in\n * from the sheet.\n */\nfunction LoginRow({\n booking,\n actions,\n overrides,\n onSignedIn,\n}: {\n booking: BookingController;\n actions: NonNullable<BookingWizardProps['actions']>;\n overrides: BookingOverrides;\n onSignedIn: (guardian: Parameters<BookingController['login']['signIn']>[0]) => void;\n}) {\n const { arrivedAs, signedIn, resumePath } = booking.login;\n const { labels } = booking.kit;\n if (signedIn === null && arrivedAs !== null) return null;\n // Named only by the login itself: an unreadable profile is «Logget inn»,\n // never the name of whoever the page arrived as.\n const who = signedIn?.guardian ?? null;\n const name =\n who === null\n ? null\n : [who.firstName, who.lastName].filter(Boolean).join(' ').trim() || who.email;\n const confirmation =\n signedIn === null\n ? null\n : name === null\n ? labelText(labels['wizard.signedIn'])\n : fill(labels['wizard.signedInAs'], { name });\n return (\n <div>\n {signedIn === null && (\n <LoginSheet\n {...overridesOf(overrides)}\n actions={actions}\n presentation=\"sheet\"\n resumePath={resumePath}\n onSignedIn={onSignedIn}\n />\n )}\n <LiveStatus text={confirmation} className=\"text-sm text-muted-foreground empty:hidden\" />\n </div>\n );\n}\n\n/** The neutral shell the restore gate shows, the header's and a step's shape. */\nfunction RestoreSkeleton({ text }: { text: string }) {\n return (\n <div data-testid=\"restore-skeleton\">\n {/* Outside the busy subtree: assistive tech may hold back what is inside it. */}\n <LiveStatus text={text} />\n <div aria-busy=\"true\" className=\"space-y-8\">\n <div aria-hidden=\"true\" className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"size-9 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"flex flex-col gap-1.5\">\n <span className=\"h-4 w-24 animate-pulse rounded bg-muted\" />\n <span className=\"h-3 w-36 animate-pulse rounded bg-muted\" />\n </span>\n </div>\n <span className=\"block h-1.5 w-full animate-pulse rounded-full bg-muted\" />\n </div>\n <div aria-hidden=\"true\" className=\"space-y-6\">\n <span className=\"block h-8 w-56 animate-pulse rounded bg-muted md:h-9\" />\n <div className=\"space-y-2\">\n {Array.from({ length: 4 }, (_, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: four identical placeholders.\n <span key={index} className=\"block h-24 animate-pulse rounded-lg bg-muted\" />\n ))}\n </div>\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Where the visitor is, the way back, and how much is left: a back arrow, the\n * title and «step N of 4», and one segment per step that IS the button to it —\n * tappable only where the machine's `canGoToStep` says so, and frozen while a\n * submission is in flight.\n */\nfunction WizardHeader({\n kit,\n state,\n submitting,\n onGo,\n}: {\n kit: BookingKit;\n state: WizardState;\n submitting: boolean;\n onGo: (step: WizardStep) => void;\n}) {\n const { labels, wizard } = kit;\n const stepLabel = (step: WizardStep) => labelText(labels[`wizard.step.${step}`]);\n const index = STEPS.indexOf(state.step);\n const previous = index > 0 ? STEPS[index - 1] : null;\n const canGoBack = previous !== null && !submitting && wizard.canGoToStep(state, previous);\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n {canGoBack ? (\n <button\n type=\"button\"\n aria-label={fill(labels['wizard.back'], { label: stepLabel(previous) })}\n onClick={() => onGo(previous)}\n className=\"flex size-9 shrink-0 items-center justify-center rounded-full border border-border text-foreground transition-colors hover:bg-muted\"\n >\n <ArrowLeft aria-hidden=\"true\" className=\"size-4\" />\n </button>\n ) : (\n // Held rather than dropped, so the title does not shuffle sideways.\n <span aria-hidden=\"true\" className=\"size-9 shrink-0\" />\n )}\n <p className=\"min-w-0\">\n <span className=\"block font-sans text-sm font-bold\">{labels['wizard.title']}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {fillParts(labels['wizard.progress'], {\n step: index + 1,\n total: STEPS.length,\n label: stepLabel(state.step),\n })}\n </span>\n </p>\n </div>\n\n <ol\n aria-label={labelText(labels['wizard.progressLabel'])}\n className=\"flex items-center gap-1.5\"\n >\n {STEPS.map((step, position) => {\n const isCurrent = step === state.step;\n const reachable = !submitting && !isCurrent && wizard.canGoToStep(state, step);\n return (\n <li key={step} className=\"flex-1\">\n <button\n type=\"button\"\n disabled={!reachable}\n aria-current={isCurrent ? 'step' : undefined}\n onClick={() => onGo(step)}\n className={`block h-1.5 w-full rounded-full transition-colors ${\n position <= index ? 'bg-primary' : 'bg-border'\n } ${reachable ? 'cursor-pointer' : 'cursor-default'}`}\n >\n <span className=\"sr-only\">{stepLabel(step)}</span>\n </button>\n </li>\n );\n })}\n </ol>\n </div>\n );\n}\n\n/**\n * The openings could not be read — this site's failure, not a full day —\n * with the telephone as the rescue and a retry as the cheaper first try.\n */\nfunction SlotsUnavailable({ kit, onRetry }: { kit: BookingKit; onRetry: () => void }) {\n const { labels, config, format } = kit;\n const phone = config.contact.phone;\n return (\n <div className=\"space-y-3\">\n <p role=\"alert\" className=\"rounded-lg border border-border bg-card px-5 py-4 text-sm\">\n {labels['wizard.slotsUnavailable.lead']}\n {phone ? (\n <a\n href={format.telHref(phone)}\n aria-label={fill(labels['wizard.slotsUnavailable.callAria'], { phone })}\n className=\"font-semibold text-primary underline underline-offset-4\"\n >\n {fillParts(labels['wizard.slotsUnavailable.call'], { phone })}\n </a>\n ) : (\n labels['wizard.slotsUnavailable.callPlain']\n )}\n {labels['wizard.slotsUnavailable.suffix']}\n </p>\n <BookingButtonOutline onClick={onRetry}>{labels['wizard.retry']}</BookingButtonOutline>\n </div>\n );\n}\n\nfunction BookingButtonOutline({ onClick, children }: { onClick: () => void; children: ReactNode }) {\n return (\n <BookingButton variant=\"outline\" onClick={onClick}>\n {children}\n </BookingButton>\n );\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { BookingLabels, BookingLabelsInput } from "./labels/index.mjs";
9
9
  import { BookingKit, createBookingKit } from "./kit.mjs";
10
10
  import { BookingClassNames, BookingComponents, BookingOverrides, BookingProvider, BookingProviderProps, ResolvedBooking, useBookingKit } from "./Provider.mjs";
11
11
  import { BOOKING_NAVIGATION_EVENT, BookingLink, BookingLinkProps, BookingPendingHost, PENDING_DELAY_MS, PENDING_TIMEOUT_MS, bookingPrefetch, bookingTarget } from "./BookingLink.mjs";
12
- import { BookingConfirmation, BookingController, BookingSeed, BookingSuggestion, ConfirmedLine, UseBookingOptions, confirmationLines, isCompleteConfirmation, personForChild, useBooking } from "./useBooking.mjs";
12
+ import { BookingConfirmation, BookingController, BookingReadEndpoint, BookingReadError, BookingSeed, BookingSuggestion, ConfirmedLine, UseBookingOptions, confirmationLines, isCompleteConfirmation, personForChild, useBooking } from "./useBooking.mjs";
13
13
  import { BookingWizard, BookingWizardEvent, BookingWizardProps } from "./BookingWizard.mjs";
14
14
  import { LoginSheet, LoginSheetProps, VippsConfirm } from "./LoginSheet.mjs";
15
15
  import { LoginFromQuery, LoginFromQueryProps, VippsNotice, returnPathFromSearch, vippsNotice } from "./LoginPageQuery.mjs";
@@ -25,4 +25,4 @@ import { SESSION_TOUCH_THROTTLE_MS, SessionTouch, sessionTouchKey } from "./port
25
25
  import { VIPPS_LINK_FLASHES, VippsLinkFlash, vippsFlashPath, vippsLinkFlash } from "./portal/vipps-flash.mjs";
26
26
  import "./PortalDashboard.mjs";
27
27
  import { nextFreeClient, useNextFree } from "./use-next-free.mjs";
28
- export { ActionAnswer, ActionFailure, BOOKING_NAVIGATION_EVENT, BookingClassNames, BookingComponents, BookingConfirmation, BookingController, BookingKit, type BookingLabels, type BookingLabelsInput, BookingLink, BookingLinkProps, BookingOverrides, BookingPendingHost, BookingProvider, BookingProviderProps, BookingSeed, BookingSuggestion, BookingWizard, BookingWizardEvent, BookingWizardProps, ConfirmedLine, DEFAULT_PORTAL_TAB, DEFAULT_PORTAL_TAB_PARAM, DEFAULT_PORTAL_TAB_SLUGS, InvalidFailure, LoginFromQuery, LoginFromQuery as LoginPageQuery, LoginFromQueryProps, type LoginLabels, LoginSheet, LoginSheetProps, ManageBooking, ManageBookingProps, type ManageLabels, type NextFree, PENDING_DELAY_MS, PENDING_TIMEOUT_MS, PORTAL_TABS, PersonActionInput, PersonActionResult, PersonActionTarget, PortalActions, PortalDashboard, type PortalDashboardProps, PortalDashboardUnreachable, type PortalDashboardUnreachableProps, type PortalLabels, type PortalTab, type PortalTabIcons, ProfileActionResult, ResolvedBooking, SESSION_TOUCH_THROTTLE_MS, SafeActionEnvelope, type ScreenLabels, SessionFailure, SessionTouch, ThrottledFailure, UseBookingOptions, VIPPS_LINK_FLASHES, type VippsConfirm, type VippsLinkFlash, VippsNotice, type WizardLabels, agePromptCookie, agePromptCookieName, bookAgainHref, bookingPrefetch, bookingTarget, confirmationLines, createBookingKit, createChildSummaries, isCompleteConfirmation, leavePortal, nextFreeClient, parseAgePromptDismissed, parsePortalTab, personForChild, readAction, readStartLogin, returnPathFromSearch, sessionTouchKey, useBooking, useBookingKit, useNextFree, vippsFlashPath, vippsLinkFlash, vippsNotice };
28
+ export { ActionAnswer, ActionFailure, BOOKING_NAVIGATION_EVENT, BookingClassNames, BookingComponents, BookingConfirmation, BookingController, BookingKit, type BookingLabels, type BookingLabelsInput, BookingLink, BookingLinkProps, BookingOverrides, BookingPendingHost, BookingProvider, BookingProviderProps, BookingReadEndpoint, BookingReadError, BookingSeed, BookingSuggestion, BookingWizard, BookingWizardEvent, BookingWizardProps, ConfirmedLine, DEFAULT_PORTAL_TAB, DEFAULT_PORTAL_TAB_PARAM, DEFAULT_PORTAL_TAB_SLUGS, InvalidFailure, LoginFromQuery, LoginFromQuery as LoginPageQuery, LoginFromQueryProps, type LoginLabels, LoginSheet, LoginSheetProps, ManageBooking, ManageBookingProps, type ManageLabels, type NextFree, PENDING_DELAY_MS, PENDING_TIMEOUT_MS, PORTAL_TABS, PersonActionInput, PersonActionResult, PersonActionTarget, PortalActions, PortalDashboard, type PortalDashboardProps, PortalDashboardUnreachable, type PortalDashboardUnreachableProps, type PortalLabels, type PortalTab, type PortalTabIcons, ProfileActionResult, ResolvedBooking, SESSION_TOUCH_THROTTLE_MS, SafeActionEnvelope, type ScreenLabels, SessionFailure, SessionTouch, ThrottledFailure, UseBookingOptions, VIPPS_LINK_FLASHES, type VippsConfirm, type VippsLinkFlash, VippsNotice, type WizardLabels, agePromptCookie, agePromptCookieName, bookAgainHref, bookingPrefetch, bookingTarget, confirmationLines, createBookingKit, createChildSummaries, isCompleteConfirmation, leavePortal, nextFreeClient, parseAgePromptDismissed, parsePortalTab, personForChild, readAction, readStartLogin, returnPathFromSearch, sessionTouchKey, useBooking, useBookingKit, useNextFree, vippsFlashPath, vippsLinkFlash, vippsNotice };
@@ -374,6 +374,8 @@ const SCREEN_LABELS_NB = {
374
374
  "time.dayChip.free": "{count} ledige",
375
375
  "time.dayChip.freeOne": "{count} ledig",
376
376
  "time.dayChip.full": "fullt",
377
+ "time.week.none": "Ingen ledige",
378
+ "time.week.more": "{count} til",
377
379
  "summary.hint.who": "Velg hvem som kommer",
378
380
  "summary.hint.service": "Trykk på en tjeneste",
379
381
  "summary.hint.when": "Trykk på en tid",
@@ -760,6 +762,8 @@ const SCREEN_LABELS_EN = {
760
762
  "time.dayChip.free": "{count} free",
761
763
  "time.dayChip.freeOne": "{count} free",
762
764
  "time.dayChip.full": "full",
765
+ "time.week.none": "Nothing free",
766
+ "time.week.more": "{count} more",
763
767
  "summary.hint.who": "Choose who is coming",
764
768
  "summary.hint.service": "Tap a service",
765
769
  "summary.hint.when": "Tap a time",