@usethrottle/auth 0.11.0 → 0.12.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/README.md +52 -0
- package/dist/AccountDashboard-V63ghvjY.d.ts +90 -0
- package/dist/AccountModal-KA54WUFW.js +13 -0
- package/dist/AccountModal-KA54WUFW.js.map +1 -0
- package/dist/AccountModal-pIMgledL.d.ts +141 -0
- package/dist/AuthModal-T2GYI6ZF.js +13 -0
- package/dist/AuthModal-T2GYI6ZF.js.map +1 -0
- package/dist/SignUpPage-BJcMUb2V.d.ts +160 -0
- package/dist/chunk-3B4DNX5T.js +2 -0
- package/dist/chunk-3B4DNX5T.js.map +1 -0
- package/dist/chunk-5EUZYJJL.js +29 -0
- package/dist/chunk-5EUZYJJL.js.map +1 -0
- package/dist/chunk-6WN3J5MM.js +2075 -0
- package/dist/chunk-6WN3J5MM.js.map +1 -0
- package/dist/chunk-CUNMJ2PH.js +1320 -0
- package/dist/chunk-CUNMJ2PH.js.map +1 -0
- package/dist/chunk-ETYOOYYV.js +2 -0
- package/dist/chunk-ETYOOYYV.js.map +1 -0
- package/dist/chunk-JSDZMPCV.js +520 -0
- package/dist/chunk-JSDZMPCV.js.map +1 -0
- package/dist/chunk-QPVS6JXP.js +39 -0
- package/dist/chunk-QPVS6JXP.js.map +1 -0
- package/dist/chunk-RCNUWITB.js +747 -0
- package/dist/chunk-RCNUWITB.js.map +1 -0
- package/dist/{chunk-YY6YATXT.js → chunk-TDQC3LMQ.js} +17 -3
- package/dist/chunk-TDQC3LMQ.js.map +1 -0
- package/dist/{chunk-EFMKBOTL.js → chunk-UFGW76AZ.js} +2 -2
- package/dist/chunk-VDK23T6L.js +230 -0
- package/dist/chunk-VDK23T6L.js.map +1 -0
- package/dist/chunk-X6D35EAX.js +19 -0
- package/dist/chunk-X6D35EAX.js.map +1 -0
- package/dist/client-7LVKfuXO.d.ts +319 -0
- package/dist/hooks-DkpLrEiC.d.ts +148 -0
- package/dist/index.cjs +16 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/localization-SnIAZXJn.d.ts +389 -0
- package/dist/react/components/account/index.d.ts +37 -0
- package/dist/react/components/account/index.js +44 -0
- package/dist/react/components/account/index.js.map +1 -0
- package/dist/react/components/auth/index.d.ts +17 -0
- package/dist/react/components/auth/index.js +40 -0
- package/dist/react/components/auth/index.js.map +1 -0
- package/dist/react/components/guards.d.ts +21 -0
- package/dist/react/components/guards.js +13 -0
- package/dist/react/components/guards.js.map +1 -0
- package/dist/react/components/index.cjs +3561 -2805
- package/dist/react/components/index.cjs.map +1 -1
- package/dist/react/components/index.d.cts +14 -2
- package/dist/react/components/index.d.ts +14 -821
- package/dist/react/components/index.js +90 -4680
- package/dist/react/components/index.js.map +1 -1
- package/dist/react/components/provider.d.ts +79 -0
- package/dist/react/components/provider.js +18 -0
- package/dist/react/components/provider.js.map +1 -0
- package/dist/react/forms/index.js +1 -1
- package/dist/react/index.cjs +16 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +34 -3
- package/dist/react/index.d.ts +5 -433
- package/dist/react/index.js +3 -2
- package/dist/styles.css +7 -4
- package/package.json +4 -1
- package/dist/chunk-YY6YATXT.js.map +0 -1
- /package/dist/{chunk-EFMKBOTL.js.map → chunk-UFGW76AZ.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/components/provider.tsx","../src/react/components/styles.ts"],"sourcesContent":["import { Component, createContext, lazy, Suspense, useCallback, useContext, useMemo, useRef, useState } from 'react';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../../client';\nimport { createAccount } from '../../account';\nimport type { ThrottleAuthConfig } from '../../types';\nimport { ThrottleAuthProvider } from '../context';\nimport { AppearanceProvider, type ThrottleAppearance } from './appearance';\nimport { BrandLogoProvider } from './branding';\nimport { LocalizationProvider, type ThrottleLocalization } from './localization';\nimport { useInjectStyles } from './styles';\nimport { ConfirmProvider } from './primitives';\nimport type { AccountTab } from './account/AccountDashboard';\n\n// Loaded on first open, so a page that only uses the provider and guards ships\n// none of the sign-in or account UI (P8-R34, P8-R114). `React.lazy` caches a\n// rejected import for good, so a failed load swaps in a fresh lazy component\n// (`resetModal`) and the next open retries the import.\nconst loadAuthModal = () => import('./auth/AuthModal').then((m) => ({ default: m.AuthModal }));\nconst loadAccountModal = () => import('./account/AccountModal').then((m) => ({ default: m.AccountModal }));\nlet AuthModal = lazy(loadAuthModal);\nlet AccountModal = lazy(loadAccountModal);\nfunction resetModal(which: 'auth' | 'account') {\n if (which === 'auth') AuthModal = lazy(loadAuthModal);\n else AccountModal = lazy(loadAccountModal);\n}\n\nlet modalLoadLogged = false;\n\n/** Contains a failed modal chunk load: renders nothing, logs once, never throws out of the provider. */\nclass ModalLoadBoundary extends Component<{ which: 'auth' | 'account'; onFail(): void; children: ReactNode }, { failed: boolean }> {\n state = { failed: false };\n static getDerivedStateFromError() {\n return { failed: true };\n }\n componentDidCatch(error: unknown) {\n resetModal(this.props.which);\n if (!modalLoadLogged) {\n modalLoadLogged = true;\n // eslint-disable-next-line no-console\n console.warn(`[@usethrottle/auth] the ${this.props.which} dialog failed to load or render: ${(error as Error)?.message ?? 'unknown error'}`);\n }\n this.props.onFail();\n }\n render() {\n return this.state.failed ? null : this.props.children;\n }\n}\n\nconst warned = new Set<string>();\n\n/** One warning per problem per page, and only in development. */\nexport function warnOnce(key: string, message: string): void {\n if (process.env.NODE_ENV === 'production') return;\n if (warned.has(key)) return;\n warned.add(key);\n // eslint-disable-next-line no-console\n console.warn(`[@usethrottle/auth] ${message}`);\n}\n\nexport interface ThrottleProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Default logo for the `branded` card variant. A card's own `logo` prop wins over this. */\n logo?: ReactNode;\n /** Default true. Set false and import `@usethrottle/auth/styles.css` for a flash-free SSR paint. */\n injectStyles?: boolean;\n /** Returns the guest cart id to attach after a successful sign-in or sign-up. */\n claimCartOnSignIn?: () => string | null | undefined;\n /** Inject a pre-built client. Tests use it. */\n auth?: ThrottleAuth;\n children: ReactNode;\n /** @internal test seam — receives the step-up callback handed to the core. */\n __captureStepUp?: (fn: () => Promise<string | null>) => void;\n}\n\ntype Overlay = 'signIn' | 'signUp' | 'account' | null;\n\n/**\n * @internal Not exported from the package. `SignInButton`/`SignUpButton` read\n * it directly (falling back to owning their own local modal state when it is\n * null, i.e. mounted under a plain `ThrottleAuthProvider`); merchants use the\n * public `useThrottle()` hook below instead.\n */\nexport interface ThrottleUIContextValue {\n overlay: Overlay;\n openSignIn(onSuccess?: () => void): void;\n openSignUp(onSuccess?: () => void): void;\n /** `tabs` restricts which panels the shared account overlay shows;\n * `restoreFocusTo` names where focus returns when the opener is itself\n * unmounted by the same click. */\n openAccount(opts?: { tabs?: AccountTab[]; restoreFocusTo?: Element | null }): void;\n close(): void;\n}\n\nexport const ThrottleUIContext = createContext<ThrottleUIContextValue | null>(null);\n\nexport interface UseThrottleResult {\n openSignIn(): void;\n openSignUp(): void;\n openAccount(): void;\n close(): void;\n isOpen: boolean;\n}\n\n/**\n * The equivalent of Clerk's `useClerk().openSignIn()`. Lets a merchant open\n * the auth/account overlay from their own UI — a pricing CTA, a gated action,\n * a keyboard shortcut — driving the SAME modal `SignInButton`/`SignUpButton`\n * open, rather than a second competing one.\n *\n * Outside a `<ThrottleProvider>` (e.g. under a plain `ThrottleAuthProvider`)\n * this degrades to no-op functions and `isOpen: false`, with a one-time\n * development warning, rather than throwing.\n */\nexport function useThrottle(): UseThrottleResult {\n const ctx = useContext(ThrottleUIContext);\n if (!ctx) {\n warnOnce(\n 'use-throttle-no-provider',\n 'useThrottle() was called without a <ThrottleProvider> above it in the tree. ' +\n 'openSignIn/openSignUp/openAccount will do nothing — wrap your app in <ThrottleProvider> to use them.',\n );\n return { openSignIn: () => {}, openSignUp: () => {}, openAccount: () => {}, close: () => {}, isOpen: false };\n }\n return {\n openSignIn: () => ctx.openSignIn(),\n openSignUp: () => ctx.openSignUp(),\n openAccount: ctx.openAccount,\n close: ctx.close,\n isOpen: ctx.overlay !== null,\n };\n}\n\n/**\n * Composes rather than replaces. It creates the client (so it can supply\n * `onStepUpRequired` at construction time, which is the only moment the core\n * reads it) and hands it to the existing `<ThrottleAuthProvider>` through the\n * `auth` injection prop that already exists for this purpose. Every hook —\n * useAuth, useCustomer, useOrders — keeps working underneath unchanged.\n *\n * It also owns the ONE modal overlay (sign-in/sign-up/account) alongside the\n * step-up dialog it already hosts, so a `SignInButton`/`SignUpButton` click\n * and a programmatic `useThrottle().openSignIn()` call always drive the same\n * dialog rather than stacking two.\n */\nexport function ThrottleProvider(props: ThrottleProviderProps) {\n const {\n publishableKey, baseUrl, storage, appearance, localization, logo,\n injectStyles = true, claimCartOnSignIn, auth: injected, children, __captureStepUp,\n } = props;\n\n useInjectStyles(injectStyles);\n\n const [stepUpOpen, setStepUpOpen] = useState(false);\n // The bridge. The dialog renders below the client that needs its answer, so\n // the resolver travels through a ref rather than through props.\n const resolveStepUp = useRef<((password: string | null) => void) | null>(null);\n // Read through a ref so a merchant passing an inline arrow function does not\n // rebuild the client on every parent render.\n const claimRef = useRef(claimCartOnSignIn);\n claimRef.current = claimCartOnSignIn;\n\n // The one piece of overlay state. `onOverlaySuccess` is a bookkeeping ref\n // alongside it — mirroring `resolveStepUp` above — not competing state: it\n // answers \"what happens on success\", not \"is something open\".\n const [overlay, setOverlay] = useState<Overlay>(null);\n const overlayRestoreFocus = useRef<Element | null>(null);\n const [accountTabs, setAccountTabs] = useState<AccountTab[] | undefined>(undefined);\n const onOverlaySuccess = useRef<(() => void) | null>(null);\n\n const openSignIn = useCallback((onSuccess?: () => void) => {\n onOverlaySuccess.current = onSuccess ?? null;\n setOverlay('signIn');\n }, []);\n const openSignUp = useCallback((onSuccess?: () => void) => {\n onOverlaySuccess.current = onSuccess ?? null;\n setOverlay('signUp');\n }, []);\n const openAccount = useCallback((opts?: { tabs?: AccountTab[]; restoreFocusTo?: Element | null }) => {\n onOverlaySuccess.current = null;\n // A caller that is itself being unmounted by the same click (UserButton's\n // menu item) must name where focus goes back to, because by the time the\n // modal mounts the opener is gone and the browser has already reset\n // activeElement to <body>.\n overlayRestoreFocus.current = opts?.restoreFocusTo ?? null;\n setAccountTabs(opts?.tabs);\n setOverlay('account');\n }, []);\n const closeOverlay = useCallback(() => {\n onOverlaySuccess.current = null;\n overlayRestoreFocus.current = null;\n setOverlay(null);\n }, []);\n const handleOverlaySuccess = useCallback(() => {\n const fn = onOverlaySuccess.current;\n onOverlaySuccess.current = null;\n setOverlay(null);\n fn?.();\n }, []);\n\n const ui = useMemo<ThrottleUIContextValue>(\n () => ({ overlay, openSignIn, openSignUp, openAccount, close: closeOverlay }),\n [overlay, openSignIn, openSignUp, openAccount, closeOverlay],\n );\n\n // Hoisted out of the memo below and stabilized with an empty deps array: it\n // closes only over refs and a setState, neither of which ever change, so it\n // must keep one identity for the client's whole life regardless of what\n // triggers the memo to recompute (e.g. a new `auth` prop object).\n const askForStepUp = useCallback(\n () =>\n new Promise<string | null>((resolve) => {\n resolveStepUp.current = resolve;\n setStepUpOpen(true);\n }),\n [],\n );\n\n // Lives outside the memo so a recompute (new `auth` prop or key) mid-claim\n // cannot drop the pending claim.\n const pendingRef = useRef<Promise<void> | null>(null);\n\n const auth = useMemo<ThrottleAuth>(() => {\n __captureStepUp?.(askForStepUp);\n\n const client =\n injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired: askForStepUp });\n\n // Claim on the explicit entry points only. `restore()` also transitions the\n // snapshot to authenticated on every page load, and re-claiming there would\n // fire a pointless request per visit.\n const account = createAccount(client);\n const claim = async () => {\n const cartId = claimRef.current?.();\n if (!cartId) return;\n try {\n await account.claimCart(cartId);\n } catch (err) {\n // A cart that will not attach must never block a sign-in.\n warnOnce('claim-cart', `claimCartOnSignIn failed: ${(err as Error)?.message ?? err}`);\n }\n };\n\n // Spreading is safe: every method on the client closes over its own state\n // rather than using `this`. The provider test asserts every method survives.\n //\n // `pending` is set synchronously when a sign-in/register starts, BEFORE the\n // credential request, so a caller that reads `customerKey` flipping cannot\n // race ahead of the claim. A second call chains onto the first. Cleared\n // only by the call that still owns it.\n const track = <T,>(run: () => Promise<T>): Promise<T> => {\n const prior = pendingRef.current;\n // Publish `pending` BEFORE the operation starts: a client whose signIn\n // notifies subscribers before its first await must already see it. The\n // second call also WAITS for the first (they are serialised, so a\n // stalled first request delays the second).\n let done!: () => void;\n const mine: Promise<void> = new Promise<void>((r) => { done = r; }).finally(() => {\n if (pendingRef.current === mine) pendingRef.current = null;\n });\n pendingRef.current = mine;\n const op = (async () => {\n if (prior) await prior;\n return run();\n })();\n op.then(done, done); // `mine` never rejects; op's rejection goes to the caller.\n return op;\n };\n return {\n ...client,\n signIn: (email: string, password: string) =>\n track(async () => {\n await client.signIn(email, password);\n await claim();\n }),\n register: (email: string, password: string) =>\n track(async () => {\n const result = await client.register(email, password);\n // `pending` means no session exists yet, so there is nothing to claim on to.\n if (result.status === 'active') await claim();\n return result;\n }),\n whenClaimSettled: () => pendingRef.current ?? Promise.resolve(),\n };\n // `storage`, `claimCartOnSignIn` and `__captureStepUp` are deliberately not\n // dependencies — same reasoning as ThrottleAuthProvider's own memo.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const finishStepUp = (password: string | null) => {\n setStepUpOpen(false);\n const resolve = resolveStepUp.current;\n resolveStepUp.current = null;\n resolve?.(password);\n };\n\n const authOverlayOpen = overlay === 'signIn' || overlay === 'signUp';\n // A modal mounts at its first open and stays mounted after, so closing keeps\n // its exit and focus-restore behaviour; before that nothing is imported.\n const [authSeen, setAuthSeen] = useState(false);\n const [accountSeen, setAccountSeen] = useState(false);\n if (authOverlayOpen && !authSeen) setAuthSeen(true);\n if (overlay === 'account' && !accountSeen) setAccountSeen(true);\n // A failed load unmounts that modal and closes the overlay; the next open mounts a fresh lazy.\n const failAuthLoad = () => { setAuthSeen(false); setOverlay(null); };\n const failAccountLoad = () => { setAccountSeen(false); setOverlay(null); };\n\n return (\n <AppearanceProvider value={appearance ?? {}}>\n <LocalizationProvider value={localization ?? {}}>\n <BrandLogoProvider value={logo ?? null}>\n <ThrottleAuthProvider auth={auth} publishableKey={publishableKey}>\n {/* One confirmation surface for the whole tree, so every panel's\n destructive action asks in the merchant's styling rather than\n the browser's. */}\n {/* Owns the single dialog surface: confirmations AND the step-up\n password challenge. Step-up is raised by the very request a\n confirmation just authorised, and the account UI may itself be\n a modal, so giving them a Modal each stacked dialogs on the\n buyer. */}\n <ConfirmProvider stepUpOpen={stepUpOpen} onStepUpResolve={finishStepUp}>\n <ThrottleUIContext.Provider value={ui}>\n {children}\n {/* Keyed on the overlay kind so each open() call mounts `AuthFlow`\n fresh with the view that call asked for, rather than reusing a\n still-mounted instance that would ignore a new `initialView`. */}\n {authSeen && (\n <ModalLoadBoundary which=\"auth\" onFail={failAuthLoad}>\n <Suspense fallback={null}>\n <AuthModal\n key={overlay === 'signUp' ? 'signUp' : 'signIn'}\n open={authOverlayOpen}\n onClose={closeOverlay}\n initialView={overlay === 'signUp' ? 'signUp' : 'signIn'}\n afterSignIn={handleOverlaySuccess}\n afterSignUp={handleOverlaySuccess}\n />\n </Suspense>\n </ModalLoadBoundary>\n )}\n {accountSeen && (\n <ModalLoadBoundary which=\"account\" onFail={failAccountLoad}>\n <Suspense fallback={null}>\n <AccountModal open={overlay === 'account'} onClose={closeOverlay} tabs={accountTabs} restoreFocusTo={overlayRestoreFocus.current} />\n </Suspense>\n </ModalLoadBoundary>\n )}\n </ThrottleUIContext.Provider>\n </ConfirmProvider>\n </ThrottleAuthProvider>\n </BrandLogoProvider>\n </LocalizationProvider>\n </AppearanceProvider>\n );\n}\n","import { useInsertionEffect } from 'react';\n\nexport const THROTTLE_STYLE_ID = 'throttle-auth-styles';\n\n/**\n * The whole component stylesheet, as a string, so one source feeds both the\n * runtime injector and the `dist/styles.css` the build emits. There is no CSS\n * pipeline in tsup — adding one would mean the file and the injected styles\n * could drift.\n *\n * Every rule a merchant might restyle uses a SINGLE class selector. Merchant\n * classNames from `appearance.elements` land on the same element, so equal\n * specificity plus source order is what lets them win. A descendant selector\n * here would silently outrank them.\n */\nexport const THROTTLE_STYLES = `\n.throttle-root {\n --throttle-color-primary: #18181b;\n --throttle-color-primary-foreground: #ffffff;\n --throttle-color-background: #ffffff;\n --throttle-color-surface: #f8fafc;\n --throttle-color-foreground: #0e1116;\n --throttle-color-muted: #eceef2;\n --throttle-color-muted-foreground: #5b6472;\n --throttle-color-border: #e2e8f0;\n /* The boundary of an INTERACTIVE control, which WCAG 1.4.11 requires to\n reach 3:1 against its adjacent colour — an input identified only by a\n 1.23:1 hairline is not identifiable at all. Decorative separators (card\n edges, table rules, dividers) keep --throttle-color-border: they do not\n identify a control, so the requirement does not apply and darkening\n them would only add noise. */\n /* The account dashboard paints nothing by default so it can drop into a\n merchant's page without fighting their background. The accountSurface and\n accountPadding appearance variables opt back into a card.\n No backticks in this comment: the sheet is a TS template literal. */\n --throttle-card-width: 400px;\n --throttle-color-account-surface: transparent;\n --throttle-account-padding: 0;\n --throttle-color-border-control: #8b93a1;\n --throttle-color-danger: #c2321f;\n --throttle-color-danger-surface: #fef2f2;\n --throttle-color-danger-border: #fecaca;\n --throttle-color-success: #127a4a;\n --throttle-color-success-surface: #ecfdf5;\n --throttle-color-success-border: #a7f3d0;\n --throttle-color-warning: #92400e;\n --throttle-color-warning-surface: #fffbeb;\n --throttle-color-warning-border: #fde68a;\n --throttle-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --throttle-font-size: 16px;\n --throttle-font-size-xs: 12px;\n --throttle-font-size-sm: 14px;\n --throttle-font-size-lg: 20px;\n --throttle-font-size-xl: 24px;\n --throttle-line-height: 1.5;\n --throttle-line-height-tight: 1.25;\n --throttle-radius: 12px;\n --throttle-radius-sm: 8px;\n --throttle-radius-full: 999px;\n --throttle-border-width: 1px;\n --throttle-spacing: 8px;\n --throttle-space-1: 4px;\n --throttle-space-2: var(--throttle-spacing);\n --throttle-space-3: 12px;\n --throttle-space-4: 16px;\n --throttle-space-5: 24px;\n --throttle-space-6: 32px;\n --throttle-control-height: 48px;\n --throttle-control-padding-x: 16px;\n --throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);\n --throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);\n --throttle-transition: 150ms cubic-bezier(.2,0,.2,1);\n --throttle-z-modal: 2147483000;\n --throttle-z-popover: 2147482000;\n font-family: var(--throttle-font-family);\n font-size: var(--throttle-font-size);\n line-height: var(--throttle-line-height);\n color: var(--throttle-color-foreground);\n box-sizing: border-box;\n}\n.throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }\n\n.throttle-root[data-throttle-theme=\"dark\"] {\n --throttle-color-primary: #e6edf3;\n --throttle-color-primary-foreground: #0d1117;\n --throttle-color-background: #0d1117;\n --throttle-color-surface: #1c2129;\n --throttle-color-foreground: #e6edf3;\n --throttle-color-muted: #21262d;\n --throttle-color-muted-foreground: #9aa4b2;\n --throttle-color-border: #30363d;\n --throttle-color-border-control: #5c6570;\n --throttle-color-danger: #ff6b58;\n --throttle-color-danger-surface: #2a1513;\n --throttle-color-danger-border: #5c2b25;\n --throttle-color-success: #45c08a;\n --throttle-color-success-surface: #0f2419;\n --throttle-color-success-border: #1f4d36;\n --throttle-color-warning: #e0a53a;\n --throttle-color-warning-surface: #2a2113;\n --throttle-color-warning-border: #5c4a25;\n --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);\n --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);\n}\n@media (prefers-color-scheme: dark) {\n .throttle-root[data-throttle-theme=\"auto\"] {\n --throttle-color-primary: #e6edf3;\n --throttle-color-primary-foreground: #0d1117;\n --throttle-color-background: #0d1117;\n --throttle-color-surface: #1c2129;\n --throttle-color-foreground: #e6edf3;\n --throttle-color-muted: #21262d;\n --throttle-color-muted-foreground: #9aa4b2;\n --throttle-color-border: #30363d;\n --throttle-color-border-control: #5c6570;\n --throttle-color-danger: #ff6b58;\n --throttle-color-danger-surface: #2a1513;\n --throttle-color-danger-border: #5c2b25;\n --throttle-color-success: #45c08a;\n --throttle-color-success-surface: #0f2419;\n --throttle-color-success-border: #1f4d36;\n --throttle-color-warning: #e0a53a;\n --throttle-color-warning-surface: #2a2113;\n --throttle-color-warning-border: #5c4a25;\n --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);\n --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);\n }\n}\n\n.throttle-card {\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-card);\n padding: var(--throttle-space-5);\n width: 100%;\n max-width: var(--throttle-card-width);\n margin-inline: auto;\n}\n/* Inside a Modal the dialog already provides the surface — background, radius\n and elevation. A card that draws its own on top is the \"box inside a box\":\n two shadowed rounded rectangles nested, with the card's max-width stranding\n it in the middle of a much wider dialog. */\n.throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }\n.throttle-card__header { margin-bottom: var(--throttle-space-4); }\n.throttle-card__header--branded { text-align: center; }\n.throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }\n.throttle-card__title { font-size: var(--throttle-font-size-xl); line-height: var(--throttle-line-height-tight); font-weight: 600; letter-spacing: -0.02em; margin: 0; }\n.throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }\n.throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }\n.throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }\n.throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }\n.throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }\n\n.throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }\n.throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }\n.throttle-field__label { font-weight: 500; }\n.throttle-field__input {\n font: inherit;\n font-size: var(--throttle-font-size);\n color: var(--throttle-color-foreground);\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border-control);\n border-radius: var(--throttle-radius-sm);\n /* padding-block stays 0 so min-height alone owns the height, exactly as\n .throttle-button does. With 12px vertical padding the box computed to\n 50px against the button's 48px — a 2px mismatch, measured in a browser\n across 19 controls. An input element centres its own text vertically. */\n padding-block: 0;\n padding-inline: var(--throttle-control-padding-x);\n min-height: var(--throttle-control-height);\n width: 100%;\n transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);\n}\n.throttle-field__input:focus-visible {\n outline: none;\n border-color: var(--throttle-color-primary);\n box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);\n}\n.throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }\n.throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }\n.throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }\n.throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }\n/* Always mounted, so it reserves its line whether or not there is an error:\n an error appearing on submit must not push the submit button out from\n under the buyer's pointer. */\n.throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); line-height: 1rem; }\n/* Reserved a full line under EVERY input even with nothing to say, which\n with the stack gap put 28px of dead space beneath each field, on every\n form and all seven account panels. The element stays mounted when empty\n because role=alert only announces changes to a live region that was\n already there; it just stops occupying a line. */\n.throttle-field__error:empty { display: none; }\n\n.throttle-field-group {\n display: flex;\n flex-direction: column;\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-sm);\n overflow: hidden;\n}\n.throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }\n.throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }\n.throttle-field-group__forgot { margin-left: auto; }\n\n.throttle-button {\n font: inherit;\n font-weight: 500;\n border-radius: var(--throttle-radius-sm);\n border: var(--throttle-border-width) solid transparent;\n padding-inline: var(--throttle-control-padding-x);\n padding-block: 0;\n min-height: var(--throttle-control-height);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n /* A label that wraps turns a control into a two-line block of text. */\n white-space: nowrap;\n transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);\n}\n.throttle-button:disabled { opacity: .55; cursor: not-allowed; }\n.throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }\n.throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: var(--throttle-button-width, 100%); }\n.throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }\n.throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border-control); }\n.throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }\n.throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }\n.throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }\n/* Link-style actions are the rows panel default, and they carry Delete,\n Remove, Cancel and Sign out. Zero padding with min-height 0 made those\n roughly 20px tall and ~8px apart — a mis-tap on a phone lands on something\n irreversible. The target is expanded with vertical padding and pulled back\n with an equal negative margin, so the hit box clears 44px while the text\n sits exactly where it did. Collapsing this back to zero padding shrinks the\n target again. */\n.throttle-button--link { background: none; border: none; color: var(--throttle-color-primary); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }\n.throttle-button--link:hover:not(:disabled) { opacity: .8; }\n.throttle-button--linkDanger { background: none; border: none; color: var(--throttle-color-danger); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }\n.throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }\n.throttle-button--xs { font-size: var(--throttle-font-size-xs); }\n\n.throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }\n.throttle-alert--error { background: var(--throttle-color-danger-surface); border: var(--throttle-border-width) solid var(--throttle-color-danger-border); color: var(--throttle-color-danger); }\n.throttle-alert--success { background: var(--throttle-color-success-surface); border: var(--throttle-border-width) solid var(--throttle-color-success-border); color: var(--throttle-color-success); }\n.throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }\n\n.throttle-spinner {\n display: inline-block; width: 1em; height: 1em;\n border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;\n animation: throttle-spin 700ms linear infinite;\n}\n@keyframes throttle-spin { to { transform: rotate(360deg); } }\n.throttle-button--loading { position: relative; }\n.throttle-button__label { display: inline-flex; align-items: center; justify-content: center; gap: var(--throttle-space-2); }\n.throttle-button__label--hidden { visibility: hidden; }\n.throttle-spinner--centred { position: absolute; top: 50%; left: 50%; margin: -0.5em 0 0 -0.5em; }\n\n@media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }\n\n.throttle-skeleton {\n background: var(--throttle-color-muted);\n border-radius: var(--throttle-radius-sm);\n animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }\n@keyframes throttle-pulse { 50% { opacity: .5; } }\n@media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }\n\n.throttle-modal__overlay {\n position: fixed; inset: 0; z-index: var(--throttle-z-modal);\n background: rgba(14,17,22,.45);\n display: flex; align-items: center; justify-content: center;\n padding: var(--throttle-space-4);\n overflow-y: auto;\n}\n.throttle-modal__container {\n background: var(--throttle-color-background);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-popover);\n max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;\n position: relative;\n}\n/* After the base rule on purpose: both are single-class, so equal specificity\n means source order decides. Declared before it, this silently lost. */\n.throttle-modal__container--compact { max-width: 440px; }\n.throttle-modal__close {\n position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);\n background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);\n color: var(--throttle-color-muted-foreground);\n transition: color var(--throttle-transition);\n}\n.throttle-modal__close:hover { color: var(--throttle-color-foreground); }\n.throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }\n.throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }\n.throttle-modal__body { padding: var(--throttle-space-4); }\n\n.throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }\n.throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }\n.throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }\n.throttle-dropdown__menu {\n position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));\n z-index: var(--throttle-z-popover);\n min-width: 260px;\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-popover);\n padding: var(--throttle-space-1);\n}\n.throttle-dropdown__item {\n display: flex; align-items: center; gap: var(--throttle-space-2);\n width: 100%; text-align: left; font: inherit; color: inherit;\n background: none; border: none; cursor: pointer;\n padding: var(--throttle-space-3);\n border-radius: var(--throttle-radius-sm);\n text-decoration: none;\n transition: background-color var(--throttle-transition);\n}\n.throttle-dropdown__item:hover { background: var(--throttle-color-surface); }\n.throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }\n\n.throttle-tabs__list {\n display: flex; gap: var(--throttle-space-1);\n border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);\n overflow-x: auto; scrollbar-width: thin;\n}\n.throttle-tabs__tab {\n font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;\n padding: var(--throttle-space-3);\n color: var(--throttle-color-muted-foreground);\n border-bottom: 2px solid transparent;\n transition: color var(--throttle-transition), border-color var(--throttle-transition);\n}\n.throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }\n.throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }\n.throttle-tabs__panel { padding-top: var(--throttle-space-4); }\n\n/* Paints its own ground. A UserButton in a navbar should stay transparent, but\n the dashboard is a large surface of themed text: in dark theme its foreground\n is near-white, so on a light host page it was rendering white-on-white. */\n.throttle-cardframe { width: 100%; min-height: 420px; border: 0; display: block; }\n.throttle-centre { display: flex; align-items: center; justify-content: center; min-height: 160px; }\n.throttle-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-4); align-items: start; }\n.throttle-section__title { font-size: var(--throttle-font-size-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--throttle-color-muted-foreground); margin: 0 0 var(--throttle-space-2); font-weight: 600; }\n.throttle-addresses { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-5); }\n.throttle-addressblock__line { margin: 0 0 2px; }\n.throttle-timeline { display: flex; flex-direction: column; gap: var(--throttle-space-2); }\n.throttle-timeline__row { display: flex; align-items: center; gap: var(--throttle-space-2); }\n.throttle-timeline__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--throttle-color-border-control); flex: none; }\n.throttle-timeline__label { flex: 1; }\n.throttle-account-root { container-type: inline-size; background: var(--throttle-color-account-surface); color: var(--throttle-color-foreground); padding: var(--throttle-account-padding); }\n.throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }\n.throttle-account__content { flex: 1; min-width: 0; }\n.throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }\n.throttle-tabs__railitem {\n font: inherit;\n font-size: var(--throttle-font-size-sm);\n display: flex;\n align-items: center;\n gap: var(--throttle-space-3);\n width: 100%;\n text-align: left;\n background: none;\n border: none;\n border-radius: var(--throttle-radius-sm);\n padding: 0 var(--throttle-space-3);\n min-height: var(--throttle-control-height);\n color: var(--throttle-color-muted-foreground);\n cursor: pointer;\n transition: background var(--throttle-transition), color var(--throttle-transition);\n}\n.throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }\n.throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }\n.throttle-tabs__railicon { flex: none; opacity: .75; }\n\n/* A CONTAINER query, not a viewport one: these components render inside a\n merchant's page, so the viewport says nothing about how much room the\n dashboard actually has — it could sit in a 400px column on a 1920px screen.\n Below ~560px of container the rail becomes a full-width list above the\n panel: all seven still visible, each still a 44px target. */\n@container (max-width: 560px) {\n /* align-items: flex-start is right in row direction and wrong in column —\n it shrinks the rail to its content width, so width:100% alone cannot make\n it span. Stretch it explicitly when stacked. */\n .throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }\n .throttle-addresses { grid-template-columns: 1fr; }\n .throttle-fieldrow { grid-template-columns: 1fr; }\n .throttle-tabs__rail { width: 100%; }\n}\n\n.throttle-table { width: 100%; border-collapse: collapse; text-align: left; }\n.throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }\n.throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }\n.throttle-table__row:hover { background: var(--throttle-color-surface); }\n.throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }\n.throttle-table__scroll { overflow-x: auto; }\n\n.throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }\n.throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }\n.throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }\n\n.throttle-avatar {\n display: inline-flex; align-items: center; justify-content: center;\n width: 32px; height: 32px; border-radius: var(--throttle-radius-full);\n background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);\n font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;\n}\n\n.throttle-badge {\n display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;\n padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);\n background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);\n}\n.throttle-badge--muted { background: none; border: var(--throttle-border-width) solid var(--throttle-color-border); color: var(--throttle-color-muted-foreground); font-weight: 400; padding: 1px calc(var(--throttle-space-2) - 1px); }\n.throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }\n.throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }\n.throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }\n\n.throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }\n.throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }\n.throttle-muted { color: var(--throttle-color-muted-foreground); }\n.throttle-banner {\n display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);\n color: var(--throttle-color-warning);\n border-radius: var(--throttle-radius-sm);\n padding: var(--throttle-space-3);\n margin-bottom: var(--throttle-space-4);\n}\n.throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }\n/* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the\n dynamic address/toolbar, so the page would be taller than the visible\n viewport and the card would sit off-centre until the toolbar collapsed. */\n.throttle-page {\n min-height: 100dvh;\n width: 100%;\n background: var(--throttle-color-surface);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--throttle-space-5);\n padding: var(--throttle-space-5);\n}\n.throttle-page__logo { display: flex; justify-content: center; }\n.throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }\n.throttle-listitem {\n display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-sm);\n padding: var(--throttle-space-3);\n}\n/* 'rows' variant: hairline rules between items instead of each item carrying\n its own border+radius, so a panel does not read as a card inside a card. */\n.throttle-rows { display: flex; flex-direction: column; }\n.throttle-rowitem {\n display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n border-top: var(--throttle-border-width) solid var(--throttle-color-border);\n padding: var(--throttle-space-3) 0;\n}\n.throttle-rowitem:first-child { border-top: none; padding-top: 0; }\n.throttle-actions {\n display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; align-items: center;\n /* Primary buttons are full-width so a form's submit fills its card. In a row\n of sibling actions that same rule made every button claim 100% and then\n shrink to share the line, squeezing Resume to 59px so its label wrapped to\n two lines inside a 48px box. A custom property INHERITS to the buttons, so\n the row opts them back to content width without a descendant selector —\n those outrank the merchant overrides in appearance.elements. */\n --throttle-button-width: auto;\n}\n.throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }\n.throttle-lineitem { display: flex; align-items: flex-start; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }\n/* The UA's 1em paragraph margins pushed a two-line item to 96px, so five\n items filled a screen with mostly whitespace. These carry the spacing\n instead. Separate classes, not a \".throttle-lineitem p\" descendant\n selector -- those outrank the merchant's appearance.elements overrides. */\n.throttle-lineitem__desc { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); }\n.throttle-lineitem__meta { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-sm); }\n.throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }\n.throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }\n\n/* Order and invoice detail: the record on the left, what it costs and what\n you can do about it on the right. The rail sticks so the total and the\n actions stay reachable on an order with thirty lines, which is exactly when\n a buyer most wants them. Collapses below, where a sticky rail would eat a\n phone screen. */\n.throttle-detail { display: grid; grid-template-columns: minmax(0, 1fr) 17rem; gap: var(--throttle-space-6); align-items: start; }\n.throttle-detail__main { display: flex; flex-direction: column; gap: var(--throttle-space-5); min-width: 0; }\n.throttle-detail__rail {\n display: flex; flex-direction: column; gap: var(--throttle-space-4);\n position: sticky; top: var(--throttle-space-4);\n padding: var(--throttle-space-4);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-md);\n background: var(--throttle-color-surface);\n}\n/* One column, and the rail stops sticking — on a phone a sticky summary covers\n the very line items it is summarising. Placed AFTER the base rules above: a\n container query adds no specificity, so the later rule wins. */\n@container (max-width: 560px) {\n .throttle-detail { grid-template-columns: 1fr; }\n .throttle-detail__rail { position: static; }\n}\n/* The one number the buyer came for. */\n.throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }\n.throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }\n.throttle-detail__label { font-size: var(--throttle-font-size-sm); font-weight: 500; color: var(--throttle-color-muted-foreground); letter-spacing: .04em; text-transform: uppercase; }\n/* A rail action column: full width, stacked, so the primary reads as primary. */\n.throttle-detail__actions { display: flex; flex-direction: column; gap: var(--throttle-space-2); --throttle-button-width: 100%; }\n.throttle-detail__link { display: inline-flex; align-items: center; gap: var(--throttle-space-1); }\n\n@media (prefers-reduced-motion: reduce) {\n .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {\n transition: none;\n }\n}\n`;\n\n/**\n * Appends the stylesheet once. `useInsertionEffect` is the right phase for\n * this — React runs it before layout effects, so styles are in place before\n * anything measures. It does not run on the server, so SSR markup paints\n * unstyled for one frame; merchants who care import `@usethrottle/auth/styles.css`\n * and pass `injectStyles={false}`.\n *\n * Guarded on the element id rather than a module flag: a merchant may have\n * already inlined the sheet under the same id, and two React roots in one page\n * must not each add a copy.\n */\nexport function useInjectStyles(enabled: boolean): void {\n useInsertionEffect(() => {\n if (!enabled) return;\n if (typeof document === 'undefined') return;\n if (document.getElementById(THROTTLE_STYLE_ID)) return;\n const tag = document.createElement('style');\n tag.id = THROTTLE_STYLE_ID;\n tag.textContent = THROTTLE_STYLES;\n document.head.appendChild(tag);\n }, [enabled]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,eAAe,MAAM,UAAU,aAAa,YAAY,SAAS,QAAQ,gBAAgB;;;ACA7G,SAAS,0BAA0B;AAE5B,IAAM,oBAAoB;AAa1B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
|
|
@@ -345,7 +345,10 @@ function createThrottleAuth(config) {
|
|
|
345
345
|
const current = mine.find((s) => s.current);
|
|
346
346
|
await call("DELETE", `/me/sessions/${encodeURIComponent(id)}`);
|
|
347
347
|
if (current?.id === id) await forget();
|
|
348
|
-
}
|
|
348
|
+
},
|
|
349
|
+
// The core never claims a cart; `ThrottleProvider` overrides this with one
|
|
350
|
+
// that tracks its sign-in claim.
|
|
351
|
+
whenClaimSettled: () => Promise.resolve()
|
|
349
352
|
};
|
|
350
353
|
}
|
|
351
354
|
|
|
@@ -392,7 +395,18 @@ function createAccount(auth) {
|
|
|
392
395
|
pauseSubscription: (id) => call("POST", `/me/subscriptions/${seg(id)}/pause`),
|
|
393
396
|
resumeSubscription: (id) => call("POST", `/me/subscriptions/${seg(id)}/resume`),
|
|
394
397
|
updateSubscriptionShippingAddress: (id, address) => call("PATCH", `/me/subscriptions/${seg(id)}/shipping-address`, address),
|
|
395
|
-
claimCart: (cartId) => call("POST", `/me/carts/${seg(cartId)}/claim`)
|
|
398
|
+
claimCart: (cartId) => call("POST", `/me/carts/${seg(cartId)}/claim`),
|
|
399
|
+
async getOpenCart({ currency }) {
|
|
400
|
+
if (typeof currency !== "string" || !/^[A-Za-z]{3}$/.test(currency)) {
|
|
401
|
+
throw new TypeError("getOpenCart: currency must be a 3-letter ISO code");
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
return await call("GET", `/me/cart?currency=${currency.toUpperCase()}`);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
if (err instanceof ThrottleAuthError && err.statusCode === 404) return null;
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
396
410
|
};
|
|
397
411
|
}
|
|
398
412
|
|
|
@@ -444,4 +458,4 @@ export {
|
|
|
444
458
|
useAuthContext,
|
|
445
459
|
useAuth
|
|
446
460
|
};
|
|
447
|
-
//# sourceMappingURL=chunk-
|
|
461
|
+
//# sourceMappingURL=chunk-TDQC3LMQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/storage.ts","../src/request.ts","../src/session.ts","../src/client.ts","../src/account.ts","../src/react/context.tsx"],"sourcesContent":["export class ThrottleAuthError extends Error {\n readonly code: string;\n readonly statusCode: number;\n readonly retryAfter?: number;\n readonly details?: unknown;\n\n constructor(args: { code: string; message: string; statusCode: number; retryAfter?: number; details?: unknown }) {\n super(args.message);\n this.name = 'ThrottleAuthError';\n this.code = args.code;\n this.statusCode = args.statusCode;\n this.retryAfter = args.retryAfter;\n this.details = args.details;\n }\n}\n","import type { AuthStorage } from './types';\n\nexport type { AuthStorage };\n\nexport function memoryStorage(): AuthStorage {\n const map = new Map<string, string>();\n return {\n get: (k) => map.get(k) ?? null,\n set: (k, v) => void map.set(k, v),\n remove: (k) => void map.delete(k),\n };\n}\n\n/**\n * localStorage when it works, memory when it does not. Safari private mode,\n * hardened browser settings and server-side rendering all make localStorage\n * either absent or throwing; none of them may take the storefront down.\n */\nexport function localStorageAdapter(): AuthStorage {\n const fallback = memoryStorage();\n const ls = (): Storage | null => {\n try {\n return typeof localStorage !== 'undefined' ? localStorage : null;\n } catch {\n return null;\n }\n };\n return {\n get(k) {\n try {\n const v = ls()?.getItem(k);\n if (v != null) return v;\n } catch { /* fall through */ }\n return fallback.get(k);\n },\n set(k, v) {\n fallback.set(k, v);\n try { ls()?.setItem(k, v); } catch { /* memory-only this session */ }\n },\n remove(k) {\n fallback.remove(k);\n try { ls()?.removeItem(k); } catch { /* already gone */ }\n },\n };\n}\n","import { ThrottleAuthError } from './errors';\n\nexport interface RequestInput {\n method: string;\n path: string;\n baseUrl: string;\n publishableKey: string;\n accessToken?: string;\n stepUpToken?: string;\n body?: unknown;\n}\n\n/** Returns the UNWRAPPED `data` from the `{ data: … }` envelope. Throws ThrottleAuthError otherwise. */\nexport async function request(input: RequestInput): Promise<any> {\n const headers: Record<string, string> = { 'X-API-Key': input.publishableKey };\n if (input.body !== undefined) headers['Content-Type'] = 'application/json';\n if (input.accessToken) headers.Authorization = `Bearer ${input.accessToken}`;\n if (input.stepUpToken) headers['X-Step-Up'] = input.stepUpToken;\n\n const res = await fetch(`${input.baseUrl}/v1/storefront${input.path}`, {\n method: input.method,\n headers,\n ...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}),\n });\n\n const text = await res.text();\n let parsed: any = {};\n try {\n parsed = text ? JSON.parse(text) : {};\n } catch {\n // Non-JSON body — an HTML gateway page, say. Leave parsed empty so the\n // branch below still throws a typed error rather than a raw SyntaxError.\n }\n\n if (!res.ok) {\n const e = parsed?.error ?? {};\n // `Retry-After` is server-controlled but not trusted: Number('soon') is\n // NaN, and a NaN reaches the UI as \"wait NaN seconds\".\n const ra = Number(res.headers.get('retry-after'));\n throw new ThrottleAuthError({\n code: e.code ?? 'request_failed',\n message: e.message ?? `Request failed with ${res.status}`,\n statusCode: res.status,\n retryAfter: Number.isFinite(ra) && ra >= 0 ? ra : undefined,\n details: e.details,\n });\n }\n\n // `'data' in parsed`, not `parsed?.data ?? parsed`: a legitimate\n // `{ \"data\": null }` is nullish, and `??` would fall through and hand the\n // caller the whole envelope instead of the null the API actually sent.\n return parsed && typeof parsed === 'object' && 'data' in parsed ? parsed.data : (parsed ?? {});\n}\n","import type { Customer, Session } from './types';\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';\n\nexport interface AuthSnapshot {\n status: AuthStatus;\n customer: Customer | null;\n /**\n * False here does NOT only mean \"we emailed them and they have not clicked\".\n * While it is false the API may also be withholding pre-existing account\n * data behind the claim cutoff — empty order lists and a 403 on `PATCH /me`\n * are expected, not broken. See the plan header.\n */\n emailVerified: boolean;\n}\n\ntype Listener = (snapshot: AuthSnapshot) => void;\n\nexport class SessionState {\n #snapshot: AuthSnapshot = { status: 'loading', customer: null, emailVerified: false };\n #accessToken: string | null = null;\n #expiresAt: number | null = null;\n #listeners = new Set<Listener>();\n\n get(): AuthSnapshot { return this.#snapshot; }\n accessToken(): string | null { return this.#accessToken; }\n expiresAt(): number | null { return this.#expiresAt; }\n\n setSession(session: Session): void {\n this.#accessToken = session.accessToken;\n this.#expiresAt = Date.parse(session.expiresAt);\n this.#publish({\n status: 'authenticated',\n customer: session.customer,\n emailVerified: Boolean(session.customer?.emailVerified),\n });\n }\n\n setStatus(status: AuthStatus): void {\n this.#publish({ ...this.#snapshot, status });\n }\n\n clear(): void {\n this.#accessToken = null;\n this.#expiresAt = null;\n this.#publish({ status: 'unauthenticated', customer: null, emailVerified: false });\n }\n\n subscribe(fn: Listener): () => void {\n this.#listeners.add(fn);\n return () => { this.#listeners.delete(fn); };\n }\n\n #publish(next: AuthSnapshot): void {\n // Identity matters: React's useSyncExternalStore re-renders on reference\n // change, and a token refresh every ten minutes must not re-render the\n // whole account area when nothing visible changed.\n const same =\n next.status === this.#snapshot.status &&\n next.emailVerified === this.#snapshot.emailVerified &&\n customerEquals(next.customer, this.#snapshot.customer);\n if (same) return;\n this.#snapshot = next;\n for (const fn of [...this.#listeners]) {\n // A merchant's subscriber throwing must not stop the rest from hearing.\n try { fn(next); } catch { /* merchant callback */ }\n }\n }\n}\n\nfunction customerEquals(a: Customer | null, b: Customer | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]) as Set<keyof Customer>;\n for (const k of keys) if (a[k] !== b[k]) return false;\n return true;\n}\n","import { request } from './request';\nimport { ThrottleAuthError } from './errors';\nimport { SessionState, type AuthSnapshot } from './session';\nimport { localStorageAdapter, type AuthStorage } from './storage';\nimport type { Customer, Session, ThrottleAuthConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://api.usethrottle.dev';\n/** Refresh this far before expiry so ordinary calls rarely see a 401 at all. */\nconst REFRESH_SKEW_MS = 30_000;\n\nexport interface DeviceSession {\n id: string;\n userAgent: string | null;\n ipPrefix: string | null;\n createdAt: string;\n lastUsedAt: string;\n current: boolean;\n}\n\nexport interface ThrottleAuth {\n register(email: string, password: string): Promise<{ status: 'active' | 'pending' }>;\n signIn(email: string, password: string): Promise<void>;\n signOut(): Promise<void>;\n signOutEverywhere(): Promise<void>;\n restore(): Promise<void>;\n getSnapshot(): AuthSnapshot;\n /**\n * The current access token, refreshed first when it is about to expire\n * (single-flight, shared with every other caller), or null when signed out.\n * Rejects when a refresh fails without ending the session (a 5xx, a 429 or\n * a network error); the buyer stays signed in. `@usethrottle/catalog`\n * treats a rejection as signed out for that request.\n * Pass it to `@usethrottle/catalog`'s `customerToken`.\n */\n getAccessToken(): Promise<string | null>;\n subscribe(fn: (s: AuthSnapshot) => void): () => void;\n stepUp(password: string): Promise<void>;\n call<T = any>(method: string, path: string, body?: unknown): Promise<T>;\n verifyEmail(token: string): Promise<void>;\n resendVerification(): Promise<void>;\n forgotPassword(email: string): Promise<void>;\n resetPassword(token: string, password: string): Promise<void>;\n changePassword(password: string): Promise<void>;\n listSessions(): Promise<DeviceSession[]>;\n revokeSession(id: string): Promise<void>;\n /**\n * Resolves once any sign-in or registration claim in flight has settled.\n * `createThrottleAuth` always supplies it (it resolves at once there);\n * `ThrottleProvider` supplies one that tracks its cart claim. Optional so a\n * hand-made `ThrottleAuth` written against 0.11 still compiles.\n */\n whenClaimSettled?(): Promise<void>;\n}\n\nexport function createThrottleAuth(\n config: ThrottleAuthConfig,\n): ThrottleAuth & Required<Pick<ThrottleAuth, 'whenClaimSettled'>> {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const storage: AuthStorage = config.storage ?? localStorageAdapter();\n // Namespaced by key so two Throttle applications on one origin cannot read\n // each other's refresh token out of shared localStorage.\n const storageKey = `throttle_auth_rt:${config.publishableKey}`;\n const lockName = `throttle_auth_refresh:${config.publishableKey}`;\n\n const state = new SessionState();\n let refreshInFlight: Promise<void> | null = null;\n let stepUpToken: string | null = null;\n let stepUpExpiresAt = 0;\n\n const base = { baseUrl, publishableKey: config.publishableKey };\n\n async function persist(session: Session): Promise<void> {\n state.setSession(session);\n await storage.set(storageKey, session.refreshToken);\n }\n\n async function forget(): Promise<void> {\n stepUpToken = null;\n state.clear();\n await storage.remove(storageKey);\n }\n\n /**\n * Cross-tab single flight. Two tabs refreshing at once would each present the\n * same stored token; the second is a replay, and the server kills the family.\n * Web Locks serialises them; where it is unavailable the in-process guard\n * below still covers the common single-tab case.\n */\n function withLock<T>(fn: () => Promise<T>): Promise<T> {\n const locks = (globalThis as any).navigator?.locks;\n return locks?.request ? locks.request(lockName, fn) : fn();\n }\n\n async function doRefresh(): Promise<void> {\n await withLock(async () => {\n // Re-read INSIDE the lock: another tab may have rotated while we waited,\n // and presenting the token we read before the lock would be a replay.\n const refreshToken = await storage.get(storageKey);\n if (!refreshToken) {\n await forget();\n throw new ThrottleAuthError({ code: 'session_revoked', message: 'No session', statusCode: 401 });\n }\n try {\n const data = await request({ ...base, method: 'POST', path: '/auth/refresh', body: { refreshToken } });\n await persist(data as Session);\n } catch (err) {\n // Drop the stored token ONLY when the server actually refused the\n // session: 401 session_revoked, or 403 environment_mismatch. Those are\n // the two the refresh route returns, and there is nothing left to\n // retry with in either case.\n //\n // Everything else keeps the token. `request()` throws a\n // ThrottleAuthError for EVERY non-2xx, so testing the error class\n // alone also caught a 502 with an HTML gateway body — a buyer whose\n // tab refreshed during a rolling deploy was permanently signed out\n // client-side even though their session was never invalidated. A 429\n // is the same shape: \"try later\", not \"you are logged out\". And a\n // fetch() that rejects outright never produces a ThrottleAuthError,\n // so it was already handled.\n const refused =\n err instanceof ThrottleAuthError && (err.statusCode === 401 || err.statusCode === 403);\n if (refused) await forget();\n throw err;\n }\n });\n }\n\n function refresh(): Promise<void> {\n if (!refreshInFlight) {\n refreshInFlight = doRefresh().finally(() => { refreshInFlight = null; });\n }\n return refreshInFlight;\n }\n\n async function accessToken(): Promise<string | null> {\n const expires = state.expiresAt();\n if (state.accessToken() && expires && expires - Date.now() > REFRESH_SKEW_MS) {\n return state.accessToken();\n }\n if (await storage.get(storageKey)) {\n await refresh();\n return state.accessToken();\n }\n return state.accessToken();\n }\n\n async function ensureStepUp(): Promise<boolean> {\n if (stepUpToken && stepUpExpiresAt > Date.now()) return true;\n if (!config.onStepUpRequired) return false;\n const password = await config.onStepUpRequired();\n if (!password) return false;\n await stepUp(password);\n return true;\n }\n\n async function stepUp(password: string): Promise<void> {\n const data = await request({\n ...base, method: 'POST', path: '/auth/step-up',\n accessToken: await accessToken() ?? undefined, body: { password },\n });\n stepUpToken = data.stepUpToken;\n stepUpExpiresAt = Date.now() + (Number(data.expiresIn) || 300) * 1000 - 5_000;\n }\n\n async function call<T = any>(method: string, path: string, body?: unknown): Promise<T> {\n let retriedAuth = false;\n let retriedStepUp = false;\n // Only true on the retry that follows a `step_up_required` 403 — never\n // attached speculatively. Sending an authorization-grade header on every\n // request once minted meant it rode along on unrelated calls too, and\n // cross-origin each of those then failed CORS preflight. The cost is one\n // extra round trip per gated call, which is how the server signals\n // step-up in the first place.\n let sendStepUp = false;\n\n for (;;) {\n const token = await accessToken();\n try {\n return await request({\n ...base, method, path, body,\n accessToken: token ?? undefined,\n stepUpToken: sendStepUp ? (stepUpToken ?? undefined) : undefined,\n });\n } catch (err) {\n const e = err as ThrottleAuthError;\n // `session_revoked` belongs here too. The server returns it for a\n // stale session EPOCH as well as for a genuinely dead session, and a\n // changed password bumps the epoch while deliberately sparing the\n // current session — so one refresh recovers it. When the session\n // really is dead the refresh fails and forget() runs, which is the\n // same outcome, one request later.\n const expired = e.statusCode === 401 &&\n (e.code === 'unauthorized' || e.code === 'token_expired' || e.code === 'session_revoked');\n if (expired && !retriedAuth) {\n retriedAuth = true;\n await refresh(); // shared — ten callers here produce ONE request\n continue;\n }\n if (e.code === 'step_up_required' && !retriedStepUp) {\n retriedStepUp = true;\n // ensureStepUp() reuses the cached token when it's still within its\n // 300s validity (no re-prompt); it was never sent above, so this is\n // the first the server has seen of it for this call.\n if (await ensureStepUp()) { sendStepUp = true; continue; }\n }\n throw err;\n }\n }\n }\n\n return {\n async register(email, password) {\n const data = await request({ ...base, method: 'POST', path: '/auth/register', body: { email, password } });\n if (data?.status === 'pending') return { status: 'pending' };\n await persist(data as Session);\n return { status: 'active' };\n },\n\n async signIn(email, password) {\n const data = await request({ ...base, method: 'POST', path: '/auth/login', body: { email, password } });\n await persist(data as Session);\n },\n\n async signOut() {\n try { await call('POST', '/auth/logout'); } catch { /* local state still goes */ }\n await forget();\n },\n\n async signOutEverywhere() {\n try { await call('POST', '/auth/logout-all'); } catch { /* local state still goes */ }\n await forget();\n },\n\n async restore() {\n if (!(await storage.get(storageKey))) { state.setStatus('unauthenticated'); return; }\n try { await refresh(); } catch { /* forget() already ran */ }\n },\n\n getSnapshot: () => state.get(),\n\n async getAccessToken() {\n try {\n return await accessToken();\n } catch (err) {\n // A refused refresh already ran forget(): the buyer is signed out.\n // Anything else (a 502, a network error) keeps the session; rethrow.\n if (state.get().status === 'unauthenticated') return null;\n throw err;\n }\n },\n\n subscribe: (fn) => state.subscribe(fn),\n stepUp,\n call,\n\n async verifyEmail(token) {\n await request({ ...base, method: 'POST', path: '/auth/verify-email', body: { token } });\n // `ver` is a claim baked into the access token, and the claim cutoff is\n // read from the session row. Without a new token the buyer stays gated —\n // and their own order history stays hidden — for the rest of this\n // token's ten minutes.\n if (await storage.get(storageKey)) {\n try { await refresh(); } catch { /* verification still succeeded */ }\n }\n },\n\n async resendVerification() {\n await call('POST', '/auth/verify-email/resend');\n },\n\n async forgotPassword(email) {\n // Always 202, whether or not the address has an account. Do not infer\n // anything from it and do not tell the buyer whether it existed.\n await request({ ...base, method: 'POST', path: '/auth/forgot-password', body: { email } });\n },\n\n async resetPassword(token, password) {\n await request({ ...base, method: 'POST', path: '/auth/reset-password', body: { token, password } });\n // The server revokes every session on reset, this one included.\n await forget();\n },\n\n async changePassword(password) {\n await call('POST', '/me/change-password', { password });\n // The change bumps the session epoch. This session was deliberately\n // spared, but the access token in hand now carries a stale epoch and the\n // next write would 401. Rotate now rather than on the buyer's next click\n // — and do not let a failed rotation report a change that already\n // succeeded server-side as a failure.\n try { await refresh(); } catch { /* the password change already succeeded */ }\n },\n\n async listSessions() {\n return call<DeviceSession[]>('GET', '/me/sessions');\n },\n\n async revokeSession(id) {\n const mine = await call<DeviceSession[]>('GET', '/me/sessions');\n const current = mine.find((s) => s.current);\n await call('DELETE', `/me/sessions/${encodeURIComponent(id)}`);\n if (current?.id === id) await forget();\n },\n\n // The core never claims a cart; `ThrottleProvider` overrides this with one\n // that tracks its sign-in claim.\n whenClaimSettled: () => Promise.resolve(),\n };\n}\n\nexport type { AuthSnapshot, Customer, Session, ThrottleAuthConfig };\n","import type { ThrottleAuth } from './client';\nimport { ThrottleAuthError } from './errors';\nimport type {\n Address, AddressInput, Customer, Invoice, OpenCart, Order, OrderDetail,\n PaymentMethod, ProfilePatch, Subscription, SubscriptionShippingAddress,\n} from './types';\n\nexport interface Page { cursor?: string; limit?: number }\n\n/** What the payment widget needs to render a card form. Passed through from\n * the processor — `buyerExternalIdentifier` in particular must reach the\n * widget unchanged, because the session token pins it and the processor\n * refuses a mismatch by rendering nothing at all. */\nexport interface CardSetupSession {\n embedToken: string;\n checkoutSessionId: string;\n amount: number;\n currency: string;\n merchantAccountId?: string;\n environment?: string;\n buyerExternalIdentifier: string;\n country: string;\n /** A ready-made page that hosts the card form, if you would rather redirect\n * or iframe than embed the widget yourself. */\n hostedUrl: string;\n embedUrl: string;\n}\n\nexport interface ThrottleAccount {\n getProfile(): Promise<Customer>;\n updateProfile(patch: ProfilePatch): Promise<Customer>;\n listAddresses(): Promise<Address[]>;\n createAddress(input: AddressInput): Promise<Address>;\n updateAddress(id: string, patch: Partial<AddressInput>): Promise<Address>;\n deleteAddress(id: string): Promise<void>;\n listPaymentMethods(): Promise<PaymentMethod[]>;\n setDefaultPaymentMethod(id: string): Promise<PaymentMethod>;\n deletePaymentMethod(id: string): Promise<void>;\n /** Opens a processor-hosted session the buyer types their card into. The\n * returned values are handed straight to the payment widget. */\n startCardSetup(): Promise<CardSetupSession>;\n /** Records the card the widget just vaulted, identified by the transaction\n * the widget returns. Safe to call twice with the same id. */\n confirmCardSetup(transactionId: string): Promise<PaymentMethod>;\n listOrders(page?: Page): Promise<Order[]>;\n getOrder(id: string): Promise<OrderDetail>;\n listInvoices(page?: Page): Promise<Invoice[]>;\n getInvoice(id: string): Promise<Invoice>;\n /** A 600-second signed URL. Fetch it on click — it expires. */\n getInvoicePdfUrl(id: string): Promise<{ url: string }>;\n listSubscriptions(page?: Page): Promise<Subscription[]>;\n getSubscription(id: string): Promise<Subscription>;\n /**\n * Voids an unpaid order. Refused once money has been captured — that is a\n * return, not a cancellation, and only the merchant may move money back.\n */\n cancelOrder(id: string, opts?: { reason?: string }): Promise<Order>;\n /** Opens an RMA the merchant approves. Changes no order state by itself. */\n requestOrderReturn(\n id: string,\n input: { items: Array<{ lineItemId: string; quantity: number }>; reason?: string },\n ): Promise<{ id: string; status: string }>;\n cancelSubscription(id: string, opts?: { atPeriodEnd?: boolean }): Promise<Subscription>;\n pauseSubscription(id: string): Promise<Subscription>;\n resumeSubscription(id: string): Promise<Subscription>;\n /**\n * Where a physical subscription's box goes, from the next renewal. Needs a\n * fresh sign-in (step-up), like cancelling. A subscription that ships\n * nothing answers `subscription_not_physical`.\n */\n updateSubscriptionShippingAddress(id: string, address: SubscriptionShippingAddress): Promise<Subscription>;\n claimCart(cartId: string): Promise<{ cartId: string; customerId: string }>;\n /**\n * The buyer's newest open or idle-abandoned cart in `currency` (see `status`), or null. Answers null as well\n * when the API predates the route (404). Always present on the account that\n * `createAccount` and `ThrottleProvider` build; optional here so a hand-made\n * `ThrottleAccount` written against 0.11 still compiles.\n */\n getOpenCart?(input: { currency: string }): Promise<OpenCart | null>;\n}\n\nconst seg = (v: string) => encodeURIComponent(v);\n\nfunction query(page?: Page): string {\n const params = new URLSearchParams();\n if (page?.cursor) params.set('cursor', page.cursor);\n if (page?.limit != null) params.set('limit', String(page.limit));\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\nexport function createAccount(\n auth: ThrottleAuth,\n): ThrottleAccount & Required<Pick<ThrottleAccount, 'getOpenCart'>> {\n const { call } = auth;\n return {\n getProfile: () => call('GET', '/me'),\n updateProfile: (patch) => call('PATCH', '/me', patch),\n\n listAddresses: () => call('GET', '/me/addresses'),\n createAddress: (input) => call('POST', '/me/addresses', input),\n updateAddress: (id, patch) => call('PATCH', `/me/addresses/${seg(id)}`, patch),\n deleteAddress: (id) => call('DELETE', `/me/addresses/${seg(id)}`),\n\n listPaymentMethods: () => call('GET', '/me/payment-methods'),\n // The platform has no \"change this subscription's card\" capability, so\n // this is how a buyer changes what they are billed on: move the default.\n setDefaultPaymentMethod: (id) => call('PATCH', `/me/payment-methods/${seg(id)}`, { isDefault: true }),\n deletePaymentMethod: (id) => call('DELETE', `/me/payment-methods/${seg(id)}`),\n\n // Saving a card is two calls because the card number never reaches us: the\n // first mints a processor-hosted embed session, the buyer types their card\n // into that, and the second records what the processor vaulted. Everything\n // about the saved card is read back from the processor, so nothing here\n // trusts what the browser claims the card was.\n startCardSetup: () => call('POST', '/me/payment-methods/embed-token'),\n confirmCardSetup: (transactionId) =>\n call('POST', '/me/payment-methods/confirm', { transactionId }),\n\n listOrders: (page) => call('GET', `/me/orders${query(page)}`),\n getOrder: (id) => call('GET', `/me/orders/${seg(id)}`),\n listInvoices: (page) => call('GET', `/me/invoices${query(page)}`),\n getInvoice: (id) => call('GET', `/me/invoices/${seg(id)}`),\n getInvoicePdfUrl: (id) => call('GET', `/me/invoices/${seg(id)}/pdf`),\n listSubscriptions: (page) => call('GET', `/me/subscriptions${query(page)}`),\n getSubscription: (id) => call('GET', `/me/subscriptions/${seg(id)}`),\n\n cancelOrder: (id, opts) => call('POST', `/me/orders/${seg(id)}/cancel`, opts),\n requestOrderReturn: (id, input) => call('POST', `/me/orders/${seg(id)}/return-request`, input),\n cancelSubscription: (id, opts) => call('POST', `/me/subscriptions/${seg(id)}/cancel`, opts),\n pauseSubscription: (id) => call('POST', `/me/subscriptions/${seg(id)}/pause`),\n resumeSubscription: (id) => call('POST', `/me/subscriptions/${seg(id)}/resume`),\n updateSubscriptionShippingAddress: (id, address) =>\n call('PATCH', `/me/subscriptions/${seg(id)}/shipping-address`, address),\n\n claimCart: (cartId) => call('POST', `/me/carts/${seg(cartId)}/claim`),\n\n async getOpenCart({ currency }) {\n if (typeof currency !== 'string' || !/^[A-Za-z]{3}$/.test(currency)) {\n throw new TypeError('getOpenCart: currency must be a 3-letter ISO code');\n }\n try {\n return await call<OpenCart | null>('GET', `/me/cart?currency=${currency.toUpperCase()}`);\n } catch (err) {\n // An API that predates the route: no cart to follow, not a failure.\n if (err instanceof ThrottleAuthError && err.statusCode === 404) return null;\n throw err;\n }\n },\n };\n}\n","import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport type { ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../client';\nimport { createAccount, type ThrottleAccount } from '../account';\nimport type { AuthSnapshot } from '../session';\nimport type { ThrottleAuthConfig } from '../types';\n\nexport interface ThrottleAuthProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n /** Inject a pre-built client. Tests use it; merchants sharing one client across roots may too. */\n auth?: ThrottleAuth;\n children: ReactNode;\n}\n\ninterface ContextValue { auth: ThrottleAuth; account: ThrottleAccount }\n\nconst Ctx = createContext<ContextValue | null>(null);\n\nexport function ThrottleAuthProvider(props: ThrottleAuthProviderProps) {\n const { auth: injected, publishableKey, baseUrl, storage, onStepUpRequired, children } = props;\n\n // `storage` and `onStepUpRequired` are deliberately NOT dependencies: they are\n // read once at mount. A merchant passing `storage={memoryStorage()}` inline —\n // which the README suggests — would otherwise get a new client on every parent\n // render, and with it a fresh empty SessionState that reads as signed out.\n const value = useMemo<ContextValue>(() => {\n const auth = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired });\n return { auth, account: createAccount(auth) };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const restored = useRef<ThrottleAuth | null>(null);\n useEffect(() => {\n // Once per client. StrictMode runs effects twice in development, and a\n // second restore() would present the refresh token that the first one\n // just rotated away.\n if (restored.current === value.auth) return;\n restored.current = value.auth;\n void value.auth.restore();\n }, [value]);\n\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\nfunction useCtx(): ContextValue {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useAuth must be used inside a <ThrottleAuthProvider>');\n return ctx;\n}\n\nexport function useAuthContext(): ContextValue { return useCtx(); }\n\nexport type UseAuthResult = AuthSnapshot & {\n isLoading: boolean;\n isAuthenticated: boolean;\n auth: ThrottleAuth;\n account: ThrottleAccount;\n};\n\nexport function useAuth(): UseAuthResult {\n const { auth, account } = useCtx();\n const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);\n return {\n ...snapshot,\n isLoading: snapshot.status === 'loading',\n isAuthenticated: snapshot.status === 'authenticated',\n auth,\n account,\n };\n}\n"],"mappings":";;;AAAO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAAqG;AAC/G,UAAM,KAAK,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,SAAK,aAAa,KAAK;AACvB,SAAK,aAAa,KAAK;AACvB,SAAK,UAAU,KAAK;AAAA,EACtB;AACF;;;ACVO,SAAS,gBAA6B;AAC3C,QAAM,MAAM,oBAAI,IAAoB;AACpC,SAAO;AAAA,IACL,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK;AAAA,IAC1B,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC;AAAA,IAChC,QAAQ,CAAC,MAAM,KAAK,IAAI,OAAO,CAAC;AAAA,EAClC;AACF;AAOO,SAAS,sBAAmC;AACjD,QAAM,WAAW,cAAc;AAC/B,QAAM,KAAK,MAAsB;AAC/B,QAAI;AACF,aAAO,OAAO,iBAAiB,cAAc,eAAe;AAAA,IAC9D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AAAA,IACL,IAAI,GAAG;AACL,UAAI;AACF,cAAM,IAAI,GAAG,GAAG,QAAQ,CAAC;AACzB,YAAI,KAAK,KAAM,QAAO;AAAA,MACxB,QAAQ;AAAA,MAAqB;AAC7B,aAAO,SAAS,IAAI,CAAC;AAAA,IACvB;AAAA,IACA,IAAI,GAAG,GAAG;AACR,eAAS,IAAI,GAAG,CAAC;AACjB,UAAI;AAAE,WAAG,GAAG,QAAQ,GAAG,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAiC;AAAA,IACtE;AAAA,IACA,OAAO,GAAG;AACR,eAAS,OAAO,CAAC;AACjB,UAAI;AAAE,WAAG,GAAG,WAAW,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAqB;AAAA,IAC1D;AAAA,EACF;AACF;;;AC/BA,eAAsB,QAAQ,OAAmC;AAC/D,QAAM,UAAkC,EAAE,aAAa,MAAM,eAAe;AAC5E,MAAI,MAAM,SAAS,OAAW,SAAQ,cAAc,IAAI;AACxD,MAAI,MAAM,YAAa,SAAQ,gBAAgB,UAAU,MAAM,WAAW;AAC1E,MAAI,MAAM,YAAa,SAAQ,WAAW,IAAI,MAAM;AAEpD,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,iBAAiB,MAAM,IAAI,IAAI;AAAA,IACrE,QAAQ,MAAM;AAAA,IACd;AAAA,IACA,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,MAAM,IAAI,EAAE,IAAI,CAAC;AAAA,EACzE,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,SAAc,CAAC;AACnB,MAAI;AACF,aAAS,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,EACtC,QAAQ;AAAA,EAGR;AAEA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,QAAQ,SAAS,CAAC;AAG5B,UAAM,KAAK,OAAO,IAAI,QAAQ,IAAI,aAAa,CAAC;AAChD,UAAM,IAAI,kBAAkB;AAAA,MAC1B,MAAM,EAAE,QAAQ;AAAA,MAChB,SAAS,EAAE,WAAW,uBAAuB,IAAI,MAAM;AAAA,MACvD,YAAY,IAAI;AAAA,MAChB,YAAY,OAAO,SAAS,EAAE,KAAK,MAAM,IAAI,KAAK;AAAA,MAClD,SAAS,EAAE;AAAA,IACb,CAAC;AAAA,EACH;AAKA,SAAO,UAAU,OAAO,WAAW,YAAY,UAAU,SAAS,OAAO,OAAQ,UAAU,CAAC;AAC9F;;;AClCO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAA0B,EAAE,QAAQ,WAAW,UAAU,MAAM,eAAe,MAAM;AAAA,EACpF,eAA8B;AAAA,EAC9B,aAA4B;AAAA,EAC5B,aAAa,oBAAI,IAAc;AAAA,EAE/B,MAAoB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EAC7C,cAA6B;AAAE,WAAO,KAAK;AAAA,EAAc;AAAA,EACzD,YAA2B;AAAE,WAAO,KAAK;AAAA,EAAY;AAAA,EAErD,WAAW,SAAwB;AACjC,SAAK,eAAe,QAAQ;AAC5B,SAAK,aAAa,KAAK,MAAM,QAAQ,SAAS;AAC9C,SAAK,SAAS;AAAA,MACZ,QAAQ;AAAA,MACR,UAAU,QAAQ;AAAA,MAClB,eAAe,QAAQ,QAAQ,UAAU,aAAa;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EAEA,UAAU,QAA0B;AAClC,SAAK,SAAS,EAAE,GAAG,KAAK,WAAW,OAAO,CAAC;AAAA,EAC7C;AAAA,EAEA,QAAc;AACZ,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,SAAS,EAAE,QAAQ,mBAAmB,UAAU,MAAM,eAAe,MAAM,CAAC;AAAA,EACnF;AAAA,EAEA,UAAU,IAA0B;AAClC,SAAK,WAAW,IAAI,EAAE;AACtB,WAAO,MAAM;AAAE,WAAK,WAAW,OAAO,EAAE;AAAA,IAAG;AAAA,EAC7C;AAAA,EAEA,SAAS,MAA0B;AAIjC,UAAM,OACJ,KAAK,WAAW,KAAK,UAAU,UAC/B,KAAK,kBAAkB,KAAK,UAAU,iBACtC,eAAe,KAAK,UAAU,KAAK,UAAU,QAAQ;AACvD,QAAI,KAAM;AACV,SAAK,YAAY;AACjB,eAAW,MAAM,CAAC,GAAG,KAAK,UAAU,GAAG;AAErC,UAAI;AAAE,WAAG,IAAI;AAAA,MAAG,QAAQ;AAAA,MAA0B;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,eAAe,GAAoB,GAA6B;AACvE,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,OAAO,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC;AAC3D,aAAW,KAAK,KAAM,KAAI,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG,QAAO;AAChD,SAAO;AACT;;;ACtEA,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AA8CjB,SAAS,mBACd,QACiE;AACjE,QAAM,WAAW,OAAO,WAAW,kBAAkB,QAAQ,OAAO,EAAE;AACtE,QAAM,UAAuB,OAAO,WAAW,oBAAoB;AAGnE,QAAM,aAAa,oBAAoB,OAAO,cAAc;AAC5D,QAAM,WAAW,yBAAyB,OAAO,cAAc;AAE/D,QAAM,QAAQ,IAAI,aAAa;AAC/B,MAAI,kBAAwC;AAC5C,MAAI,cAA6B;AACjC,MAAI,kBAAkB;AAEtB,QAAM,OAAO,EAAE,SAAS,gBAAgB,OAAO,eAAe;AAE9D,iBAAe,QAAQ,SAAiC;AACtD,UAAM,WAAW,OAAO;AACxB,UAAM,QAAQ,IAAI,YAAY,QAAQ,YAAY;AAAA,EACpD;AAEA,iBAAe,SAAwB;AACrC,kBAAc;AACd,UAAM,MAAM;AACZ,UAAM,QAAQ,OAAO,UAAU;AAAA,EACjC;AAQA,WAAS,SAAY,IAAkC;AACrD,UAAM,QAAS,WAAmB,WAAW;AAC7C,WAAO,OAAO,UAAU,MAAM,QAAQ,UAAU,EAAE,IAAI,GAAG;AAAA,EAC3D;AAEA,iBAAe,YAA2B;AACxC,UAAM,SAAS,YAAY;AAGzB,YAAM,eAAe,MAAM,QAAQ,IAAI,UAAU;AACjD,UAAI,CAAC,cAAc;AACjB,cAAM,OAAO;AACb,cAAM,IAAI,kBAAkB,EAAE,MAAM,mBAAmB,SAAS,cAAc,YAAY,IAAI,CAAC;AAAA,MACjG;AACA,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,iBAAiB,MAAM,EAAE,aAAa,EAAE,CAAC;AACrG,cAAM,QAAQ,IAAe;AAAA,MAC/B,SAAS,KAAK;AAcZ,cAAM,UACJ,eAAe,sBAAsB,IAAI,eAAe,OAAO,IAAI,eAAe;AACpF,YAAI,QAAS,OAAM,OAAO;AAC1B,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,UAAyB;AAChC,QAAI,CAAC,iBAAiB;AACpB,wBAAkB,UAAU,EAAE,QAAQ,MAAM;AAAE,0BAAkB;AAAA,MAAM,CAAC;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,cAAsC;AACnD,UAAM,UAAU,MAAM,UAAU;AAChC,QAAI,MAAM,YAAY,KAAK,WAAW,UAAU,KAAK,IAAI,IAAI,iBAAiB;AAC5E,aAAO,MAAM,YAAY;AAAA,IAC3B;AACA,QAAI,MAAM,QAAQ,IAAI,UAAU,GAAG;AACjC,YAAM,QAAQ;AACd,aAAO,MAAM,YAAY;AAAA,IAC3B;AACA,WAAO,MAAM,YAAY;AAAA,EAC3B;AAEA,iBAAe,eAAiC;AAC9C,QAAI,eAAe,kBAAkB,KAAK,IAAI,EAAG,QAAO;AACxD,QAAI,CAAC,OAAO,iBAAkB,QAAO;AACrC,UAAM,WAAW,MAAM,OAAO,iBAAiB;AAC/C,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,OAAO,QAAQ;AACrB,WAAO;AAAA,EACT;AAEA,iBAAe,OAAO,UAAiC;AACrD,UAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,GAAG;AAAA,MAAM,QAAQ;AAAA,MAAQ,MAAM;AAAA,MAC/B,aAAa,MAAM,YAAY,KAAK;AAAA,MAAW,MAAM,EAAE,SAAS;AAAA,IAClE,CAAC;AACD,kBAAc,KAAK;AACnB,sBAAkB,KAAK,IAAI,KAAK,OAAO,KAAK,SAAS,KAAK,OAAO,MAAO;AAAA,EAC1E;AAEA,iBAAe,KAAc,QAAgB,MAAc,MAA4B;AACrF,QAAI,cAAc;AAClB,QAAI,gBAAgB;AAOpB,QAAI,aAAa;AAEjB,eAAS;AACP,YAAM,QAAQ,MAAM,YAAY;AAChC,UAAI;AACF,eAAO,MAAM,QAAQ;AAAA,UACnB,GAAG;AAAA,UAAM;AAAA,UAAQ;AAAA,UAAM;AAAA,UACvB,aAAa,SAAS;AAAA,UACtB,aAAa,aAAc,eAAe,SAAa;AAAA,QACzD,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,IAAI;AAOV,cAAM,UAAU,EAAE,eAAe,QAC9B,EAAE,SAAS,kBAAkB,EAAE,SAAS,mBAAmB,EAAE,SAAS;AACzE,YAAI,WAAW,CAAC,aAAa;AAC3B,wBAAc;AACd,gBAAM,QAAQ;AACd;AAAA,QACF;AACA,YAAI,EAAE,SAAS,sBAAsB,CAAC,eAAe;AACnD,0BAAgB;AAIhB,cAAI,MAAM,aAAa,GAAG;AAAE,yBAAa;AAAM;AAAA,UAAU;AAAA,QAC3D;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,SAAS,OAAO,UAAU;AAC9B,YAAM,OAAO,MAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,kBAAkB,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC;AACzG,UAAI,MAAM,WAAW,UAAW,QAAO,EAAE,QAAQ,UAAU;AAC3D,YAAM,QAAQ,IAAe;AAC7B,aAAO,EAAE,QAAQ,SAAS;AAAA,IAC5B;AAAA,IAEA,MAAM,OAAO,OAAO,UAAU;AAC5B,YAAM,OAAO,MAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,eAAe,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC;AACtG,YAAM,QAAQ,IAAe;AAAA,IAC/B;AAAA,IAEA,MAAM,UAAU;AACd,UAAI;AAAE,cAAM,KAAK,QAAQ,cAAc;AAAA,MAAG,QAAQ;AAAA,MAA+B;AACjF,YAAM,OAAO;AAAA,IACf;AAAA,IAEA,MAAM,oBAAoB;AACxB,UAAI;AAAE,cAAM,KAAK,QAAQ,kBAAkB;AAAA,MAAG,QAAQ;AAAA,MAA+B;AACrF,YAAM,OAAO;AAAA,IACf;AAAA,IAEA,MAAM,UAAU;AACd,UAAI,CAAE,MAAM,QAAQ,IAAI,UAAU,GAAI;AAAE,cAAM,UAAU,iBAAiB;AAAG;AAAA,MAAQ;AACpF,UAAI;AAAE,cAAM,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAA6B;AAAA,IAC9D;AAAA,IAEA,aAAa,MAAM,MAAM,IAAI;AAAA,IAE7B,MAAM,iBAAiB;AACrB,UAAI;AACF,eAAO,MAAM,YAAY;AAAA,MAC3B,SAAS,KAAK;AAGZ,YAAI,MAAM,IAAI,EAAE,WAAW,kBAAmB,QAAO;AACrD,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,WAAW,CAAC,OAAO,MAAM,UAAU,EAAE;AAAA,IACrC;AAAA,IACA;AAAA,IAEA,MAAM,YAAY,OAAO;AACvB,YAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,sBAAsB,MAAM,EAAE,MAAM,EAAE,CAAC;AAKtF,UAAI,MAAM,QAAQ,IAAI,UAAU,GAAG;AACjC,YAAI;AAAE,gBAAM,QAAQ;AAAA,QAAG,QAAQ;AAAA,QAAqC;AAAA,MACtE;AAAA,IACF;AAAA,IAEA,MAAM,qBAAqB;AACzB,YAAM,KAAK,QAAQ,2BAA2B;AAAA,IAChD;AAAA,IAEA,MAAM,eAAe,OAAO;AAG1B,YAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,yBAAyB,MAAM,EAAE,MAAM,EAAE,CAAC;AAAA,IAC3F;AAAA,IAEA,MAAM,cAAc,OAAO,UAAU;AACnC,YAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,wBAAwB,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC;AAElG,YAAM,OAAO;AAAA,IACf;AAAA,IAEA,MAAM,eAAe,UAAU;AAC7B,YAAM,KAAK,QAAQ,uBAAuB,EAAE,SAAS,CAAC;AAMtD,UAAI;AAAE,cAAM,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAA8C;AAAA,IAC/E;AAAA,IAEA,MAAM,eAAe;AACnB,aAAO,KAAsB,OAAO,cAAc;AAAA,IACpD;AAAA,IAEA,MAAM,cAAc,IAAI;AACtB,YAAM,OAAO,MAAM,KAAsB,OAAO,cAAc;AAC9D,YAAM,UAAU,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO;AAC1C,YAAM,KAAK,UAAU,gBAAgB,mBAAmB,EAAE,CAAC,EAAE;AAC7D,UAAI,SAAS,OAAO,GAAI,OAAM,OAAO;AAAA,IACvC;AAAA;AAAA;AAAA,IAIA,kBAAkB,MAAM,QAAQ,QAAQ;AAAA,EAC1C;AACF;;;AClOA,IAAM,MAAM,CAAC,MAAc,mBAAmB,CAAC;AAE/C,SAAS,MAAM,MAAqB;AAClC,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,MAAM,OAAQ,QAAO,IAAI,UAAU,KAAK,MAAM;AAClD,MAAI,MAAM,SAAS,KAAM,QAAO,IAAI,SAAS,OAAO,KAAK,KAAK,CAAC;AAC/D,QAAM,IAAI,OAAO,SAAS;AAC1B,SAAO,IAAI,IAAI,CAAC,KAAK;AACvB;AAEO,SAAS,cACd,MACkE;AAClE,QAAM,EAAE,KAAK,IAAI;AACjB,SAAO;AAAA,IACL,YAAY,MAAM,KAAK,OAAO,KAAK;AAAA,IACnC,eAAe,CAAC,UAAU,KAAK,SAAS,OAAO,KAAK;AAAA,IAEpD,eAAe,MAAM,KAAK,OAAO,eAAe;AAAA,IAChD,eAAe,CAAC,UAAU,KAAK,QAAQ,iBAAiB,KAAK;AAAA,IAC7D,eAAe,CAAC,IAAI,UAAU,KAAK,SAAS,iBAAiB,IAAI,EAAE,CAAC,IAAI,KAAK;AAAA,IAC7E,eAAe,CAAC,OAAO,KAAK,UAAU,iBAAiB,IAAI,EAAE,CAAC,EAAE;AAAA,IAEhE,oBAAoB,MAAM,KAAK,OAAO,qBAAqB;AAAA;AAAA;AAAA,IAG3D,yBAAyB,CAAC,OAAO,KAAK,SAAS,uBAAuB,IAAI,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,CAAC;AAAA,IACpG,qBAAqB,CAAC,OAAO,KAAK,UAAU,uBAAuB,IAAI,EAAE,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAO5E,gBAAgB,MAAM,KAAK,QAAQ,iCAAiC;AAAA,IACpE,kBAAkB,CAAC,kBACjB,KAAK,QAAQ,+BAA+B,EAAE,cAAc,CAAC;AAAA,IAE/D,YAAY,CAAC,SAAS,KAAK,OAAO,aAAa,MAAM,IAAI,CAAC,EAAE;AAAA,IAC5D,UAAU,CAAC,OAAO,KAAK,OAAO,cAAc,IAAI,EAAE,CAAC,EAAE;AAAA,IACrD,cAAc,CAAC,SAAS,KAAK,OAAO,eAAe,MAAM,IAAI,CAAC,EAAE;AAAA,IAChE,YAAY,CAAC,OAAO,KAAK,OAAO,gBAAgB,IAAI,EAAE,CAAC,EAAE;AAAA,IACzD,kBAAkB,CAAC,OAAO,KAAK,OAAO,gBAAgB,IAAI,EAAE,CAAC,MAAM;AAAA,IACnE,mBAAmB,CAAC,SAAS,KAAK,OAAO,oBAAoB,MAAM,IAAI,CAAC,EAAE;AAAA,IAC1E,iBAAiB,CAAC,OAAO,KAAK,OAAO,qBAAqB,IAAI,EAAE,CAAC,EAAE;AAAA,IAEnE,aAAa,CAAC,IAAI,SAAS,KAAK,QAAQ,cAAc,IAAI,EAAE,CAAC,WAAW,IAAI;AAAA,IAC5E,oBAAoB,CAAC,IAAI,UAAU,KAAK,QAAQ,cAAc,IAAI,EAAE,CAAC,mBAAmB,KAAK;AAAA,IAC7F,oBAAoB,CAAC,IAAI,SAAS,KAAK,QAAQ,qBAAqB,IAAI,EAAE,CAAC,WAAW,IAAI;AAAA,IAC1F,mBAAmB,CAAC,OAAO,KAAK,QAAQ,qBAAqB,IAAI,EAAE,CAAC,QAAQ;AAAA,IAC5E,oBAAoB,CAAC,OAAO,KAAK,QAAQ,qBAAqB,IAAI,EAAE,CAAC,SAAS;AAAA,IAC9E,mCAAmC,CAAC,IAAI,YACtC,KAAK,SAAS,qBAAqB,IAAI,EAAE,CAAC,qBAAqB,OAAO;AAAA,IAExE,WAAW,CAAC,WAAW,KAAK,QAAQ,aAAa,IAAI,MAAM,CAAC,QAAQ;AAAA,IAEpE,MAAM,YAAY,EAAE,SAAS,GAAG;AAC9B,UAAI,OAAO,aAAa,YAAY,CAAC,gBAAgB,KAAK,QAAQ,GAAG;AACnE,cAAM,IAAI,UAAU,mDAAmD;AAAA,MACzE;AACA,UAAI;AACF,eAAO,MAAM,KAAsB,OAAO,qBAAqB,SAAS,YAAY,CAAC,EAAE;AAAA,MACzF,SAAS,KAAK;AAEZ,YAAI,eAAe,qBAAqB,IAAI,eAAe,IAAK,QAAO;AACvE,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;ACtJA,SAAS,eAAe,YAAY,WAAW,SAAS,QAAQ,4BAA4B;AAyCnF;AAzBT,IAAM,MAAM,cAAmC,IAAI;AAE5C,SAAS,qBAAqB,OAAkC;AACrE,QAAM,EAAE,MAAM,UAAU,gBAAgB,SAAS,SAAS,kBAAkB,SAAS,IAAI;AAMzF,QAAM,QAAQ,QAAsB,MAAM;AACxC,UAAM,OAAO,YAAY,mBAAmB,EAAE,gBAAgB,SAAS,SAAS,iBAAiB,CAAC;AAClG,WAAO,EAAE,MAAM,SAAS,cAAc,IAAI,EAAE;AAAA,EAE9C,GAAG,CAAC,UAAU,gBAAgB,OAAO,CAAC;AAEtC,QAAM,WAAW,OAA4B,IAAI;AACjD,YAAU,MAAM;AAId,QAAI,SAAS,YAAY,MAAM,KAAM;AACrC,aAAS,UAAU,MAAM;AACzB,SAAK,MAAM,KAAK,QAAQ;AAAA,EAC1B,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO,oBAAC,IAAI,UAAJ,EAAa,OAAe,UAAS;AAC/C;AAEA,SAAS,SAAuB;AAC9B,QAAM,MAAM,WAAW,GAAG;AAC1B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sDAAsD;AAChF,SAAO;AACT;AAEO,SAAS,iBAA+B;AAAE,SAAO,OAAO;AAAG;AAS3D,SAAS,UAAyB;AACvC,QAAM,EAAE,MAAM,QAAQ,IAAI,OAAO;AACjC,QAAM,WAAW,qBAAqB,KAAK,WAAW,KAAK,aAAa,KAAK,WAAW;AACxF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,WAAW,SAAS,WAAW;AAAA,IAC/B,iBAAiB,SAAS,WAAW;AAAA,IACrC;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ThrottleAuthError,
|
|
4
4
|
useAuth
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TDQC3LMQ.js";
|
|
6
6
|
|
|
7
7
|
// src/react/hooks.ts
|
|
8
8
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -138,4 +138,4 @@ export {
|
|
|
138
138
|
useOrder,
|
|
139
139
|
useInvoice
|
|
140
140
|
};
|
|
141
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-UFGW76AZ.js.map
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
ThrottleUIContext
|
|
4
|
+
} from "./chunk-RCNUWITB.js";
|
|
5
|
+
import {
|
|
6
|
+
AuthModal,
|
|
7
|
+
SignIn,
|
|
8
|
+
SignUp
|
|
9
|
+
} from "./chunk-JSDZMPCV.js";
|
|
10
|
+
import {
|
|
11
|
+
useAction
|
|
12
|
+
} from "./chunk-QPVS6JXP.js";
|
|
13
|
+
import {
|
|
14
|
+
AppearanceProvider,
|
|
15
|
+
Button,
|
|
16
|
+
CardBody,
|
|
17
|
+
Field,
|
|
18
|
+
Modal,
|
|
19
|
+
useAppearance,
|
|
20
|
+
useAppearanceProviderValue,
|
|
21
|
+
useCardClass,
|
|
22
|
+
useCopy
|
|
23
|
+
} from "./chunk-CUNMJ2PH.js";
|
|
24
|
+
import {
|
|
25
|
+
useAuth
|
|
26
|
+
} from "./chunk-TDQC3LMQ.js";
|
|
27
|
+
|
|
28
|
+
// src/react/components/auth/StepUpDialog.tsx
|
|
29
|
+
import { useEffect, useId, useState } from "react";
|
|
30
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
31
|
+
function StepUpDialog(props) {
|
|
32
|
+
const a = useAppearance();
|
|
33
|
+
const cardClass = useCardClass();
|
|
34
|
+
const { t } = useCopy();
|
|
35
|
+
const [password, setPassword] = useState("");
|
|
36
|
+
const titleId = useId();
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!props.open) setPassword("");
|
|
39
|
+
}, [props.open]);
|
|
40
|
+
return /* @__PURE__ */ jsx(Modal, { open: props.open, onClose: () => props.onResolve(null), labelledBy: titleId, children: /* @__PURE__ */ jsx("div", { ...a.el("stepUp.root", cardClass), children: /* @__PURE__ */ jsxs(
|
|
41
|
+
"form",
|
|
42
|
+
{
|
|
43
|
+
onSubmit: (e) => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
props.onResolve(password);
|
|
46
|
+
},
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("div", { className: "throttle-card__header", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("h2", { id: titleId, ...a.el("stepUp.title", "throttle-card__title"), children: t.stepUp.title }),
|
|
50
|
+
/* @__PURE__ */ jsx("p", { className: "throttle-card__description", children: t.stepUp.description })
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsxs(CardBody, { children: [
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
Field,
|
|
55
|
+
{
|
|
56
|
+
label: t.stepUp.password,
|
|
57
|
+
type: "password",
|
|
58
|
+
autoComplete: "current-password",
|
|
59
|
+
value: password,
|
|
60
|
+
onChange: setPassword,
|
|
61
|
+
required: true,
|
|
62
|
+
elementKey: "stepUp.passwordField"
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", elementKey: "stepUp.submitButton", children: t.stepUp.submitButton }),
|
|
66
|
+
/* @__PURE__ */ jsx(Button, { variant: "link", elementKey: "stepUp.cancelButton", onClick: () => props.onResolve(null), children: t.stepUp.cancelButton })
|
|
67
|
+
] })
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
) }) });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/react/components/auth/SignInButton.tsx
|
|
74
|
+
import { cloneElement, isValidElement, useContext, useState as useState2 } from "react";
|
|
75
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
76
|
+
function SignInButton(props) {
|
|
77
|
+
const a = useAppearance(props.appearance);
|
|
78
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
79
|
+
const { t } = useCopy(props.localization);
|
|
80
|
+
const ui = useContext(ThrottleUIContext);
|
|
81
|
+
const [localOpen, setLocalOpen] = useState2(false);
|
|
82
|
+
const useShared = ui !== null && props.initialView === void 0;
|
|
83
|
+
const label = props.children ?? t.signIn.submitButton;
|
|
84
|
+
if (props.mode === "redirect") {
|
|
85
|
+
return /* @__PURE__ */ jsx2(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx2("span", { ...a.rootProps, children: /* @__PURE__ */ jsx2("a", { ...a.el("signInButton.root", "throttle-button throttle-button--primary"), href: props.url ?? "#", children: label }) }) });
|
|
86
|
+
}
|
|
87
|
+
const openModal = () => {
|
|
88
|
+
if (useShared) ui.openSignIn(props.afterSignIn);
|
|
89
|
+
else setLocalOpen(true);
|
|
90
|
+
};
|
|
91
|
+
const closeModal = () => {
|
|
92
|
+
if (useShared) ui.close();
|
|
93
|
+
else setLocalOpen(false);
|
|
94
|
+
};
|
|
95
|
+
const trigger = props.asChild && isValidElement(props.children) ? cloneElement(props.children, { onClick: openModal }) : /* @__PURE__ */ jsx2(Button, { elementKey: "signInButton.root", onClick: openModal, children: label });
|
|
96
|
+
return /* @__PURE__ */ jsxs2(AppearanceProvider, { value: scoped, children: [
|
|
97
|
+
/* @__PURE__ */ jsx2("span", { ...a.rootProps, children: trigger }),
|
|
98
|
+
!useShared ? /* @__PURE__ */ jsx2(
|
|
99
|
+
AuthModal,
|
|
100
|
+
{
|
|
101
|
+
open: localOpen,
|
|
102
|
+
onClose: closeModal,
|
|
103
|
+
initialView: props.initialView,
|
|
104
|
+
appearance: props.appearance,
|
|
105
|
+
localization: props.localization,
|
|
106
|
+
afterSignIn: () => {
|
|
107
|
+
closeModal();
|
|
108
|
+
props.afterSignIn?.();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
) : null
|
|
112
|
+
] });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/react/components/auth/SignUpButton.tsx
|
|
116
|
+
import { cloneElement as cloneElement2, isValidElement as isValidElement2, useContext as useContext2, useState as useState3 } from "react";
|
|
117
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
118
|
+
function SignUpButton(props) {
|
|
119
|
+
const a = useAppearance(props.appearance);
|
|
120
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
121
|
+
const { t } = useCopy(props.localization);
|
|
122
|
+
const ui = useContext2(ThrottleUIContext);
|
|
123
|
+
const [localOpen, setLocalOpen] = useState3(false);
|
|
124
|
+
const useShared = ui !== null && props.initialView === void 0;
|
|
125
|
+
const label = props.children ?? t.signUp.submitButton;
|
|
126
|
+
if (props.mode === "redirect") {
|
|
127
|
+
return /* @__PURE__ */ jsx3(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx3("span", { ...a.rootProps, children: /* @__PURE__ */ jsx3("a", { ...a.el("signUpButton.root", "throttle-button throttle-button--primary"), href: props.url ?? "#", children: label }) }) });
|
|
128
|
+
}
|
|
129
|
+
const openModal = () => {
|
|
130
|
+
if (useShared) ui.openSignUp(props.afterSignUp);
|
|
131
|
+
else setLocalOpen(true);
|
|
132
|
+
};
|
|
133
|
+
const closeModal = () => {
|
|
134
|
+
if (useShared) ui.close();
|
|
135
|
+
else setLocalOpen(false);
|
|
136
|
+
};
|
|
137
|
+
const trigger = props.asChild && isValidElement2(props.children) ? cloneElement2(props.children, { onClick: openModal }) : /* @__PURE__ */ jsx3(Button, { elementKey: "signUpButton.root", onClick: openModal, children: label });
|
|
138
|
+
return /* @__PURE__ */ jsxs3(AppearanceProvider, { value: scoped, children: [
|
|
139
|
+
/* @__PURE__ */ jsx3("span", { ...a.rootProps, children: trigger }),
|
|
140
|
+
!useShared ? /* @__PURE__ */ jsx3(
|
|
141
|
+
AuthModal,
|
|
142
|
+
{
|
|
143
|
+
open: localOpen,
|
|
144
|
+
onClose: closeModal,
|
|
145
|
+
initialView: props.initialView ?? "signUp",
|
|
146
|
+
appearance: props.appearance,
|
|
147
|
+
localization: props.localization,
|
|
148
|
+
afterSignUp: () => {
|
|
149
|
+
closeModal();
|
|
150
|
+
props.afterSignUp?.();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
) : null
|
|
154
|
+
] });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/react/components/auth/SignOutButton.tsx
|
|
158
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
159
|
+
function SignOutButton(props) {
|
|
160
|
+
const a = useAppearance();
|
|
161
|
+
const { auth } = useAuth();
|
|
162
|
+
const { t } = useCopy(props.localization);
|
|
163
|
+
const { submitting, run } = useAction();
|
|
164
|
+
const label = props.children ?? (props.everywhere ? t.userButton.signOutAll : t.userButton.signOut);
|
|
165
|
+
return /* @__PURE__ */ jsx4("span", { ...a.rootProps, children: /* @__PURE__ */ jsx4(
|
|
166
|
+
Button,
|
|
167
|
+
{
|
|
168
|
+
variant: "secondary",
|
|
169
|
+
loading: submitting,
|
|
170
|
+
elementKey: "signOutButton.root",
|
|
171
|
+
onClick: () => void run(async () => {
|
|
172
|
+
if (props.everywhere) await auth.signOutEverywhere();
|
|
173
|
+
else await auth.signOut();
|
|
174
|
+
props.onSignedOut?.();
|
|
175
|
+
}),
|
|
176
|
+
children: label
|
|
177
|
+
}
|
|
178
|
+
) });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/react/components/auth/SignInPage.tsx
|
|
182
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
183
|
+
function SignInPage(props) {
|
|
184
|
+
const a = useAppearance(props.appearance);
|
|
185
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
186
|
+
return /* @__PURE__ */ jsx5(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx5("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs4("div", { ...a.el("signInPage.root", "throttle-page"), children: [
|
|
187
|
+
props.logo ? /* @__PURE__ */ jsx5("div", { ...a.el("signInPage.logo", "throttle-page__logo"), children: props.logo }) : null,
|
|
188
|
+
/* @__PURE__ */ jsx5(
|
|
189
|
+
SignIn,
|
|
190
|
+
{
|
|
191
|
+
onSuccess: props.onSuccess,
|
|
192
|
+
onForgotPassword: props.onForgotPassword,
|
|
193
|
+
onSignUp: props.onSignUp,
|
|
194
|
+
appearance: props.appearance,
|
|
195
|
+
localization: props.localization
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
props.footer ? /* @__PURE__ */ jsx5("div", { ...a.el("signInPage.footer", "throttle-page__footer"), children: props.footer }) : null
|
|
199
|
+
] }) }) });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/react/components/auth/SignUpPage.tsx
|
|
203
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
204
|
+
function SignUpPage(props) {
|
|
205
|
+
const a = useAppearance(props.appearance);
|
|
206
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
207
|
+
return /* @__PURE__ */ jsx6(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx6("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs5("div", { ...a.el("signUpPage.root", "throttle-page"), children: [
|
|
208
|
+
props.logo ? /* @__PURE__ */ jsx6("div", { ...a.el("signUpPage.logo", "throttle-page__logo"), children: props.logo }) : null,
|
|
209
|
+
/* @__PURE__ */ jsx6(
|
|
210
|
+
SignUp,
|
|
211
|
+
{
|
|
212
|
+
onSuccess: props.onSuccess,
|
|
213
|
+
onSignIn: props.onSignIn,
|
|
214
|
+
appearance: props.appearance,
|
|
215
|
+
localization: props.localization
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
props.footer ? /* @__PURE__ */ jsx6("div", { ...a.el("signUpPage.footer", "throttle-page__footer"), children: props.footer }) : null
|
|
219
|
+
] }) }) });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export {
|
|
223
|
+
StepUpDialog,
|
|
224
|
+
SignInButton,
|
|
225
|
+
SignUpButton,
|
|
226
|
+
SignOutButton,
|
|
227
|
+
SignInPage,
|
|
228
|
+
SignUpPage
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=chunk-VDK23T6L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/components/auth/StepUpDialog.tsx","../src/react/components/auth/SignInButton.tsx","../src/react/components/auth/SignUpButton.tsx","../src/react/components/auth/SignOutButton.tsx","../src/react/components/auth/SignInPage.tsx","../src/react/components/auth/SignUpPage.tsx"],"sourcesContent":["import { useEffect, useId, useState } from 'react';\nimport { useAppearance } from '../appearance';\nimport { useCopy } from '../localization';\nimport { useCardClass } from '../surface';\nimport { Button, CardBody, Field, Modal } from '../primitives';\n\n/**\n * The dialog behind the core's `onStepUpRequired`. It resolves that promise\n * with a password or with null; the core does the retrying. Nothing here\n * knows which request triggered it, which is exactly why one dialog serves\n * every sensitive action in the app.\n */\nexport function StepUpDialog(props: { open: boolean; onResolve(password: string | null): void }) {\n const a = useAppearance();\n const cardClass = useCardClass();\n const { t } = useCopy();\n const [password, setPassword] = useState('');\n const titleId = useId();\n\n // Never let a password linger in state between prompts.\n useEffect(() => { if (!props.open) setPassword(''); }, [props.open]);\n\n return (\n <Modal open={props.open} onClose={() => props.onResolve(null)} labelledBy={titleId}>\n <div {...a.el('stepUp.root', cardClass)}>\n <form\n onSubmit={(e) => { e.preventDefault(); props.onResolve(password); }}\n >\n <div className=\"throttle-card__header\">\n <h2 id={titleId} {...a.el('stepUp.title', 'throttle-card__title')}>{t.stepUp.title}</h2>\n <p className=\"throttle-card__description\">{t.stepUp.description}</p>\n </div>\n <CardBody>\n <Field\n label={t.stepUp.password}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n onChange={setPassword}\n required\n elementKey=\"stepUp.passwordField\"\n />\n <Button type=\"submit\" elementKey=\"stepUp.submitButton\">{t.stepUp.submitButton}</Button>\n <Button variant=\"link\" elementKey=\"stepUp.cancelButton\" onClick={() => props.onResolve(null)}>\n {t.stepUp.cancelButton}\n </Button>\n </CardBody>\n </form>\n </div>\n </Modal>\n );\n}\n","import { cloneElement, isValidElement, useContext, useState } from 'react';\nimport type { ReactElement, ReactNode } from 'react';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { Button } from '../primitives';\nimport { ThrottleUIContext } from '../provider';\nimport { AuthModal } from './AuthModal';\nimport type { AuthView } from './AuthFlow';\n\nexport interface SignInButtonProps {\n mode?: 'modal' | 'redirect';\n /** Required when mode is \"redirect\". */\n url?: string;\n initialView?: AuthView;\n asChild?: boolean;\n children?: ReactNode;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n afterSignIn?(): void;\n}\n\nexport function SignInButton(props: SignInButtonProps) {\n const a = useAppearance(props.appearance);\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t } = useCopy(props.localization);\n // Mounted under a <ThrottleProvider>, this drives ITS single shared modal\n // instead of opening a second one of our own — see the provider's\n // ThrottleUIContext doc. Falls back to fully local state when there is no\n // provider above (a plain <ThrottleAuthProvider>), so this component keeps\n // working on its own.\n const ui = useContext(ThrottleUIContext);\n const [localOpen, setLocalOpen] = useState(false);\n // The shared modal only knows how to open on the plain sign-in/sign-up\n // split; an explicit `initialView` (e.g. straight to forgot-password) is a\n // per-button customization the shared instance cannot represent, so that\n // one case keeps its own local modal even under a provider.\n const useShared = ui !== null && props.initialView === undefined;\n const label = props.children ?? t.signIn.submitButton;\n\n if (props.mode === 'redirect') {\n return (\n <AppearanceProvider value={scoped}>\n <span {...a.rootProps}>\n <a {...a.el('signInButton.root', 'throttle-button throttle-button--primary')} href={props.url ?? '#'}>\n {label}\n </a>\n </span>\n </AppearanceProvider>\n );\n }\n\n const openModal = () => {\n if (useShared) ui!.openSignIn(props.afterSignIn);\n else setLocalOpen(true);\n };\n const closeModal = () => {\n if (useShared) ui!.close();\n else setLocalOpen(false);\n };\n\n const trigger =\n props.asChild && isValidElement(props.children)\n ? cloneElement(props.children as ReactElement<any>, { onClick: openModal })\n : (\n <Button elementKey=\"signInButton.root\" onClick={openModal}>{label}</Button>\n );\n\n return (\n <AppearanceProvider value={scoped}>\n <span {...a.rootProps}>\n {trigger}\n </span>\n {!useShared ? (\n <AuthModal\n open={localOpen}\n onClose={closeModal}\n initialView={props.initialView}\n appearance={props.appearance}\n localization={props.localization}\n afterSignIn={() => { closeModal(); props.afterSignIn?.(); }}\n />\n ) : null}\n </AppearanceProvider>\n );\n}\n","import { cloneElement, isValidElement, useContext, useState } from 'react';\nimport type { ReactElement, ReactNode } from 'react';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { Button } from '../primitives';\nimport { ThrottleUIContext } from '../provider';\nimport { AuthModal } from './AuthModal';\nimport type { AuthView } from './AuthFlow';\n\n/** The twin of `SignInButton`, opening the same flow on the sign-up view. */\nexport interface SignUpButtonProps {\n mode?: 'modal' | 'redirect';\n /** Required when mode is \"redirect\". */\n url?: string;\n initialView?: AuthView;\n asChild?: boolean;\n children?: ReactNode;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n afterSignUp?(): void;\n}\n\nexport function SignUpButton(props: SignUpButtonProps) {\n const a = useAppearance(props.appearance);\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t } = useCopy(props.localization);\n // See SignInButton: drives the provider's single shared modal when one is\n // above it, falling back to a fully local modal otherwise.\n const ui = useContext(ThrottleUIContext);\n const [localOpen, setLocalOpen] = useState(false);\n const useShared = ui !== null && props.initialView === undefined;\n const label = props.children ?? t.signUp.submitButton;\n\n if (props.mode === 'redirect') {\n return (\n <AppearanceProvider value={scoped}>\n <span {...a.rootProps}>\n <a {...a.el('signUpButton.root', 'throttle-button throttle-button--primary')} href={props.url ?? '#'}>\n {label}\n </a>\n </span>\n </AppearanceProvider>\n );\n }\n\n const openModal = () => {\n if (useShared) ui!.openSignUp(props.afterSignUp);\n else setLocalOpen(true);\n };\n const closeModal = () => {\n if (useShared) ui!.close();\n else setLocalOpen(false);\n };\n\n const trigger =\n props.asChild && isValidElement(props.children)\n ? cloneElement(props.children as ReactElement<any>, { onClick: openModal })\n : (\n <Button elementKey=\"signUpButton.root\" onClick={openModal}>{label}</Button>\n );\n\n return (\n <AppearanceProvider value={scoped}>\n <span {...a.rootProps}>\n {trigger}\n </span>\n {!useShared ? (\n <AuthModal\n open={localOpen}\n onClose={closeModal}\n initialView={props.initialView ?? 'signUp'}\n appearance={props.appearance}\n localization={props.localization}\n afterSignUp={() => { closeModal(); props.afterSignUp?.(); }}\n />\n ) : null}\n </AppearanceProvider>\n );\n}\n","import type { ReactNode } from 'react';\nimport { useAuth } from '../../context';\nimport { useAppearance } from '../appearance';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { useAction } from '../useAction';\nimport { Button } from '../primitives';\n\nexport interface SignOutButtonProps {\n /** Revokes every session, not just this device. May raise a step-up prompt. */\n everywhere?: boolean;\n children?: ReactNode;\n localization?: ThrottleLocalization;\n onSignedOut?(): void;\n}\n\n// Mountable on its own (not necessarily nested under another root-bearing\n// component), so it declares `.throttle-root` itself — otherwise every\n// `var(--throttle-*)` the shipped Button styles reference resolves to\n// nothing and the button renders unstyled.\nexport function SignOutButton(props: SignOutButtonProps) {\n const a = useAppearance();\n const { auth } = useAuth();\n const { t } = useCopy(props.localization);\n const { submitting, run } = useAction();\n const label = props.children ?? (props.everywhere ? t.userButton.signOutAll : t.userButton.signOut);\n\n return (\n <span {...a.rootProps}>\n <Button\n variant=\"secondary\"\n loading={submitting}\n elementKey=\"signOutButton.root\"\n onClick={() => void run(async () => {\n // signOutEverywhere can return step_up_required; the core opens the\n // provider's dialog and retries, so nothing extra is needed here.\n if (props.everywhere) await auth.signOutEverywhere();\n else await auth.signOut();\n props.onSignedOut?.();\n })}\n >\n {label}\n </Button>\n </span>\n );\n}\n","import type { ReactNode } from 'react';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport type { ThrottleLocalization } from '../localization';\nimport { SignIn } from './SignIn';\n\nexport interface SignInPageProps {\n /** Rendered above the card. Absent by default. */\n logo?: ReactNode;\n /** Rendered below the card. Absent by default. */\n footer?: ReactNode;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n onSuccess?(): void;\n onForgotPassword?(): void;\n onSignUp?(): void;\n}\n\n/**\n * A full-viewport, centred shell around `<SignIn/>` for merchants who drop it\n * directly on a route (e.g. `/sign-in`) and would otherwise get a card stuck\n * at the top of a bare page. `100dvh`, not `100vh` — see styles.ts.\n */\nexport function SignInPage(props: SignInPageProps) {\n const a = useAppearance(props.appearance);\n const scoped = useAppearanceProviderValue(props.appearance);\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <div {...a.el('signInPage.root', 'throttle-page')}>\n {props.logo ? (\n <div {...a.el('signInPage.logo', 'throttle-page__logo')}>{props.logo}</div>\n ) : null}\n <SignIn\n onSuccess={props.onSuccess}\n onForgotPassword={props.onForgotPassword}\n onSignUp={props.onSignUp}\n appearance={props.appearance}\n localization={props.localization}\n />\n {props.footer ? (\n <div {...a.el('signInPage.footer', 'throttle-page__footer')}>{props.footer}</div>\n ) : null}\n </div>\n </div>\n </AppearanceProvider>\n );\n}\n","import type { ReactNode } from 'react';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport type { ThrottleLocalization } from '../localization';\nimport { SignUp } from './SignUp';\n\nexport interface SignUpPageProps {\n /** Rendered above the card. Absent by default. */\n logo?: ReactNode;\n /** Rendered below the card. Absent by default. */\n footer?: ReactNode;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n onSuccess?(): void;\n onSignIn?(): void;\n}\n\n/** The `<SignUp/>` twin of `SignInPage`. */\nexport function SignUpPage(props: SignUpPageProps) {\n const a = useAppearance(props.appearance);\n const scoped = useAppearanceProviderValue(props.appearance);\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <div {...a.el('signUpPage.root', 'throttle-page')}>\n {props.logo ? (\n <div {...a.el('signUpPage.logo', 'throttle-page__logo')}>{props.logo}</div>\n ) : null}\n <SignUp\n onSuccess={props.onSuccess}\n onSignIn={props.onSignIn}\n appearance={props.appearance}\n localization={props.localization}\n />\n {props.footer ? (\n <div {...a.el('signUpPage.footer', 'throttle-page__footer')}>{props.footer}</div>\n ) : null}\n </div>\n </div>\n </AppearanceProvider>\n );\n}\n"],"mappings":"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","names":["useState","jsx","jsxs","useState","cloneElement","isValidElement","useContext","useState","jsx","jsxs","useContext","useState","isValidElement","cloneElement","jsx","jsx","jsxs","jsx","jsxs"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react/components/branding.tsx
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var Ctx = createContext(null);
|
|
7
|
+
function BrandLogoProvider(props) {
|
|
8
|
+
return /* @__PURE__ */ jsx(Ctx.Provider, { value: props.value, children: props.children });
|
|
9
|
+
}
|
|
10
|
+
function useBrandLogo(local) {
|
|
11
|
+
const provider = useContext(Ctx);
|
|
12
|
+
return local ?? provider ?? null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
BrandLogoProvider,
|
|
17
|
+
useBrandLogo
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=chunk-X6D35EAX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/components/branding.tsx"],"sourcesContent":["import { createContext, useContext } from 'react';\nimport type { ReactNode } from 'react';\n\n/**\n * Carries `ThrottleProvider`'s `logo` down to the auth cards for the\n * `branded` variant, so a merchant sets it once instead of on every card.\n * A card's own `logo` prop wins over this when both are given. Kept in its\n * own module (rather than `provider.tsx`) so the auth cards can import the\n * hook without creating an import cycle back through `AuthModal`.\n */\nconst Ctx = createContext<ReactNode>(null);\n\nexport function BrandLogoProvider(props: { value: ReactNode; children: ReactNode }) {\n return <Ctx.Provider value={props.value}>{props.children}</Ctx.Provider>;\n}\n\nexport function useBrandLogo(local?: ReactNode): ReactNode {\n const provider = useContext(Ctx);\n return local ?? provider ?? null;\n}\n"],"mappings":";;;AAAA,SAAS,eAAe,kBAAkB;AAajC;AAHT,IAAM,MAAM,cAAyB,IAAI;AAElC,SAAS,kBAAkB,OAAkD;AAClF,SAAO,oBAAC,IAAI,UAAJ,EAAa,OAAO,MAAM,OAAQ,gBAAM,UAAS;AAC3D;AAEO,SAAS,aAAa,OAA8B;AACzD,QAAM,WAAW,WAAW,GAAG;AAC/B,SAAO,SAAS,YAAY;AAC9B;","names":[]}
|