@movmo_app/react-common 0.13.0 → 0.14.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.
package/dist/index.d.ts CHANGED
@@ -1,12 +1,16 @@
1
- import { AuthenticationChallengeName } from '@movmo_app/types';
2
- import { AuthenticationUsernameType } from '@movmo_app/types';
1
+ import { AuthErrorCode } from '@movmo_app/types';
3
2
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
4
3
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5
4
  import { default as default_2 } from 'react';
6
5
  import { Gender } from '@movmo_app/types';
7
6
  import { JSX as JSX_2 } from 'react/jsx-runtime';
8
- import { LoginUserResponse } from '@movmo_app/types';
7
+ import { MovmoCodeFlow } from '@movmo_app/types';
8
+ import { MovmoLoginResponse } from '@movmo_app/types';
9
+ import { MovmoVerifyResponse } from '@movmo_app/types';
9
10
  import { OTPAction } from '@movmo_app/types';
11
+ import { PasskeyOptionsResponse } from '@movmo_app/types';
12
+ import { PasskeyRegistrationStart } from '@movmo_app/types';
13
+ import { PasskeySummary } from '@movmo_app/types';
10
14
  import * as React_2 from 'react';
11
15
  import * as SeparatorPrimitive from '@radix-ui/react-separator';
12
16
  import { SingleValue } from 'react-select';
@@ -15,6 +19,21 @@ import * as SwitchPrimitive from '@radix-ui/react-switch';
15
19
  import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
16
20
  import { User } from '@movmo_app/types';
17
21
 
22
+ /** Untitled UI `alert-circle`. */
23
+ export declare const AlertCircleIcon: (props: IconProps) => JSX_2.Element;
24
+
25
+ /** Uniform error every `MovmoAuthApi` method rejects with. */
26
+ export declare class AuthError extends Error {
27
+ status: number;
28
+ code: AuthErrorCode | 'network' | 'unknown';
29
+ retryAfter?: number | undefined;
30
+ attemptsRemaining?: number | undefined;
31
+ constructor(status: number, code: AuthErrorCode | 'network' | 'unknown', retryAfter?: number | undefined, attemptsRemaining?: number | undefined);
32
+ }
33
+
34
+ /** Autofill options live 5 min on the server; refresh at 4½. */
35
+ export declare const AUTOFILL_REFRESH_MS = 270000;
36
+
18
37
  export declare const Avatar: React_2.ForwardRefExoticComponent<AvatarProps & React_2.RefAttributes<HTMLSpanElement>>;
19
38
 
20
39
  export declare const AvatarFallback: React_2.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React_2.RefAttributes<HTMLSpanElement>, "ref"> & React_2.RefAttributes<HTMLSpanElement>>;
@@ -53,8 +72,25 @@ export declare const CardHeader: React_2.ForwardRefExoticComponent<React_2.HTMLA
53
72
 
54
73
  export declare const CardTitle: React_2.ForwardRefExoticComponent<React_2.HTMLAttributes<HTMLDivElement> & React_2.RefAttributes<HTMLDivElement>>;
55
74
 
75
+ /**
76
+ * Guards a single in-flight WebAuthn ceremony at a time. `begin()` aborts
77
+ * whatever ceremony was previously outstanding (so a stale
78
+ * `navigator.credentials.get/create` call settles with an `AbortError`
79
+ * instead of racing a newer one) and hands back a fresh signal; `abort()`
80
+ * cancels the current ceremony outright (e.g. the user navigated away).
81
+ */
82
+ export declare class CeremonyGate {
83
+ private current;
84
+ begin(): AbortSignal;
85
+ abort(): void;
86
+ private abortCurrent;
87
+ }
88
+
56
89
  export declare const Checkbox: React_2.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & React_2.RefAttributes<HTMLButtonElement>>;
57
90
 
91
+ /** Untitled UI `check-circle`. */
92
+ export declare const CheckCircleIcon: (props: IconProps) => JSX_2.Element;
93
+
58
94
  export declare const Chip: React_2.ForwardRefExoticComponent<ChipProps & React_2.RefAttributes<HTMLSpanElement>>;
59
95
 
60
96
  declare interface ChipProps extends React_2.HTMLAttributes<HTMLSpanElement> {
@@ -62,6 +98,28 @@ declare interface ChipProps extends React_2.HTMLAttributes<HTMLSpanElement> {
62
98
  onRemove?: () => void;
63
99
  }
64
100
 
101
+ /**
102
+ * Classifies a thrown WebAuthn error by its `DOMException` name:
103
+ * `NotAllowedError` → cancelled, `AbortError` → aborted,
104
+ * `InvalidStateError` → exists (credential already registered),
105
+ * `NotSupportedError` → unsupported, `SecurityError` → security.
106
+ * Anything else (including non-`DOMException` values) → unknown.
107
+ */
108
+ export declare function classifyWebAuthnError(e: unknown): WebAuthnFailure;
109
+
110
+ /** Untitled UI `cloud-01`. */
111
+ export declare const CloudIcon: (props: IconProps) => JSX_2.Element;
112
+
113
+ /**
114
+ * The `Domain` attribute value for a prompt-cookie write on this host, or
115
+ * `undefined` for a host-only cookie: `localhost`, any bare hostname (fewer
116
+ * than two labels), an IPv4 dotted quad, or anything containing `:` (IPv6 —
117
+ * literal or bracketed). Otherwise `.` + the last two labels, so every
118
+ * `*.movmo.io` host (including white-label `*.app.movmo.io` airline hosts)
119
+ * shares the same cookie.
120
+ */
121
+ export declare function cookieDomain(host: string): string | undefined;
122
+
65
123
  /**
66
124
  * Full ISO 3166-1 alpha-2 country list as SelectDropdownOption[].
67
125
  * US, CA, GB, AU are pinned at the top for quick access.
@@ -69,6 +127,44 @@ declare interface ChipProps extends React_2.HTMLAttributes<HTMLSpanElement> {
69
127
  */
70
128
  export declare const COUNTRY_OPTIONS: SelectDropdownOption[];
71
129
 
130
+ /**
131
+ * Builds a `MovmoAuthApi` over a caller-supplied `HttpClient`. `opts.locale`
132
+ * is read at call time (not cached), defaulting to `'en-US'`.
133
+ */
134
+ export declare function createMovmoAuthApi(client: HttpClient, opts?: {
135
+ locale?: () => string;
136
+ }): MovmoAuthApi;
137
+
138
+ /**
139
+ * Runs a `navigator.credentials.create` ceremony (registration).
140
+ * `securityKey: true` sets `publicKey.hints = ['security-key']` and
141
+ * `publicKey.authenticatorSelection.authenticatorAttachment =
142
+ * 'cross-platform'` on a clone of the caller's options — `o` itself, and
143
+ * any nested object on it, is never mutated.
144
+ */
145
+ export declare function createPasskey(o: WebAuthnOptionsLike, a: {
146
+ signal: AbortSignal;
147
+ securityKey?: boolean;
148
+ }): Promise<CredentialJSON>;
149
+
150
+ /**
151
+ * Browser-level WebAuthn helpers (MOVMO-685, task 4.2).
152
+ *
153
+ * Wraps `navigator.credentials.get`/`create` plus the newer JSON conversion
154
+ * helpers (`PublicKeyCredential.parseRequestOptionsFromJSON`,
155
+ * `parseCreationOptionsFromJSON`, and the instance `toJSON()`), which are not
156
+ * yet part of TypeScript's `lib.dom` (TS 5.6). Those are feature-detected at
157
+ * runtime and declared locally below so the extra surface never leaks into
158
+ * consumers' global types. When unavailable, options/credentials are
159
+ * converted by hand using base64url.
160
+ *
161
+ * `@movmo_app/types` doesn't yet publish WebAuthn option/credential types
162
+ * (pending 1.21.0), so `CredentialJSON`/`WebAuthnOptionsLike` are defined
163
+ * here — structurally identical to what that package will eventually export.
164
+ */
165
+ /** A WebAuthn credential serialized to its JSON-friendly (base64url) shape. */
166
+ export declare type CredentialJSON = Record<string, unknown>;
167
+
72
168
  export declare const CURRENCY_OPTIONS: CurrencyOption[];
73
169
 
74
170
  export declare const CurrencyModal: default_2.FC<CurrencyModalProps>;
@@ -114,14 +210,50 @@ export declare interface DateDropdownsProps {
114
210
 
115
211
  export declare const DEFAULT_LOCALE: LocaleOption;
116
212
 
213
+ export declare function detectSupport(): Promise<WebAuthnSupport>;
214
+
215
+ /** Records that the traveler dismissed the prompt ("Not now") today (epoch days), resetting the sign-in count. */
216
+ export declare function dismissPrompt(now?: number): void;
217
+
218
+ /** Untitled UI `face-id`. */
219
+ export declare const FaceIdIcon: (props: IconProps) => JSX_2.Element;
220
+
117
221
  export declare const findCurrencyOption: (code: string) => CurrencyOption;
118
222
 
119
223
  export declare const findLocaleOption: (languageCode: string, regionCode: string) => LocaleOption;
120
224
 
225
+ /** Untitled UI `fingerprint-04`. */
226
+ export declare const FingerprintIcon: (props: IconProps) => JSX_2.Element;
227
+
121
228
  export declare const getCurrencyDisplayText: (option: CurrencyOption) => string;
122
229
 
123
230
  export declare const getLanguageDisplayName: (option: LocaleOption) => string;
124
231
 
232
+ /**
233
+ * Runs a `navigator.credentials.get` ceremony (sign-in). `conditional: true`
234
+ * requests autofill-UI mediation; the caller's `o` is never mutated.
235
+ */
236
+ export declare function getPasskey(o: WebAuthnOptionsLike, a: {
237
+ signal: AbortSignal;
238
+ conditional?: boolean;
239
+ }): Promise<CredentialJSON>;
240
+
241
+ /** Minimal HTTP surface `createMovmoAuthApi` needs from a caller's client. */
242
+ export declare interface HttpClient {
243
+ get<T = unknown>(url: string): Promise<{
244
+ status: number;
245
+ data: T;
246
+ }>;
247
+ post<T = unknown>(url: string, body?: unknown): Promise<{
248
+ status: number;
249
+ data: T;
250
+ }>;
251
+ delete<T = unknown>(url: string): Promise<{
252
+ status: number;
253
+ data: T;
254
+ }>;
255
+ }
256
+
125
257
  export declare const IconButton: React_2.ForwardRefExoticComponent<IconButtonProps & React_2.RefAttributes<HTMLButtonElement>>;
126
258
 
127
259
  declare interface IconButtonProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -129,10 +261,29 @@ declare interface IconButtonProps extends React_2.ButtonHTMLAttributes<HTMLButto
129
261
  size?: 'sm' | 'md' | 'lg';
130
262
  }
131
263
 
264
+ export declare type IconProps = React_2.SVGProps<SVGSVGElement> & {
265
+ /** Width/height in px. Defaults to 20. */
266
+ size?: number;
267
+ /**
268
+ * Accessible name. When set, the icon renders `role="img"` with a `<title>`
269
+ * instead of being hidden from assistive tech.
270
+ */
271
+ title?: string;
272
+ };
273
+
274
+ /** "Incorrect code. N attempts left." (singular "1 attempt left."). */
275
+ export declare function incorrectCode(attemptsRemaining?: number): string;
276
+
132
277
  export declare const Input: React_2.ForwardRefExoticComponent<Omit<React_2.ClassAttributes<HTMLInputElement> & React_2.InputHTMLAttributes<HTMLInputElement> & {
133
278
  required?: boolean;
134
279
  }, "ref"> & React_2.RefAttributes<HTMLInputElement>>;
135
280
 
281
+ /** Untitled UI `key-01`. */
282
+ export declare const KeyIcon: (props: IconProps) => JSX_2.Element;
283
+
284
+ /** Untitled UI `laptop-01`. */
285
+ export declare const LaptopIcon: (props: IconProps) => JSX_2.Element;
286
+
136
287
  export declare const LOCALE_OPTIONS: LocaleOption[];
137
288
 
138
289
  export declare const LOCALE_STORAGE_KEY = "movmo_locale_preferences";
@@ -156,22 +307,119 @@ export declare interface LocaleOption {
156
307
  regionDisplay: string;
157
308
  }
158
309
 
310
+ /** Untitled UI `log-in-01`. */
311
+ export declare const LogInIcon: (props: IconProps) => JSX_2.Element;
312
+
313
+ /** Untitled UI `mail-01`. */
314
+ export declare const MailIcon: (props: IconProps) => JSX_2.Element;
315
+
159
316
  export declare const Modal: default_2.FC<ModalProps>;
160
317
 
161
318
  export declare interface ModalProps {
162
319
  isOpen: boolean;
163
320
  onClose: () => void;
321
+ /**
322
+ * Header title. With `header="none"` it isn't rendered; a string title then
323
+ * becomes the dialog's accessible name (`aria-label`).
324
+ */
164
325
  title: default_2.ReactNode;
165
326
  children: default_2.ReactNode;
166
327
  footer?: default_2.ReactNode;
167
328
  maxWidth?: string;
168
329
  maxWidthPx?: number;
169
330
  footerHasBorder?: boolean;
331
+ /** Hides the X and disables backdrop click and Escape: the dialog can't be dismissed. */
170
332
  hideCloseButton?: boolean;
171
333
  allowOverflow?: boolean;
334
+ /**
335
+ * `'bar'` (default): the bordered header with the X on the left and the
336
+ * centered title. `'none'`: no header; the X sits at the panel's top right
337
+ * and the content supplies its own heading (the MOVMO-685 sign-in dialogs).
338
+ */
339
+ header?: 'bar' | 'none';
340
+ }
341
+
342
+ /** Typed client for every Movmo sign-in and passkey-management call. */
343
+ export declare interface MovmoAuthApi {
344
+ login(email: string): Promise<MovmoLoginResponse>;
345
+ sendCode(flowId: string): Promise<MovmoCodeFlow>;
346
+ passkeyOptions(): Promise<PasskeyOptionsResponse>;
347
+ verifyCode(flowId: string, code: string): Promise<MovmoVerifyResponse>;
348
+ verifyPasskey(flowId: string, credential: CredentialJSON): Promise<MovmoVerifyResponse>;
349
+ listPasskeys(): Promise<PasskeySummary[]>;
350
+ startRegistration(): Promise<PasskeyRegistrationStart>;
351
+ completeRegistration(flowId: string, credential: CredentialJSON): Promise<PasskeySummary>;
352
+ /** Rejects an id that isn't base64url (1–512 chars) with `invalid_request`, before any request. */
353
+ deletePasskey(id: string): Promise<void>;
354
+ /**
355
+ * `POST /v1/auth/logout {}`: ends this browser's Movmo session. Never
356
+ * rejects: a failure is normalized and swallowed, so a failed logout can't
357
+ * throw into the UI (the sign-in flow calls it on `account_mismatch`).
358
+ */
359
+ logout(): Promise<void>;
360
+ }
361
+
362
+ export declare interface MovmoSignIn {
363
+ step: SignInStep;
364
+ email: string;
365
+ setEmail(v: string): void;
366
+ busy: boolean;
367
+ /** User-facing copy (copy.ts), never a raw code. */
368
+ error: string | null;
369
+ errorCode: SignInErrorCode | null;
370
+ codeLength: number;
371
+ resendIn: number;
372
+ attemptsRemaining?: number;
373
+ canUsePasskeyButton: boolean;
374
+ /** Passkey step: the last ceremony failed; offer Try again (`retryPasskey`). */
375
+ passkeyFailed: boolean;
376
+ liveMessage: string;
377
+ submitEmail(): void;
378
+ signInWithPasskey(): void;
379
+ retryPasskey(): void;
380
+ useCodeInstead(): void;
381
+ /** Passkey/code step: back to email with the email kept. */
382
+ changeEmail(): void;
383
+ submitCode(code: string): void;
384
+ resendCode(): void;
385
+ acceptEnrollment(): void;
386
+ declineEnrollment(): void;
387
+ /** 'enrolled' → 'done', then `onSignedIn`. */
388
+ finish(): void;
389
+ /**
390
+ * During 'enroll' = decline, then finish (so `onSignedIn` fires); at
391
+ * 'enrolled' = finish; otherwise aborts any ceremony.
392
+ */
393
+ close(): void;
394
+ }
395
+
396
+ /** Whether the traveler has permanently opted out of the passkey prompt. */
397
+ export declare function neverAsk(): boolean;
398
+
399
+ /**
400
+ * The per-digit code boxes shared by `OTPModal` and `SignInFlow`: type, paste,
401
+ * backspace, one-time-code autofill, and auto-submit on the last digit. The
402
+ * look is the MOVMO-685 spec's "today's OTPModal" boxes (no brand color).
403
+ * Box ids are `otp-input-${i}`, so render one instance per page.
404
+ */
405
+ export declare const OtpCodeInput: ({ length, onComplete, onChange, disabled, autoFocus, }: OtpCodeInputProps) => JSX_2.Element;
406
+
407
+ export declare interface OtpCodeInputProps {
408
+ /** Number of boxes (the code length). */
409
+ length: number;
410
+ /**
411
+ * Called once per completed code (typed, pasted or autofilled). A re-render
412
+ * doesn't call it again; clearing a digit and retyping it does.
413
+ */
414
+ onComplete: (code: string) => void;
415
+ /** Called on every edit with the digits entered so far. Not called while `disabled`. */
416
+ onChange?: (code: string) => void;
417
+ disabled?: boolean;
418
+ /** Focus the first box on mount (through `safeFocus`, MOVMO-437). */
419
+ autoFocus?: boolean;
172
420
  }
173
421
 
174
- export declare const OTPModal: ({ email, phone, otpLength, otpAction, onVerify, onClose, externalError, externalErrorVariant, disabled, title, description, onOtpChange, }: OTPModalProps) => JSX_2.Element;
422
+ export declare const OTPModal: ({ email, phone, otpLength, otpAction, onVerify, onClose, externalError, externalErrorVariant, disabled, title, description, onOtpChange, onResend, resendAfter, attemptsRemaining, }: OTPModalProps) => JSX_2.Element;
175
423
 
176
424
  export declare interface OTPModalProps {
177
425
  email: string;
@@ -184,6 +432,7 @@ export declare interface OTPModalProps {
184
432
  externalErrorVariant?: 'error' | 'warning';
185
433
  disabled?: boolean;
186
434
  title?: string;
435
+ /** Replaces "Enter the code we sent to" and hides the email/phone line below it. */
187
436
  description?: string;
188
437
  /**
189
438
  * Fires when the user edits the OTP (type, paste, backspace). Use to clear
@@ -191,6 +440,16 @@ export declare interface OTPModalProps {
191
440
  * as the user starts correcting their input. Not fired while `disabled`.
192
441
  */
193
442
  onOtpChange?: (otp: string) => void;
443
+ /** Shows the resend row ("Didn't get it? Resend code"). */
444
+ onResend?: () => void;
445
+ /**
446
+ * Seconds before a resend is allowed. The modal counts it down itself
447
+ * ("Resend code in 0:SS") and restarts it when this value changes or the
448
+ * traveler resends.
449
+ */
450
+ resendAfter?: number;
451
+ /** Shown under the boxes as "N attempts left.". */
452
+ attemptsRemaining?: number;
194
453
  }
195
454
 
196
455
  export declare const Pagination: React_2.FC<PaginationProps>;
@@ -202,6 +461,9 @@ declare interface PaginationProps {
202
461
  className?: string;
203
462
  }
204
463
 
464
+ /** Untitled UI `passcode`. */
465
+ export declare const PasscodeIcon: (props: IconProps) => JSX_2.Element;
466
+
205
467
  export declare const PassengerForm: default_2.FC<PassengerFormProps>;
206
468
 
207
469
  export declare interface PassengerFormProps {
@@ -248,6 +510,69 @@ export declare interface PassengerFormSkeletonProps {
248
510
  showFooter?: boolean;
249
511
  }
250
512
 
513
+ /**
514
+ * "Skip the code next time" — offered after a traveler's first code sign-in.
515
+ * Not a modal itself: the sign-in flow renders this inside its own
516
+ * modal/popup body.
517
+ */
518
+ export declare const PasskeyEnrollPrompt: ({ status, onAccept, onDecline, onDone, busy, error, }: PasskeyEnrollPromptProps) => JSX_2.Element;
519
+
520
+ export declare interface PasskeyEnrollPromptProps {
521
+ /**
522
+ * `'prompt'` (default) offers to set up a passkey. `'added'` shows the
523
+ * post-registration confirmation.
524
+ */
525
+ status?: 'prompt' | 'added';
526
+ /** Runs registration. Called from the primary button in `'prompt'` status. */
527
+ onAccept: () => void;
528
+ /** Records the dismissal. Called from the secondary button in `'prompt'` status. */
529
+ onDecline: () => void;
530
+ /** Called from the primary button in `'added'` status. */
531
+ onDone?: () => void;
532
+ /** Disables + `aria-busy`s the primary button while registration is in flight. */
533
+ busy?: boolean;
534
+ /**
535
+ * Shown as an alert above the buttons. The prompt never blocks on an
536
+ * error — `onDecline` (Not now) stays enabled.
537
+ */
538
+ error?: string | null;
539
+ }
540
+
541
+ /**
542
+ * The Security page's Passkeys card body (MOVMO-718): list, add a passkey or a
543
+ * security key, remove with a confirm, and re-authenticate in place when the
544
+ * server asks (`403 reauth_required`). The app renders it inside its own card
545
+ * header ("Passkeys" / subtitle).
546
+ *
547
+ * In a framed window it only renders "Manage passkeys on the Movmo Security
548
+ * page.": no list request and no WebAuthn call.
549
+ *
550
+ * Every add goes through `registerPasskey` (ruling P4). One add or remove at a
551
+ * time: the buttons disable while one is in flight, and a second click is
552
+ * ignored. After a successful re-authentication the pending action retries
553
+ * exactly once; if that retried add's `create()` is cancelled (Safari may
554
+ * refuse it without a fresh click) an info notice asks for one more click.
555
+ */
556
+ export declare const PasskeyList: (props: PasskeyListProps) => JSX_2.Element;
557
+
558
+ export declare interface PasskeyListProps {
559
+ api: MovmoAuthApi;
560
+ /** The signed-in account; re-authentication must sign this same account in. */
561
+ account: {
562
+ id: string;
563
+ email: string;
564
+ };
565
+ getCurrentUser: () => Promise<User>;
566
+ /** `403 movmo_session_required` (a legacy session): open the normal sign-in. */
567
+ onSessionRequired: () => void;
568
+ /**
569
+ * A different account signed in during re-authentication. The server session
570
+ * has already been ended by the package; clear local auth state and prompt
571
+ * sign-in.
572
+ */
573
+ onAccountMismatch: () => void;
574
+ }
575
+
251
576
  export declare const PhoneInput: default_2.FC<PhoneInputProps>;
252
577
 
253
578
  export declare interface PhoneInputProps {
@@ -265,6 +590,100 @@ export declare interface PhoneInputProps {
265
590
  autoComplete?: string;
266
591
  }
267
592
 
593
+ /** Untitled UI `plus`. */
594
+ export declare const PlusIcon: (props: IconProps) => JSX_2.Element;
595
+
596
+ /**
597
+ * `movmo_pk_prompt` — the passkey-enrollment prompt cookie (MOVMO-685, task
598
+ * 4.3, D4).
599
+ *
600
+ * Records whether this browser has already been offered passkey enrollment,
601
+ * so a traveler who taps "Not now" isn't re-prompted on every code sign-in.
602
+ * State lives in a single parent-domain cookie (`Domain=.movmo.io`) rather
603
+ * than per-host storage so the dismissal is honored across every Movmo host
604
+ * — including white-label `*.app.movmo.io` airline hosts — not just the one
605
+ * the traveler happened to dismiss it on. `localhost`/IP hosts (local dev)
606
+ * fall back to a host-only cookie (no `Domain` attribute).
607
+ *
608
+ * A functional cookie that holds no identifier, at day precision: it records
609
+ * only whether this browser has been asked, never who or exactly when.
610
+ *
611
+ * Cookie payload: `{ v: 1, d, n, x }`
612
+ * - `d` — epoch DAYS (UTC, `floor(ms / 86_400_000)`) of the last dismissal
613
+ * (0 = never dismissed)
614
+ * - `n` — code sign-ins completed since that dismissal (capped at 99)
615
+ * - `x` — 1 once the traveler has opted out of ever being asked again
616
+ *
617
+ * The prompt is offered again once BOTH 14 days have passed since dismissal
618
+ * AND the traveler has completed at least 3 more code sign-ins — never on
619
+ * `x: 1`. A value that can't be right (a `d` in the future, or two
620
+ * `movmo_pk_prompt` cookies, e.g. host-only and `.movmo.io`) reads as no
621
+ * state, so the prompt is offered.
622
+ */
623
+ export declare const PROMPT_COOKIE = "movmo_pk_prompt";
624
+
625
+ export declare type PromptState = {
626
+ v: 1;
627
+ d: number;
628
+ n: number;
629
+ x: 0 | 1;
630
+ };
631
+
632
+ /** "Too many requests. Try again in M minutes." with M = ceil(retryAfter/60), at least 1. */
633
+ export declare function rateLimited(retryAfterSeconds?: number): string;
634
+
635
+ /**
636
+ * Parses the prompt cookie out of a `document.cookie`-shaped string. Never
637
+ * throws; `null` (no state, so the prompt is offered) when it's absent,
638
+ * malformed, present more than once, or dated after today (`now`).
639
+ */
640
+ export declare function readPromptState(cookie?: string, now?: number): PromptState | null;
641
+
642
+ /**
643
+ * "Confirm it's you" (MOVMO-718): the Security page asks for a fresh sign-in
644
+ * when adding or removing a passkey answers `403 reauth_required`.
645
+ *
646
+ * The sign-in flow runs email-first only, with the account's email locked,
647
+ * and continues at once (`autoContinue`): the login's `allowCredentials` limit
648
+ * a passkey choice to this account, and **Use a code instead** stays
649
+ * available. No autofill, no discoverable "Sign in with passkey", no
650
+ * enrollment prompt. The flow is single-use and is mounted only while open,
651
+ * so every open starts fresh. In a framed window it only says "Manage
652
+ * passkeys on the Movmo Security page." and makes no sign-in or WebAuthn call.
653
+ */
654
+ export declare const ReauthDialog: ({ isOpen, ...props }: ReauthDialogProps) => JSX_2.Element | null;
655
+
656
+ export declare interface ReauthDialogProps {
657
+ isOpen: boolean;
658
+ /** Picks the description: "…to add a passkey." or "…to remove this passkey." */
659
+ action: 'add' | 'remove';
660
+ /** The signed-in account. Its email is locked; its id must match whoever signs in. */
661
+ account: {
662
+ id: string;
663
+ email: string;
664
+ };
665
+ api: MovmoAuthApi;
666
+ getCurrentUser: () => Promise<User>;
667
+ /** The same account signed in again. Retry the pending action once. */
668
+ onConfirmed: () => void;
669
+ /** Cancel, the X, the backdrop or Escape. Drop the pending action. */
670
+ onCancel: () => void;
671
+ /**
672
+ * A different account signed in. The package has already ended that
673
+ * session (`api.logout()`) when this fires; clear local auth state and
674
+ * prompt sign-in. Never retry the pending action.
675
+ */
676
+ onMismatch: () => void;
677
+ }
678
+
679
+ /** Records a completed code sign-in, advancing the count toward re-offering the prompt — but only while a dismissal is pending. */
680
+ export declare function recordCodeSignIn(): void;
681
+
682
+ export declare function registerPasskey(api: MovmoAuthApi, opts: {
683
+ securityKey?: boolean;
684
+ signal: AbortSignal;
685
+ }): Promise<PasskeySummary>;
686
+
268
687
  /**
269
688
  * Programmatically focuses `el`, swallowing only the cross-origin `SecurityError` that
270
689
  * browsers throw when focus would cross a cross-origin iframe boundary. Any other error is
@@ -308,49 +727,122 @@ declare interface SeparatorProps extends React_2.ComponentPropsWithoutRef<typeof
308
727
  text?: string;
309
728
  }
310
729
 
730
+ /** Sets (or clears) the permanent opt-out, keeping any existing `d`/`n`. */
731
+ export declare function setNeverAsk(never: boolean): void;
732
+
733
+ /** Untitled UI `shield-tick`. */
734
+ export declare const ShieldTickIcon: (props: IconProps) => JSX_2.Element;
735
+
736
+ /**
737
+ * Whether the passkey-enrollment prompt should be offered: no state at all,
738
+ * or never dismissed (`d === 0`) → true. Opted out (`x === 1`) → false.
739
+ * Otherwise true once 14 days have elapsed since the last dismissal AND at
740
+ * least 3 code sign-ins have completed since then.
741
+ */
742
+ export declare function shouldOfferPasskey(now?: number, s?: PromptState | null): boolean;
743
+
744
+ /**
745
+ * User-facing copy for the sign-in state machine (`useMovmoSignIn`).
746
+ * English only (D3); strings are verbatim from the MOVMO-685/718 design
747
+ * spec wherever it gives them. Components render these, never raw codes.
748
+ */
749
+ export declare const signInCopy: {
750
+ readonly passkeyNotRecognized: "That passkey isn't recognized. We've sent you a code instead.";
751
+ readonly tooManyAttempts: "Too many attempts. Start again.";
752
+ readonly flowExpired: "That took too long. Start again.";
753
+ readonly codeSent: "Code sent";
754
+ readonly signedIn: "Signed in";
755
+ readonly generic: "Something went wrong. Try again.";
756
+ readonly accountMismatch: "That's a different account. Sign in with the account you're using.";
757
+ /** Security page copy (spec "Add results"), reused by the enrollment prompt. */
758
+ readonly passkeyExists: "This passkey is already on your account.";
759
+ readonly enrollExpired: "That took too long. Try again.";
760
+ readonly invalidEmail: "Enter a valid email address.";
761
+ readonly passkeyUnavailable: "This browser couldn't use a passkey. Use a code instead.";
762
+ readonly discoverableNotRecognized: "That passkey isn't recognized. Continue with your email instead.";
763
+ };
764
+
765
+ export declare type SignInErrorCode = AuthErrorCode | 'network' | 'unknown' | WebAuthnFailure | 'account_mismatch';
766
+
767
+ /**
768
+ * The non-modal sign-in UI (flights-ui `/connect`, `ReauthDialog`): email →
769
+ * passkey or code → optional passkey enrollment, rendered from the
770
+ * `useMovmoSignIn` state machine.
771
+ *
772
+ * The machine is single-use: once it signs someone in (or ends on an account
773
+ * mismatch) it stays there. Mount a fresh `SignInFlow` for every attempt
774
+ * (render it conditionally, or change its `key`); `SignInModal` does this on
775
+ * every open. Every `useMovmoSignIn` option passes through, including
776
+ * `onVerified` and `onAccountMismatch`.
777
+ */
778
+ export declare const SignInFlow: ({ title, description, onCancel, ...options }: SignInFlowProps) => JSX_2.Element;
779
+
780
+ export declare interface SignInFlowProps extends UseMovmoSignInOptions {
781
+ /** Replaces the email, passkey and code step titles (e.g. "Confirm it's you"). */
782
+ title?: string;
783
+ /** Replaces the email and passkey step descriptions; the code step keeps "Enter the code we sent to …". */
784
+ description?: string;
785
+ /** Adds a Cancel button beside the primary action; it aborts the flow, then calls this. */
786
+ onCancel?: () => void;
787
+ }
788
+
789
+ export declare type SignInMethod = 'otp' | 'passkey';
790
+
791
+ /**
792
+ * "Sign in to Movmo": `SignInFlow` inside the focus-trapping `Modal`. Code or
793
+ * passkey sign-in, then the passkey enrollment prompt when eligible.
794
+ *
795
+ * Breaking change in 0.14.0 (MOVMO-685): `loginUser`, `verifyOTP`,
796
+ * `showPhone` and `isRateLimitError` are gone; pass `api`
797
+ * (`createMovmoAuthApi(client)`), `surface` and `getCurrentUser` instead.
798
+ */
311
799
  export declare const SignInModal: default_2.FC<SignInModalProps>;
312
800
 
313
- export declare interface SignInModalProps {
801
+ export declare interface SignInModalProps extends Omit<UseMovmoSignInOptions, 'onSignedIn'> {
314
802
  isOpen: boolean;
315
- onClose: () => void;
316
803
  /**
317
- * Called to start the sign-in flow. Rejected errors are inspected via
318
- * `isRateLimitError` to decide whether to show the rate-limit notice. The
319
- * default detector expects an axios-shaped rejection (`error.response.status`
320
- * === 429); pass a custom `isRateLimitError` if you use a different HTTP client.
804
+ * A real dismissal only: the X, the backdrop or Escape. A step change
805
+ * (email → code, a passkey sheet, the enrollment prompt) never calls it.
321
806
  */
322
- loginUser: (email: string, phone: string, usernameType: AuthenticationUsernameType) => Promise<LoginUserResponse>;
323
- /**
324
- * Called to verify the OTP code. Rejected errors are surfaced to the user:
325
- * - 429 (per `isRateLimitError`) → OTP modal locked + warning notice.
326
- * - 400 → "Invalid or expired code…" (most common: typo / expired code).
327
- * - 401 → "Your session has expired…".
328
- * - anything else → "Something went wrong. Please try again."
329
- * Status is read from `error.response.status` (axios shape).
330
- */
331
- verifyOTP: (username: string, code: string, session: string, challengeName: AuthenticationChallengeName | null) => Promise<void>;
332
- getCurrentUser: () => Promise<User>;
333
- onLoginSuccess: (user: User) => void;
807
+ onClose: () => void;
808
+ /** Makes the dialog non-dismissable (no X, backdrop or Escape). */
334
809
  hideCloseButton?: boolean;
335
810
  /**
336
- * Show the phone input alongside email. Defaults to false (MOVMO-445):
337
- * sign-in is email-only — email → Continue → OTP to the email. Pass true
338
- * only for legacy flows that still collect a phone at sign-in.
339
- */
340
- showPhone?: boolean;
341
- /**
342
- * Predicate used to detect HTTP 429 rate-limit rejections. Defaults to an
343
- * axios-shaped check (`error.response?.status === 429`). Supply a custom
344
- * implementation for non-axios clients (fetch, ky, custom error types).
811
+ * The traveler is signed in (after the enrollment prompt, when one is
812
+ * shown). Fires once per open. The dialog hides itself at this point; set
813
+ * `isOpen` to false here so the next open starts a fresh flow. Closing the
814
+ * dialog at the prompt (or at "Passkey added") counts as finishing: this
815
+ * fires first, then `onClose`. `onVerified` (optional) fires earlier, before
816
+ * the prompt; `onAccountMismatch` (re-auth with `expectedUserId`) fires
817
+ * after the package has ended the other account's session.
345
818
  */
346
- isRateLimitError?: (error: unknown) => boolean;
819
+ onLoginSuccess: (user: User, info: {
820
+ method: SignInMethod;
821
+ newUser: boolean;
822
+ }) => void;
347
823
  }
348
824
 
825
+ /** `mismatch` is terminal: re-auth signed in a different account (see `onAccountMismatch`). */
826
+ export declare type SignInStep = 'email' | 'passkey' | 'code' | 'enroll' | 'enrolled' | 'done' | 'mismatch';
827
+
349
828
  export declare const Textarea: React_2.ForwardRefExoticComponent<TextareaProps & React_2.RefAttributes<HTMLTextAreaElement>>;
350
829
 
351
830
  declare interface TextareaProps extends React_2.TextareaHTMLAttributes<HTMLTextAreaElement> {
352
831
  }
353
832
 
833
+ /** Resolves the browser's IANA time zone. `undefined` on any failure. */
834
+ export declare const timeZone: () => string | undefined;
835
+
836
+ /**
837
+ * Normalizes any rejection a `MovmoAuthApi` call can produce into an
838
+ * `AuthError`. Never throws. An axios-shaped error with a `response` maps
839
+ * its status/body; an axios-shaped error with no `response` (or a
840
+ * `fetch`-style network failure) maps to `'network'`; anything else maps
841
+ * to `'unknown'`. Never puts the request's email, code or credential into
842
+ * the resulting error.
843
+ */
844
+ export declare function toAuthError(err: unknown): AuthError;
845
+
354
846
  export declare const Toggle: React_2.ForwardRefExoticComponent<ToggleProps & React_2.RefAttributes<HTMLButtonElement>>;
355
847
 
356
848
  export declare const ToggleGroup: React_2.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React_2.RefAttributes<HTMLDivElement>, "ref"> & {
@@ -368,6 +860,56 @@ declare interface ToggleProps extends React_2.ComponentPropsWithoutRef<typeof Sw
368
860
  size?: 'sm' | 'md';
369
861
  }
370
862
 
863
+ /** Untitled UI `trash-01`. */
864
+ export declare const TrashIcon: (props: IconProps) => JSX_2.Element;
865
+
866
+ /** Untitled UI `usb-flash-drive`. */
867
+ export declare const UsbKeyIcon: (props: IconProps) => JSX_2.Element;
868
+
869
+ export declare function useMovmoSignIn(o: UseMovmoSignInOptions): MovmoSignIn;
870
+
871
+ export declare interface UseMovmoSignInOptions {
872
+ api: MovmoAuthApi;
873
+ /** A framed window is always treated as `'embedded'`, whatever this says. */
874
+ surface: 'top-level' | 'embedded';
875
+ getCurrentUser: () => Promise<User>;
876
+ /** The traveler is signed in: fires exactly once, after the enrollment prompt when one is shown. */
877
+ onSignedIn: (user: User, info: {
878
+ method: SignInMethod;
879
+ newUser: boolean;
880
+ }) => void;
881
+ /**
882
+ * Fires once, right after a successful verify and `getCurrentUser()` (the
883
+ * server session exists), BEFORE the enrollment prompt. Use it for work that
884
+ * mustn't wait for the traveler to answer the prompt; `onSignedIn` keeps its
885
+ * meaning.
886
+ */
887
+ onVerified?: (user: User, info: {
888
+ method: SignInMethod;
889
+ newUser: boolean;
890
+ }) => void;
891
+ /**
892
+ * Re-auth only (`expectedUserId`): a different account signed in. The server
893
+ * session has already been ended by the package (`api.logout()`) when this
894
+ * fires, even if the flow unmounted meanwhile; clear local auth state and
895
+ * prompt sign-in. The flow is terminal (`step: 'mismatch'`).
896
+ */
897
+ onAccountMismatch?: () => void;
898
+ onEvent?: (name: string, props: Record<string, unknown>) => void;
899
+ initialEmail?: string;
900
+ /** Re-auth: the email can't change. Email-first only (no autofill, no passkey button). */
901
+ lockedEmail?: string;
902
+ autoContinue?: boolean;
903
+ /** Default true. */
904
+ offerEnrollment?: boolean;
905
+ /**
906
+ * Re-auth: refuse a different account (checked on verify's `user_id` and on
907
+ * `getCurrentUser().id`). Email-first only, like `lockedEmail`.
908
+ */
909
+ expectedUserId?: string;
910
+ promptSurface?: 'modal' | 'connect';
911
+ }
912
+
371
913
  export declare const validateEmail: (email: string) => boolean;
372
914
 
373
915
  export declare function validateName(value: string): {
@@ -377,4 +919,27 @@ export declare function validateName(value: string): {
377
919
 
378
920
  export declare function validatePhoneNumber(phone: string, countryCode?: string): boolean;
379
921
 
922
+ /** Coarse, user-facing classification of a failed WebAuthn ceremony. */
923
+ export declare type WebAuthnFailure = 'cancelled' | 'aborted' | 'exists' | 'unsupported' | 'security' | 'unknown';
924
+
925
+ /**
926
+ * The `{ publicKey, mediation? }` shape the backend returns for both
927
+ * registration and authentication ceremonies (`publicKey` mirrors
928
+ * `PublicKeyCredentialCreationOptionsJSON`/`RequestOptionsJSON`).
929
+ */
930
+ export declare type WebAuthnOptionsLike = {
931
+ publicKey: Record<string, unknown>;
932
+ mediation?: string;
933
+ };
934
+
935
+ /** Detected browser capabilities for WebAuthn. Never throws. */
936
+ export declare interface WebAuthnSupport {
937
+ webauthn: boolean;
938
+ conditional: boolean;
939
+ platform: boolean;
940
+ }
941
+
942
+ /** Untitled UI `x-close`. */
943
+ export declare const XCloseIcon: (props: IconProps) => JSX_2.Element;
944
+
380
945
  export { }