@medalsocial/booking 0.0.0-placeholder.0 → 0.2.0

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 (266) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +6 -0
  4. package/README.md +220 -1
  5. package/dist/core/age.d.mts +34 -0
  6. package/dist/core/age.mjs +95 -0
  7. package/dist/core/age.mjs.map +1 -0
  8. package/dist/core/attempt-store.d.mts +111 -0
  9. package/dist/core/attempt-store.mjs +159 -0
  10. package/dist/core/attempt-store.mjs.map +1 -0
  11. package/dist/core/categories.d.mts +27 -0
  12. package/dist/core/categories.mjs +42 -0
  13. package/dist/core/categories.mjs.map +1 -0
  14. package/dist/core/clock.d.mts +33 -0
  15. package/dist/core/clock.mjs +251 -0
  16. package/dist/core/clock.mjs.map +1 -0
  17. package/dist/core/config.d.mts +178 -0
  18. package/dist/core/config.mjs +394 -0
  19. package/dist/core/config.mjs.map +1 -0
  20. package/dist/core/consent.d.mts +14 -0
  21. package/dist/core/consent.mjs +17 -0
  22. package/dist/core/consent.mjs.map +1 -0
  23. package/dist/core/deep-link.d.mts +40 -0
  24. package/dist/core/deep-link.mjs +160 -0
  25. package/dist/core/deep-link.mjs.map +1 -0
  26. package/dist/core/display-name.d.mts +18 -0
  27. package/dist/core/display-name.mjs +30 -0
  28. package/dist/core/display-name.mjs.map +1 -0
  29. package/dist/core/draft-store.d.mts +89 -0
  30. package/dist/core/draft-store.mjs +131 -0
  31. package/dist/core/draft-store.mjs.map +1 -0
  32. package/dist/core/dto.d.mts +46 -0
  33. package/dist/core/dto.mjs +157 -0
  34. package/dist/core/dto.mjs.map +1 -0
  35. package/dist/core/ics.d.mts +63 -0
  36. package/dist/core/ics.mjs +130 -0
  37. package/dist/core/ics.mjs.map +1 -0
  38. package/dist/core/index.d.mts +35 -0
  39. package/dist/core/index.mjs +32 -0
  40. package/dist/core/labels.d.mts +88 -0
  41. package/dist/core/labels.mjs +93 -0
  42. package/dist/core/labels.mjs.map +1 -0
  43. package/dist/core/machine.d.mts +645 -0
  44. package/dist/core/machine.mjs +988 -0
  45. package/dist/core/machine.mjs.map +1 -0
  46. package/dist/core/money.d.mts +12 -0
  47. package/dist/core/money.mjs +51 -0
  48. package/dist/core/money.mjs.map +1 -0
  49. package/dist/core/next-available.d.mts +20 -0
  50. package/dist/core/next-available.mjs +36 -0
  51. package/dist/core/next-available.mjs.map +1 -0
  52. package/dist/core/next-free.d.mts +25 -0
  53. package/dist/core/next-free.mjs +44 -0
  54. package/dist/core/next-free.mjs.map +1 -0
  55. package/dist/core/party-slots.d.mts +79 -0
  56. package/dist/core/party-slots.mjs +229 -0
  57. package/dist/core/party-slots.mjs.map +1 -0
  58. package/dist/core/paths.d.mts +11 -0
  59. package/dist/core/paths.mjs +56 -0
  60. package/dist/core/paths.mjs.map +1 -0
  61. package/dist/core/phone.d.mts +17 -0
  62. package/dist/core/phone.mjs +58 -0
  63. package/dist/core/phone.mjs.map +1 -0
  64. package/dist/core/portal/cookies.d.mts +29 -0
  65. package/dist/core/portal/dto.d.mts +140 -0
  66. package/dist/core/portal/dto.mjs +142 -0
  67. package/dist/core/portal/dto.mjs.map +1 -0
  68. package/dist/core/portal/family.d.mts +39 -0
  69. package/dist/core/portal/family.mjs +39 -0
  70. package/dist/core/portal/family.mjs.map +1 -0
  71. package/dist/core/portal/login-input.d.mts +29 -0
  72. package/dist/core/portal/login-input.mjs +29 -0
  73. package/dist/core/portal/login-input.mjs.map +1 -0
  74. package/dist/core/portal/next-path.d.mts +12 -0
  75. package/dist/core/portal/next-path.mjs +98 -0
  76. package/dist/core/portal/next-path.mjs.map +1 -0
  77. package/dist/core/portal/return-path.d.mts +7 -0
  78. package/dist/core/portal/return-path.mjs +49 -0
  79. package/dist/core/portal/return-path.mjs.map +1 -0
  80. package/dist/core/portal/session-cookie.d.mts +27 -0
  81. package/dist/core/portal/session-cookie.mjs +30 -0
  82. package/dist/core/portal/session-cookie.mjs.map +1 -0
  83. package/dist/core/portal/vipps-link.d.mts +56 -0
  84. package/dist/core/portal/vipps-link.mjs +136 -0
  85. package/dist/core/portal/vipps-link.mjs.map +1 -0
  86. package/dist/core/portal/vipps-return.d.mts +39 -0
  87. package/dist/core/portal/vipps-return.mjs +69 -0
  88. package/dist/core/portal/vipps-return.mjs.map +1 -0
  89. package/dist/core/rebook-store.d.mts +28 -0
  90. package/dist/core/rebook-store.mjs +60 -0
  91. package/dist/core/rebook-store.mjs.map +1 -0
  92. package/dist/core/restore-gate.d.mts +40 -0
  93. package/dist/core/restore-gate.mjs +96 -0
  94. package/dist/core/restore-gate.mjs.map +1 -0
  95. package/dist/core/stores.d.mts +8 -0
  96. package/dist/core/stores.mjs +23 -0
  97. package/dist/core/stores.mjs.map +1 -0
  98. package/dist/core/types.d.mts +245 -0
  99. package/dist/core/visit.d.mts +26 -0
  100. package/dist/core/visit.mjs +40 -0
  101. package/dist/core/visit.mjs.map +1 -0
  102. package/dist/core/wire.d.mts +37 -0
  103. package/dist/next/cache/memory.d.mts +14 -0
  104. package/dist/next/cache/memory.mjs +51 -0
  105. package/dist/next/cache/memory.mjs.map +1 -0
  106. package/dist/next/cache/next-data.d.mts +6 -0
  107. package/dist/next/cache/next-data.mjs +35 -0
  108. package/dist/next/cache/next-data.mjs.map +1 -0
  109. package/dist/next/cache/noop.d.mts +6 -0
  110. package/dist/next/cache/noop.mjs +15 -0
  111. package/dist/next/cache/noop.mjs.map +1 -0
  112. package/dist/next/cache/workers.d.mts +29 -0
  113. package/dist/next/cache/workers.mjs +74 -0
  114. package/dist/next/cache/workers.mjs.map +1 -0
  115. package/dist/next/catalogue.d.mts +38 -0
  116. package/dist/next/catalogue.mjs +215 -0
  117. package/dist/next/catalogue.mjs.map +1 -0
  118. package/dist/next/handler.d.mts +11 -0
  119. package/dist/next/handler.mjs +150 -0
  120. package/dist/next/handler.mjs.map +1 -0
  121. package/dist/next/index.d.mts +17 -0
  122. package/dist/next/index.mjs +14 -0
  123. package/dist/next/loaders.d.mts +79 -0
  124. package/dist/next/loaders.mjs +195 -0
  125. package/dist/next/loaders.mjs.map +1 -0
  126. package/dist/next/manage-dto.d.mts +6 -0
  127. package/dist/next/manage-dto.mjs +28 -0
  128. package/dist/next/manage-dto.mjs.map +1 -0
  129. package/dist/next/medal.d.mts +105 -0
  130. package/dist/next/medal.mjs +155 -0
  131. package/dist/next/medal.mjs.map +1 -0
  132. package/dist/next/options.d.mts +158 -0
  133. package/dist/next/options.mjs +65 -0
  134. package/dist/next/options.mjs.map +1 -0
  135. package/dist/next/portal/guardian.d.mts +42 -0
  136. package/dist/next/portal/guardian.mjs +76 -0
  137. package/dist/next/portal/guardian.mjs.map +1 -0
  138. package/dist/next/portal/medal-portal.d.mts +161 -0
  139. package/dist/next/portal/medal-portal.mjs +825 -0
  140. package/dist/next/portal/medal-portal.mjs.map +1 -0
  141. package/dist/next/portal/session.d.mts +38 -0
  142. package/dist/next/portal/session.mjs +40 -0
  143. package/dist/next/portal/session.mjs.map +1 -0
  144. package/dist/next/portal/vipps-flash.d.mts +14 -0
  145. package/dist/next/portal/vipps-flash.mjs +41 -0
  146. package/dist/next/portal/vipps-flash.mjs.map +1 -0
  147. package/dist/next/portal.d.mts +205 -0
  148. package/dist/next/portal.mjs +469 -0
  149. package/dist/next/portal.mjs.map +1 -0
  150. package/dist/next/request.d.mts +51 -0
  151. package/dist/next/request.mjs +123 -0
  152. package/dist/next/request.mjs.map +1 -0
  153. package/dist/next/routes/catalogue-routes.mjs +344 -0
  154. package/dist/next/routes/catalogue-routes.mjs.map +1 -0
  155. package/dist/next/routes/create.mjs +460 -0
  156. package/dist/next/routes/create.mjs.map +1 -0
  157. package/dist/next/routes/manage.mjs +191 -0
  158. package/dist/next/routes/manage.mjs.map +1 -0
  159. package/dist/next/routes/portal-routes.mjs +312 -0
  160. package/dist/next/routes/portal-routes.mjs.map +1 -0
  161. package/dist/next/routes/shared.mjs +162 -0
  162. package/dist/next/routes/shared.mjs.map +1 -0
  163. package/dist/next/routes/vipps-return.mjs +166 -0
  164. package/dist/next/routes/vipps-return.mjs.map +1 -0
  165. package/dist/next/runtime.d.mts +49 -0
  166. package/dist/next/runtime.mjs +94 -0
  167. package/dist/next/runtime.mjs.map +1 -0
  168. package/dist/next/seed.d.mts +71 -0
  169. package/dist/next/seed.mjs +495 -0
  170. package/dist/next/seed.mjs.map +1 -0
  171. package/dist/next/server.d.mts +26 -0
  172. package/dist/next/server.mjs +63 -0
  173. package/dist/next/server.mjs.map +1 -0
  174. package/dist/react/BookingLink.d.mts +61 -0
  175. package/dist/react/BookingLink.mjs +102 -0
  176. package/dist/react/BookingLink.mjs.map +1 -0
  177. package/dist/react/BookingWizard.d.mts +49 -0
  178. package/dist/react/BookingWizard.mjs +598 -0
  179. package/dist/react/BookingWizard.mjs.map +1 -0
  180. package/dist/react/LoginPageQuery.d.mts +37 -0
  181. package/dist/react/LoginPageQuery.mjs +106 -0
  182. package/dist/react/LoginPageQuery.mjs.map +1 -0
  183. package/dist/react/LoginSheet.d.mts +49 -0
  184. package/dist/react/LoginSheet.mjs +98 -0
  185. package/dist/react/LoginSheet.mjs.map +1 -0
  186. package/dist/react/ManageBooking.d.mts +50 -0
  187. package/dist/react/ManageBooking.mjs +197 -0
  188. package/dist/react/ManageBooking.mjs.map +1 -0
  189. package/dist/react/PortalDashboard.d.mts +10 -0
  190. package/dist/react/Provider.d.mts +82 -0
  191. package/dist/react/Provider.mjs +132 -0
  192. package/dist/react/Provider.mjs.map +1 -0
  193. package/dist/react/actions.d.mts +133 -0
  194. package/dist/react/actions.mjs +90 -0
  195. package/dist/react/actions.mjs.map +1 -0
  196. package/dist/react/index.d.mts +28 -0
  197. package/dist/react/index.mjs +20 -0
  198. package/dist/react/kit.d.mts +54 -0
  199. package/dist/react/kit.mjs +127 -0
  200. package/dist/react/kit.mjs.map +1 -0
  201. package/dist/react/labels/index.d.mts +23 -0
  202. package/dist/react/labels/index.mjs +44 -0
  203. package/dist/react/labels/index.mjs.map +1 -0
  204. package/dist/react/labels/login.d.mts +10 -0
  205. package/dist/react/labels/login.mjs +13 -0
  206. package/dist/react/labels/login.mjs.map +1 -0
  207. package/dist/react/labels/manage.d.mts +15 -0
  208. package/dist/react/labels/manage.mjs +19 -0
  209. package/dist/react/labels/manage.mjs.map +1 -0
  210. package/dist/react/labels/portal.d.mts +25 -0
  211. package/dist/react/labels/portal.mjs +41 -0
  212. package/dist/react/labels/portal.mjs.map +1 -0
  213. package/dist/react/labels/screens.d.mts +11 -0
  214. package/dist/react/labels/screens.mjs +721 -0
  215. package/dist/react/labels/screens.mjs.map +1 -0
  216. package/dist/react/labels/wizard.d.mts +76 -0
  217. package/dist/react/labels/wizard.mjs +97 -0
  218. package/dist/react/labels/wizard.mjs.map +1 -0
  219. package/dist/react/link/index.d.mts +5 -0
  220. package/dist/react/link/index.mjs +5 -0
  221. package/dist/react/portal/SessionTouch.d.mts +19 -0
  222. package/dist/react/portal/SessionTouch.mjs +40 -0
  223. package/dist/react/portal/SessionTouch.mjs.map +1 -0
  224. package/dist/react/portal/age-prompt.d.mts +10 -0
  225. package/dist/react/portal/age-prompt.mjs +21 -0
  226. package/dist/react/portal/age-prompt.mjs.map +1 -0
  227. package/dist/react/portal/book-again.d.mts +18 -0
  228. package/dist/react/portal/book-again.mjs +21 -0
  229. package/dist/react/portal/book-again.mjs.map +1 -0
  230. package/dist/react/portal/child-summary.d.mts +17 -0
  231. package/dist/react/portal/child-summary.mjs +45 -0
  232. package/dist/react/portal/child-summary.mjs.map +1 -0
  233. package/dist/react/portal/dashboard.d.mts +74 -0
  234. package/dist/react/portal/dashboard.mjs +204 -0
  235. package/dist/react/portal/dashboard.mjs.map +1 -0
  236. package/dist/react/portal/leave.d.mts +17 -0
  237. package/dist/react/portal/leave.mjs +21 -0
  238. package/dist/react/portal/leave.mjs.map +1 -0
  239. package/dist/react/portal/sections.d.mts +12 -0
  240. package/dist/react/portal/sections.mjs +48 -0
  241. package/dist/react/portal/sections.mjs.map +1 -0
  242. package/dist/react/portal/tabs.d.mts +23 -0
  243. package/dist/react/portal/tabs.mjs +36 -0
  244. package/dist/react/portal/tabs.mjs.map +1 -0
  245. package/dist/react/portal/vipps-flash.d.mts +11 -0
  246. package/dist/react/portal/vipps-flash.mjs +18 -0
  247. package/dist/react/portal/vipps-flash.mjs.map +1 -0
  248. package/dist/react/portal/wired.mjs +202 -0
  249. package/dist/react/portal/wired.mjs.map +1 -0
  250. package/dist/react/screen-labels.mjs +14 -0
  251. package/dist/react/screen-labels.mjs.map +1 -0
  252. package/dist/react/shared.d.mts +13 -0
  253. package/dist/react/shared.mjs +7 -0
  254. package/dist/react/use-next-free.d.mts +19 -0
  255. package/dist/react/use-next-free.mjs +56 -0
  256. package/dist/react/use-next-free.mjs.map +1 -0
  257. package/dist/react/useBooking.d.mts +179 -0
  258. package/dist/react/useBooking.mjs +1331 -0
  259. package/dist/react/useBooking.mjs.map +1 -0
  260. package/dist/react/wizard/adapters.mjs +189 -0
  261. package/dist/react/wizard/adapters.mjs.map +1 -0
  262. package/dist/react/wizard/index.d.mts +11 -0
  263. package/dist/react/wizard/index.mjs +6 -0
  264. package/dist/react/wizard-error.mjs +29 -0
  265. package/dist/react/wizard-error.mjs.map +1 -0
  266. package/package.json +126 -5
@@ -0,0 +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 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 guestChoices,\n partyPeople,\n partySizeWord,\n serviceFitsFor,\n serviceScreenBase,\n weekendNoteFor,\n} from './wizard/adapters';\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 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 booking = useBooking(props);\n const { kit } = booking;\n const { labels } = kit;\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 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 const loginOffered = props.actions !== undefined && kit.config.portal.enabled;\n const loginRow = loginOffered ? (\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 {state.step === 'details' && loginRow}\n\n {loginOffered && booking.login.vippsConfirm !== null && signedIn === null && (\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' && <DetailsStep booking={booking} resolved={resolved} />}\n </div>\n\n {!multiSelect && (\n <SummaryBar\n line={booking.derived.summary}\n canAdvance={booking.derived.canAdvance}\n step={state.step}\n onNext={booking.next}\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 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))}\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 return (\n <>\n <StylistScreen\n labels={screenLabels(labels)}\n format={format}\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 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 phone={phone}\n weekendNote={(dayTs) => weekendNoteFor(kit, party ? items : items.slice(0, 1), dayTs)}\n onPick={booking.pickSlot}\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\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 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\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 const who = signedIn === null ? null : (signedIn.guardian ?? arrivedAs);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,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,UAAU,WAAW,KAAK;CAChC,MAAM,EAAE,QAAQ;CAChB,MAAM,EAAE,WAAW;CACnB,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;CACzB,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;CACxF,MAAM,eAAe,MAAM,YAAY,KAAA,KAAa,IAAI,OAAO,OAAO;CACtE,MAAM,WAAW,eACf,oBAAC,UAAD;EACW;EACT,SAAS,MAAM;EACf,WAAW;EACC;CACb,CAAA,IACC;CAEJ,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;GAGN,MAAM,SAAS,aAAa;GAE5B,gBAAgB,QAAQ,MAAM,iBAAiB,QAAQ,aAAa,QACnE,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,aAAa,oBAAC,aAAD;MAAsB;MAAmB;KAAW,CAAA;IAC9E;GAbE,GAAA,MAAM,IAaR;GAEJ,CAAC,eACA,oBAAC,YAAD;IACE,MAAM,QAAQ,QAAQ;IACtB,YAAY,QAAQ,QAAQ;IAC5B,MAAM,MAAM;IACZ,QAAQ,QAAQ;IAChB,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;EACT;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,CAAC;EACxD,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,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,eAAD;GACE,QAAQ,aAAa,MAAM;GACnB;GAGR,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,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;GACZ;GACP,cAAc,UAAU,eAAe,KAAK,QAAQ,QAAQ,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK;GACpF,QAAQ,QAAQ;GAChB,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;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;EAClC,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;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;CACpD,MAAM,MAAM,aAAa,OAAO,OAAQ,SAAS,YAAY;CAC7D,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"}
@@ -0,0 +1,37 @@
1
+ import { BookingConfig } from "../core/config.mjs";
2
+ import { LoginLabels } from "./labels/login.mjs";
3
+ import "./labels/index.mjs";
4
+ import { BookingOverrides } from "./Provider.mjs";
5
+ import { LoginSheetProps } from "./LoginSheet.mjs";
6
+ //#region src/react/LoginPageQuery.d.ts
7
+ /**
8
+ * The sentence for `?vipps=<value>`, or `null` for anything that is not an
9
+ * outcome. The query is writable by anyone with a URL bar, so the value itself
10
+ * is never rendered: it is a key into the table or it is nothing. A repeated
11
+ * parameter is nothing too.
12
+ */
13
+ export declare function vippsNotice(values: readonly string[], labels: LoginLabels): string | null;
14
+ /**
15
+ * `?vipps=` — why a Vipps login did not end in a cookie. `useSearchParams` on
16
+ * a prerendered page must sit under a `<Suspense>` (the page provides it, with
17
+ * a `null` fallback: a box reserved for the rare notice would be a layout
18
+ * shift for everybody else).
19
+ */
20
+ export declare function VippsNotice(props: BookingOverrides): import("react").JSX.Element | null;
21
+ /** `?return=`, validated. A repeated parameter is refused, as on the server. */
22
+ export declare function returnPathFromSearch(search: string, config: Pick<BookingConfig, 'portal'>): string | null;
23
+ export type LoginFromQueryProps = BookingOverrides & Pick<LoginSheetProps, 'actions'>;
24
+ /**
25
+ * The inline login, told where to go once it succeeds.
26
+ *
27
+ * NOT `useSearchParams`: that would need a Suspense boundary, and the login
28
+ * form is the page — as a fallback it would be swapped for a fresh copy on
29
+ * hydration, dropping anything typed before the JavaScript arrived.
30
+ * `useSyncExternalStore` hydrates with the server snapshot (`null`) and
31
+ * re-renders the SAME tree with the real value. The return path is never
32
+ * rendered, only handed to the login's `router.replace` and the Vipps start.
33
+ */
34
+ export declare function LoginFromQuery({ actions, ...overrides }: LoginFromQueryProps): import("react").JSX.Element;
35
+ //#endregion
36
+ export { LoginFromQuery as LoginPageQuery };
37
+ //# sourceMappingURL=LoginPageQuery.d.mts.map
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+ import { labelText } from "../core/labels.mjs";
3
+ import { createReturnPath } from "../core/portal/return-path.mjs";
4
+ import { stripVippsReturn, vippsConfirmFrom } from "../core/portal/vipps-return.mjs";
5
+ import { useBookingKit } from "./Provider.mjs";
6
+ import { LoginSheet } from "./LoginSheet.mjs";
7
+ import { useSearchParams } from "next/navigation";
8
+ import { createElement, useCallback, useEffect, useState, useSyncExternalStore } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ //#region src/react/LoginPageQuery.tsx
11
+ /**
12
+ * The two things the login page reads off its query string, read in the
13
+ * browser so the page itself can be prerendered: one static document, and
14
+ * these two components pick up the query after it lands.
15
+ */
16
+ /** The label for each outcome the Vipps return route can report. */
17
+ const VIPPS_NOTICE = {
18
+ needs_email_login: "loginPage.vipps.needsEmailLogin",
19
+ failed: "loginPage.vipps.failed"
20
+ };
21
+ /**
22
+ * The sentence for `?vipps=<value>`, or `null` for anything that is not an
23
+ * outcome. The query is writable by anyone with a URL bar, so the value itself
24
+ * is never rendered: it is a key into the table or it is nothing. A repeated
25
+ * parameter is nothing too.
26
+ */
27
+ function vippsNotice(values, labels) {
28
+ if (values.length !== 1) return null;
29
+ const [value] = values;
30
+ return Object.hasOwn(VIPPS_NOTICE, value) ? labelText(labels[VIPPS_NOTICE[value]]) : null;
31
+ }
32
+ /**
33
+ * `?vipps=` — why a Vipps login did not end in a cookie. `useSearchParams` on
34
+ * a prerendered page must sit under a `<Suspense>` (the page provides it, with
35
+ * a `null` fallback: a box reserved for the rare notice would be a layout
36
+ * shift for everybody else).
37
+ */
38
+ function VippsNotice(props) {
39
+ const { kit } = useBookingKit(props);
40
+ const notice = vippsNotice(useSearchParams()?.getAll("vipps") ?? [], kit.labels);
41
+ if (!notice) return null;
42
+ return /* @__PURE__ */ jsx("p", {
43
+ role: "status",
44
+ className: "rounded-[3px] bg-muted px-4 py-3 text-sm",
45
+ children: notice
46
+ });
47
+ }
48
+ function subscribe(onChange) {
49
+ window.addEventListener("popstate", onChange);
50
+ return () => window.removeEventListener("popstate", onChange);
51
+ }
52
+ /** `?return=`, validated. A repeated parameter is refused, as on the server. */
53
+ function returnPathFromSearch(search, config) {
54
+ const values = new URLSearchParams(search).getAll("return");
55
+ return createReturnPath(config)(values.length === 1 ? values[0] : void 0);
56
+ }
57
+ /**
58
+ * `?vipps=confirm_email[&to=…]` as a primitive, so the snapshot is stable:
59
+ * `null` for an ordinary visit, `''` for a confirm without a usable address,
60
+ * else the masked address.
61
+ */
62
+ function readVippsConfirm() {
63
+ const confirm = vippsConfirmFrom(new URLSearchParams(window.location.search));
64
+ return confirm === void 0 ? null : confirm.to ?? "";
65
+ }
66
+ function nothing() {
67
+ return null;
68
+ }
69
+ /**
70
+ * The inline login, told where to go once it succeeds.
71
+ *
72
+ * NOT `useSearchParams`: that would need a Suspense boundary, and the login
73
+ * form is the page — as a fallback it would be swapped for a fresh copy on
74
+ * hydration, dropping anything typed before the JavaScript arrived.
75
+ * `useSyncExternalStore` hydrates with the server snapshot (`null`) and
76
+ * re-renders the SAME tree with the real value. The return path is never
77
+ * rendered, only handed to the login's `router.replace` and the Vipps start.
78
+ */
79
+ function LoginFromQuery({ actions, ...overrides }) {
80
+ const { config } = useBookingKit(overrides).kit;
81
+ const readReturnPath = useCallback(() => returnPathFromSearch(window.location.search, config), [config]);
82
+ const returnPath = useSyncExternalStore(subscribe, readReturnPath, nothing);
83
+ const live = useSyncExternalStore(subscribe, readVippsConfirm, nothing);
84
+ /**
85
+ * LATCHED on the first read that has one: the address bar is cleaned
86
+ * straight after, and a later render reading it again must not take the
87
+ * visitor off the code screen they are typing into.
88
+ */
89
+ const [confirm, setConfirm] = useState(null);
90
+ if (live !== null && confirm === null) setConfirm({ to: live === "" ? null : live });
91
+ useEffect(() => {
92
+ if (confirm !== null) stripVippsReturn();
93
+ }, [confirm]);
94
+ return /* @__PURE__ */ createElement(LoginSheet, {
95
+ ...overrides,
96
+ actions,
97
+ key: confirm === null ? "email" : "vipps",
98
+ presentation: "inline",
99
+ returnPath,
100
+ vippsConfirm: confirm
101
+ });
102
+ }
103
+ //#endregion
104
+ export { LoginFromQuery, LoginFromQuery as LoginPageQuery, VippsNotice, returnPathFromSearch, vippsNotice };
105
+
106
+ //# sourceMappingURL=LoginPageQuery.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoginPageQuery.mjs","names":[],"sources":["../../src/react/LoginPageQuery.tsx"],"sourcesContent":["import { useSearchParams } from 'next/navigation';\nimport { useCallback, useEffect, useState, useSyncExternalStore } from 'react';\nimport type { BookingConfig } from '../core/config';\nimport { labelText } from '../core/labels';\nimport { createReturnPath } from '../core/portal/return-path';\nimport { stripVippsReturn, vippsConfirmFrom } from '../core/portal/vipps-return';\nimport { LoginSheet, type LoginSheetProps, type VippsConfirm } from './LoginSheet';\nimport type { LoginLabels } from './labels';\nimport { type BookingOverrides, useBookingKit } from './Provider';\n\n/**\n * The two things the login page reads off its query string, read in the\n * browser so the page itself can be prerendered: one static document, and\n * these two components pick up the query after it lands.\n */\n\n/** The label for each outcome the Vipps return route can report. */\nconst VIPPS_NOTICE: Record<string, keyof LoginLabels> = {\n needs_email_login: 'loginPage.vipps.needsEmailLogin',\n failed: 'loginPage.vipps.failed',\n};\n\n/**\n * The sentence for `?vipps=<value>`, or `null` for anything that is not an\n * outcome. The query is writable by anyone with a URL bar, so the value itself\n * is never rendered: it is a key into the table or it is nothing. A repeated\n * parameter is nothing too.\n */\nexport function vippsNotice(values: readonly string[], labels: LoginLabels): string | null {\n if (values.length !== 1) return null;\n const [value] = values;\n return Object.hasOwn(VIPPS_NOTICE, value) ? labelText(labels[VIPPS_NOTICE[value]]) : null;\n}\n\n/**\n * `?vipps=` — why a Vipps login did not end in a cookie. `useSearchParams` on\n * a prerendered page must sit under a `<Suspense>` (the page provides it, with\n * a `null` fallback: a box reserved for the rare notice would be a layout\n * shift for everybody else).\n */\nexport function VippsNotice(props: BookingOverrides) {\n const { kit } = useBookingKit(props);\n const notice = vippsNotice(useSearchParams()?.getAll('vipps') ?? [], kit.labels);\n if (!notice) return null;\n return (\n <p role=\"status\" className=\"rounded-[3px] bg-muted px-4 py-3 text-sm\">\n {notice}\n </p>\n );\n}\n\nfunction subscribe(onChange: () => void): () => void {\n window.addEventListener('popstate', onChange);\n return () => window.removeEventListener('popstate', onChange);\n}\n\n/** `?return=`, validated. A repeated parameter is refused, as on the server. */\nexport function returnPathFromSearch(\n search: string,\n config: Pick<BookingConfig, 'portal'>\n): string | null {\n const values = new URLSearchParams(search).getAll('return');\n return createReturnPath(config)(values.length === 1 ? values[0] : undefined);\n}\n\n/**\n * `?vipps=confirm_email[&to=…]` as a primitive, so the snapshot is stable:\n * `null` for an ordinary visit, `''` for a confirm without a usable address,\n * else the masked address.\n */\nfunction readVippsConfirm(): string | null {\n const confirm = vippsConfirmFrom(new URLSearchParams(window.location.search));\n return confirm === undefined ? null : (confirm.to ?? '');\n}\n\nfunction nothing(): null {\n return null;\n}\n\nexport type LoginFromQueryProps = BookingOverrides & Pick<LoginSheetProps, 'actions'>;\n\n/**\n * The inline login, told where to go once it succeeds.\n *\n * NOT `useSearchParams`: that would need a Suspense boundary, and the login\n * form is the page — as a fallback it would be swapped for a fresh copy on\n * hydration, dropping anything typed before the JavaScript arrived.\n * `useSyncExternalStore` hydrates with the server snapshot (`null`) and\n * re-renders the SAME tree with the real value. The return path is never\n * rendered, only handed to the login's `router.replace` and the Vipps start.\n */\nexport function LoginFromQuery({ actions, ...overrides }: LoginFromQueryProps) {\n const { config } = useBookingKit(overrides).kit;\n const readReturnPath = useCallback(\n () => returnPathFromSearch(window.location.search, config),\n [config]\n );\n const returnPath = useSyncExternalStore(subscribe, readReturnPath, nothing);\n const live = useSyncExternalStore(subscribe, readVippsConfirm, nothing);\n /**\n * LATCHED on the first read that has one: the address bar is cleaned\n * straight after, and a later render reading it again must not take the\n * visitor off the code screen they are typing into.\n */\n const [confirm, setConfirm] = useState<VippsConfirm | null>(null);\n if (live !== null && confirm === null) setConfirm({ to: live === '' ? null : live });\n useEffect(() => {\n if (confirm !== null) stripVippsReturn();\n }, [confirm]);\n return (\n <LoginSheet\n {...overrides}\n actions={actions}\n // A fresh login for the code step: its memory starts on the code.\n key={confirm === null ? 'email' : 'vipps'}\n presentation=\"inline\"\n returnPath={returnPath}\n vippsConfirm={confirm}\n />\n );\n}\n\n/** The login page's component under the module's own name. */\nexport { LoginFromQuery as LoginPageQuery };\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAM,eAAkD;CACtD,mBAAmB;CACnB,QAAQ;AACV;;;;;;;AAQA,SAAgB,YAAY,QAA2B,QAAoC;CACzF,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,MAAM,CAAC,SAAS;CAChB,OAAO,OAAO,OAAO,cAAc,KAAK,IAAI,UAAU,OAAO,aAAa,OAAO,IAAI;AACvF;;;;;;;AAQA,SAAgB,YAAY,OAAyB;CACnD,MAAM,EAAE,QAAQ,cAAc,KAAK;CACnC,MAAM,SAAS,YAAY,gBAAgB,CAAC,EAAE,OAAO,OAAO,KAAK,CAAC,GAAG,IAAI,MAAM;CAC/E,IAAI,CAAC,QAAQ,OAAO;CACpB,OACE,oBAAC,KAAD;EAAG,MAAK;EAAS,WAAU;EACxB,UAAA;CACA,CAAA;AAEP;AAEA,SAAS,UAAU,UAAkC;CACnD,OAAO,iBAAiB,YAAY,QAAQ;CAC5C,aAAa,OAAO,oBAAoB,YAAY,QAAQ;AAC9D;;AAGA,SAAgB,qBACd,QACA,QACe;CACf,MAAM,SAAS,IAAI,gBAAgB,MAAM,CAAC,CAAC,OAAO,QAAQ;CAC1D,OAAO,iBAAiB,MAAM,CAAC,CAAC,OAAO,WAAW,IAAI,OAAO,KAAK,KAAA,CAAS;AAC7E;;;;;;AAOA,SAAS,mBAAkC;CACzC,MAAM,UAAU,iBAAiB,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC;CAC5E,OAAO,YAAY,KAAA,IAAY,OAAQ,QAAQ,MAAM;AACvD;AAEA,SAAS,UAAgB;CACvB,OAAO;AACT;;;;;;;;;;;AAcA,SAAgB,eAAe,EAAE,SAAS,GAAG,aAAkC;CAC7E,MAAM,EAAE,WAAW,cAAc,SAAS,CAAC,CAAC;CAC5C,MAAM,iBAAiB,kBACf,qBAAqB,OAAO,SAAS,QAAQ,MAAM,GACzD,CAAC,MAAM,CACT;CACA,MAAM,aAAa,qBAAqB,WAAW,gBAAgB,OAAO;CAC1E,MAAM,OAAO,qBAAqB,WAAW,kBAAkB,OAAO;;;;;;CAMtE,MAAM,CAAC,SAAS,cAAc,SAA8B,IAAI;CAChE,IAAI,SAAS,QAAQ,YAAY,MAAM,WAAW,EAAE,IAAI,SAAS,KAAK,OAAO,KAAK,CAAC;CACnF,gBAAgB;EACd,IAAI,YAAY,MAAM,iBAAiB;CACzC,GAAG,CAAC,OAAO,CAAC;CACZ,OACE,8BAAC,YAAD;EACE,GAAI;EACK;EAET,KAAK,YAAY,OAAO,UAAU;EAClC,cAAa;EACD;EACZ,cAAc;CACf,CAAA;AAEL"}
@@ -0,0 +1,49 @@
1
+ import { BookingGuardian } from "../core/types.mjs";
2
+ import { PortalActions } from "./actions.mjs";
3
+ import { BookingOverrides } from "./Provider.mjs";
4
+ import { VippsConfirm } from "@medalsocial/meda/booking";
5
+ //#region src/react/LoginSheet.d.ts
6
+ /**
7
+ * The site's one login — Vipps, or a six-digit code by e-mail — in the two
8
+ * places a visitor takes it, over meda's `LoginSheet` / `LoginPanel`.
9
+ *
10
+ * - `presentation="sheet"` is the booking wizard's: an offer the wizard never
11
+ * waits on, that never navigates. A good code hands the visitor back
12
+ * through `onSignedIn` with the `guardian` the verify route answers with.
13
+ * - `presentation="inline"` is the login page's. A good code ends in ONE
14
+ * `router.replace` to `returnPath` or the portal — the destination is a
15
+ * server page that reads the fresh cookie on the way in, and Back should not
16
+ * land on a login whose job is done.
17
+ *
18
+ * THE CODE IS CHECKED BY `fetch`, NOT BY A SERVER ACTION
19
+ * (`POST <portalApi>/login/verify`): in Next 16 an action that sets a cookie
20
+ * makes the client re-fetch the route it was called from — the whole booking
21
+ * page behind the sheet, or a second render racing the `replace`. Asking for
22
+ * the code stays the app's action (`actions.startLogin`): it sets no cookie.
23
+ *
24
+ * Vipps is offered when the site has it (`config.portal.methods`) and the app
25
+ * passed its start action.
26
+ */
27
+ export type LoginSheetProps = BookingOverrides & {
28
+ /** The app's server actions (Decision 8). */
29
+ actions: Pick<PortalActions, 'startLogin' | 'startVipps'>;
30
+ } & ({
31
+ presentation: 'sheet';
32
+ /** Where a Vipps login should land: the wizard's resume URL. */
33
+ resumePath: string;
34
+ /** A good code; `guardian` is `null` when the read after it failed. */
35
+ onSignedIn: (guardian: BookingGuardian | null) => void;
36
+ /** A Vipps return asking for the e-mailed code: the sheet opens on it. */
37
+ vippsConfirm?: VippsConfirm | null;
38
+ /** Draw the «have an account? Log in» row. Default `true`. */
39
+ trigger?: boolean;
40
+ } | {
41
+ presentation: 'inline';
42
+ /** Already through `createReturnPath(config)`; `null` is the portal. */
43
+ returnPath?: string | null;
44
+ vippsConfirm?: VippsConfirm | null;
45
+ });
46
+ declare function LoginSheet$1(props: LoginSheetProps): import("react").JSX.Element;
47
+ //#endregion
48
+ export { LoginSheet$1 as LoginSheet, type VippsConfirm };
49
+ //# sourceMappingURL=LoginSheet.d.mts.map
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+ import { readStartLogin } from "./actions.mjs";
3
+ import { useBookingKit } from "./Provider.mjs";
4
+ import { screenLabels } from "./screen-labels.mjs";
5
+ import { LoginPanel, LoginSheet } from "@medalsocial/meda/booking";
6
+ import { useRouter } from "next/navigation";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/react/LoginSheet.tsx
9
+ /**
10
+ * Ask a verify route, and say what its answer means for the form. The
11
+ * route's own reasons are the form's; anything else (a 403, a body that is
12
+ * not the route's, the network failing) is `unreachable`.
13
+ */
14
+ async function askVerify(path, payload) {
15
+ let body;
16
+ try {
17
+ body = await (await fetch(path, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify(payload),
21
+ cache: "no-store",
22
+ credentials: "same-origin"
23
+ })).json();
24
+ } catch {
25
+ return {
26
+ ok: false,
27
+ reason: "unreachable"
28
+ };
29
+ }
30
+ const answer = body;
31
+ if (answer?.ok === true) return {
32
+ ok: true,
33
+ guardian: answer.guardian ?? null
34
+ };
35
+ const reason = answer?.reason;
36
+ return reason === "invalid" || reason === "throttled" || reason === "conflict" ? {
37
+ ok: false,
38
+ reason
39
+ } : {
40
+ ok: false,
41
+ reason: "unreachable"
42
+ };
43
+ }
44
+ function LoginSheet$1(props) {
45
+ const { kit, classNames } = useBookingKit(props);
46
+ const { config, labels } = kit;
47
+ const { actions } = props;
48
+ const api = config.paths.portalApi;
49
+ const screen = {
50
+ labels: screenLabels(labels),
51
+ onStartLogin: async (email) => {
52
+ try {
53
+ return readStartLogin(await actions.startLogin({ email }));
54
+ } catch {
55
+ return {
56
+ ok: false,
57
+ reason: "unreachable"
58
+ };
59
+ }
60
+ },
61
+ onVerify: (code, context) => context.mode === "vipps" ? askVerify(`${api}/vipps/link/verify`, { code }) : askVerify(`${api}/login/verify`, {
62
+ email: context.email,
63
+ code
64
+ }),
65
+ onVipps: config.portal.methods.includes("vipps") ? actions.startVipps : void 0,
66
+ vippsConfirm: props.vippsConfirm ?? null,
67
+ otpClassNames: classNames.otp,
68
+ vippsClassNames: classNames.vipps
69
+ };
70
+ if (props.presentation === "inline") return /* @__PURE__ */ jsx(InlineLogin, {
71
+ screen,
72
+ classNames: classNames.loginPanel,
73
+ target: props.returnPath ?? null,
74
+ fallback: config.paths.portal ?? "/"
75
+ });
76
+ return /* @__PURE__ */ jsx(LoginSheet, {
77
+ ...screen,
78
+ resumePath: props.resumePath,
79
+ onSignedIn: props.onSignedIn,
80
+ trigger: props.trigger,
81
+ classNames: classNames.loginSheet,
82
+ panelClassNames: classNames.loginPanel
83
+ });
84
+ }
85
+ /** Its own component so only the page login reads the router. */
86
+ function InlineLogin({ screen, classNames, target, fallback }) {
87
+ const router = useRouter();
88
+ return /* @__PURE__ */ jsx(LoginPanel, {
89
+ ...screen,
90
+ classNames,
91
+ vippsNext: target,
92
+ onSignedIn: () => router.replace(target ?? fallback)
93
+ });
94
+ }
95
+ //#endregion
96
+ export { LoginSheet$1 as LoginSheet };
97
+
98
+ //# sourceMappingURL=LoginSheet.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoginSheet.mjs","names":["LoginSheet","SheetScreen"],"sources":["../../src/react/LoginSheet.tsx"],"sourcesContent":["import {\n LoginPanel,\n type LoginPanelProps,\n type LoginStartResult,\n type LoginVerifyResult,\n LoginSheet as SheetScreen,\n type VippsConfirm,\n} from '@medalsocial/meda/booking';\nimport { useRouter } from 'next/navigation';\nimport type { BookingGuardian } from '../core/types';\nimport { type PortalActions, readStartLogin } from './actions';\nimport { type BookingOverrides, useBookingKit } from './Provider';\nimport { screenLabels } from './screen-labels';\n\nexport type { VippsConfirm };\n\n/**\n * The site's one login — Vipps, or a six-digit code by e-mail — in the two\n * places a visitor takes it, over meda's `LoginSheet` / `LoginPanel`.\n *\n * - `presentation=\"sheet\"` is the booking wizard's: an offer the wizard never\n * waits on, that never navigates. A good code hands the visitor back\n * through `onSignedIn` with the `guardian` the verify route answers with.\n * - `presentation=\"inline\"` is the login page's. A good code ends in ONE\n * `router.replace` to `returnPath` or the portal — the destination is a\n * server page that reads the fresh cookie on the way in, and Back should not\n * land on a login whose job is done.\n *\n * THE CODE IS CHECKED BY `fetch`, NOT BY A SERVER ACTION\n * (`POST <portalApi>/login/verify`): in Next 16 an action that sets a cookie\n * makes the client re-fetch the route it was called from — the whole booking\n * page behind the sheet, or a second render racing the `replace`. Asking for\n * the code stays the app's action (`actions.startLogin`): it sets no cookie.\n *\n * Vipps is offered when the site has it (`config.portal.methods`) and the app\n * passed its start action.\n */\nexport type LoginSheetProps = BookingOverrides & {\n /** The app's server actions (Decision 8). */\n actions: Pick<PortalActions, 'startLogin' | 'startVipps'>;\n} & (\n | {\n presentation: 'sheet';\n /** Where a Vipps login should land: the wizard's resume URL. */\n resumePath: string;\n /** A good code; `guardian` is `null` when the read after it failed. */\n onSignedIn: (guardian: BookingGuardian | null) => void;\n /** A Vipps return asking for the e-mailed code: the sheet opens on it. */\n vippsConfirm?: VippsConfirm | null;\n /** Draw the «have an account? Log in» row. Default `true`. */\n trigger?: boolean;\n }\n | {\n presentation: 'inline';\n /** Already through `createReturnPath(config)`; `null` is the portal. */\n returnPath?: string | null;\n vippsConfirm?: VippsConfirm | null;\n }\n );\n\n/**\n * Ask a verify route, and say what its answer means for the form. The\n * route's own reasons are the form's; anything else (a 403, a body that is\n * not the route's, the network failing) is `unreachable`.\n */\nasync function askVerify(\n path: string,\n payload: Record<string, string>\n): Promise<LoginVerifyResult> {\n let body: unknown;\n try {\n const response = await fetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n cache: 'no-store',\n credentials: 'same-origin',\n });\n body = await response.json();\n } catch {\n return { ok: false, reason: 'unreachable' };\n }\n const answer = body as {\n ok?: unknown;\n guardian?: BookingGuardian | null;\n reason?: unknown;\n } | null;\n if (answer?.ok === true) return { ok: true, guardian: answer.guardian ?? null };\n const reason = answer?.reason;\n return reason === 'invalid' || reason === 'throttled' || reason === 'conflict'\n ? { ok: false, reason }\n : { ok: false, reason: 'unreachable' };\n}\n\nexport function LoginSheet(props: LoginSheetProps) {\n const { kit, classNames } = useBookingKit(props);\n const { config, labels } = kit;\n const { actions } = props;\n const api = config.paths.portalApi;\n const screen = {\n labels: screenLabels(labels),\n // «Sent» for every address; only a failed action is not (so the form is\n // never an oracle for who books here, and never claims a code it did not send).\n onStartLogin: async (email: string): Promise<LoginStartResult> => {\n try {\n return readStartLogin(await actions.startLogin({ email }));\n } catch {\n return { ok: false, reason: 'unreachable' };\n }\n },\n // The Vipps confirm code is checked by the code alone: the pending link is\n // in an httpOnly cookie the route reads.\n onVerify: (code: string, context: { mode: 'email' | 'vipps'; email: string }) =>\n context.mode === 'vipps'\n ? askVerify(`${api}/vipps/link/verify`, { code })\n : askVerify(`${api}/login/verify`, { email: context.email, code }),\n onVipps: config.portal.methods.includes('vipps') ? actions.startVipps : undefined,\n vippsConfirm: props.vippsConfirm ?? null,\n otpClassNames: classNames.otp,\n vippsClassNames: classNames.vipps,\n };\n if (props.presentation === 'inline') {\n return (\n <InlineLogin\n screen={screen}\n classNames={classNames.loginPanel}\n target={props.returnPath ?? null}\n fallback={config.paths.portal ?? '/'}\n />\n );\n }\n return (\n <SheetScreen\n {...screen}\n resumePath={props.resumePath}\n onSignedIn={props.onSignedIn}\n trigger={props.trigger}\n classNames={classNames.loginSheet}\n panelClassNames={classNames.loginPanel}\n />\n );\n}\n\n/** Its own component so only the page login reads the router. */\nfunction InlineLogin({\n screen,\n classNames,\n target,\n fallback,\n}: {\n screen: Omit<LoginPanelProps, 'onSignedIn'>;\n classNames: LoginPanelProps['classNames'];\n target: string | null;\n fallback: string;\n}) {\n const router = useRouter();\n return (\n <LoginPanel\n {...screen}\n classNames={classNames}\n vippsNext={target}\n onSignedIn={() => router.replace(target ?? fallback)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiEA,eAAe,UACb,MACA,SAC4B;CAC5B,IAAI;CACJ,IAAI;EAQF,OAAO,OAAM,MAPU,MAAM,MAAM;GACjC,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,OAAO;GAC5B,OAAO;GACP,aAAa;EACf,CAAC,EAAA,CACqB,KAAK;CAC7B,QAAQ;EACN,OAAO;GAAE,IAAI;GAAO,QAAQ;EAAc;CAC5C;CACA,MAAM,SAAS;CAKf,IAAI,QAAQ,OAAO,MAAM,OAAO;EAAE,IAAI;EAAM,UAAU,OAAO,YAAY;CAAK;CAC9E,MAAM,SAAS,QAAQ;CACvB,OAAO,WAAW,aAAa,WAAW,eAAe,WAAW,aAChE;EAAE,IAAI;EAAO;CAAO,IACpB;EAAE,IAAI;EAAO,QAAQ;CAAc;AACzC;AAEA,SAAgBA,aAAW,OAAwB;CACjD,MAAM,EAAE,KAAK,eAAe,cAAc,KAAK;CAC/C,MAAM,EAAE,QAAQ,WAAW;CAC3B,MAAM,EAAE,YAAY;CACpB,MAAM,MAAM,OAAO,MAAM;CACzB,MAAM,SAAS;EACb,QAAQ,aAAa,MAAM;EAG3B,cAAc,OAAO,UAA6C;GAChE,IAAI;IACF,OAAO,eAAe,MAAM,QAAQ,WAAW,EAAE,MAAM,CAAC,CAAC;GAC3D,QAAQ;IACN,OAAO;KAAE,IAAI;KAAO,QAAQ;IAAc;GAC5C;EACF;EAGA,WAAW,MAAc,YACvB,QAAQ,SAAS,UACb,UAAU,GAAG,IAAI,qBAAqB,EAAE,KAAK,CAAC,IAC9C,UAAU,GAAG,IAAI,gBAAgB;GAAE,OAAO,QAAQ;GAAO;EAAK,CAAC;EACrE,SAAS,OAAO,OAAO,QAAQ,SAAS,OAAO,IAAI,QAAQ,aAAa,KAAA;EACxE,cAAc,MAAM,gBAAgB;EACpC,eAAe,WAAW;EAC1B,iBAAiB,WAAW;CAC9B;CACA,IAAI,MAAM,iBAAiB,UACzB,OACE,oBAAC,aAAD;EACU;EACR,YAAY,WAAW;EACvB,QAAQ,MAAM,cAAc;EAC5B,UAAU,OAAO,MAAM,UAAU;CAClC,CAAA;CAGL,OACE,oBAACC,YAAD;EACE,GAAI;EACJ,YAAY,MAAM;EAClB,YAAY,MAAM;EAClB,SAAS,MAAM;EACf,YAAY,WAAW;EACvB,iBAAiB,WAAW;CAC7B,CAAA;AAEL;;AAGA,SAAS,YAAY,EACnB,QACA,YACA,QACA,YAMC;CACD,MAAM,SAAS,UAAU;CACzB,OACE,oBAAC,YAAD;EACE,GAAI;EACQ;EACZ,WAAW;EACX,kBAAkB,OAAO,QAAQ,UAAU,QAAQ;CACpD,CAAA;AAEL"}
@@ -0,0 +1,50 @@
1
+ import { BookingManageDto, BookingServiceDto } from "../core/types.mjs";
2
+ import { BookingOverrides } from "./Provider.mjs";
3
+ //#region src/react/ManageBooking.d.ts
4
+ export interface ManageBookingProps extends BookingOverrides {
5
+ booking: BookingManageDto;
6
+ /**
7
+ * The business's number. Absent = `config.contact.phone`; `null` renders
8
+ * every phone sentence unlinked (a `tel:` that dials nothing is worse than
9
+ * plain text, and worst here, where the phone is the last way to change it).
10
+ */
11
+ phone?: string | null;
12
+ /** The address under the booking and in the calendar entry. Absent = `config.contact.address`. */
13
+ address?: string | null;
14
+ /** The catalogue entry for the booked service, for the weekend note. `null` = no note. */
15
+ service?: BookingServiceDto | null;
16
+ /**
17
+ * Where the two mutations POST: `${config.paths.api}/manage/<token>`, built by
18
+ * the server that holds the token. A path, not the token, so no prop, state
19
+ * or error payload ever names the credential. The default matches no route
20
+ * and so fails loudly rather than posting a tokenless action.
21
+ */
22
+ actionPath?: string;
23
+ /**
24
+ * This page's own manage path (`paths.managePath(token)`), for the calendar
25
+ * entry's «change or cancel» line. `null` = no such line.
26
+ */
27
+ selfManagePath?: string | null;
28
+ /** Where «book a new time» goes. Absent = `config.paths.booking`. */
29
+ bookHref?: string;
30
+ /**
31
+ * The site's origin, to make the calendar entry's manage link absolute. Absent
32
+ * = the link stays root-relative, so server and client render the same.
33
+ */
34
+ siteUrl?: string;
35
+ }
36
+ /**
37
+ * `<ManageBooking>` — the page at the end of the «manage your booking» link.
38
+ *
39
+ * Renders meda's `ManageScreen` and does what it leaves to the caller: loads
40
+ * the openings around the appointment from `${paths.api}/availability`, posts
41
+ * cancel and reschedule to `actionPath`, maps the route's error codes, puts the
42
+ * freshly minted manage link in the address bar after a move, and builds the
43
+ * calendar entry (UID = the booking the entry was created under).
44
+ *
45
+ * The change windows are the engine's: `canCancel` / `canReschedule` are
46
+ * relayed, never recomputed.
47
+ */
48
+ export declare function ManageBooking({ booking, phone, address, service, actionPath, selfManagePath, bookHref, siteUrl, ...overrides }: ManageBookingProps): import("react").JSX.Element;
49
+ //#endregion
50
+ //# sourceMappingURL=ManageBooking.d.mts.map