@usethrottle/auth 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3918 @@
1
+ "use client";
2
+ import {
3
+ useAddresses,
4
+ useCustomer,
5
+ useInvoice,
6
+ useInvoices,
7
+ useOrder,
8
+ useOrders,
9
+ useSavedCards,
10
+ useSubscriptions
11
+ } from "../../chunk-HGBAIM6B.js";
12
+ import {
13
+ messageForError
14
+ } from "../../chunk-AIXBUDOT.js";
15
+ import {
16
+ ThrottleAuthError,
17
+ ThrottleAuthProvider,
18
+ createAccount,
19
+ createThrottleAuth,
20
+ localStorageAdapter,
21
+ memoryStorage,
22
+ useAuth,
23
+ useAuthContext
24
+ } from "../../chunk-SZ427BQ3.js";
25
+
26
+ // src/react/components/provider.tsx
27
+ import { createContext as createContext5, useCallback as useCallback3, useContext as useContext5, useMemo as useMemo3, useRef as useRef6, useState as useState19 } from "react";
28
+
29
+ // src/react/components/appearance.tsx
30
+ import { createContext, useContext, useMemo } from "react";
31
+
32
+ // src/react/components/contrast.ts
33
+ var DEFAULT_DANGER = { light: "#c2321f", dark: "#ff6b58" };
34
+ var DEFAULT_PRIMARY_FOREGROUND = { light: "#ffffff", dark: "#0d1117" };
35
+ function channel(value) {
36
+ const c = value / 255;
37
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
38
+ }
39
+ function luminance(hex) {
40
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
41
+ if (!m) return null;
42
+ const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
43
+ const [r, g, b] = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
44
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
45
+ }
46
+ function contrastRatio(a, b) {
47
+ const la = luminance(a);
48
+ const lb = luminance(b);
49
+ if (la === null || lb === null) return null;
50
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
51
+ }
52
+ function primaryContrastWarning(variables, theme) {
53
+ const primary = variables.colorPrimary;
54
+ if (!primary) return null;
55
+ const themes = theme === "auto" ? ["light", "dark"] : [theme];
56
+ const failures = [];
57
+ for (const t of themes) {
58
+ const fg = variables.colorPrimaryForeground ?? DEFAULT_PRIMARY_FOREGROUND[t];
59
+ const ratio = contrastRatio(primary, fg);
60
+ if (ratio === null) return null;
61
+ if (ratio < 4.5) failures.push(`${t} ${ratio.toFixed(2)}:1 against ${fg}`);
62
+ }
63
+ if (failures.length === 0) return null;
64
+ const advice = variables.colorPrimaryForeground ? "Pick a foreground with more contrast against it." : "You set `colorPrimary` without `colorPrimaryForeground`, so the theme default is being used. Set both.";
65
+ return `[@usethrottle/auth] Primary button text may be hard to read on ${primary}: ${failures.join("; ")}. WCAG asks for 4.5:1. ${advice}`;
66
+ }
67
+ function toLab(hex) {
68
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
69
+ if (!m) return null;
70
+ const h = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
71
+ const [r, g, b] = [0, 2, 4].map((i) => {
72
+ const c = parseInt(h.slice(i, i + 2), 16) / 255;
73
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
74
+ });
75
+ const X = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
76
+ const Y = r * 0.2126 + g * 0.7152 + b * 0.0722;
77
+ const Z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
78
+ const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
79
+ const [fx, fy, fz] = [f(X), f(Y), f(Z)];
80
+ return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
81
+ }
82
+ function colourDistance(a, b) {
83
+ const la = toLab(a);
84
+ const lb = toLab(b);
85
+ if (!la || !lb) return null;
86
+ return Math.hypot(la[0] - lb[0], la[1] - lb[1], la[2] - lb[2]);
87
+ }
88
+ var MIN_DISTINCT = 25;
89
+ function dangerCollisionWarning(variables, theme) {
90
+ const primary = variables.colorPrimary;
91
+ if (!primary) return null;
92
+ const themes = theme === "auto" ? ["light", "dark"] : [theme];
93
+ const failures = [];
94
+ for (const t of themes) {
95
+ const danger = variables.colorDanger ?? DEFAULT_DANGER[t];
96
+ const d = colourDistance(primary, danger);
97
+ if (d === null) return null;
98
+ if (d < MIN_DISTINCT) failures.push(`${t} \u0394E ${d.toFixed(1)} against ${danger}`);
99
+ }
100
+ if (failures.length === 0) return null;
101
+ return `[@usethrottle/auth] Your brand colour ${primary} is too close to the destructive colour to tell apart: ${failures.join("; ")}. Delete and Edit sit side by side in the account panels and a buyer distinguishes them by colour alone, so pick a more distinct \`colorPrimary\`, or override \`colorDanger\` to something further from it.`;
102
+ }
103
+
104
+ // src/react/components/appearance.tsx
105
+ import { jsx } from "react/jsx-runtime";
106
+ var warnedPairings = /* @__PURE__ */ new Set();
107
+ function warnOnUnreadablePrimary(variables, theme) {
108
+ if (process.env.NODE_ENV === "production") return;
109
+ const key = `${variables.colorPrimary}|${variables.colorPrimaryForeground}|${variables.colorDanger}|${theme}`;
110
+ if (warnedPairings.has(key)) return;
111
+ const messages = [
112
+ primaryContrastWarning(variables, theme),
113
+ dangerCollisionWarning(variables, theme)
114
+ ].filter(Boolean);
115
+ if (messages.length === 0) return;
116
+ warnedPairings.add(key);
117
+ for (const m of messages) console.warn(m);
118
+ }
119
+ var VARIABLE_PROPERTY = {
120
+ colorPrimary: "--throttle-color-primary",
121
+ colorPrimaryForeground: "--throttle-color-primary-foreground",
122
+ colorBackground: "--throttle-color-background",
123
+ colorSurface: "--throttle-color-surface",
124
+ colorForeground: "--throttle-color-foreground",
125
+ colorMuted: "--throttle-color-muted",
126
+ colorMutedForeground: "--throttle-color-muted-foreground",
127
+ colorBorder: "--throttle-color-border",
128
+ colorDanger: "--throttle-color-danger",
129
+ colorDangerSurface: "--throttle-color-danger-surface",
130
+ colorDangerBorder: "--throttle-color-danger-border",
131
+ colorSuccess: "--throttle-color-success",
132
+ colorSuccessSurface: "--throttle-color-success-surface",
133
+ colorSuccessBorder: "--throttle-color-success-border",
134
+ colorWarning: "--throttle-color-warning",
135
+ colorWarningSurface: "--throttle-color-warning-surface",
136
+ colorWarningBorder: "--throttle-color-warning-border",
137
+ fontFamily: "--throttle-font-family",
138
+ fontSize: "--throttle-font-size",
139
+ borderRadius: "--throttle-radius",
140
+ borderWidth: "--throttle-border-width",
141
+ spacing: "--throttle-spacing",
142
+ shadowCard: "--throttle-shadow-card",
143
+ shadowPopover: "--throttle-shadow-popover",
144
+ transition: "--throttle-transition",
145
+ zModal: "--throttle-z-modal",
146
+ zPopover: "--throttle-z-popover"
147
+ };
148
+ var THROTTLE_ELEMENT_KEYS = [
149
+ // shared primitives
150
+ "card",
151
+ "card.header",
152
+ "card.title",
153
+ "card.description",
154
+ "card.body",
155
+ "card.footer",
156
+ "card.logo",
157
+ "field",
158
+ "field.label",
159
+ "field.input",
160
+ "field.hint",
161
+ "field.error",
162
+ "field.group",
163
+ "button",
164
+ "alert",
165
+ "spinner",
166
+ "skeleton",
167
+ "avatar",
168
+ "badge",
169
+ "emptyState",
170
+ "emptyState.title",
171
+ "emptyState.description",
172
+ "modal.overlay",
173
+ "modal.container",
174
+ "modal.closeButton",
175
+ "dropdown.trigger",
176
+ "dropdown.menu",
177
+ "dropdown.item",
178
+ "dropdown.separator",
179
+ "tabs.list",
180
+ "tabs.tab",
181
+ "table",
182
+ "table.head",
183
+ "table.row",
184
+ "table.cell",
185
+ // auth
186
+ "signIn.root",
187
+ "signIn.title",
188
+ "signIn.logo",
189
+ "signIn.emailField",
190
+ "signIn.passwordField",
191
+ "signIn.submitButton",
192
+ "signIn.forgotLink",
193
+ "signIn.switchLink",
194
+ "signUp.root",
195
+ "signUp.title",
196
+ "signUp.logo",
197
+ "signUp.emailField",
198
+ "signUp.passwordField",
199
+ "signUp.submitButton",
200
+ "signUp.switchLink",
201
+ "forgotPassword.root",
202
+ "forgotPassword.title",
203
+ "forgotPassword.logo",
204
+ "forgotPassword.emailField",
205
+ "forgotPassword.submitButton",
206
+ "forgotPassword.backLink",
207
+ "resetPassword.root",
208
+ "resetPassword.title",
209
+ "resetPassword.logo",
210
+ "resetPassword.passwordField",
211
+ "resetPassword.submitButton",
212
+ "verifyEmail.root",
213
+ "verifyEmail.title",
214
+ "verifyEmail.logo",
215
+ "verifyEmail.resendButton",
216
+ "authFlow.root",
217
+ "authModal.overlay",
218
+ "authModal.container",
219
+ "signInButton.root",
220
+ "signUpButton.root",
221
+ "signOutButton.root",
222
+ "stepUp.root",
223
+ "stepUp.title",
224
+ "stepUp.passwordField",
225
+ "stepUp.submitButton",
226
+ "stepUp.cancelButton",
227
+ "signInPage.root",
228
+ "signInPage.logo",
229
+ "signInPage.footer",
230
+ "signUpPage.root",
231
+ "signUpPage.logo",
232
+ "signUpPage.footer",
233
+ // user button
234
+ "userButton.root",
235
+ "userButton.trigger",
236
+ "userButton.menu",
237
+ "userButton.identity",
238
+ "userButton.email",
239
+ "userButton.badge",
240
+ "userButton.item",
241
+ "userButton.manageButton",
242
+ "userButton.signOutButton",
243
+ "userButton.signOutAllButton",
244
+ // account
245
+ "accountDashboard.root",
246
+ "accountDashboard.tabs",
247
+ "accountDashboard.sidebar",
248
+ "accountDashboard.panel",
249
+ "accountModal.overlay",
250
+ "accountModal.container",
251
+ "accountModal.header",
252
+ "accountModal.title",
253
+ "accountModal.body",
254
+ "verifyBanner.root",
255
+ "verifyBanner.message",
256
+ "verifyBanner.resendButton",
257
+ "verifyBanner.dismissButton",
258
+ "profilePanel.root",
259
+ "profilePanel.form",
260
+ "profilePanel.submitButton",
261
+ "addressesPanel.root",
262
+ "addressesPanel.list",
263
+ "addressesPanel.item",
264
+ "addressesPanel.addButton",
265
+ "addressesPanel.form",
266
+ "addressesPanel.defaultButton",
267
+ "addressesPanel.editButton",
268
+ "addressesPanel.deleteButton",
269
+ "addressesPanel.table",
270
+ "addressesPanel.row",
271
+ "paymentMethodsPanel.root",
272
+ "paymentMethodsPanel.list",
273
+ "paymentMethodsPanel.item",
274
+ "paymentMethodsPanel.defaultButton",
275
+ "paymentMethodsPanel.deleteButton",
276
+ "paymentMethodsPanel.table",
277
+ "paymentMethodsPanel.row",
278
+ "ordersPanel.root",
279
+ "ordersPanel.table",
280
+ "ordersPanel.row",
281
+ "ordersPanel.list",
282
+ "ordersPanel.item",
283
+ "ordersPanel.viewButton",
284
+ "orderDetail.root",
285
+ "orderDetail.backButton",
286
+ "orderDetail.lineItems",
287
+ "orderDetail.lineItem",
288
+ "orderDetail.totals",
289
+ "invoicesPanel.root",
290
+ "invoicesPanel.table",
291
+ "invoicesPanel.row",
292
+ "invoicesPanel.list",
293
+ "invoicesPanel.item",
294
+ "invoicesPanel.viewButton",
295
+ "invoiceDetail.root",
296
+ "invoiceDetail.backButton",
297
+ "invoiceDetail.downloadButton",
298
+ "invoiceDetail.totals",
299
+ "subscriptionsPanel.root",
300
+ "subscriptionsPanel.list",
301
+ "subscriptionsPanel.item",
302
+ "subscriptionsPanel.cancelButton",
303
+ "subscriptionsPanel.cancelNowButton",
304
+ "subscriptionsPanel.pauseButton",
305
+ "subscriptionsPanel.resumeButton",
306
+ "subscriptionsPanel.table",
307
+ "subscriptionsPanel.row",
308
+ "securityPanel.root",
309
+ "securityPanel.passwordForm",
310
+ "securityPanel.passwordButton",
311
+ "securityPanel.sessionList",
312
+ "securityPanel.sessionItem",
313
+ "securityPanel.revokeButton",
314
+ "securityPanel.revokeAllButton",
315
+ "securityPanel.table",
316
+ "securityPanel.row"
317
+ ];
318
+ var Ctx = createContext(null);
319
+ function AppearanceProvider(props) {
320
+ return /* @__PURE__ */ jsx(Ctx.Provider, { value: props.value, children: props.children });
321
+ }
322
+ function useAppearance(local) {
323
+ const provider = useContext(Ctx);
324
+ return useMemo(() => {
325
+ const theme = local?.theme ?? provider?.theme ?? "light";
326
+ const variables = { ...provider?.variables, ...local?.variables };
327
+ const elements = { ...provider?.elements, ...local?.elements };
328
+ const slots = { ...provider?.components, ...local?.components };
329
+ const style = {};
330
+ for (const [key, value] of Object.entries(variables)) {
331
+ const property = VARIABLE_PROPERTY[key];
332
+ if (property && value != null) style[property] = value;
333
+ }
334
+ warnOnUnreadablePrimary(variables, theme);
335
+ return {
336
+ theme,
337
+ rootProps: { className: "throttle-root", style, "data-throttle-theme": theme },
338
+ el(key, base) {
339
+ const extra = elements[key];
340
+ return {
341
+ className: [base, extra].filter(Boolean).join(" ") || void 0,
342
+ "data-throttle-element": key
343
+ };
344
+ },
345
+ slots
346
+ };
347
+ }, [provider, local]);
348
+ }
349
+ function useAppearanceProviderValue(local) {
350
+ const provider = useContext(Ctx);
351
+ return useMemo(
352
+ () => ({
353
+ theme: local?.theme ?? provider?.theme,
354
+ variables: { ...provider?.variables, ...local?.variables },
355
+ elements: { ...provider?.elements, ...local?.elements },
356
+ components: { ...provider?.components, ...local?.components }
357
+ }),
358
+ [provider, local]
359
+ );
360
+ }
361
+
362
+ // src/react/components/branding.tsx
363
+ import { createContext as createContext2, useContext as useContext2 } from "react";
364
+ import { jsx as jsx2 } from "react/jsx-runtime";
365
+ var Ctx2 = createContext2(null);
366
+ function BrandLogoProvider(props) {
367
+ return /* @__PURE__ */ jsx2(Ctx2.Provider, { value: props.value, children: props.children });
368
+ }
369
+ function useBrandLogo(local) {
370
+ const provider = useContext2(Ctx2);
371
+ return local ?? provider ?? null;
372
+ }
373
+
374
+ // src/react/components/localization.tsx
375
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo2 } from "react";
376
+ import { jsx as jsx3 } from "react/jsx-runtime";
377
+ var enUS = {
378
+ locale: "en-US",
379
+ common: {
380
+ save: "Save",
381
+ cancel: "Cancel",
382
+ delete: "Delete",
383
+ edit: "Edit",
384
+ back: "Back",
385
+ retry: "Try again",
386
+ loading: "Loading\u2026",
387
+ close: "Close",
388
+ confirm: "Are you sure?",
389
+ somethingWentWrong: "Something went wrong.",
390
+ actions: "Actions",
391
+ view: "View"
392
+ },
393
+ signIn: {
394
+ title: "Sign in",
395
+ description: "Sign in to your account.",
396
+ email: "Email",
397
+ password: "Password",
398
+ submitButton: "Sign in",
399
+ forgotLink: "Forgot your password?",
400
+ switchPrompt: "Don't have an account?",
401
+ switchLink: "Sign up"
402
+ },
403
+ signUp: {
404
+ title: "Create your account",
405
+ description: "",
406
+ email: "Email",
407
+ password: "Password",
408
+ submitButton: "Create account",
409
+ switchPrompt: "Already have an account?",
410
+ switchLink: "Sign in",
411
+ // Deliberately conditional. Registering onto an address that already has
412
+ // an account returns the same `pending` status and sends no email, so
413
+ // asserting one was sent would be false for that buyer.
414
+ pendingNotice: "If that email is new, check your inbox for a link to finish signing up."
415
+ },
416
+ forgotPassword: {
417
+ title: "Reset your password",
418
+ description: "We will email you a link if an account exists.",
419
+ email: "Email",
420
+ submitButton: "Send reset link",
421
+ sentNotice: "If an account exists for that email, a reset link is on its way.",
422
+ backLink: "Back to sign in"
423
+ },
424
+ resetPassword: {
425
+ title: "Choose a new password",
426
+ password: "New password",
427
+ submitButton: "Update password",
428
+ doneNotice: "Your password has been updated.",
429
+ missingToken: "That link is missing its token. Request a new one."
430
+ },
431
+ verifyEmail: {
432
+ title: "Verify your email",
433
+ verifying: "Confirming your email\u2026",
434
+ success: "Your email is verified.",
435
+ resendButton: "Resend the link",
436
+ resentNotice: "We sent a new link.",
437
+ instructions: "Check your inbox for a verification link."
438
+ },
439
+ stepUp: {
440
+ title: "Confirm your password",
441
+ description: "For your security, please re-enter your password to continue.",
442
+ password: "Password",
443
+ submitButton: "Confirm",
444
+ cancelButton: "Cancel"
445
+ },
446
+ userButton: {
447
+ manage: "Manage account",
448
+ signOut: "Sign out",
449
+ signOutAll: "Sign out of all devices",
450
+ verified: "Verified",
451
+ unverified: "Unverified",
452
+ openMenu: "Account menu"
453
+ },
454
+ verifyBanner: {
455
+ message: "Verify your email to see your full account history.",
456
+ resendButton: "Resend link",
457
+ dismissButton: "Dismiss"
458
+ },
459
+ account: {
460
+ title: "Your account",
461
+ tabs: {
462
+ profile: "Profile",
463
+ addresses: "Addresses",
464
+ paymentMethods: "Saved cards",
465
+ orders: "Orders",
466
+ invoices: "Invoices",
467
+ subscriptions: "Subscriptions",
468
+ security: "Security"
469
+ }
470
+ },
471
+ profile: {
472
+ firstName: "First name",
473
+ lastName: "Last name",
474
+ phone: "Phone",
475
+ company: "Company",
476
+ acceptsMarketing: "Send me product news and offers",
477
+ email: "Email",
478
+ emailReadOnly: "Your email address cannot be changed here.",
479
+ saveButton: "Save changes",
480
+ savedNotice: "Profile updated."
481
+ },
482
+ addresses: {
483
+ addButton: "Add address",
484
+ defaultBadge: "Default",
485
+ makeDefault: "Make default",
486
+ empty: "No saved addresses",
487
+ emptyDescription: "Addresses you use at checkout can be saved here.",
488
+ label: "Label",
489
+ firstName: "First name",
490
+ lastName: "Last name",
491
+ company: "Company",
492
+ addressLine1: "Address",
493
+ addressLine2: "Apartment, suite, etc.",
494
+ city: "City",
495
+ stateProvince: "State / Province",
496
+ postalCode: "Postal code",
497
+ countryCode: "Country code",
498
+ phone: "Phone",
499
+ deleteConfirm: "Delete this address?",
500
+ columnAddress: "Address"
501
+ },
502
+ paymentMethods: {
503
+ empty: "No saved cards",
504
+ // True today and still true once a buyer-plane add-card route exists.
505
+ emptyDescription: "Cards you use at checkout will be saved here.",
506
+ // Shown whenever the panel is empty with no error and the buyer is not
507
+ // verified — including simply being signed out — matching the copy
508
+ // orders/invoices/subscriptions already show in that state.
509
+ gatedDescription: "Verify your email to see your saved cards.",
510
+ defaultBadge: "Default",
511
+ makeDefault: "Make default",
512
+ expires: "Expires",
513
+ deleteConfirm: "Remove this card?",
514
+ columnCard: "Card"
515
+ },
516
+ orders: {
517
+ empty: "No orders yet",
518
+ emptyDescription: "Orders you place will appear here.",
519
+ gatedDescription: "Verify your email to see your order history.",
520
+ number: "Order",
521
+ date: "Date",
522
+ status: "Status",
523
+ total: "Total",
524
+ /**
525
+ * Display copy for `order.status`. Mirrors ORDER_STATUS_META in
526
+ * `@commerce/orders`, which is the server's single source of status copy —
527
+ * copied rather than imported because this package ships with no runtime
528
+ * dependencies and is a client for the wire API, not for server packages.
529
+ * If a status is added there, add it here.
530
+ */
531
+ statusLabels: {
532
+ draft: "Draft",
533
+ pending: "Pending",
534
+ processing: "Processing",
535
+ partiallyFulfilled: "Partially fulfilled",
536
+ fulfilled: "Fulfilled",
537
+ cancelled: "Cancelled",
538
+ closed: "Closed"
539
+ },
540
+ /**
541
+ * The money half of the label, shown as a second badge beside the
542
+ * lifecycle one. `captured` is deliberately empty: paid in full is the
543
+ * assumed happy path, and announcing it on every settled order is noise.
544
+ */
545
+ paymentLabels: {
546
+ pending: "Awaiting payment",
547
+ authorized: "Authorized",
548
+ captured: "",
549
+ partiallyPaid: "Partially paid",
550
+ partiallyRefunded: "Partially refunded",
551
+ refunded: "Refunded",
552
+ failed: "Payment failed",
553
+ voided: "Voided",
554
+ processing: "Payment processing",
555
+ disputed: "Disputed",
556
+ expired: "Authorization expired"
557
+ },
558
+ items: "Items",
559
+ subtotal: "Subtotal",
560
+ discount: "Discount",
561
+ shipping: "Shipping",
562
+ tax: "Tax",
563
+ quantity: "Qty",
564
+ shippingAddress: "Shipping address",
565
+ billingAddress: "Billing address",
566
+ viewSubscription: "View subscription"
567
+ },
568
+ invoices: {
569
+ empty: "No invoices yet",
570
+ emptyDescription: "Invoices will appear here.",
571
+ gatedDescription: "Verify your email to see your invoices.",
572
+ number: "Invoice",
573
+ issued: "Issued",
574
+ due: "Due",
575
+ status: "Status",
576
+ total: "Total",
577
+ refunded: "Refunded",
578
+ period: "Period",
579
+ downloadButton: "Download PDF",
580
+ /** Display copy for `invoice.status` — the wire values are paid|open|void. */
581
+ statusLabels: { paid: "Paid", open: "Open", void: "Void" },
582
+ downloadUnavailable: "This invoice is not available to download right now."
583
+ },
584
+ subscriptions: {
585
+ empty: "No subscriptions",
586
+ emptyDescription: "Subscriptions will appear here.",
587
+ gatedDescription: "Verify your email to see your subscriptions.",
588
+ renewsOn: "Renews on",
589
+ endsOn: "Ends on",
590
+ cancelledOn: "Cancelled on",
591
+ trialEnds: "Trial ends",
592
+ cancelButton: "Cancel",
593
+ cancelAtPeriodEnd: "Cancel at period end",
594
+ cancelNow: "Cancel immediately",
595
+ pauseButton: "Pause",
596
+ resumeButton: "Resume",
597
+ cancelConfirm: "Cancel this subscription?",
598
+ cancelScheduled: "Cancels at the end of the period",
599
+ columnPlan: "Plan",
600
+ columnPrice: "Price",
601
+ columnRenews: "Renews",
602
+ columnStatus: "Status",
603
+ /**
604
+ * Display copy for `subscription.status`. Mirrors SubscriptionStatus in
605
+ * `@commerce/subscriptions`; copied rather than imported because this
606
+ * package has no runtime dependencies. Without it a buyer in dunning saw
607
+ * the literal pill `past_due`.
608
+ */
609
+ statusLabels: {
610
+ active: "Active",
611
+ paused: "Paused",
612
+ cancelled: "Cancelled",
613
+ pastDue: "Payment overdue",
614
+ trialing: "Trial",
615
+ unpaid: "Unpaid"
616
+ }
617
+ },
618
+ security: {
619
+ passwordTitle: "Change password",
620
+ newPassword: "New password",
621
+ changeButton: "Update password",
622
+ changedNotice: "Password updated.",
623
+ sessionsTitle: "Devices",
624
+ thisDevice: "This device",
625
+ lastActive: "Last active",
626
+ revokeButton: "Sign out",
627
+ revokeAllButton: "Sign out of all other devices",
628
+ sessionsEmpty: "No other devices",
629
+ columnDevice: "Device"
630
+ },
631
+ errors: {}
632
+ };
633
+ function isPlainObject(value) {
634
+ return typeof value === "object" && value !== null && !Array.isArray(value);
635
+ }
636
+ function deepMerge(base, over, path = "") {
637
+ if (!over) return base;
638
+ const out = Array.isArray(base) ? [...base] : { ...base };
639
+ for (const [key, value] of Object.entries(over)) {
640
+ if (value == null) continue;
641
+ const current = base[key];
642
+ const keyPath = path ? `${path}.${key}` : key;
643
+ const currentIsObject = isPlainObject(current);
644
+ const valueIsObject = isPlainObject(value);
645
+ if (current !== void 0 && currentIsObject !== valueIsObject) {
646
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
647
+ console.warn(
648
+ `[useCopy] ignoring localization override at "${keyPath}": expected ${currentIsObject ? "an object" : "a string"}, got ${valueIsObject ? "an object" : typeof value}`
649
+ );
650
+ }
651
+ continue;
652
+ }
653
+ out[key] = currentIsObject && valueIsObject ? deepMerge(current, value, keyPath) : value;
654
+ }
655
+ return out;
656
+ }
657
+ var Ctx3 = createContext3(null);
658
+ function LocalizationProvider(props) {
659
+ return /* @__PURE__ */ jsx3(Ctx3.Provider, { value: props.value, children: props.children });
660
+ }
661
+ function useCopy(local) {
662
+ const provider = useContext3(Ctx3);
663
+ return useMemo2(() => {
664
+ const t = deepMerge(deepMerge(enUS, provider), local);
665
+ return {
666
+ t,
667
+ locale: t.locale,
668
+ /**
669
+ * A merchant's `errors` map wins; otherwise the shipped mapper decides.
670
+ * `messageForError` stays the floor because it encodes two deliberate
671
+ * non-passthroughs — `invalid_credentials` stays vague to avoid account
672
+ * enumeration, and unknown codes stay generic.
673
+ */
674
+ errorMessage(err) {
675
+ const code = err?.code;
676
+ const override = code ? t.errors[code] : void 0;
677
+ return override ?? messageForError(err);
678
+ }
679
+ };
680
+ }, [provider, local]);
681
+ }
682
+
683
+ // src/react/components/styles.ts
684
+ import { useInsertionEffect } from "react";
685
+ var THROTTLE_STYLE_ID = "throttle-auth-styles";
686
+ var THROTTLE_STYLES = `
687
+ .throttle-root {
688
+ --throttle-color-primary: #18181b;
689
+ --throttle-color-primary-foreground: #ffffff;
690
+ --throttle-color-background: #ffffff;
691
+ --throttle-color-surface: #f8fafc;
692
+ --throttle-color-foreground: #0e1116;
693
+ --throttle-color-muted: #eceef2;
694
+ --throttle-color-muted-foreground: #5b6472;
695
+ --throttle-color-border: #e2e8f0;
696
+ --throttle-color-danger: #c2321f;
697
+ --throttle-color-danger-surface: #fef2f2;
698
+ --throttle-color-danger-border: #fecaca;
699
+ --throttle-color-success: #127a4a;
700
+ --throttle-color-success-surface: #ecfdf5;
701
+ --throttle-color-success-border: #a7f3d0;
702
+ --throttle-color-warning: #92400e;
703
+ --throttle-color-warning-surface: #fffbeb;
704
+ --throttle-color-warning-border: #fde68a;
705
+ --throttle-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
706
+ --throttle-font-size: 16px;
707
+ --throttle-font-size-xs: 12px;
708
+ --throttle-font-size-sm: 14px;
709
+ --throttle-font-size-lg: 20px;
710
+ --throttle-font-size-xl: 24px;
711
+ --throttle-line-height: 1.5;
712
+ --throttle-line-height-tight: 1.25;
713
+ --throttle-radius: 12px;
714
+ --throttle-radius-sm: 8px;
715
+ --throttle-radius-full: 999px;
716
+ --throttle-border-width: 1px;
717
+ --throttle-spacing: 8px;
718
+ --throttle-space-1: 4px;
719
+ --throttle-space-2: var(--throttle-spacing);
720
+ --throttle-space-3: 12px;
721
+ --throttle-space-4: 16px;
722
+ --throttle-space-5: 24px;
723
+ --throttle-space-6: 32px;
724
+ --throttle-control-height: 48px;
725
+ --throttle-control-padding-x: 16px;
726
+ --throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);
727
+ --throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);
728
+ --throttle-transition: 150ms cubic-bezier(.2,0,.2,1);
729
+ --throttle-z-modal: 2147483000;
730
+ --throttle-z-popover: 2147482000;
731
+ font-family: var(--throttle-font-family);
732
+ font-size: var(--throttle-font-size);
733
+ line-height: var(--throttle-line-height);
734
+ color: var(--throttle-color-foreground);
735
+ box-sizing: border-box;
736
+ }
737
+ .throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }
738
+
739
+ .throttle-root[data-throttle-theme="dark"] {
740
+ --throttle-color-primary: #e6edf3;
741
+ --throttle-color-primary-foreground: #0d1117;
742
+ --throttle-color-background: #0d1117;
743
+ --throttle-color-surface: #1c2129;
744
+ --throttle-color-foreground: #e6edf3;
745
+ --throttle-color-muted: #21262d;
746
+ --throttle-color-muted-foreground: #9aa4b2;
747
+ --throttle-color-border: #30363d;
748
+ --throttle-color-danger: #ff6b58;
749
+ --throttle-color-danger-surface: #2a1513;
750
+ --throttle-color-danger-border: #5c2b25;
751
+ --throttle-color-success: #45c08a;
752
+ --throttle-color-success-surface: #0f2419;
753
+ --throttle-color-success-border: #1f4d36;
754
+ --throttle-color-warning: #e0a53a;
755
+ --throttle-color-warning-surface: #2a2113;
756
+ --throttle-color-warning-border: #5c4a25;
757
+ --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
758
+ --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
759
+ }
760
+ @media (prefers-color-scheme: dark) {
761
+ .throttle-root[data-throttle-theme="auto"] {
762
+ --throttle-color-primary: #e6edf3;
763
+ --throttle-color-primary-foreground: #0d1117;
764
+ --throttle-color-background: #0d1117;
765
+ --throttle-color-surface: #1c2129;
766
+ --throttle-color-foreground: #e6edf3;
767
+ --throttle-color-muted: #21262d;
768
+ --throttle-color-muted-foreground: #9aa4b2;
769
+ --throttle-color-border: #30363d;
770
+ --throttle-color-danger: #ff6b58;
771
+ --throttle-color-danger-surface: #2a1513;
772
+ --throttle-color-danger-border: #5c2b25;
773
+ --throttle-color-success: #45c08a;
774
+ --throttle-color-success-surface: #0f2419;
775
+ --throttle-color-success-border: #1f4d36;
776
+ --throttle-color-warning: #e0a53a;
777
+ --throttle-color-warning-surface: #2a2113;
778
+ --throttle-color-warning-border: #5c4a25;
779
+ --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
780
+ --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
781
+ }
782
+ }
783
+
784
+ .throttle-card {
785
+ background: var(--throttle-color-background);
786
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
787
+ border-radius: var(--throttle-radius);
788
+ box-shadow: var(--throttle-shadow-card);
789
+ padding: var(--throttle-space-5);
790
+ width: 100%;
791
+ max-width: 400px;
792
+ margin-inline: auto;
793
+ }
794
+ /* Inside a Modal the dialog already provides the surface \u2014 background, radius
795
+ and elevation. A card that draws its own on top is the "box inside a box":
796
+ two shadowed rounded rectangles nested, with the card's max-width stranding
797
+ it in the middle of a much wider dialog. */
798
+ .throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }
799
+ .throttle-card__header { margin-bottom: var(--throttle-space-4); }
800
+ .throttle-card__header--branded { text-align: center; }
801
+ .throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }
802
+ .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; }
803
+ .throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }
804
+ .throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }
805
+ .throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
806
+ .throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }
807
+ .throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }
808
+
809
+ .throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
810
+ .throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }
811
+ .throttle-field__label { font-weight: 500; }
812
+ .throttle-field__input {
813
+ font: inherit;
814
+ font-size: var(--throttle-font-size);
815
+ color: var(--throttle-color-foreground);
816
+ background: var(--throttle-color-background);
817
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
818
+ border-radius: var(--throttle-radius-sm);
819
+ /* padding-block stays 0 so min-height alone owns the height, exactly as
820
+ .throttle-button does. With 12px vertical padding the box computed to
821
+ 50px against the button's 48px \u2014 a 2px mismatch, measured in a browser
822
+ across 19 controls. An input element centres its own text vertically. */
823
+ padding-block: 0;
824
+ padding-inline: var(--throttle-control-padding-x);
825
+ min-height: var(--throttle-control-height);
826
+ width: 100%;
827
+ transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);
828
+ }
829
+ .throttle-field__input:focus-visible {
830
+ outline: none;
831
+ border-color: var(--throttle-color-primary);
832
+ box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);
833
+ }
834
+ .throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }
835
+ .throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }
836
+ .throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }
837
+ .throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }
838
+ /* Always mounted, so it reserves its line whether or not there is an error:
839
+ an error appearing on submit must not push the submit button out from
840
+ under the buyer's pointer. */
841
+ .throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); min-height: 1rem; line-height: 1rem; }
842
+
843
+ .throttle-field-group {
844
+ display: flex;
845
+ flex-direction: column;
846
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
847
+ border-radius: var(--throttle-radius-sm);
848
+ overflow: hidden;
849
+ }
850
+ .throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }
851
+ .throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }
852
+ .throttle-field-group__forgot { margin-left: auto; }
853
+
854
+ .throttle-button {
855
+ font: inherit;
856
+ font-weight: 500;
857
+ border-radius: var(--throttle-radius-sm);
858
+ border: var(--throttle-border-width) solid transparent;
859
+ padding-inline: var(--throttle-control-padding-x);
860
+ padding-block: 0;
861
+ min-height: var(--throttle-control-height);
862
+ display: inline-flex;
863
+ align-items: center;
864
+ justify-content: center;
865
+ cursor: pointer;
866
+ transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);
867
+ }
868
+ .throttle-button:disabled { opacity: .55; cursor: not-allowed; }
869
+ .throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }
870
+ .throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: 100%; }
871
+ .throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }
872
+ .throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border); }
873
+ .throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }
874
+ .throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }
875
+ .throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }
876
+ /* Link-style actions are the rows panel default, and they carry Delete,
877
+ Remove, Cancel and Sign out. Zero padding with min-height 0 made those
878
+ roughly 20px tall and ~8px apart \u2014 a mis-tap on a phone lands on something
879
+ irreversible. The target is expanded with vertical padding and pulled back
880
+ with an equal negative margin, so the hit box clears 44px while the text
881
+ sits exactly where it did. Collapsing this back to zero padding shrinks the
882
+ target again. */
883
+ .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; }
884
+ .throttle-button--link:hover:not(:disabled) { opacity: .8; }
885
+ .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; }
886
+ .throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }
887
+ .throttle-button--xs { font-size: var(--throttle-font-size-xs); }
888
+
889
+ .throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }
890
+ .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); }
891
+ .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); }
892
+ .throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }
893
+
894
+ .throttle-spinner {
895
+ display: inline-block; width: 1em; height: 1em;
896
+ border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
897
+ animation: throttle-spin 700ms linear infinite;
898
+ }
899
+ @keyframes throttle-spin { to { transform: rotate(360deg); } }
900
+ @media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }
901
+
902
+ .throttle-skeleton {
903
+ background: var(--throttle-color-muted);
904
+ border-radius: var(--throttle-radius-sm);
905
+ animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }
906
+ @keyframes throttle-pulse { 50% { opacity: .5; } }
907
+ @media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }
908
+
909
+ .throttle-modal__overlay {
910
+ position: fixed; inset: 0; z-index: var(--throttle-z-modal);
911
+ background: rgba(14,17,22,.45);
912
+ display: flex; align-items: center; justify-content: center;
913
+ padding: var(--throttle-space-4);
914
+ overflow-y: auto;
915
+ }
916
+ .throttle-modal__container {
917
+ background: var(--throttle-color-background);
918
+ border-radius: var(--throttle-radius);
919
+ box-shadow: var(--throttle-shadow-popover);
920
+ max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;
921
+ position: relative;
922
+ }
923
+ /* After the base rule on purpose: both are single-class, so equal specificity
924
+ means source order decides. Declared before it, this silently lost. */
925
+ .throttle-modal__container--compact { max-width: 440px; }
926
+ .throttle-modal__close {
927
+ position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);
928
+ background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);
929
+ color: var(--throttle-color-muted-foreground);
930
+ transition: color var(--throttle-transition);
931
+ }
932
+ .throttle-modal__close:hover { color: var(--throttle-color-foreground); }
933
+ .throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }
934
+ .throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }
935
+ .throttle-modal__body { padding: var(--throttle-space-4); }
936
+
937
+ .throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }
938
+ .throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }
939
+ .throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }
940
+ .throttle-dropdown__menu {
941
+ position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));
942
+ z-index: var(--throttle-z-popover);
943
+ min-width: 260px;
944
+ background: var(--throttle-color-background);
945
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
946
+ border-radius: var(--throttle-radius);
947
+ box-shadow: var(--throttle-shadow-popover);
948
+ padding: var(--throttle-space-1);
949
+ }
950
+ .throttle-dropdown__item {
951
+ display: flex; align-items: center; gap: var(--throttle-space-2);
952
+ width: 100%; text-align: left; font: inherit; color: inherit;
953
+ background: none; border: none; cursor: pointer;
954
+ padding: var(--throttle-space-3);
955
+ border-radius: var(--throttle-radius-sm);
956
+ text-decoration: none;
957
+ transition: background-color var(--throttle-transition);
958
+ }
959
+ .throttle-dropdown__item:hover { background: var(--throttle-color-surface); }
960
+ .throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
961
+ .throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }
962
+
963
+ .throttle-tabs__list {
964
+ display: flex; gap: var(--throttle-space-1);
965
+ border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);
966
+ overflow-x: auto; scrollbar-width: thin;
967
+ }
968
+ .throttle-tabs__tab {
969
+ font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;
970
+ padding: var(--throttle-space-3);
971
+ color: var(--throttle-color-muted-foreground);
972
+ border-bottom: 2px solid transparent;
973
+ transition: color var(--throttle-transition), border-color var(--throttle-transition);
974
+ }
975
+ .throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }
976
+ .throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }
977
+ .throttle-tabs__panel { padding-top: var(--throttle-space-4); }
978
+
979
+ /* Paints its own ground. A UserButton in a navbar should stay transparent, but
980
+ the dashboard is a large surface of themed text: in dark theme its foreground
981
+ is near-white, so on a light host page it was rendering white-on-white. */
982
+ .throttle-account-root { container-type: inline-size; background: var(--throttle-color-background); color: var(--throttle-color-foreground); }
983
+ .throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }
984
+ .throttle-account__content { flex: 1; min-width: 0; }
985
+ .throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }
986
+ .throttle-tabs__railitem {
987
+ font: inherit;
988
+ font-size: var(--throttle-font-size-sm);
989
+ display: flex;
990
+ align-items: center;
991
+ gap: var(--throttle-space-3);
992
+ width: 100%;
993
+ text-align: left;
994
+ background: none;
995
+ border: none;
996
+ border-radius: var(--throttle-radius-sm);
997
+ padding: 0 var(--throttle-space-3);
998
+ min-height: var(--throttle-control-height);
999
+ color: var(--throttle-color-muted-foreground);
1000
+ cursor: pointer;
1001
+ transition: background var(--throttle-transition), color var(--throttle-transition);
1002
+ }
1003
+ .throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }
1004
+ .throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1005
+ .throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1006
+ .throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }
1007
+ .throttle-tabs__railicon { flex: none; opacity: .75; }
1008
+
1009
+ /* A CONTAINER query, not a viewport one: these components render inside a
1010
+ merchant's page, so the viewport says nothing about how much room the
1011
+ dashboard actually has \u2014 it could sit in a 400px column on a 1920px screen.
1012
+ Below ~560px of container the rail becomes a full-width list above the
1013
+ panel: all seven still visible, each still a 44px target. */
1014
+ @container (max-width: 560px) {
1015
+ /* align-items: flex-start is right in row direction and wrong in column \u2014
1016
+ it shrinks the rail to its content width, so width:100% alone cannot make
1017
+ it span. Stretch it explicitly when stacked. */
1018
+ .throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }
1019
+ .throttle-tabs__rail { width: 100%; }
1020
+ }
1021
+
1022
+ .throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
1023
+ .throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }
1024
+ .throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }
1025
+ .throttle-table__row:hover { background: var(--throttle-color-surface); }
1026
+ .throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1027
+ .throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }
1028
+ .throttle-table__scroll { overflow-x: auto; }
1029
+
1030
+ .throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }
1031
+ .throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }
1032
+ .throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }
1033
+
1034
+ .throttle-avatar {
1035
+ display: inline-flex; align-items: center; justify-content: center;
1036
+ width: 32px; height: 32px; border-radius: var(--throttle-radius-full);
1037
+ background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);
1038
+ font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;
1039
+ }
1040
+
1041
+ .throttle-badge {
1042
+ display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;
1043
+ padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);
1044
+ background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);
1045
+ }
1046
+ .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); }
1047
+ .throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }
1048
+ .throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }
1049
+ .throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }
1050
+
1051
+ .throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
1052
+ .throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }
1053
+ .throttle-muted { color: var(--throttle-color-muted-foreground); }
1054
+ .throttle-banner {
1055
+ display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);
1056
+ flex-wrap: wrap;
1057
+ background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);
1058
+ color: var(--throttle-color-warning);
1059
+ border-radius: var(--throttle-radius-sm);
1060
+ padding: var(--throttle-space-3);
1061
+ margin-bottom: var(--throttle-space-4);
1062
+ }
1063
+ .throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }
1064
+ /* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the
1065
+ dynamic address/toolbar, so the page would be taller than the visible
1066
+ viewport and the card would sit off-centre until the toolbar collapsed. */
1067
+ .throttle-page {
1068
+ min-height: 100dvh;
1069
+ width: 100%;
1070
+ background: var(--throttle-color-surface);
1071
+ display: flex;
1072
+ flex-direction: column;
1073
+ align-items: center;
1074
+ justify-content: center;
1075
+ gap: var(--throttle-space-5);
1076
+ padding: var(--throttle-space-5);
1077
+ }
1078
+ .throttle-page__logo { display: flex; justify-content: center; }
1079
+ .throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }
1080
+ .throttle-listitem {
1081
+ display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
1082
+ flex-wrap: wrap;
1083
+ border: var(--throttle-border-width) solid var(--throttle-color-border);
1084
+ border-radius: var(--throttle-radius-sm);
1085
+ padding: var(--throttle-space-3);
1086
+ }
1087
+ /* 'rows' variant: hairline rules between items instead of each item carrying
1088
+ its own border+radius, so a panel does not read as a card inside a card. */
1089
+ .throttle-rows { display: flex; flex-direction: column; }
1090
+ .throttle-rowitem {
1091
+ display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
1092
+ flex-wrap: wrap;
1093
+ border-top: var(--throttle-border-width) solid var(--throttle-color-border);
1094
+ padding: var(--throttle-space-3) 0;
1095
+ }
1096
+ .throttle-rowitem:first-child { border-top: none; padding-top: 0; }
1097
+ .throttle-actions { display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; }
1098
+ .throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }
1099
+ .throttle-lineitem { display: flex; align-items: center; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }
1100
+ .throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }
1101
+ .throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
1102
+
1103
+ @media (prefers-reduced-motion: reduce) {
1104
+ .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {
1105
+ transition: none;
1106
+ }
1107
+ }
1108
+ `;
1109
+ function useInjectStyles(enabled) {
1110
+ useInsertionEffect(() => {
1111
+ if (!enabled) return;
1112
+ if (typeof document === "undefined") return;
1113
+ if (document.getElementById(THROTTLE_STYLE_ID)) return;
1114
+ const tag = document.createElement("style");
1115
+ tag.id = THROTTLE_STYLE_ID;
1116
+ tag.textContent = THROTTLE_STYLES;
1117
+ document.head.appendChild(tag);
1118
+ }, [enabled]);
1119
+ }
1120
+
1121
+ // src/react/components/auth/StepUpDialog.tsx
1122
+ import { useEffect as useEffect3, useId as useId2, useState as useState3 } from "react";
1123
+
1124
+ // src/react/components/surface.tsx
1125
+ import { createContext as createContext4, useContext as useContext4 } from "react";
1126
+ import { jsx as jsx4 } from "react/jsx-runtime";
1127
+ var Ctx4 = createContext4("standalone");
1128
+ function SurfaceProvider(props) {
1129
+ return /* @__PURE__ */ jsx4(Ctx4.Provider, { value: props.value, children: props.children });
1130
+ }
1131
+ function useSurface() {
1132
+ return useContext4(Ctx4);
1133
+ }
1134
+ function useCardClass() {
1135
+ return useSurface() === "modal" ? "throttle-card throttle-card--bare" : "throttle-card";
1136
+ }
1137
+
1138
+ // src/react/components/primitives/Card.tsx
1139
+ import { jsx as jsx5, jsxs } from "react/jsx-runtime";
1140
+ function CardHeader(props) {
1141
+ const a = useAppearance();
1142
+ const headerClass = `throttle-card__header${props.branded ? " throttle-card__header--branded" : ""}`;
1143
+ const titleClass = `throttle-card__title${props.branded ? " throttle-card__title--branded" : ""}`;
1144
+ return /* @__PURE__ */ jsxs("div", { ...a.el("card.header", headerClass), children: [
1145
+ props.logo ? /* @__PURE__ */ jsx5("div", { ...a.el(props.logoKey ?? "card.logo", "throttle-card__logo"), children: props.logo }) : null,
1146
+ /* @__PURE__ */ jsx5("h2", { ...a.el(props.titleKey ?? "card.title", titleClass), children: props.title }),
1147
+ props.description ? /* @__PURE__ */ jsx5("p", { ...a.el("card.description", "throttle-card__description"), children: props.description }) : null
1148
+ ] });
1149
+ }
1150
+ function CardBody(props) {
1151
+ const a = useAppearance();
1152
+ return /* @__PURE__ */ jsx5("div", { ...a.el("card.body", "throttle-card__body"), children: props.children });
1153
+ }
1154
+ function CardFooter(props) {
1155
+ const a = useAppearance();
1156
+ const className = `throttle-card__footer${props.ruled ? " throttle-card__footer--ruled" : ""}`;
1157
+ return /* @__PURE__ */ jsx5("div", { ...a.el("card.footer", className), children: props.children });
1158
+ }
1159
+
1160
+ // src/react/components/primitives/Spinner.tsx
1161
+ import { jsx as jsx6 } from "react/jsx-runtime";
1162
+ function Spinner() {
1163
+ const a = useAppearance();
1164
+ const Slot = a.slots.Spinner;
1165
+ if (Slot) return /* @__PURE__ */ jsx6(Slot, {});
1166
+ return /* @__PURE__ */ jsx6("span", { ...a.el("spinner", "throttle-spinner"), "aria-hidden": "true" });
1167
+ }
1168
+
1169
+ // src/react/components/primitives/Button.tsx
1170
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
1171
+ function Button({ variant = "primary", size = "default", elementKey, loading, children, ...rest }) {
1172
+ const a = useAppearance();
1173
+ const Slot = a.slots.Button;
1174
+ if (Slot) return /* @__PURE__ */ jsx7(Slot, { variant, loading, ...rest, children });
1175
+ const base = `throttle-button throttle-button--${variant}${size === "xs" ? " throttle-button--xs" : ""}`;
1176
+ return /* @__PURE__ */ jsxs2(
1177
+ "button",
1178
+ {
1179
+ type: "button",
1180
+ ...rest,
1181
+ ...a.el(elementKey ?? "button", base),
1182
+ disabled: rest.disabled || loading,
1183
+ children: [
1184
+ loading ? /* @__PURE__ */ jsx7(Spinner, {}) : null,
1185
+ children
1186
+ ]
1187
+ }
1188
+ );
1189
+ }
1190
+
1191
+ // src/react/components/primitives/Field.tsx
1192
+ import { Children, Fragment, useId } from "react";
1193
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1194
+ function Field(props) {
1195
+ const a = useAppearance();
1196
+ const base = useId();
1197
+ const inputId = `${base}-input`;
1198
+ const hintId = `${base}-hint`;
1199
+ const errorId = `${base}-error`;
1200
+ const describedBy = [props.hint ? hintId : null, props.error ? errorId : null].filter(Boolean).join(" ");
1201
+ const label = /* @__PURE__ */ jsx8("label", { ...a.el("field.label", "throttle-field__label"), htmlFor: inputId, children: props.label });
1202
+ return /* @__PURE__ */ jsxs3("div", { ...a.el(props.elementKey ?? "field", "throttle-field"), children: [
1203
+ props.labelExtra ? /* @__PURE__ */ jsxs3("div", { className: "throttle-field__labelrow", children: [
1204
+ label,
1205
+ props.labelExtra
1206
+ ] }) : label,
1207
+ /* @__PURE__ */ jsx8(
1208
+ "input",
1209
+ {
1210
+ ...a.el("field.input", `throttle-field__input${props.grouped ? " throttle-field__input--grouped" : ""}`),
1211
+ id: inputId,
1212
+ name: props.name,
1213
+ type: props.type ?? "text",
1214
+ value: props.value,
1215
+ required: props.required,
1216
+ disabled: props.disabled,
1217
+ autoComplete: props.autoComplete,
1218
+ "aria-describedby": describedBy || void 0,
1219
+ "aria-invalid": props.error ? true : void 0,
1220
+ onChange: (e) => props.onChange(e.target.value)
1221
+ }
1222
+ ),
1223
+ props.hint ? /* @__PURE__ */ jsx8("span", { ...a.el("field.hint", "throttle-field__hint"), id: hintId, children: props.hint }) : null,
1224
+ /* @__PURE__ */ jsx8("span", { ...a.el("field.error", "throttle-field__error"), id: errorId, role: "alert", children: props.error ?? "" })
1225
+ ] });
1226
+ }
1227
+ function FieldGroup(props) {
1228
+ const a = useAppearance();
1229
+ const items = Children.toArray(props.children).filter(Boolean);
1230
+ return /* @__PURE__ */ jsx8("div", { ...a.el(props.elementKey ?? "field.group", "throttle-field-group"), children: items.map((child, i) => /* @__PURE__ */ jsxs3(Fragment, { children: [
1231
+ i > 0 ? /* @__PURE__ */ jsx8("div", { className: "throttle-field-group__divider" }) : null,
1232
+ /* @__PURE__ */ jsx8("div", { className: "throttle-field-group__item", children: child })
1233
+ ] }, i)) });
1234
+ }
1235
+
1236
+ // src/react/components/primitives/Alert.tsx
1237
+ import { jsx as jsx9 } from "react/jsx-runtime";
1238
+ function Alert(props) {
1239
+ const a = useAppearance();
1240
+ const Slot = a.slots.Alert;
1241
+ if (Slot) return /* @__PURE__ */ jsx9(Slot, { tone: props.tone, children: props.children });
1242
+ return /* @__PURE__ */ jsx9(
1243
+ "p",
1244
+ {
1245
+ ...a.el("alert", `throttle-alert throttle-alert--${props.tone}`),
1246
+ role: props.tone === "error" ? "alert" : "status",
1247
+ children: props.children
1248
+ }
1249
+ );
1250
+ }
1251
+
1252
+ // src/react/components/primitives/Skeleton.tsx
1253
+ import { jsx as jsx10 } from "react/jsx-runtime";
1254
+ function Skeleton(props) {
1255
+ const a = useAppearance();
1256
+ const rows = Array.from({ length: props.count ?? 1 });
1257
+ return /* @__PURE__ */ jsx10("div", { "aria-hidden": "true", className: "throttle-stack", children: rows.map((_, i) => /* @__PURE__ */ jsx10("span", { ...a.el("skeleton", "throttle-skeleton") }, i)) });
1258
+ }
1259
+
1260
+ // src/react/components/primitives/Modal.tsx
1261
+ import { useEffect, useRef, useState } from "react";
1262
+ import { createPortal } from "react-dom";
1263
+ import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
1264
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
1265
+ function Modal(props) {
1266
+ const a = useAppearance();
1267
+ const { t } = useCopy();
1268
+ const panel = useRef(null);
1269
+ const returnFocusTo = useRef(null);
1270
+ const [mounted, setMounted] = useState(false);
1271
+ useEffect(() => {
1272
+ setMounted(true);
1273
+ }, []);
1274
+ const onCloseRef = useRef(props.onClose);
1275
+ useEffect(() => {
1276
+ onCloseRef.current = props.onClose;
1277
+ });
1278
+ useEffect(() => {
1279
+ if (!props.open || !mounted) return;
1280
+ returnFocusTo.current = props.restoreFocusTo ?? document.activeElement;
1281
+ const previousOverflow = document.body.style.overflow;
1282
+ document.body.style.overflow = "hidden";
1283
+ panel.current?.focus();
1284
+ const onKeyDown = (e) => {
1285
+ if (e.key === "Escape") {
1286
+ onCloseRef.current();
1287
+ return;
1288
+ }
1289
+ if (e.key !== "Tab" || !panel.current) return;
1290
+ const items = Array.from(panel.current.querySelectorAll(FOCUSABLE));
1291
+ if (items.length === 0) {
1292
+ e.preventDefault();
1293
+ return;
1294
+ }
1295
+ const first = items[0];
1296
+ const last = items[items.length - 1];
1297
+ if (e.shiftKey && document.activeElement === first) {
1298
+ e.preventDefault();
1299
+ last.focus();
1300
+ } else if (!e.shiftKey && document.activeElement === last) {
1301
+ e.preventDefault();
1302
+ first.focus();
1303
+ }
1304
+ };
1305
+ document.addEventListener("keydown", onKeyDown);
1306
+ return () => {
1307
+ document.removeEventListener("keydown", onKeyDown);
1308
+ document.body.style.overflow = previousOverflow;
1309
+ returnFocusTo.current?.focus?.();
1310
+ };
1311
+ }, [props.open, mounted]);
1312
+ if (!props.open || !mounted) return null;
1313
+ return createPortal(
1314
+ /* @__PURE__ */ jsx11("div", { ...a.rootProps, children: /* @__PURE__ */ jsx11(
1315
+ "div",
1316
+ {
1317
+ ...a.el(props.overlayKey ?? "modal.overlay", "throttle-modal__overlay"),
1318
+ "data-testid": "throttle-modal-overlay",
1319
+ onClick: (e) => {
1320
+ if (e.target === e.currentTarget) props.onClose();
1321
+ },
1322
+ children: /* @__PURE__ */ jsxs4(
1323
+ "div",
1324
+ {
1325
+ ...a.el(props.containerKey ?? "modal.container", props.size === "compact" ? "throttle-modal__container throttle-modal__container--compact" : "throttle-modal__container"),
1326
+ ref: panel,
1327
+ role: "dialog",
1328
+ "aria-modal": "true",
1329
+ "aria-labelledby": props.labelledBy,
1330
+ tabIndex: -1,
1331
+ children: [
1332
+ /* @__PURE__ */ jsx11(
1333
+ "button",
1334
+ {
1335
+ ...a.el("modal.closeButton", "throttle-modal__close"),
1336
+ type: "button",
1337
+ "aria-label": t.common.close,
1338
+ onClick: props.onClose,
1339
+ children: "\xD7"
1340
+ }
1341
+ ),
1342
+ /* @__PURE__ */ jsx11(SurfaceProvider, { value: "modal", children: props.children })
1343
+ ]
1344
+ }
1345
+ )
1346
+ }
1347
+ ) }),
1348
+ document.body
1349
+ );
1350
+ }
1351
+
1352
+ // src/react/components/primitives/Dropdown.tsx
1353
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1354
+ import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
1355
+ function Dropdown(props) {
1356
+ const a = useAppearance();
1357
+ const [open, setOpen] = useState2(false);
1358
+ const root = useRef2(null);
1359
+ const trigger = useRef2(null);
1360
+ const menu = useRef2(null);
1361
+ useEffect2(() => {
1362
+ if (!open) return;
1363
+ menu.current?.querySelector('[role="menuitem"]')?.focus();
1364
+ const onDown = (e) => {
1365
+ if (root.current && !root.current.contains(e.target)) setOpen(false);
1366
+ };
1367
+ const onKey = (e) => {
1368
+ if (e.key === "Escape") {
1369
+ setOpen(false);
1370
+ trigger.current?.focus();
1371
+ }
1372
+ };
1373
+ document.addEventListener("mousedown", onDown);
1374
+ document.addEventListener("keydown", onKey);
1375
+ return () => {
1376
+ document.removeEventListener("mousedown", onDown);
1377
+ document.removeEventListener("keydown", onKey);
1378
+ };
1379
+ }, [open]);
1380
+ const move = (delta) => {
1381
+ const items = Array.from(menu.current?.querySelectorAll('[role="menuitem"]') ?? []);
1382
+ if (items.length === 0) return;
1383
+ const at = items.indexOf(document.activeElement);
1384
+ const next = (at + delta + items.length) % items.length;
1385
+ items[next < 0 ? items.length - 1 : next].focus();
1386
+ };
1387
+ return /* @__PURE__ */ jsxs5("div", { ref: root, style: { position: "relative", display: "inline-block" }, children: [
1388
+ /* @__PURE__ */ jsx12(
1389
+ "button",
1390
+ {
1391
+ ref: trigger,
1392
+ type: "button",
1393
+ ...a.el(props.triggerKey ?? "dropdown.trigger", "throttle-dropdown__trigger"),
1394
+ "aria-haspopup": "menu",
1395
+ "aria-expanded": open,
1396
+ "aria-label": props.label,
1397
+ onClick: () => setOpen((v) => !v),
1398
+ children: props.trigger
1399
+ }
1400
+ ),
1401
+ open ? /* @__PURE__ */ jsx12(
1402
+ "div",
1403
+ {
1404
+ ref: menu,
1405
+ ...a.el(props.menuKey ?? "dropdown.menu", "throttle-dropdown__menu"),
1406
+ role: "menu",
1407
+ onClick: () => setOpen(false),
1408
+ onKeyDown: (e) => {
1409
+ if (e.key === "ArrowDown") {
1410
+ e.preventDefault();
1411
+ move(1);
1412
+ }
1413
+ if (e.key === "ArrowUp") {
1414
+ e.preventDefault();
1415
+ move(-1);
1416
+ }
1417
+ },
1418
+ children: props.children
1419
+ }
1420
+ ) : null
1421
+ ] });
1422
+ }
1423
+ function DropdownItem(props) {
1424
+ const a = useAppearance();
1425
+ const attrs = a.el(props.itemKey ?? "dropdown.item", "throttle-dropdown__item");
1426
+ if (props.href) {
1427
+ return /* @__PURE__ */ jsx12("a", { ...attrs, role: "menuitem", href: props.href, tabIndex: -1, children: props.children });
1428
+ }
1429
+ return /* @__PURE__ */ jsx12("button", { ...attrs, role: "menuitem", type: "button", tabIndex: -1, onClick: props.onClick, children: props.children });
1430
+ }
1431
+ function DropdownSeparator() {
1432
+ const a = useAppearance();
1433
+ return /* @__PURE__ */ jsx12("hr", { ...a.el("dropdown.separator", "throttle-dropdown__separator") });
1434
+ }
1435
+
1436
+ // src/react/components/primitives/Tabs.tsx
1437
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
1438
+ function Tabs(props) {
1439
+ const a = useAppearance();
1440
+ const at = props.tabs.findIndex((t) => t.id === props.active);
1441
+ const vertical = props.orientation === "vertical";
1442
+ const listClass = vertical ? "throttle-tabs__rail" : "throttle-tabs__list";
1443
+ const itemClass = vertical ? "throttle-tabs__railitem" : "throttle-tabs__tab";
1444
+ return /* @__PURE__ */ jsx13(
1445
+ "div",
1446
+ {
1447
+ ...a.el("tabs.list", listClass),
1448
+ role: "tablist",
1449
+ "aria-orientation": vertical ? "vertical" : void 0,
1450
+ onKeyDown: (e) => {
1451
+ const next = vertical ? "ArrowDown" : "ArrowRight";
1452
+ const prev = vertical ? "ArrowUp" : "ArrowLeft";
1453
+ const delta = e.key === next ? 1 : e.key === prev ? -1 : 0;
1454
+ if (!delta) return;
1455
+ e.preventDefault();
1456
+ const nextIndex = (at + delta + props.tabs.length) % props.tabs.length;
1457
+ props.onChange(props.tabs[nextIndex].id);
1458
+ e.currentTarget.querySelectorAll('[role="tab"]')[nextIndex]?.focus();
1459
+ },
1460
+ children: props.tabs.map((tab) => {
1461
+ const selected = tab.id === props.active;
1462
+ const { className, ...rest } = a.el("tabs.tab", itemClass);
1463
+ return /* @__PURE__ */ jsxs6(
1464
+ "button",
1465
+ {
1466
+ ...rest,
1467
+ className: [className, selected ? `${itemClass}--active` : null].filter(Boolean).join(" "),
1468
+ role: "tab",
1469
+ type: "button",
1470
+ id: props.idBase ? `${props.idBase}-tab-${tab.id}` : void 0,
1471
+ "aria-controls": props.idBase ? `${props.idBase}-panel-${tab.id}` : void 0,
1472
+ "aria-selected": selected,
1473
+ tabIndex: selected ? 0 : -1,
1474
+ onClick: () => props.onChange(tab.id),
1475
+ children: [
1476
+ tab.icon,
1477
+ tab.label
1478
+ ]
1479
+ },
1480
+ tab.id
1481
+ );
1482
+ })
1483
+ }
1484
+ );
1485
+ }
1486
+
1487
+ // src/react/components/primitives/DataTable.tsx
1488
+ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
1489
+ function DataTable(props) {
1490
+ const a = useAppearance();
1491
+ const clickable = Boolean(props.onRowClick);
1492
+ return (
1493
+ // Tables are the one thing allowed to be wider than the panel; it scrolls
1494
+ // rather than forcing the whole page to.
1495
+ /* @__PURE__ */ jsx14("div", { className: "throttle-table__scroll", children: /* @__PURE__ */ jsxs7("table", { ...a.el(props.tableKey ?? "table", "throttle-table"), children: [
1496
+ /* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsx14("tr", { children: props.columns.map((col) => /* @__PURE__ */ jsx14("th", { ...a.el("table.head", "throttle-table__head"), scope: "col", children: col.label }, col.key)) }) }),
1497
+ /* @__PURE__ */ jsx14("tbody", { children: props.rows.map((row) => /* @__PURE__ */ jsx14(
1498
+ "tr",
1499
+ {
1500
+ ...a.el(props.rowElementKey ?? "table.row", "throttle-table__row"),
1501
+ tabIndex: clickable ? 0 : void 0,
1502
+ onClick: clickable ? () => props.onRowClick(row) : void 0,
1503
+ onKeyDown: clickable ? (e) => {
1504
+ if (e.key === "Enter" || e.key === " ") {
1505
+ e.preventDefault();
1506
+ props.onRowClick(row);
1507
+ }
1508
+ } : void 0,
1509
+ children: props.columns.map((col) => /* @__PURE__ */ jsx14("td", { ...a.el("table.cell", "throttle-table__cell"), children: col.render ? col.render(row) : String(row[col.key] ?? "") }, col.key))
1510
+ },
1511
+ props.rowKey(row)
1512
+ )) })
1513
+ ] }) })
1514
+ );
1515
+ }
1516
+
1517
+ // src/react/components/primitives/EmptyState.tsx
1518
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1519
+ function EmptyState(props) {
1520
+ const a = useAppearance();
1521
+ const Slot = a.slots.EmptyState;
1522
+ if (Slot) return /* @__PURE__ */ jsx15(Slot, { title: props.title, description: props.description });
1523
+ return /* @__PURE__ */ jsxs8("div", { ...a.el("emptyState", "throttle-empty"), children: [
1524
+ /* @__PURE__ */ jsx15("p", { ...a.el("emptyState.title", "throttle-empty__title"), children: props.title }),
1525
+ props.description ? /* @__PURE__ */ jsx15("p", { ...a.el("emptyState.description", "throttle-empty__description"), children: props.description }) : null
1526
+ ] });
1527
+ }
1528
+
1529
+ // src/react/components/primitives/Avatar.tsx
1530
+ import { jsx as jsx16 } from "react/jsx-runtime";
1531
+ function Avatar(props) {
1532
+ const a = useAppearance();
1533
+ const Slot = a.slots.Avatar;
1534
+ if (Slot) return /* @__PURE__ */ jsx16(Slot, { name: props.name, email: props.email });
1535
+ const initials = props.name.split(" ").map((part) => part.trim()[0]).filter(Boolean).slice(0, 2).join("") || props.email.trim()[0] || "?";
1536
+ return /* @__PURE__ */ jsx16("span", { ...a.el("avatar", "throttle-avatar"), title: props.email, children: initials.toUpperCase() });
1537
+ }
1538
+
1539
+ // src/react/components/primitives/Badge.tsx
1540
+ import { jsx as jsx17 } from "react/jsx-runtime";
1541
+ function Badge(props) {
1542
+ const a = useAppearance();
1543
+ const tone = props.tone ?? "neutral";
1544
+ const base = tone === "neutral" ? "throttle-badge" : `throttle-badge throttle-badge--${tone}`;
1545
+ return /* @__PURE__ */ jsx17("span", { ...a.el(props.elementKey ?? "badge", base), children: props.children });
1546
+ }
1547
+
1548
+ // src/react/components/auth/StepUpDialog.tsx
1549
+ import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
1550
+ function StepUpDialog(props) {
1551
+ const a = useAppearance();
1552
+ const cardClass = useCardClass();
1553
+ const { t } = useCopy();
1554
+ const [password, setPassword] = useState3("");
1555
+ const titleId = useId2();
1556
+ useEffect3(() => {
1557
+ if (!props.open) setPassword("");
1558
+ }, [props.open]);
1559
+ return /* @__PURE__ */ jsx18(Modal, { open: props.open, onClose: () => props.onResolve(null), labelledBy: titleId, children: /* @__PURE__ */ jsx18("div", { ...a.el("stepUp.root", cardClass), children: /* @__PURE__ */ jsxs9(
1560
+ "form",
1561
+ {
1562
+ onSubmit: (e) => {
1563
+ e.preventDefault();
1564
+ props.onResolve(password);
1565
+ },
1566
+ children: [
1567
+ /* @__PURE__ */ jsxs9("div", { className: "throttle-card__header", children: [
1568
+ /* @__PURE__ */ jsx18("h2", { id: titleId, ...a.el("stepUp.title", "throttle-card__title"), children: t.stepUp.title }),
1569
+ /* @__PURE__ */ jsx18("p", { className: "throttle-card__description", children: t.stepUp.description })
1570
+ ] }),
1571
+ /* @__PURE__ */ jsxs9(CardBody, { children: [
1572
+ /* @__PURE__ */ jsx18(
1573
+ Field,
1574
+ {
1575
+ label: t.stepUp.password,
1576
+ type: "password",
1577
+ autoComplete: "current-password",
1578
+ value: password,
1579
+ onChange: setPassword,
1580
+ required: true,
1581
+ elementKey: "stepUp.passwordField"
1582
+ }
1583
+ ),
1584
+ /* @__PURE__ */ jsx18(Button, { type: "submit", elementKey: "stepUp.submitButton", children: t.stepUp.submitButton }),
1585
+ /* @__PURE__ */ jsx18(Button, { variant: "link", elementKey: "stepUp.cancelButton", onClick: () => props.onResolve(null), children: t.stepUp.cancelButton })
1586
+ ] })
1587
+ ]
1588
+ }
1589
+ ) }) });
1590
+ }
1591
+
1592
+ // src/react/components/auth/AuthModal.tsx
1593
+ import { useId as useId3 } from "react";
1594
+
1595
+ // src/react/components/auth/AuthFlow.tsx
1596
+ import { useEffect as useEffect5, useState as useState10 } from "react";
1597
+
1598
+ // src/react/components/auth/SignIn.tsx
1599
+ import { useState as useState5 } from "react";
1600
+
1601
+ // src/react/components/useAction.ts
1602
+ import { useCallback, useRef as useRef3, useState as useState4 } from "react";
1603
+ function useAction() {
1604
+ const { errorMessage } = useCopy();
1605
+ const [submitting, setSubmitting] = useState4(false);
1606
+ const [error, setError] = useState4(null);
1607
+ const [notice, setNotice] = useState4(null);
1608
+ const inFlight = useRef3(false);
1609
+ const run = useCallback(
1610
+ async (fn) => {
1611
+ if (inFlight.current) return;
1612
+ inFlight.current = true;
1613
+ setSubmitting(true);
1614
+ setError(null);
1615
+ setNotice(null);
1616
+ try {
1617
+ const message = await fn();
1618
+ if (typeof message === "string") setNotice(message);
1619
+ } catch (err) {
1620
+ setError(errorMessage(err));
1621
+ } finally {
1622
+ inFlight.current = false;
1623
+ setSubmitting(false);
1624
+ }
1625
+ },
1626
+ [errorMessage]
1627
+ );
1628
+ return { submitting, error, notice, run };
1629
+ }
1630
+
1631
+ // src/react/components/auth/SignIn.tsx
1632
+ import { Fragment as Fragment2, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
1633
+ function SignIn(props) {
1634
+ const a = useAppearance(props.appearance);
1635
+ const cardClass = useCardClass();
1636
+ const scoped = useAppearanceProviderValue(props.appearance);
1637
+ const { t } = useCopy(props.localization);
1638
+ const { auth } = useAuth();
1639
+ const { submitting, error, run } = useAction();
1640
+ const [email, setEmail] = useState5("");
1641
+ const [password, setPassword] = useState5("");
1642
+ const variant = props.variant ?? "quiet";
1643
+ const branded = variant === "branded";
1644
+ const grouped = variant === "grouped";
1645
+ const logo = useBrandLogo(props.logo);
1646
+ const forgotLink = props.onForgotPassword ? /* @__PURE__ */ jsx19(Button, { variant: "link", size: "xs", elementKey: "signIn.forgotLink", onClick: props.onForgotPassword, children: t.signIn.forgotLink }) : null;
1647
+ const emailField = /* @__PURE__ */ jsx19(
1648
+ Field,
1649
+ {
1650
+ label: t.signIn.email,
1651
+ type: "email",
1652
+ autoComplete: "email",
1653
+ required: true,
1654
+ value: email,
1655
+ onChange: setEmail,
1656
+ elementKey: "signIn.emailField",
1657
+ grouped
1658
+ }
1659
+ );
1660
+ const passwordField = /* @__PURE__ */ jsx19(
1661
+ Field,
1662
+ {
1663
+ label: t.signIn.password,
1664
+ type: "password",
1665
+ autoComplete: "current-password",
1666
+ required: true,
1667
+ value: password,
1668
+ onChange: setPassword,
1669
+ elementKey: "signIn.passwordField",
1670
+ grouped,
1671
+ labelExtra: grouped ? void 0 : forgotLink
1672
+ }
1673
+ );
1674
+ return /* @__PURE__ */ jsx19(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx19("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs10(
1675
+ "form",
1676
+ {
1677
+ ...a.el("signIn.root", cardClass),
1678
+ onSubmit: (e) => {
1679
+ e.preventDefault();
1680
+ void run(async () => {
1681
+ await auth.signIn(email, password);
1682
+ props.onSuccess?.();
1683
+ });
1684
+ },
1685
+ children: [
1686
+ /* @__PURE__ */ jsx19(
1687
+ CardHeader,
1688
+ {
1689
+ title: t.signIn.title,
1690
+ description: branded ? t.signIn.description : void 0,
1691
+ titleKey: "signIn.title",
1692
+ logo: branded ? logo : null,
1693
+ logoKey: "signIn.logo",
1694
+ branded
1695
+ }
1696
+ ),
1697
+ /* @__PURE__ */ jsxs10(CardBody, { children: [
1698
+ grouped ? /* @__PURE__ */ jsxs10(FieldGroup, { children: [
1699
+ emailField,
1700
+ passwordField
1701
+ ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
1702
+ emailField,
1703
+ passwordField
1704
+ ] }),
1705
+ grouped && forgotLink ? /* @__PURE__ */ jsx19("div", { className: "throttle-field-group__forgot", children: forgotLink }) : null,
1706
+ error ? /* @__PURE__ */ jsx19(Alert, { tone: "error", children: error }) : null,
1707
+ /* @__PURE__ */ jsx19(Button, { type: "submit", loading: submitting, elementKey: "signIn.submitButton", children: t.signIn.submitButton })
1708
+ ] }),
1709
+ props.onSignUp ? /* @__PURE__ */ jsxs10(CardFooter, { ruled: !branded, children: [
1710
+ /* @__PURE__ */ jsxs10("span", { className: "throttle-muted", children: [
1711
+ t.signIn.switchPrompt,
1712
+ " "
1713
+ ] }),
1714
+ /* @__PURE__ */ jsx19(Button, { variant: "link", elementKey: "signIn.switchLink", onClick: props.onSignUp, children: t.signIn.switchLink })
1715
+ ] }) : null
1716
+ ]
1717
+ }
1718
+ ) }) });
1719
+ }
1720
+
1721
+ // src/react/components/auth/SignUp.tsx
1722
+ import { useState as useState6 } from "react";
1723
+ import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
1724
+ function SignUp(props) {
1725
+ const a = useAppearance(props.appearance);
1726
+ const cardClass = useCardClass();
1727
+ const scoped = useAppearanceProviderValue(props.appearance);
1728
+ const { t } = useCopy(props.localization);
1729
+ const { auth } = useAuth();
1730
+ const { submitting, error, notice, run } = useAction();
1731
+ const [email, setEmail] = useState6("");
1732
+ const [password, setPassword] = useState6("");
1733
+ const variant = props.variant ?? "quiet";
1734
+ const branded = variant === "branded";
1735
+ const grouped = variant === "grouped";
1736
+ const logo = useBrandLogo(props.logo);
1737
+ const emailField = /* @__PURE__ */ jsx20(
1738
+ Field,
1739
+ {
1740
+ label: t.signUp.email,
1741
+ type: "email",
1742
+ autoComplete: "email",
1743
+ required: true,
1744
+ value: email,
1745
+ onChange: setEmail,
1746
+ elementKey: "signUp.emailField",
1747
+ grouped
1748
+ }
1749
+ );
1750
+ const passwordField = /* @__PURE__ */ jsx20(
1751
+ Field,
1752
+ {
1753
+ label: t.signUp.password,
1754
+ type: "password",
1755
+ autoComplete: "new-password",
1756
+ required: true,
1757
+ value: password,
1758
+ onChange: setPassword,
1759
+ elementKey: "signUp.passwordField",
1760
+ grouped
1761
+ }
1762
+ );
1763
+ return /* @__PURE__ */ jsx20(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx20("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs11(
1764
+ "form",
1765
+ {
1766
+ ...a.el("signUp.root", cardClass),
1767
+ onSubmit: (e) => {
1768
+ e.preventDefault();
1769
+ void run(async () => {
1770
+ const result = await auth.register(email, password);
1771
+ if (result.status === "active") {
1772
+ props.onSuccess?.();
1773
+ return;
1774
+ }
1775
+ return t.signUp.pendingNotice;
1776
+ });
1777
+ },
1778
+ children: [
1779
+ /* @__PURE__ */ jsx20(
1780
+ CardHeader,
1781
+ {
1782
+ title: t.signUp.title,
1783
+ description: branded ? t.signUp.description : void 0,
1784
+ titleKey: "signUp.title",
1785
+ logo: branded ? logo : null,
1786
+ logoKey: "signUp.logo",
1787
+ branded
1788
+ }
1789
+ ),
1790
+ /* @__PURE__ */ jsxs11(CardBody, { children: [
1791
+ grouped ? /* @__PURE__ */ jsxs11(FieldGroup, { children: [
1792
+ emailField,
1793
+ passwordField
1794
+ ] }) : /* @__PURE__ */ jsxs11(Fragment3, { children: [
1795
+ emailField,
1796
+ passwordField
1797
+ ] }),
1798
+ error ? /* @__PURE__ */ jsx20(Alert, { tone: "error", children: error }) : null,
1799
+ notice ? /* @__PURE__ */ jsx20(Alert, { tone: "info", children: notice }) : null,
1800
+ /* @__PURE__ */ jsx20(Button, { type: "submit", loading: submitting, elementKey: "signUp.submitButton", children: t.signUp.submitButton })
1801
+ ] }),
1802
+ props.onSignIn ? /* @__PURE__ */ jsxs11(CardFooter, { ruled: !branded, children: [
1803
+ /* @__PURE__ */ jsxs11("span", { className: "throttle-muted", children: [
1804
+ t.signUp.switchPrompt,
1805
+ " "
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx20(Button, { variant: "link", elementKey: "signUp.switchLink", onClick: props.onSignIn, children: t.signUp.switchLink })
1808
+ ] }) : null
1809
+ ]
1810
+ }
1811
+ ) }) });
1812
+ }
1813
+
1814
+ // src/react/components/auth/ForgotPassword.tsx
1815
+ import { useState as useState7 } from "react";
1816
+ import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
1817
+ function ForgotPassword(props) {
1818
+ const a = useAppearance(props.appearance);
1819
+ const cardClass = useCardClass();
1820
+ const scoped = useAppearanceProviderValue(props.appearance);
1821
+ const { t } = useCopy(props.localization);
1822
+ const { auth } = useAuth();
1823
+ const { submitting, error, notice, run } = useAction();
1824
+ const [email, setEmail] = useState7("");
1825
+ const branded = (props.variant ?? "quiet") === "branded";
1826
+ const logo = useBrandLogo(props.logo);
1827
+ return /* @__PURE__ */ jsx21(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx21("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs12(
1828
+ "form",
1829
+ {
1830
+ ...a.el("forgotPassword.root", cardClass),
1831
+ onSubmit: (e) => {
1832
+ e.preventDefault();
1833
+ void run(async () => {
1834
+ await auth.forgotPassword(email);
1835
+ return t.forgotPassword.sentNotice;
1836
+ });
1837
+ },
1838
+ children: [
1839
+ /* @__PURE__ */ jsx21(
1840
+ CardHeader,
1841
+ {
1842
+ title: t.forgotPassword.title,
1843
+ description: branded ? t.forgotPassword.description : void 0,
1844
+ titleKey: "forgotPassword.title",
1845
+ logo: branded ? logo : null,
1846
+ logoKey: "forgotPassword.logo",
1847
+ branded
1848
+ }
1849
+ ),
1850
+ /* @__PURE__ */ jsxs12(CardBody, { children: [
1851
+ /* @__PURE__ */ jsx21(
1852
+ Field,
1853
+ {
1854
+ label: t.forgotPassword.email,
1855
+ type: "email",
1856
+ autoComplete: "email",
1857
+ required: true,
1858
+ value: email,
1859
+ onChange: setEmail,
1860
+ elementKey: "forgotPassword.emailField"
1861
+ }
1862
+ ),
1863
+ error ? /* @__PURE__ */ jsx21(Alert, { tone: "error", children: error }) : null,
1864
+ notice ? /* @__PURE__ */ jsx21(Alert, { tone: "success", children: notice }) : null,
1865
+ /* @__PURE__ */ jsx21(Button, { type: "submit", loading: submitting, elementKey: "forgotPassword.submitButton", children: t.forgotPassword.submitButton })
1866
+ ] }),
1867
+ props.onBack ? /* @__PURE__ */ jsx21(CardFooter, { ruled: !branded, children: /* @__PURE__ */ jsx21(Button, { variant: "link", elementKey: "forgotPassword.backLink", onClick: props.onBack, children: t.forgotPassword.backLink }) }) : null
1868
+ ]
1869
+ }
1870
+ ) }) });
1871
+ }
1872
+
1873
+ // src/react/components/auth/ResetPassword.tsx
1874
+ import { useState as useState8 } from "react";
1875
+ import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
1876
+ function ResetPassword(props) {
1877
+ const a = useAppearance(props.appearance);
1878
+ const cardClass = useCardClass();
1879
+ const scoped = useAppearanceProviderValue(props.appearance);
1880
+ const { t } = useCopy(props.localization);
1881
+ const { auth } = useAuth();
1882
+ const { submitting, error, notice, run } = useAction();
1883
+ const [password, setPassword] = useState8("");
1884
+ const branded = (props.variant ?? "quiet") === "branded";
1885
+ const logo = useBrandLogo(props.logo);
1886
+ return /* @__PURE__ */ jsx22(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx22("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs13("div", { ...a.el("resetPassword.root", cardClass), children: [
1887
+ /* @__PURE__ */ jsx22(
1888
+ CardHeader,
1889
+ {
1890
+ title: t.resetPassword.title,
1891
+ titleKey: "resetPassword.title",
1892
+ logo: branded ? logo : null,
1893
+ logoKey: "resetPassword.logo",
1894
+ branded
1895
+ }
1896
+ ),
1897
+ /* @__PURE__ */ jsx22(CardBody, { children: !props.token ? (
1898
+ // No form at all without a token. A submit button that can only
1899
+ // fail is worse than an explanation.
1900
+ /* @__PURE__ */ jsx22(Alert, { tone: "error", children: t.resetPassword.missingToken })
1901
+ ) : /* @__PURE__ */ jsx22(
1902
+ "form",
1903
+ {
1904
+ onSubmit: (e) => {
1905
+ e.preventDefault();
1906
+ void run(async () => {
1907
+ await auth.resetPassword(props.token, password);
1908
+ props.onSuccess?.();
1909
+ return t.resetPassword.doneNotice;
1910
+ });
1911
+ },
1912
+ children: /* @__PURE__ */ jsxs13("div", { className: "throttle-stack", children: [
1913
+ /* @__PURE__ */ jsx22(
1914
+ Field,
1915
+ {
1916
+ label: t.resetPassword.password,
1917
+ type: "password",
1918
+ autoComplete: "new-password",
1919
+ required: true,
1920
+ value: password,
1921
+ onChange: setPassword,
1922
+ elementKey: "resetPassword.passwordField"
1923
+ }
1924
+ ),
1925
+ error ? /* @__PURE__ */ jsx22(Alert, { tone: "error", children: error }) : null,
1926
+ notice ? /* @__PURE__ */ jsx22(Alert, { tone: "success", children: notice }) : null,
1927
+ /* @__PURE__ */ jsx22(Button, { type: "submit", loading: submitting, elementKey: "resetPassword.submitButton", children: t.resetPassword.submitButton })
1928
+ ] })
1929
+ }
1930
+ ) })
1931
+ ] }) }) });
1932
+ }
1933
+
1934
+ // src/react/components/auth/VerifyEmail.tsx
1935
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState9 } from "react";
1936
+ import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1937
+ function VerifyEmail(props) {
1938
+ const a = useAppearance(props.appearance);
1939
+ const cardClass = useCardClass();
1940
+ const scoped = useAppearanceProviderValue(props.appearance);
1941
+ const { t, errorMessage } = useCopy(props.localization);
1942
+ const { auth } = useAuth();
1943
+ const { submitting, notice, run } = useAction();
1944
+ const [state, setState] = useState9(props.token ? "verifying" : "idle");
1945
+ const [error, setError] = useState9(null);
1946
+ const branded = (props.variant ?? "quiet") === "branded";
1947
+ const logo = useBrandLogo(props.logo);
1948
+ const attempted = useRef4(null);
1949
+ useEffect4(() => {
1950
+ const token = props.token;
1951
+ if (!token || attempted.current === token) return;
1952
+ attempted.current = token;
1953
+ setState("verifying");
1954
+ auth.verifyEmail(token).then(() => {
1955
+ setState("done");
1956
+ props.onSuccess?.();
1957
+ }).catch((err) => {
1958
+ setError(errorMessage(err));
1959
+ setState("failed");
1960
+ });
1961
+ }, [props.token, auth]);
1962
+ return /* @__PURE__ */ jsx23(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx23("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs14("div", { ...a.el("verifyEmail.root", cardClass), children: [
1963
+ /* @__PURE__ */ jsx23(
1964
+ CardHeader,
1965
+ {
1966
+ title: t.verifyEmail.title,
1967
+ titleKey: "verifyEmail.title",
1968
+ logo: branded ? logo : null,
1969
+ logoKey: "verifyEmail.logo",
1970
+ branded
1971
+ }
1972
+ ),
1973
+ /* @__PURE__ */ jsxs14(CardBody, { children: [
1974
+ state === "verifying" ? /* @__PURE__ */ jsxs14("p", { children: [
1975
+ /* @__PURE__ */ jsx23(Spinner, {}),
1976
+ " ",
1977
+ t.verifyEmail.verifying
1978
+ ] }) : null,
1979
+ state === "done" ? /* @__PURE__ */ jsx23(Alert, { tone: "success", children: t.verifyEmail.success }) : null,
1980
+ state === "failed" && error ? /* @__PURE__ */ jsx23(Alert, { tone: "error", children: error }) : null,
1981
+ state === "idle" ? /* @__PURE__ */ jsx23("p", { className: "throttle-muted", children: t.verifyEmail.instructions }) : null,
1982
+ notice ? /* @__PURE__ */ jsx23(Alert, { tone: "success", children: notice }) : null,
1983
+ state !== "done" ? /* @__PURE__ */ jsx23(
1984
+ Button,
1985
+ {
1986
+ variant: "secondary",
1987
+ loading: submitting,
1988
+ elementKey: "verifyEmail.resendButton",
1989
+ onClick: () => void run(async () => {
1990
+ await auth.resendVerification();
1991
+ return t.verifyEmail.resentNotice;
1992
+ }),
1993
+ children: t.verifyEmail.resendButton
1994
+ }
1995
+ ) : null
1996
+ ] })
1997
+ ] }) }) });
1998
+ }
1999
+
2000
+ // src/react/components/auth/AuthFlow.tsx
2001
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
2002
+ var HASH_FOR = {
2003
+ signIn: "#/sign-in",
2004
+ signUp: "#/sign-up",
2005
+ forgotPassword: "#/forgot-password",
2006
+ resetPassword: "#/reset-password",
2007
+ verifyEmail: "#/verify-email"
2008
+ };
2009
+ var VIEW_FOR_HASH = Object.fromEntries(
2010
+ Object.entries(HASH_FOR).map(([view, hash]) => [hash, view])
2011
+ );
2012
+ function readLocation() {
2013
+ if (typeof window === "undefined") return { view: null };
2014
+ const params = new URLSearchParams(window.location.search);
2015
+ const token = params.get("token") ?? void 0;
2016
+ const hashView = VIEW_FOR_HASH[window.location.hash];
2017
+ if (hashView) return { view: hashView, token };
2018
+ if (!token) return { view: null };
2019
+ const path = window.location.pathname.toLowerCase();
2020
+ if (path.includes("reset")) return { view: "resetPassword", token };
2021
+ if (path.includes("verif")) return { view: "verifyEmail", token };
2022
+ return { view: null, token };
2023
+ }
2024
+ function AuthFlow(props) {
2025
+ const a = useAppearance(props.appearance);
2026
+ const controlled = props.view !== void 0;
2027
+ const [internal, setInternal] = useState10(props.initialView ?? "signIn");
2028
+ const [urlToken, setUrlToken] = useState10(void 0);
2029
+ useEffect5(() => {
2030
+ const found = readLocation();
2031
+ if (found.token) setUrlToken(found.token);
2032
+ if (found.view && !controlled && props.initialView === void 0) setInternal(found.view);
2033
+ }, []);
2034
+ const view = controlled ? props.view : internal;
2035
+ const token = props.token ?? urlToken;
2036
+ const go = (next) => {
2037
+ props.onViewChange?.(next);
2038
+ if (!controlled) setInternal(next);
2039
+ if (props.routing === "hash" && typeof window !== "undefined") {
2040
+ window.location.hash = HASH_FOR[next];
2041
+ }
2042
+ };
2043
+ useEffect5(() => {
2044
+ if (props.routing !== "hash" || typeof window === "undefined") return;
2045
+ const onHashChange = () => {
2046
+ if (controlled) return;
2047
+ const hash = window.location.hash;
2048
+ if (hash === "") {
2049
+ setInternal(props.initialView ?? "signIn");
2050
+ return;
2051
+ }
2052
+ const next = VIEW_FOR_HASH[hash];
2053
+ if (next) setInternal(next);
2054
+ };
2055
+ window.addEventListener("hashchange", onHashChange);
2056
+ return () => window.removeEventListener("hashchange", onHashChange);
2057
+ }, [props.routing, controlled]);
2058
+ const shared = { appearance: props.appearance, localization: props.localization };
2059
+ return /* @__PURE__ */ jsx24("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs15("div", { ...a.el("authFlow.root"), children: [
2060
+ view === "signIn" ? /* @__PURE__ */ jsx24(
2061
+ SignIn,
2062
+ {
2063
+ ...shared,
2064
+ onSuccess: props.afterSignIn,
2065
+ onForgotPassword: () => go("forgotPassword"),
2066
+ onSignUp: () => go("signUp")
2067
+ }
2068
+ ) : null,
2069
+ view === "signUp" ? /* @__PURE__ */ jsx24(SignUp, { ...shared, onSuccess: props.afterSignUp, onSignIn: () => go("signIn") }) : null,
2070
+ view === "forgotPassword" ? /* @__PURE__ */ jsx24(ForgotPassword, { ...shared, onBack: () => go("signIn") }) : null,
2071
+ view === "resetPassword" ? /* @__PURE__ */ jsx24(ResetPassword, { ...shared, token, onSuccess: () => go("signIn") }) : null,
2072
+ view === "verifyEmail" ? /* @__PURE__ */ jsx24(VerifyEmail, { ...shared, token }) : null
2073
+ ] }) });
2074
+ }
2075
+
2076
+ // src/react/components/auth/AuthModal.tsx
2077
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
2078
+ function AuthModal(props) {
2079
+ const scoped = useAppearanceProviderValue(props.appearance);
2080
+ const titleId = useId3();
2081
+ return /* @__PURE__ */ jsx25(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsxs16(
2082
+ Modal,
2083
+ {
2084
+ open: props.open,
2085
+ onClose: props.onClose,
2086
+ labelledBy: titleId,
2087
+ size: "compact",
2088
+ overlayKey: "authModal.overlay",
2089
+ containerKey: "authModal.container",
2090
+ children: [
2091
+ /* @__PURE__ */ jsx25("h2", { id: titleId, style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: "Account" }),
2092
+ /* @__PURE__ */ jsx25(
2093
+ AuthFlow,
2094
+ {
2095
+ initialView: props.initialView,
2096
+ afterSignIn: props.afterSignIn,
2097
+ afterSignUp: props.afterSignUp,
2098
+ appearance: props.appearance,
2099
+ localization: props.localization
2100
+ }
2101
+ )
2102
+ ]
2103
+ }
2104
+ ) });
2105
+ }
2106
+
2107
+ // src/react/components/account/AccountModal.tsx
2108
+ import { useId as useId5 } from "react";
2109
+
2110
+ // src/react/components/account/AccountDashboard.tsx
2111
+ import { useEffect as useEffect8, useId as useId4, useState as useState18 } from "react";
2112
+
2113
+ // src/react/components/account/VerifyEmailBanner.tsx
2114
+ import { useState as useState11 } from "react";
2115
+ import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
2116
+ var DISMISS_KEY = "throttle-auth:verify-banner-dismissed";
2117
+ function readDismissed() {
2118
+ try {
2119
+ return sessionStorage.getItem(DISMISS_KEY) === "1";
2120
+ } catch {
2121
+ return false;
2122
+ }
2123
+ }
2124
+ function writeDismissed() {
2125
+ try {
2126
+ sessionStorage.setItem(DISMISS_KEY, "1");
2127
+ } catch {
2128
+ }
2129
+ }
2130
+ function VerifyEmailBanner(props) {
2131
+ const a = useAppearance(props.appearance);
2132
+ const scoped = useAppearanceProviderValue(props.appearance);
2133
+ const { t } = useCopy(props.localization);
2134
+ const { auth, status, emailVerified } = useAuth();
2135
+ const { submitting, notice, error, run } = useAction();
2136
+ const [dismissed, setDismissed] = useState11(readDismissed);
2137
+ if (status !== "authenticated" || emailVerified || dismissed) return null;
2138
+ return /* @__PURE__ */ jsx26(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx26("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs17("div", { ...a.el("verifyBanner.root", "throttle-banner"), children: [
2139
+ /* @__PURE__ */ jsx26("span", { ...a.el("verifyBanner.message"), children: t.verifyBanner.message }),
2140
+ notice ? /* @__PURE__ */ jsx26(Alert, { tone: "success", children: notice }) : null,
2141
+ error ? /* @__PURE__ */ jsx26(Alert, { tone: "error", children: error }) : null,
2142
+ /* @__PURE__ */ jsxs17("span", { className: "throttle-actions", children: [
2143
+ /* @__PURE__ */ jsx26(
2144
+ Button,
2145
+ {
2146
+ variant: "secondary",
2147
+ loading: submitting,
2148
+ elementKey: "verifyBanner.resendButton",
2149
+ onClick: () => void run(async () => {
2150
+ await auth.resendVerification();
2151
+ return t.verifyEmail.resentNotice;
2152
+ }),
2153
+ children: t.verifyBanner.resendButton
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ jsx26(
2157
+ Button,
2158
+ {
2159
+ variant: "link",
2160
+ elementKey: "verifyBanner.dismissButton",
2161
+ onClick: () => {
2162
+ writeDismissed();
2163
+ setDismissed(true);
2164
+ },
2165
+ children: t.verifyBanner.dismissButton
2166
+ }
2167
+ )
2168
+ ] })
2169
+ ] }) }) });
2170
+ }
2171
+
2172
+ // src/react/components/account/icons.tsx
2173
+ import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
2174
+ function Glyph(props) {
2175
+ return /* @__PURE__ */ jsx27(
2176
+ "svg",
2177
+ {
2178
+ className: "throttle-tabs__railicon",
2179
+ width: "18",
2180
+ height: "18",
2181
+ viewBox: "0 0 24 24",
2182
+ fill: "none",
2183
+ stroke: "currentColor",
2184
+ strokeWidth: "1.6",
2185
+ strokeLinecap: "round",
2186
+ strokeLinejoin: "round",
2187
+ "aria-hidden": "true",
2188
+ focusable: "false",
2189
+ children: props.children
2190
+ }
2191
+ );
2192
+ }
2193
+ var ACCOUNT_ICONS = {
2194
+ profile: /* @__PURE__ */ jsxs18(Glyph, { children: [
2195
+ /* @__PURE__ */ jsx27("circle", { cx: "12", cy: "8", r: "4" }),
2196
+ /* @__PURE__ */ jsx27("path", { d: "M4 21c0-4 3.6-6 8-6s8 2 8 6" })
2197
+ ] }),
2198
+ addresses: /* @__PURE__ */ jsxs18(Glyph, { children: [
2199
+ /* @__PURE__ */ jsx27("path", { d: "M12 21s7-5.3 7-11a7 7 0 1 0-14 0c0 5.7 7 11 7 11Z" }),
2200
+ /* @__PURE__ */ jsx27("circle", { cx: "12", cy: "10", r: "2.5" })
2201
+ ] }),
2202
+ paymentMethods: /* @__PURE__ */ jsxs18(Glyph, { children: [
2203
+ /* @__PURE__ */ jsx27("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
2204
+ /* @__PURE__ */ jsx27("path", { d: "M2.5 10h19" })
2205
+ ] }),
2206
+ orders: /* @__PURE__ */ jsxs18(Glyph, { children: [
2207
+ /* @__PURE__ */ jsx27("path", { d: "M4 7h16v13H4z" }),
2208
+ /* @__PURE__ */ jsx27("path", { d: "M8 7V4h8v3" }),
2209
+ /* @__PURE__ */ jsx27("path", { d: "M8 12h8" })
2210
+ ] }),
2211
+ invoices: /* @__PURE__ */ jsxs18(Glyph, { children: [
2212
+ /* @__PURE__ */ jsx27("path", { d: "M6 3h12v18l-3-2-3 2-3-2-3 2Z" }),
2213
+ /* @__PURE__ */ jsx27("path", { d: "M9 8h6M9 12h6" })
2214
+ ] }),
2215
+ subscriptions: /* @__PURE__ */ jsxs18(Glyph, { children: [
2216
+ /* @__PURE__ */ jsx27("path", { d: "M3 12a9 9 0 0 1 15.5-6.2" }),
2217
+ /* @__PURE__ */ jsx27("path", { d: "M21 12a9 9 0 0 1-15.5 6.2" }),
2218
+ /* @__PURE__ */ jsx27("path", { d: "M18 3v4h-4M6 21v-4h4" })
2219
+ ] }),
2220
+ security: /* @__PURE__ */ jsxs18(Glyph, { children: [
2221
+ /* @__PURE__ */ jsx27("path", { d: "M12 3 4 6.5V12c0 5 3.4 8.3 8 9 4.6-.7 8-4 8-9V6.5Z" }),
2222
+ /* @__PURE__ */ jsx27("path", { d: "m9 12 2 2 4-4" })
2223
+ ] })
2224
+ };
2225
+
2226
+ // src/react/components/account/ProfilePanel.tsx
2227
+ import { useEffect as useEffect6, useState as useState12 } from "react";
2228
+
2229
+ // src/react/components/account/PanelState.tsx
2230
+ import { Fragment as Fragment4, jsx as jsx28, jsxs as jsxs19 } from "react/jsx-runtime";
2231
+ function PanelState(props) {
2232
+ const { t, errorMessage } = useCopy();
2233
+ const { data, isLoading, error, refetch } = props.resource;
2234
+ const empty = props.isEmpty(data);
2235
+ if (isLoading && empty) return /* @__PURE__ */ jsx28(Skeleton, { count: props.skeletonRows ?? 3 });
2236
+ const errorBanner = error ? /* @__PURE__ */ jsxs19("div", { className: "throttle-stack", children: [
2237
+ /* @__PURE__ */ jsx28(Alert, { tone: "error", children: errorMessage(error) }),
2238
+ /* @__PURE__ */ jsx28(Button, { variant: "secondary", onClick: () => void refetch(), children: t.common.retry })
2239
+ ] }) : null;
2240
+ const gatedByVerification = error?.code === "verification_required";
2241
+ if (gatedByVerification && empty) {
2242
+ return /* @__PURE__ */ jsx28(EmptyState, { title: props.emptyTitle, description: props.gatedDescription ?? props.emptyDescription });
2243
+ }
2244
+ if (error && empty) return errorBanner;
2245
+ if (empty) {
2246
+ return props.gated ? /* @__PURE__ */ jsx28(EmptyState, { title: props.emptyTitle, description: props.gatedDescription }) : /* @__PURE__ */ jsx28(EmptyState, { title: props.emptyTitle, description: props.emptyDescription });
2247
+ }
2248
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
2249
+ errorBanner,
2250
+ props.children(data)
2251
+ ] });
2252
+ }
2253
+
2254
+ // src/react/components/account/ProfilePanel.tsx
2255
+ import { jsx as jsx29, jsxs as jsxs20 } from "react/jsx-runtime";
2256
+ function ProfilePanel(props) {
2257
+ const a = useAppearance(props.appearance);
2258
+ const scoped = useAppearanceProviderValue(props.appearance);
2259
+ const { t } = useCopy(props.localization);
2260
+ const { status } = useAuth();
2261
+ const resource = useCustomer();
2262
+ const { submitting, error, notice, run } = useAction();
2263
+ const [form, setForm] = useState12({ firstName: "", lastName: "", phone: "", company: "", acceptsMarketing: false });
2264
+ useEffect6(() => {
2265
+ const c = resource.data;
2266
+ if (!c) return;
2267
+ setForm({
2268
+ firstName: c.firstName ?? "",
2269
+ lastName: c.lastName ?? "",
2270
+ phone: c.phone ?? "",
2271
+ company: c.company ?? "",
2272
+ acceptsMarketing: Boolean(c.acceptsMarketing)
2273
+ });
2274
+ }, [resource.data]);
2275
+ if (status === "unauthenticated") return null;
2276
+ return /* @__PURE__ */ jsx29(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx29("div", { ...a.rootProps, children: /* @__PURE__ */ jsx29("div", { ...a.el("profilePanel.root", "throttle-panel"), children: /* @__PURE__ */ jsx29(
2277
+ PanelState,
2278
+ {
2279
+ resource,
2280
+ isEmpty: (c) => c === null,
2281
+ emptyTitle: t.common.somethingWentWrong,
2282
+ skeletonRows: 4,
2283
+ children: (customer) => /* @__PURE__ */ jsxs20(
2284
+ "form",
2285
+ {
2286
+ ...a.el("profilePanel.form", "throttle-stack"),
2287
+ onSubmit: (e) => {
2288
+ e.preventDefault();
2289
+ void run(async () => {
2290
+ await resource.update({
2291
+ firstName: form.firstName,
2292
+ lastName: form.lastName,
2293
+ phone: form.phone,
2294
+ company: form.company || null,
2295
+ acceptsMarketing: form.acceptsMarketing
2296
+ });
2297
+ return t.profile.savedNotice;
2298
+ });
2299
+ },
2300
+ children: [
2301
+ /* @__PURE__ */ jsx29(
2302
+ Field,
2303
+ {
2304
+ label: t.profile.email,
2305
+ type: "email",
2306
+ value: customer.email,
2307
+ onChange: () => {
2308
+ },
2309
+ disabled: true,
2310
+ hint: t.profile.emailReadOnly
2311
+ }
2312
+ ),
2313
+ /* @__PURE__ */ jsx29(
2314
+ Field,
2315
+ {
2316
+ label: t.profile.firstName,
2317
+ value: form.firstName,
2318
+ autoComplete: "given-name",
2319
+ onChange: (v) => setForm((f) => ({ ...f, firstName: v }))
2320
+ }
2321
+ ),
2322
+ /* @__PURE__ */ jsx29(
2323
+ Field,
2324
+ {
2325
+ label: t.profile.lastName,
2326
+ value: form.lastName,
2327
+ autoComplete: "family-name",
2328
+ onChange: (v) => setForm((f) => ({ ...f, lastName: v }))
2329
+ }
2330
+ ),
2331
+ /* @__PURE__ */ jsx29(
2332
+ Field,
2333
+ {
2334
+ label: t.profile.phone,
2335
+ type: "tel",
2336
+ value: form.phone,
2337
+ autoComplete: "tel",
2338
+ onChange: (v) => setForm((f) => ({ ...f, phone: v }))
2339
+ }
2340
+ ),
2341
+ /* @__PURE__ */ jsx29(
2342
+ Field,
2343
+ {
2344
+ label: t.profile.company,
2345
+ value: form.company,
2346
+ autoComplete: "organization",
2347
+ onChange: (v) => setForm((f) => ({ ...f, company: v }))
2348
+ }
2349
+ ),
2350
+ /* @__PURE__ */ jsxs20("label", { className: "throttle-row", children: [
2351
+ /* @__PURE__ */ jsx29(
2352
+ "input",
2353
+ {
2354
+ type: "checkbox",
2355
+ checked: form.acceptsMarketing,
2356
+ onChange: (e) => setForm((f) => ({ ...f, acceptsMarketing: e.target.checked }))
2357
+ }
2358
+ ),
2359
+ /* @__PURE__ */ jsx29("span", { children: t.profile.acceptsMarketing })
2360
+ ] }),
2361
+ error ? /* @__PURE__ */ jsx29(Alert, { tone: "error", children: error }) : null,
2362
+ notice ? /* @__PURE__ */ jsx29(Alert, { tone: "success", children: notice }) : null,
2363
+ /* @__PURE__ */ jsx29(Button, { type: "submit", loading: submitting, elementKey: "profilePanel.submitButton", children: t.profile.saveButton })
2364
+ ]
2365
+ }
2366
+ )
2367
+ }
2368
+ ) }) }) });
2369
+ }
2370
+
2371
+ // src/react/components/account/AddressesPanel.tsx
2372
+ import { useState as useState14 } from "react";
2373
+
2374
+ // src/react/components/account/AddressForm.tsx
2375
+ import { useState as useState13 } from "react";
2376
+ import { jsx as jsx30, jsxs as jsxs21 } from "react/jsx-runtime";
2377
+ function AddressForm(props) {
2378
+ const a = useAppearance();
2379
+ const { t } = useCopy();
2380
+ const i = props.initial;
2381
+ const [form, setForm] = useState13({
2382
+ label: i?.label ?? "",
2383
+ firstName: i?.firstName ?? "",
2384
+ lastName: i?.lastName ?? "",
2385
+ company: i?.company ?? "",
2386
+ addressLine1: i?.addressLine1 ?? "",
2387
+ addressLine2: i?.addressLine2 ?? "",
2388
+ city: i?.city ?? "",
2389
+ stateProvince: i?.stateProvince ?? "",
2390
+ postalCode: i?.postalCode ?? "",
2391
+ countryCode: i?.countryCode ?? "",
2392
+ phone: i?.phone ?? ""
2393
+ });
2394
+ const set = (key) => (value) => setForm((f) => ({ ...f, [key]: value }));
2395
+ return /* @__PURE__ */ jsxs21(
2396
+ "form",
2397
+ {
2398
+ ...a.el("addressesPanel.form", "throttle-stack"),
2399
+ onSubmit: (e) => {
2400
+ e.preventDefault();
2401
+ props.onSubmit({
2402
+ label: form.label || void 0,
2403
+ firstName: form.firstName || void 0,
2404
+ lastName: form.lastName || void 0,
2405
+ company: form.company || void 0,
2406
+ addressLine1: form.addressLine1,
2407
+ addressLine2: form.addressLine2 || void 0,
2408
+ city: form.city,
2409
+ stateProvince: form.stateProvince || void 0,
2410
+ postalCode: form.postalCode || void 0,
2411
+ // The API wants ISO-3166 alpha-2; uppercase so "gb" is not rejected.
2412
+ countryCode: form.countryCode.toUpperCase(),
2413
+ phone: form.phone || void 0
2414
+ });
2415
+ },
2416
+ children: [
2417
+ /* @__PURE__ */ jsx30(Field, { label: t.addresses.label, value: form.label, onChange: set("label") }),
2418
+ /* @__PURE__ */ jsx30(Field, { label: t.addresses.firstName, value: form.firstName, onChange: set("firstName"), autoComplete: "given-name" }),
2419
+ /* @__PURE__ */ jsx30(Field, { label: t.addresses.lastName, value: form.lastName, onChange: set("lastName"), autoComplete: "family-name" }),
2420
+ /* @__PURE__ */ jsx30(Field, { label: t.addresses.company, value: form.company, onChange: set("company"), autoComplete: "organization" }),
2421
+ /* @__PURE__ */ jsx30(
2422
+ Field,
2423
+ {
2424
+ label: t.addresses.addressLine1,
2425
+ value: form.addressLine1,
2426
+ onChange: set("addressLine1"),
2427
+ required: true,
2428
+ autoComplete: "address-line1"
2429
+ }
2430
+ ),
2431
+ /* @__PURE__ */ jsx30(
2432
+ Field,
2433
+ {
2434
+ label: t.addresses.addressLine2,
2435
+ value: form.addressLine2,
2436
+ onChange: set("addressLine2"),
2437
+ autoComplete: "address-line2"
2438
+ }
2439
+ ),
2440
+ /* @__PURE__ */ jsx30(Field, { label: t.addresses.city, value: form.city, onChange: set("city"), required: true, autoComplete: "address-level2" }),
2441
+ /* @__PURE__ */ jsx30(
2442
+ Field,
2443
+ {
2444
+ label: t.addresses.stateProvince,
2445
+ value: form.stateProvince,
2446
+ onChange: set("stateProvince"),
2447
+ autoComplete: "address-level1"
2448
+ }
2449
+ ),
2450
+ /* @__PURE__ */ jsx30(
2451
+ Field,
2452
+ {
2453
+ label: t.addresses.postalCode,
2454
+ value: form.postalCode,
2455
+ onChange: set("postalCode"),
2456
+ autoComplete: "postal-code"
2457
+ }
2458
+ ),
2459
+ /* @__PURE__ */ jsx30(
2460
+ Field,
2461
+ {
2462
+ label: t.addresses.countryCode,
2463
+ value: form.countryCode,
2464
+ onChange: set("countryCode"),
2465
+ required: true,
2466
+ autoComplete: "country",
2467
+ hint: "ISO 3166-1 alpha-2, e.g. GB"
2468
+ }
2469
+ ),
2470
+ /* @__PURE__ */ jsx30(Field, { label: t.addresses.phone, type: "tel", value: form.phone, onChange: set("phone"), autoComplete: "tel" }),
2471
+ props.error ? /* @__PURE__ */ jsx30(Alert, { tone: "error", children: props.error }) : null,
2472
+ /* @__PURE__ */ jsxs21("div", { className: "throttle-actions", children: [
2473
+ /* @__PURE__ */ jsx30(Button, { type: "submit", loading: props.submitting, children: t.common.save }),
2474
+ /* @__PURE__ */ jsx30(Button, { variant: "link", onClick: props.onCancel, children: t.common.cancel })
2475
+ ] })
2476
+ ]
2477
+ }
2478
+ );
2479
+ }
2480
+
2481
+ // src/react/components/account/AddressesPanel.tsx
2482
+ import { Fragment as Fragment5, jsx as jsx31, jsxs as jsxs22 } from "react/jsx-runtime";
2483
+ function AddressesPanel(props) {
2484
+ const a = useAppearance(props.appearance);
2485
+ const scoped = useAppearanceProviderValue(props.appearance);
2486
+ const { t } = useCopy(props.localization);
2487
+ const resource = useAddresses();
2488
+ const { submitting, error, run } = useAction();
2489
+ const [editing, setEditing] = useState14(null);
2490
+ const variant = props.variant ?? "rows";
2491
+ const rowsVariant = variant === "rows";
2492
+ const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
2493
+ const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
2494
+ const actionVariant = (base) => rowsVariant ? base === "danger" ? "linkDanger" : "link" : base;
2495
+ const close = () => setEditing(null);
2496
+ const formatAddress = (address) => [address.addressLine2, address.city, address.stateProvince, address.postalCode, address.countryCode].filter(Boolean).join(", ");
2497
+ return /* @__PURE__ */ jsx31(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx31("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs22("div", { ...a.el("addressesPanel.root", "throttle-panel"), children: [
2498
+ !editing && error ? /* @__PURE__ */ jsx31(Alert, { tone: "error", children: error }) : null,
2499
+ editing ? /* @__PURE__ */ jsx31(
2500
+ AddressForm,
2501
+ {
2502
+ initial: editing === "new" ? void 0 : editing,
2503
+ submitting,
2504
+ error,
2505
+ onCancel: close,
2506
+ onSubmit: (input) => void run(async () => {
2507
+ if (editing === "new") await resource.create(input);
2508
+ else await resource.update(editing.id, input);
2509
+ close();
2510
+ })
2511
+ }
2512
+ ) : /* @__PURE__ */ jsxs22(Fragment5, { children: [
2513
+ /* @__PURE__ */ jsx31(Button, { elementKey: "addressesPanel.addButton", variant: "secondary", onClick: () => setEditing("new"), children: t.addresses.addButton }),
2514
+ /* @__PURE__ */ jsx31(
2515
+ PanelState,
2516
+ {
2517
+ resource,
2518
+ isEmpty: (rows) => rows.length === 0,
2519
+ emptyTitle: t.addresses.empty,
2520
+ emptyDescription: t.addresses.emptyDescription,
2521
+ children: (rows) => variant === "table" ? /* @__PURE__ */ jsx31(
2522
+ DataTable,
2523
+ {
2524
+ tableKey: "addressesPanel.table",
2525
+ rowElementKey: "addressesPanel.row",
2526
+ rowKey: (address) => address.id,
2527
+ rows,
2528
+ columns: [
2529
+ { key: "label", label: t.addresses.label, render: (address) => address.label || address.addressLine1 },
2530
+ { key: "address", label: t.addresses.columnAddress, render: (address) => /* @__PURE__ */ jsxs22(Fragment5, { children: [
2531
+ address.addressLine1,
2532
+ formatAddress(address) ? `, ${formatAddress(address)}` : ""
2533
+ ] }) },
2534
+ { key: "default", label: t.addresses.defaultBadge, render: (address) => address.isDefault ? /* @__PURE__ */ jsx31(Badge, { tone: "success", children: t.addresses.defaultBadge }) : null },
2535
+ { key: "actions", label: t.common.actions, render: (address) => /* @__PURE__ */ jsxs22("div", { className: "throttle-actions", children: [
2536
+ !address.isDefault ? /* @__PURE__ */ jsx31(
2537
+ Button,
2538
+ {
2539
+ variant: "secondary",
2540
+ elementKey: "addressesPanel.defaultButton",
2541
+ onClick: () => void run(() => resource.update(address.id, { isDefault: true })),
2542
+ children: t.addresses.makeDefault
2543
+ }
2544
+ ) : null,
2545
+ /* @__PURE__ */ jsx31(
2546
+ Button,
2547
+ {
2548
+ variant: "secondary",
2549
+ elementKey: "addressesPanel.editButton",
2550
+ onClick: () => setEditing(address),
2551
+ children: t.common.edit
2552
+ }
2553
+ ),
2554
+ /* @__PURE__ */ jsx31(
2555
+ Button,
2556
+ {
2557
+ variant: "danger",
2558
+ elementKey: "addressesPanel.deleteButton",
2559
+ disabled: submitting,
2560
+ onClick: () => {
2561
+ if (!window.confirm(t.addresses.deleteConfirm)) return;
2562
+ void run(() => resource.remove(address.id));
2563
+ },
2564
+ children: t.common.delete
2565
+ }
2566
+ )
2567
+ ] }) }
2568
+ ]
2569
+ }
2570
+ ) : /* @__PURE__ */ jsx31("div", { ...a.el("addressesPanel.list", listBase), children: rows.map((address) => /* @__PURE__ */ jsxs22("div", { ...a.el("addressesPanel.item", itemBase), children: [
2571
+ /* @__PURE__ */ jsxs22("div", { children: [
2572
+ /* @__PURE__ */ jsxs22("div", { className: "throttle-row", children: [
2573
+ /* @__PURE__ */ jsx31("strong", { children: address.label || address.addressLine1 }),
2574
+ address.isDefault ? /* @__PURE__ */ jsx31(Badge, { tone: "success", children: t.addresses.defaultBadge }) : null
2575
+ ] }),
2576
+ /* @__PURE__ */ jsx31("p", { className: "throttle-muted", children: address.addressLine1 }),
2577
+ /* @__PURE__ */ jsx31("p", { className: "throttle-muted", children: formatAddress(address) })
2578
+ ] }),
2579
+ /* @__PURE__ */ jsxs22("div", { className: "throttle-actions", children: [
2580
+ !address.isDefault ? /* @__PURE__ */ jsx31(
2581
+ Button,
2582
+ {
2583
+ variant: actionVariant("secondary"),
2584
+ elementKey: "addressesPanel.defaultButton",
2585
+ onClick: () => void run(() => resource.update(address.id, { isDefault: true })),
2586
+ children: t.addresses.makeDefault
2587
+ }
2588
+ ) : null,
2589
+ /* @__PURE__ */ jsx31(
2590
+ Button,
2591
+ {
2592
+ variant: actionVariant("secondary"),
2593
+ elementKey: "addressesPanel.editButton",
2594
+ onClick: () => setEditing(address),
2595
+ children: t.common.edit
2596
+ }
2597
+ ),
2598
+ /* @__PURE__ */ jsx31(
2599
+ Button,
2600
+ {
2601
+ variant: actionVariant("danger"),
2602
+ elementKey: "addressesPanel.deleteButton",
2603
+ disabled: submitting,
2604
+ onClick: () => {
2605
+ if (!window.confirm(t.addresses.deleteConfirm)) return;
2606
+ void run(() => resource.remove(address.id));
2607
+ },
2608
+ children: t.common.delete
2609
+ }
2610
+ )
2611
+ ] })
2612
+ ] }, address.id)) })
2613
+ }
2614
+ )
2615
+ ] })
2616
+ ] }) }) });
2617
+ }
2618
+
2619
+ // src/react/components/account/PaymentMethodsPanel.tsx
2620
+ import { jsx as jsx32, jsxs as jsxs23 } from "react/jsx-runtime";
2621
+ function PaymentMethodsPanel(props) {
2622
+ const a = useAppearance(props.appearance);
2623
+ const scoped = useAppearanceProviderValue(props.appearance);
2624
+ const { t } = useCopy(props.localization);
2625
+ const { emailVerified } = useAuth();
2626
+ const resource = useSavedCards();
2627
+ const { submitting, error, run } = useAction();
2628
+ const variant = props.variant ?? "rows";
2629
+ const rowsVariant = variant === "rows";
2630
+ const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
2631
+ const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
2632
+ const actionVariant = (base) => rowsVariant ? base === "danger" ? "linkDanger" : "link" : base;
2633
+ const cardLabel = (card) => `${(card.cardBrand ?? card.methodType).toUpperCase()} \u2022\u2022\u2022\u2022 ${card.cardLastFour ?? "____"}`;
2634
+ const expiryLabel = (card) => card.cardExpMonth && card.cardExpYear ? `${String(card.cardExpMonth).padStart(2, "0")}/${card.cardExpYear}` : "";
2635
+ return /* @__PURE__ */ jsx32(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx32("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs23("div", { ...a.el("paymentMethodsPanel.root", "throttle-panel"), children: [
2636
+ error ? /* @__PURE__ */ jsx32(Alert, { tone: "error", children: error }) : null,
2637
+ /* @__PURE__ */ jsx32(
2638
+ PanelState,
2639
+ {
2640
+ resource,
2641
+ gated: !emailVerified,
2642
+ gatedDescription: t.paymentMethods.gatedDescription,
2643
+ isEmpty: (rows) => rows.length === 0,
2644
+ emptyTitle: t.paymentMethods.empty,
2645
+ emptyDescription: t.paymentMethods.emptyDescription,
2646
+ children: (rows) => variant === "table" ? /* @__PURE__ */ jsx32(
2647
+ DataTable,
2648
+ {
2649
+ tableKey: "paymentMethodsPanel.table",
2650
+ rowElementKey: "paymentMethodsPanel.row",
2651
+ rowKey: (card) => card.id,
2652
+ rows,
2653
+ columns: [
2654
+ { key: "card", label: t.paymentMethods.columnCard, render: (card) => cardLabel(card) },
2655
+ { key: "expires", label: t.paymentMethods.expires, render: (card) => expiryLabel(card) },
2656
+ { key: "default", label: t.paymentMethods.defaultBadge, render: (card) => card.isDefault ? /* @__PURE__ */ jsx32(Badge, { tone: "success", children: t.paymentMethods.defaultBadge }) : null },
2657
+ { key: "actions", label: t.common.actions, render: (card) => /* @__PURE__ */ jsxs23("div", { className: "throttle-actions", children: [
2658
+ !card.isDefault ? /* @__PURE__ */ jsx32(
2659
+ Button,
2660
+ {
2661
+ variant: "secondary",
2662
+ loading: submitting,
2663
+ elementKey: "paymentMethodsPanel.defaultButton",
2664
+ onClick: () => void run(() => resource.setDefault(card.id)),
2665
+ children: t.paymentMethods.makeDefault
2666
+ }
2667
+ ) : null,
2668
+ /* @__PURE__ */ jsx32(
2669
+ Button,
2670
+ {
2671
+ variant: "danger",
2672
+ elementKey: "paymentMethodsPanel.deleteButton",
2673
+ disabled: submitting,
2674
+ onClick: () => {
2675
+ if (!window.confirm(t.paymentMethods.deleteConfirm)) return;
2676
+ void run(() => resource.remove(card.id));
2677
+ },
2678
+ children: t.common.delete
2679
+ }
2680
+ )
2681
+ ] }) }
2682
+ ]
2683
+ }
2684
+ ) : /* @__PURE__ */ jsx32("div", { ...a.el("paymentMethodsPanel.list", listBase), children: rows.map((card) => /* @__PURE__ */ jsxs23("div", { ...a.el("paymentMethodsPanel.item", itemBase), children: [
2685
+ /* @__PURE__ */ jsxs23("div", { children: [
2686
+ /* @__PURE__ */ jsxs23("div", { className: "throttle-row", children: [
2687
+ /* @__PURE__ */ jsx32("strong", { children: cardLabel(card) }),
2688
+ card.isDefault ? /* @__PURE__ */ jsx32(Badge, { tone: "success", children: t.paymentMethods.defaultBadge }) : null
2689
+ ] }),
2690
+ card.cardExpMonth && card.cardExpYear ? /* @__PURE__ */ jsxs23("p", { className: "throttle-muted", children: [
2691
+ t.paymentMethods.expires,
2692
+ " ",
2693
+ String(card.cardExpMonth).padStart(2, "0"),
2694
+ "/",
2695
+ card.cardExpYear
2696
+ ] }) : null
2697
+ ] }),
2698
+ /* @__PURE__ */ jsxs23("div", { className: "throttle-actions", children: [
2699
+ !card.isDefault ? /* @__PURE__ */ jsx32(
2700
+ Button,
2701
+ {
2702
+ variant: actionVariant("secondary"),
2703
+ loading: submitting,
2704
+ elementKey: "paymentMethodsPanel.defaultButton",
2705
+ onClick: () => void run(() => resource.setDefault(card.id)),
2706
+ children: t.paymentMethods.makeDefault
2707
+ }
2708
+ ) : null,
2709
+ /* @__PURE__ */ jsx32(
2710
+ Button,
2711
+ {
2712
+ variant: actionVariant("danger"),
2713
+ elementKey: "paymentMethodsPanel.deleteButton",
2714
+ disabled: submitting,
2715
+ onClick: () => {
2716
+ if (!window.confirm(t.paymentMethods.deleteConfirm)) return;
2717
+ void run(() => resource.remove(card.id));
2718
+ },
2719
+ children: t.common.delete
2720
+ }
2721
+ )
2722
+ ] })
2723
+ ] }, card.id)) })
2724
+ }
2725
+ )
2726
+ ] }) }) });
2727
+ }
2728
+
2729
+ // src/react/components/account/OrdersPanel.tsx
2730
+ import { useState as useState15 } from "react";
2731
+
2732
+ // src/react/components/format.ts
2733
+ function formatMoney(minorUnits, currency, locale) {
2734
+ const code = (currency || "USD").toUpperCase();
2735
+ let digits = 2;
2736
+ try {
2737
+ digits = new Intl.NumberFormat("en-US", { style: "currency", currency: code }).resolvedOptions().maximumFractionDigits ?? 2;
2738
+ } catch {
2739
+ }
2740
+ const major = minorUnits / 10 ** digits;
2741
+ try {
2742
+ return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(major);
2743
+ } catch {
2744
+ try {
2745
+ return new Intl.NumberFormat("en-US", { style: "currency", currency: code }).format(major);
2746
+ } catch {
2747
+ return `${code} ${major.toFixed(digits)}`;
2748
+ }
2749
+ }
2750
+ }
2751
+ function formatDate(value, locale) {
2752
+ const date = value instanceof Date ? value : new Date(value);
2753
+ if (Number.isNaN(date.getTime())) return "";
2754
+ try {
2755
+ return new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(date);
2756
+ } catch {
2757
+ return date.toISOString().slice(0, 10);
2758
+ }
2759
+ }
2760
+
2761
+ // src/react/components/account/OrderStatus.tsx
2762
+ import { Fragment as Fragment6, jsx as jsx33, jsxs as jsxs24 } from "react/jsx-runtime";
2763
+ function copyKey(wire) {
2764
+ return wire.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
2765
+ }
2766
+ var ATTENTION_PAYMENT_STATES = /* @__PURE__ */ new Set(["pending", "failed", "disputed", "expired"]);
2767
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["cancelled", "closed", "void", "expired", "voided"]);
2768
+ function StatusBadge(props) {
2769
+ const label = props.labels[copyKey(props.status)] ?? props.status.replace(/_/g, " ");
2770
+ return /* @__PURE__ */ jsx33(Badge, { tone: TERMINAL_STATUSES.has(props.status) ? "muted" : "neutral", children: label });
2771
+ }
2772
+ function OrderStatus(props) {
2773
+ const labels = props.t.orders.statusLabels;
2774
+ const payLabels = props.t.orders.paymentLabels;
2775
+ const lifecycle = labels[copyKey(props.status)] ?? props.status.replace(/_/g, " ");
2776
+ const money = props.paymentStatus ? payLabels[copyKey(props.paymentStatus)] : void 0;
2777
+ return /* @__PURE__ */ jsxs24(Fragment6, { children: [
2778
+ /* @__PURE__ */ jsx33(Badge, { tone: TERMINAL_STATUSES.has(props.status) ? "muted" : "neutral", children: lifecycle }),
2779
+ money ? /* @__PURE__ */ jsx33(Badge, { tone: ATTENTION_PAYMENT_STATES.has(props.paymentStatus) ? "warning" : "neutral", children: money }) : null
2780
+ ] });
2781
+ }
2782
+
2783
+ // src/react/components/account/OrderDetail.tsx
2784
+ import { Fragment as Fragment7, jsx as jsx34, jsxs as jsxs25 } from "react/jsx-runtime";
2785
+ function Line(props) {
2786
+ return /* @__PURE__ */ jsxs25("div", { className: "throttle-row", children: [
2787
+ /* @__PURE__ */ jsx34("span", { className: "throttle-muted", children: props.label }),
2788
+ /* @__PURE__ */ jsx34("span", { children: props.value })
2789
+ ] });
2790
+ }
2791
+ function OrderDetail(props) {
2792
+ const a = useAppearance(props.appearance);
2793
+ const scoped = useAppearanceProviderValue(props.appearance);
2794
+ const { t, locale } = useCopy(props.localization);
2795
+ const resource = useOrder(props.orderId);
2796
+ return /* @__PURE__ */ jsx34(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx34("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs25("div", { ...a.el("orderDetail.root", "throttle-panel"), children: [
2797
+ props.onBack ? /* @__PURE__ */ jsx34(Button, { variant: "link", elementKey: "orderDetail.backButton", onClick: props.onBack, children: t.common.back }) : null,
2798
+ /* @__PURE__ */ jsx34(
2799
+ PanelState,
2800
+ {
2801
+ resource,
2802
+ isEmpty: (o) => o === null,
2803
+ emptyTitle: t.orders.empty,
2804
+ skeletonRows: 5,
2805
+ children: (order) => /* @__PURE__ */ jsxs25(Fragment7, { children: [
2806
+ /* @__PURE__ */ jsxs25("div", { className: "throttle-row", children: [
2807
+ /* @__PURE__ */ jsx34("h3", { children: order.orderNumber }),
2808
+ /* @__PURE__ */ jsx34(OrderStatus, { status: order.status, paymentStatus: order.paymentStatus, t })
2809
+ ] }),
2810
+ /* @__PURE__ */ jsx34("p", { className: "throttle-muted", children: formatDate(order.createdAt, locale) }),
2811
+ /* @__PURE__ */ jsx34("div", { ...a.el("orderDetail.lineItems", "throttle-stack"), children: order.lineItems.map((item) => /* @__PURE__ */ jsxs25("div", { ...a.el("orderDetail.lineItem", "throttle-lineitem"), children: [
2812
+ item.imageUrl ? /* @__PURE__ */ jsx34("img", { className: "throttle-lineitem__image", src: item.imageUrl, alt: "" }) : null,
2813
+ /* @__PURE__ */ jsxs25("div", { style: { flex: 1 }, children: [
2814
+ /* @__PURE__ */ jsx34("strong", { children: item.name }),
2815
+ item.description ? /* @__PURE__ */ jsx34("p", { className: "throttle-muted", children: item.description }) : null,
2816
+ /* @__PURE__ */ jsxs25("p", { className: "throttle-muted", children: [
2817
+ t.orders.quantity,
2818
+ " ",
2819
+ item.quantity,
2820
+ " \xD7 ",
2821
+ formatMoney(item.unitPrice, order.currency, locale)
2822
+ ] })
2823
+ ] }),
2824
+ /* @__PURE__ */ jsx34("span", { children: formatMoney(item.total, order.currency, locale) })
2825
+ ] }, item.id)) }),
2826
+ /* @__PURE__ */ jsxs25("div", { ...a.el("orderDetail.totals", "throttle-totals"), children: [
2827
+ /* @__PURE__ */ jsx34(Line, { label: t.orders.subtotal, value: formatMoney(order.subtotal, order.currency, locale) }),
2828
+ order.discountTotal ? /* @__PURE__ */ jsx34(Line, { label: t.orders.discount, value: formatMoney(-order.discountTotal, order.currency, locale) }) : null,
2829
+ /* @__PURE__ */ jsx34(Line, { label: t.orders.shipping, value: formatMoney(order.shippingTotal, order.currency, locale) }),
2830
+ /* @__PURE__ */ jsx34(Line, { label: t.orders.tax, value: formatMoney(order.taxTotal, order.currency, locale) }),
2831
+ /* @__PURE__ */ jsx34(Line, { label: t.orders.total, value: formatMoney(order.total, order.currency, locale) })
2832
+ ] }),
2833
+ order.subscriptionId && props.onViewSubscription ? /* @__PURE__ */ jsx34(Button, { variant: "secondary", onClick: () => props.onViewSubscription(order.subscriptionId), children: t.orders.viewSubscription }) : null
2834
+ ] })
2835
+ }
2836
+ )
2837
+ ] }) }) });
2838
+ }
2839
+
2840
+ // src/react/components/account/OrdersPanel.tsx
2841
+ import { jsx as jsx35, jsxs as jsxs26 } from "react/jsx-runtime";
2842
+ function OrdersPanel(props) {
2843
+ const a = useAppearance(props.appearance);
2844
+ const scoped = useAppearanceProviderValue(props.appearance);
2845
+ const { t, locale } = useCopy(props.localization);
2846
+ const { emailVerified } = useAuth();
2847
+ const resource = useOrders();
2848
+ const [openId, setOpenId] = useState15(null);
2849
+ const variant = props.variant ?? "table";
2850
+ const rowsVariant = variant === "rows";
2851
+ const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
2852
+ const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
2853
+ if (openId) {
2854
+ return /* @__PURE__ */ jsx35(
2855
+ OrderDetail,
2856
+ {
2857
+ orderId: openId,
2858
+ onBack: () => setOpenId(null),
2859
+ onViewSubscription: props.onViewSubscription,
2860
+ appearance: props.appearance,
2861
+ localization: props.localization
2862
+ }
2863
+ );
2864
+ }
2865
+ return /* @__PURE__ */ jsx35(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx35("div", { ...a.rootProps, children: /* @__PURE__ */ jsx35("div", { ...a.el("ordersPanel.root", "throttle-panel"), children: /* @__PURE__ */ jsx35(
2866
+ PanelState,
2867
+ {
2868
+ resource,
2869
+ gated: !emailVerified,
2870
+ gatedDescription: t.orders.gatedDescription,
2871
+ isEmpty: (rows) => rows.length === 0,
2872
+ emptyTitle: t.orders.empty,
2873
+ emptyDescription: t.orders.emptyDescription,
2874
+ children: (rows) => variant === "table" ? /* @__PURE__ */ jsx35(
2875
+ DataTable,
2876
+ {
2877
+ tableKey: "ordersPanel.table",
2878
+ rowElementKey: "ordersPanel.row",
2879
+ rowKey: (o) => o.id,
2880
+ onRowClick: (o) => setOpenId(o.id),
2881
+ rows,
2882
+ columns: [
2883
+ { key: "orderNumber", label: t.orders.number },
2884
+ { key: "createdAt", label: t.orders.date, render: (o) => formatDate(o.createdAt, locale) },
2885
+ {
2886
+ key: "status",
2887
+ label: t.orders.status,
2888
+ render: (o) => /* @__PURE__ */ jsx35(OrderStatus, { status: o.status, paymentStatus: o.paymentStatus, t })
2889
+ },
2890
+ {
2891
+ key: "total",
2892
+ label: t.orders.total,
2893
+ render: (o) => /* @__PURE__ */ jsx35("span", { className: "throttle-table__money", children: formatMoney(o.total, o.currency, locale) })
2894
+ }
2895
+ ]
2896
+ }
2897
+ ) : /* @__PURE__ */ jsx35("div", { ...a.el("ordersPanel.list", listBase), children: rows.map((o) => /* @__PURE__ */ jsxs26("div", { ...a.el("ordersPanel.item", itemBase), children: [
2898
+ /* @__PURE__ */ jsxs26("div", { children: [
2899
+ /* @__PURE__ */ jsxs26("div", { className: "throttle-row", children: [
2900
+ /* @__PURE__ */ jsx35("strong", { children: o.orderNumber }),
2901
+ /* @__PURE__ */ jsx35(OrderStatus, { status: o.status, paymentStatus: o.paymentStatus, t })
2902
+ ] }),
2903
+ /* @__PURE__ */ jsx35("p", { className: "throttle-muted", children: formatDate(o.createdAt, locale) }),
2904
+ /* @__PURE__ */ jsx35("p", { className: "throttle-muted", children: formatMoney(o.total, o.currency, locale) })
2905
+ ] }),
2906
+ /* @__PURE__ */ jsx35("div", { className: "throttle-actions", children: /* @__PURE__ */ jsx35(
2907
+ Button,
2908
+ {
2909
+ variant: rowsVariant ? "link" : "secondary",
2910
+ elementKey: "ordersPanel.viewButton",
2911
+ onClick: () => setOpenId(o.id),
2912
+ children: t.common.view
2913
+ }
2914
+ ) })
2915
+ ] }, o.id)) })
2916
+ }
2917
+ ) }) }) });
2918
+ }
2919
+
2920
+ // src/react/components/account/InvoicesPanel.tsx
2921
+ import { useState as useState16 } from "react";
2922
+
2923
+ // src/react/components/account/InvoiceDetail.tsx
2924
+ import { Fragment as Fragment8, jsx as jsx36, jsxs as jsxs27 } from "react/jsx-runtime";
2925
+ function Line2(props) {
2926
+ return /* @__PURE__ */ jsxs27("div", { className: "throttle-row", children: [
2927
+ /* @__PURE__ */ jsx36("span", { className: "throttle-muted", children: props.label }),
2928
+ /* @__PURE__ */ jsx36("span", { children: props.value })
2929
+ ] });
2930
+ }
2931
+ function InvoiceDetail(props) {
2932
+ const a = useAppearance(props.appearance);
2933
+ const scoped = useAppearanceProviderValue(props.appearance);
2934
+ const { t, locale } = useCopy(props.localization);
2935
+ const { account } = useAuth();
2936
+ const resource = useInvoice(props.invoiceId);
2937
+ const { submitting, error, run } = useAction();
2938
+ return /* @__PURE__ */ jsx36(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx36("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs27("div", { ...a.el("invoiceDetail.root", "throttle-panel"), children: [
2939
+ props.onBack ? /* @__PURE__ */ jsx36(Button, { variant: "link", elementKey: "invoiceDetail.backButton", onClick: props.onBack, children: t.common.back }) : null,
2940
+ /* @__PURE__ */ jsx36(PanelState, { resource, isEmpty: (i) => i === null, emptyTitle: t.invoices.empty, skeletonRows: 4, children: (invoice) => /* @__PURE__ */ jsxs27(Fragment8, { children: [
2941
+ /* @__PURE__ */ jsxs27("div", { className: "throttle-row", children: [
2942
+ /* @__PURE__ */ jsx36("h3", { children: invoice.number }),
2943
+ /* @__PURE__ */ jsx36(StatusBadge, { status: invoice.status, labels: t.invoices.statusLabels })
2944
+ ] }),
2945
+ /* @__PURE__ */ jsxs27("div", { ...a.el("invoiceDetail.totals", "throttle-totals"), children: [
2946
+ /* @__PURE__ */ jsx36(Line2, { label: t.invoices.issued, value: formatDate(invoice.issuedAt, locale) }),
2947
+ invoice.dueAt ? /* @__PURE__ */ jsx36(Line2, { label: t.invoices.due, value: formatDate(invoice.dueAt, locale) }) : null,
2948
+ invoice.periodStart && invoice.periodEnd ? /* @__PURE__ */ jsx36(
2949
+ Line2,
2950
+ {
2951
+ label: t.invoices.period,
2952
+ value: `${formatDate(invoice.periodStart, locale)} \u2013 ${formatDate(invoice.periodEnd, locale)}`
2953
+ }
2954
+ ) : null,
2955
+ /* @__PURE__ */ jsx36(Line2, { label: t.invoices.total, value: formatMoney(invoice.total, invoice.currency, locale) }),
2956
+ invoice.amountRefunded ? /* @__PURE__ */ jsx36(
2957
+ Line2,
2958
+ {
2959
+ label: t.invoices.refunded,
2960
+ value: formatMoney(invoice.amountRefunded, invoice.currency, locale)
2961
+ }
2962
+ ) : null
2963
+ ] }),
2964
+ error ? /* @__PURE__ */ jsx36(Alert, { tone: "error", children: error }) : null,
2965
+ /* @__PURE__ */ jsx36(
2966
+ Button,
2967
+ {
2968
+ variant: "secondary",
2969
+ loading: submitting,
2970
+ elementKey: "invoiceDetail.downloadButton",
2971
+ onClick: () => void run(async () => {
2972
+ const { url } = await account.getInvoicePdfUrl(invoice.id);
2973
+ const opened = window.open(url, "_blank", "noopener,noreferrer");
2974
+ if (!opened) window.location.assign(url);
2975
+ }),
2976
+ children: t.invoices.downloadButton
2977
+ }
2978
+ )
2979
+ ] }) })
2980
+ ] }) }) });
2981
+ }
2982
+
2983
+ // src/react/components/account/InvoicesPanel.tsx
2984
+ import { jsx as jsx37, jsxs as jsxs28 } from "react/jsx-runtime";
2985
+ function InvoicesPanel(props) {
2986
+ const a = useAppearance(props.appearance);
2987
+ const scoped = useAppearanceProviderValue(props.appearance);
2988
+ const { t, locale } = useCopy(props.localization);
2989
+ const { emailVerified } = useAuth();
2990
+ const resource = useInvoices();
2991
+ const [openId, setOpenId] = useState16(null);
2992
+ const variant = props.variant ?? "table";
2993
+ const rowsVariant = variant === "rows";
2994
+ const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
2995
+ const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
2996
+ if (openId) {
2997
+ return /* @__PURE__ */ jsx37(
2998
+ InvoiceDetail,
2999
+ {
3000
+ invoiceId: openId,
3001
+ onBack: () => setOpenId(null),
3002
+ appearance: props.appearance,
3003
+ localization: props.localization
3004
+ }
3005
+ );
3006
+ }
3007
+ return /* @__PURE__ */ jsx37(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx37("div", { ...a.rootProps, children: /* @__PURE__ */ jsx37("div", { ...a.el("invoicesPanel.root", "throttle-panel"), children: /* @__PURE__ */ jsx37(
3008
+ PanelState,
3009
+ {
3010
+ resource,
3011
+ gated: !emailVerified,
3012
+ gatedDescription: t.invoices.gatedDescription,
3013
+ isEmpty: (rows) => rows.length === 0,
3014
+ emptyTitle: t.invoices.empty,
3015
+ emptyDescription: t.invoices.emptyDescription,
3016
+ children: (rows) => variant === "table" ? /* @__PURE__ */ jsx37(
3017
+ DataTable,
3018
+ {
3019
+ tableKey: "invoicesPanel.table",
3020
+ rowElementKey: "invoicesPanel.row",
3021
+ rowKey: (i) => i.id,
3022
+ onRowClick: (i) => setOpenId(i.id),
3023
+ rows,
3024
+ columns: [
3025
+ { key: "number", label: t.invoices.number },
3026
+ { key: "issuedAt", label: t.invoices.issued, render: (i) => formatDate(i.issuedAt, locale) },
3027
+ { key: "status", label: t.invoices.status, render: (i) => /* @__PURE__ */ jsx37(StatusBadge, { status: i.status, labels: t.invoices.statusLabels }) },
3028
+ {
3029
+ key: "total",
3030
+ label: t.invoices.total,
3031
+ render: (i) => /* @__PURE__ */ jsx37("span", { className: "throttle-table__money", children: formatMoney(i.total, i.currency, locale) })
3032
+ }
3033
+ ]
3034
+ }
3035
+ ) : /* @__PURE__ */ jsx37("div", { ...a.el("invoicesPanel.list", listBase), children: rows.map((i) => /* @__PURE__ */ jsxs28("div", { ...a.el("invoicesPanel.item", itemBase), children: [
3036
+ /* @__PURE__ */ jsxs28("div", { children: [
3037
+ /* @__PURE__ */ jsxs28("div", { className: "throttle-row", children: [
3038
+ /* @__PURE__ */ jsx37("strong", { children: i.number }),
3039
+ /* @__PURE__ */ jsx37(StatusBadge, { status: i.status, labels: t.invoices.statusLabels })
3040
+ ] }),
3041
+ /* @__PURE__ */ jsx37("p", { className: "throttle-muted", children: formatDate(i.issuedAt, locale) }),
3042
+ /* @__PURE__ */ jsx37("p", { className: "throttle-muted", children: formatMoney(i.total, i.currency, locale) })
3043
+ ] }),
3044
+ /* @__PURE__ */ jsx37("div", { className: "throttle-actions", children: /* @__PURE__ */ jsx37(
3045
+ Button,
3046
+ {
3047
+ variant: rowsVariant ? "link" : "secondary",
3048
+ elementKey: "invoicesPanel.viewButton",
3049
+ onClick: () => setOpenId(i.id),
3050
+ children: t.common.view
3051
+ }
3052
+ ) })
3053
+ ] }, i.id)) })
3054
+ }
3055
+ ) }) }) });
3056
+ }
3057
+
3058
+ // src/react/components/account/SubscriptionsPanel.tsx
3059
+ import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
3060
+ var CAN_PAUSE = /* @__PURE__ */ new Set(["active", "trialing"]);
3061
+ var CAN_RESUME = /* @__PURE__ */ new Set(["paused"]);
3062
+ var CAN_CANCEL = /* @__PURE__ */ new Set(["active", "trialing", "paused", "past_due", "unpaid"]);
3063
+ function SubscriptionsPanel(props) {
3064
+ const a = useAppearance(props.appearance);
3065
+ const scoped = useAppearanceProviderValue(props.appearance);
3066
+ const { t, locale } = useCopy(props.localization);
3067
+ const { emailVerified } = useAuth();
3068
+ const resource = useSubscriptions();
3069
+ const { submitting, error, run } = useAction();
3070
+ const variant = props.variant ?? "rows";
3071
+ const rowsVariant = variant === "rows";
3072
+ const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
3073
+ const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
3074
+ const actionVariant = (base) => rowsVariant ? base === "danger" ? "linkDanger" : "link" : base;
3075
+ const confirmThen = (fn) => () => {
3076
+ if (!window.confirm(t.subscriptions.cancelConfirm)) return;
3077
+ void run(fn);
3078
+ };
3079
+ const priceLabel = (sub) => `${formatMoney(sub.amount, sub.currency, locale)} / ${sub.interval}${sub.quantity > 1 ? ` \xD7 ${sub.quantity}` : ""}`;
3080
+ const renewsLabel = (sub) => formatDate(sub.status === "cancelled" ? sub.cancelledAt ?? sub.currentPeriodEnd : sub.currentPeriodEnd, locale);
3081
+ const lifecycleActions = (sub, variantFor) => /* @__PURE__ */ jsxs29("div", { className: "throttle-actions", children: [
3082
+ CAN_PAUSE.has(sub.status) ? /* @__PURE__ */ jsx38(
3083
+ Button,
3084
+ {
3085
+ variant: variantFor("secondary"),
3086
+ loading: submitting,
3087
+ elementKey: "subscriptionsPanel.pauseButton",
3088
+ onClick: () => void run(() => resource.pause(sub.id)),
3089
+ children: t.subscriptions.pauseButton
3090
+ }
3091
+ ) : null,
3092
+ CAN_RESUME.has(sub.status) ? /* @__PURE__ */ jsx38(
3093
+ Button,
3094
+ {
3095
+ variant: variantFor("secondary"),
3096
+ loading: submitting,
3097
+ elementKey: "subscriptionsPanel.resumeButton",
3098
+ onClick: () => void run(() => resource.resume(sub.id)),
3099
+ children: t.subscriptions.resumeButton
3100
+ }
3101
+ ) : null,
3102
+ CAN_CANCEL.has(sub.status) && !sub.cancelAtPeriodEnd ? /* @__PURE__ */ jsx38(
3103
+ Button,
3104
+ {
3105
+ variant: variantFor("secondary"),
3106
+ elementKey: "subscriptionsPanel.cancelButton",
3107
+ disabled: submitting,
3108
+ onClick: confirmThen(() => resource.cancel(sub.id, { atPeriodEnd: true })),
3109
+ children: t.subscriptions.cancelAtPeriodEnd
3110
+ }
3111
+ ) : null,
3112
+ CAN_CANCEL.has(sub.status) ? /* @__PURE__ */ jsx38(
3113
+ Button,
3114
+ {
3115
+ variant: variantFor("danger"),
3116
+ elementKey: "subscriptionsPanel.cancelNowButton",
3117
+ disabled: submitting,
3118
+ onClick: confirmThen(() => resource.cancel(sub.id, { atPeriodEnd: false })),
3119
+ children: t.subscriptions.cancelNow
3120
+ }
3121
+ ) : null
3122
+ ] });
3123
+ return /* @__PURE__ */ jsx38(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx38("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs29("div", { ...a.el("subscriptionsPanel.root", "throttle-panel"), children: [
3124
+ error ? /* @__PURE__ */ jsx38(Alert, { tone: "error", children: error }) : null,
3125
+ /* @__PURE__ */ jsx38(
3126
+ PanelState,
3127
+ {
3128
+ resource,
3129
+ gated: !emailVerified,
3130
+ gatedDescription: t.subscriptions.gatedDescription,
3131
+ isEmpty: (rows) => rows.length === 0,
3132
+ emptyTitle: t.subscriptions.empty,
3133
+ emptyDescription: t.subscriptions.emptyDescription,
3134
+ children: (rows) => variant === "table" ? /* @__PURE__ */ jsx38(
3135
+ DataTable,
3136
+ {
3137
+ tableKey: "subscriptionsPanel.table",
3138
+ rowElementKey: "subscriptionsPanel.row",
3139
+ rowKey: (sub) => sub.id,
3140
+ rows,
3141
+ columns: [
3142
+ { key: "plan", label: t.subscriptions.columnPlan, render: (sub) => sub.planName ?? sub.planReference },
3143
+ { key: "price", label: t.subscriptions.columnPrice, render: (sub) => /* @__PURE__ */ jsx38("span", { className: "throttle-table__money", children: priceLabel(sub) }) },
3144
+ { key: "renews", label: t.subscriptions.columnRenews, render: (sub) => renewsLabel(sub) },
3145
+ { key: "status", label: t.subscriptions.columnStatus, render: (sub) => /* @__PURE__ */ jsxs29("div", { className: "throttle-row", children: [
3146
+ /* @__PURE__ */ jsx38(StatusBadge, { status: sub.status, labels: t.subscriptions.statusLabels }),
3147
+ sub.cancelAtPeriodEnd ? /* @__PURE__ */ jsx38(Badge, { tone: "warning", children: t.subscriptions.cancelScheduled }) : null
3148
+ ] }) },
3149
+ { key: "actions", label: t.common.actions, render: (sub) => lifecycleActions(sub, (base) => base) }
3150
+ ]
3151
+ }
3152
+ ) : /* @__PURE__ */ jsx38("div", { ...a.el("subscriptionsPanel.list", listBase), children: rows.map((sub) => /* @__PURE__ */ jsxs29("div", { ...a.el("subscriptionsPanel.item", itemBase), children: [
3153
+ /* @__PURE__ */ jsxs29("div", { children: [
3154
+ /* @__PURE__ */ jsxs29("div", { className: "throttle-row", children: [
3155
+ /* @__PURE__ */ jsx38("strong", { children: sub.planName ?? sub.planReference }),
3156
+ /* @__PURE__ */ jsx38(StatusBadge, { status: sub.status, labels: t.subscriptions.statusLabels })
3157
+ ] }),
3158
+ /* @__PURE__ */ jsxs29("p", { className: "throttle-muted", children: [
3159
+ formatMoney(sub.amount, sub.currency, locale),
3160
+ " / ",
3161
+ sub.interval,
3162
+ sub.quantity > 1 ? ` \xD7 ${sub.quantity}` : ""
3163
+ ] }),
3164
+ sub.status === "cancelled" ? /* @__PURE__ */ jsxs29("p", { className: "throttle-muted", children: [
3165
+ t.subscriptions.cancelledOn,
3166
+ " ",
3167
+ formatDate(sub.cancelledAt ?? sub.currentPeriodEnd, locale)
3168
+ ] }) : /* @__PURE__ */ jsxs29("p", { className: "throttle-muted", children: [
3169
+ sub.cancelAtPeriodEnd ? t.subscriptions.endsOn : t.subscriptions.renewsOn,
3170
+ " ",
3171
+ formatDate(sub.currentPeriodEnd, locale)
3172
+ ] }),
3173
+ sub.trialEnd ? /* @__PURE__ */ jsxs29("p", { className: "throttle-muted", children: [
3174
+ t.subscriptions.trialEnds,
3175
+ " ",
3176
+ formatDate(sub.trialEnd, locale)
3177
+ ] }) : null,
3178
+ sub.cancelAtPeriodEnd ? /* @__PURE__ */ jsx38(Badge, { tone: "warning", children: t.subscriptions.cancelScheduled }) : null
3179
+ ] }),
3180
+ lifecycleActions(sub, actionVariant)
3181
+ ] }, sub.id)) })
3182
+ }
3183
+ )
3184
+ ] }) }) });
3185
+ }
3186
+
3187
+ // src/react/components/account/SecurityPanel.tsx
3188
+ import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef5, useState as useState17 } from "react";
3189
+ import { Fragment as Fragment9, jsx as jsx39, jsxs as jsxs30 } from "react/jsx-runtime";
3190
+ function SecurityPanel(props) {
3191
+ const a = useAppearance(props.appearance);
3192
+ const scoped = useAppearanceProviderValue(props.appearance);
3193
+ const { t, locale, errorMessage } = useCopy(props.localization);
3194
+ const { auth } = useAuth();
3195
+ const variant = props.variant ?? "rows";
3196
+ const rowsVariant = variant === "rows";
3197
+ const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
3198
+ const revokeVariant = rowsVariant ? "linkDanger" : "danger";
3199
+ const [password, setPassword] = useState17("");
3200
+ const passwordAction = useAction();
3201
+ const sessionAction = useAction();
3202
+ const [sessions, setSessions] = useState17(null);
3203
+ const [listError, setListError] = useState17(null);
3204
+ const errorMessageRef = useRef5(errorMessage);
3205
+ useEffect7(() => {
3206
+ errorMessageRef.current = errorMessage;
3207
+ });
3208
+ const load = useCallback2(async () => {
3209
+ try {
3210
+ setSessions(await auth.listSessions());
3211
+ setListError(null);
3212
+ } catch (err) {
3213
+ setListError(errorMessageRef.current(err));
3214
+ }
3215
+ }, [auth]);
3216
+ useEffect7(() => {
3217
+ void load();
3218
+ }, [load]);
3219
+ const others = (sessions ?? []).filter((s) => !s.current);
3220
+ return /* @__PURE__ */ jsx39(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx39("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs30("div", { ...a.el("securityPanel.root", "throttle-panel"), children: [
3221
+ /* @__PURE__ */ jsxs30(
3222
+ "form",
3223
+ {
3224
+ ...a.el("securityPanel.passwordForm", "throttle-stack"),
3225
+ onSubmit: (e) => {
3226
+ e.preventDefault();
3227
+ void passwordAction.run(async () => {
3228
+ await auth.changePassword(password);
3229
+ setPassword("");
3230
+ return t.security.changedNotice;
3231
+ });
3232
+ },
3233
+ children: [
3234
+ /* @__PURE__ */ jsx39("h3", { children: t.security.passwordTitle }),
3235
+ /* @__PURE__ */ jsx39(
3236
+ Field,
3237
+ {
3238
+ label: t.security.newPassword,
3239
+ type: "password",
3240
+ autoComplete: "new-password",
3241
+ required: true,
3242
+ value: password,
3243
+ onChange: setPassword
3244
+ }
3245
+ ),
3246
+ passwordAction.error ? /* @__PURE__ */ jsx39(Alert, { tone: "error", children: passwordAction.error }) : null,
3247
+ passwordAction.notice ? /* @__PURE__ */ jsx39(Alert, { tone: "success", children: passwordAction.notice }) : null,
3248
+ /* @__PURE__ */ jsx39(Button, { type: "submit", loading: passwordAction.submitting, elementKey: "securityPanel.passwordButton", children: t.security.changeButton })
3249
+ ]
3250
+ }
3251
+ ),
3252
+ /* @__PURE__ */ jsxs30("div", { ...a.el("securityPanel.sessionList", "throttle-stack"), children: [
3253
+ /* @__PURE__ */ jsx39("h3", { children: t.security.sessionsTitle }),
3254
+ listError ? /* @__PURE__ */ jsxs30(Fragment9, { children: [
3255
+ /* @__PURE__ */ jsx39(Alert, { tone: "error", children: listError }),
3256
+ /* @__PURE__ */ jsx39(Button, { variant: "secondary", onClick: () => void load(), children: t.common.retry })
3257
+ ] }) : null,
3258
+ sessionAction.error ? /* @__PURE__ */ jsx39(Alert, { tone: "error", children: sessionAction.error }) : null,
3259
+ sessions === null && !listError ? /* @__PURE__ */ jsx39(Skeleton, { count: 2 }) : null,
3260
+ sessions !== null && !listError && variant === "table" ? /* @__PURE__ */ jsx39(
3261
+ DataTable,
3262
+ {
3263
+ tableKey: "securityPanel.table",
3264
+ rowElementKey: "securityPanel.row",
3265
+ rowKey: (session) => session.id,
3266
+ rows: sessions,
3267
+ columns: [
3268
+ { key: "device", label: t.security.columnDevice, render: (session) => /* @__PURE__ */ jsxs30("div", { className: "throttle-row", children: [
3269
+ /* @__PURE__ */ jsx39("span", { children: session.userAgent || session.ipPrefix || session.id }),
3270
+ session.current ? /* @__PURE__ */ jsx39(Badge, { tone: "success", children: t.security.thisDevice }) : null
3271
+ ] }) },
3272
+ { key: "lastActive", label: t.security.lastActive, render: (session) => formatDate(session.lastUsedAt, locale) },
3273
+ { key: "actions", label: t.common.actions, render: (session) => !session.current ? /* @__PURE__ */ jsx39(
3274
+ Button,
3275
+ {
3276
+ variant: "danger",
3277
+ loading: sessionAction.submitting,
3278
+ elementKey: "securityPanel.revokeButton",
3279
+ onClick: () => void sessionAction.run(async () => {
3280
+ await auth.revokeSession(session.id);
3281
+ await load();
3282
+ }),
3283
+ children: t.security.revokeButton
3284
+ }
3285
+ ) : null }
3286
+ ]
3287
+ }
3288
+ ) : (sessions ?? []).map((session) => /* @__PURE__ */ jsxs30("div", { ...a.el("securityPanel.sessionItem", itemBase), children: [
3289
+ /* @__PURE__ */ jsxs30("div", { children: [
3290
+ /* @__PURE__ */ jsxs30("div", { className: "throttle-row", children: [
3291
+ /* @__PURE__ */ jsx39("strong", { children: session.userAgent || session.ipPrefix || session.id }),
3292
+ session.current ? /* @__PURE__ */ jsx39(Badge, { tone: "success", children: t.security.thisDevice }) : null
3293
+ ] }),
3294
+ /* @__PURE__ */ jsxs30("p", { className: "throttle-muted", children: [
3295
+ t.security.lastActive,
3296
+ " ",
3297
+ formatDate(session.lastUsedAt, locale)
3298
+ ] })
3299
+ ] }),
3300
+ !session.current ? /* @__PURE__ */ jsx39(
3301
+ Button,
3302
+ {
3303
+ variant: revokeVariant,
3304
+ loading: sessionAction.submitting,
3305
+ elementKey: "securityPanel.revokeButton",
3306
+ onClick: () => void sessionAction.run(async () => {
3307
+ await auth.revokeSession(session.id);
3308
+ await load();
3309
+ }),
3310
+ children: t.security.revokeButton
3311
+ }
3312
+ ) : null
3313
+ ] }, session.id)),
3314
+ sessions !== null && others.length === 0 ? /* @__PURE__ */ jsx39("p", { className: "throttle-muted", children: t.security.sessionsEmpty }) : null,
3315
+ others.length > 0 ? /* @__PURE__ */ jsx39(
3316
+ Button,
3317
+ {
3318
+ variant: "danger",
3319
+ loading: sessionAction.submitting,
3320
+ elementKey: "securityPanel.revokeAllButton",
3321
+ onClick: () => void sessionAction.run(async () => {
3322
+ for (const session of others) {
3323
+ await auth.revokeSession(session.id);
3324
+ }
3325
+ await load();
3326
+ }),
3327
+ children: t.security.revokeAllButton
3328
+ }
3329
+ ) : null
3330
+ ] })
3331
+ ] }) }) });
3332
+ }
3333
+
3334
+ // src/react/components/account/AccountDashboard.tsx
3335
+ import { Fragment as Fragment10, jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
3336
+ var ACCOUNT_TABS = [
3337
+ "profile",
3338
+ "addresses",
3339
+ "paymentMethods",
3340
+ "orders",
3341
+ "invoices",
3342
+ "subscriptions",
3343
+ "security"
3344
+ ];
3345
+ var HASH_FOR2 = {
3346
+ profile: "#/profile",
3347
+ addresses: "#/addresses",
3348
+ paymentMethods: "#/payment-methods",
3349
+ orders: "#/orders",
3350
+ invoices: "#/invoices",
3351
+ subscriptions: "#/subscriptions",
3352
+ security: "#/security"
3353
+ };
3354
+ var TAB_FOR_HASH = Object.fromEntries(
3355
+ Object.entries(HASH_FOR2).map(([tab, hash]) => [hash, tab])
3356
+ );
3357
+ function AccountDashboard(props) {
3358
+ const a = useAppearance(props.appearance);
3359
+ const scoped = useAppearanceProviderValue(props.appearance);
3360
+ const { t } = useCopy(props.localization);
3361
+ const { status } = useAuth();
3362
+ const shown = props.tabs ?? [...ACCOUNT_TABS];
3363
+ const controlled = props.tab !== void 0;
3364
+ const [internal, setInternal] = useState18(shown[0] ?? "profile");
3365
+ const idBase = useId4();
3366
+ useEffect8(() => {
3367
+ if (props.routing !== "hash" || typeof window === "undefined") return;
3368
+ const apply = () => {
3369
+ const next = TAB_FOR_HASH[window.location.hash] ?? shown[0];
3370
+ if (!next || !shown.includes(next)) return;
3371
+ if (controlled) props.onTabChange?.(next);
3372
+ else setInternal(next);
3373
+ };
3374
+ apply();
3375
+ window.addEventListener("hashchange", apply);
3376
+ return () => window.removeEventListener("hashchange", apply);
3377
+ }, [props.routing, controlled, shown.join(",")]);
3378
+ if (status !== "authenticated") return null;
3379
+ const active = controlled ? props.tab : internal;
3380
+ const go = (next) => {
3381
+ props.onTabChange?.(next);
3382
+ if (!controlled) setInternal(next);
3383
+ if (props.routing === "hash" && typeof window !== "undefined") window.location.hash = HASH_FOR2[next];
3384
+ };
3385
+ const shared = { appearance: props.appearance, localization: props.localization };
3386
+ const sidebar = (props.layout ?? "sidebar") === "sidebar";
3387
+ const nav = /* @__PURE__ */ jsx40(
3388
+ Tabs,
3389
+ {
3390
+ idBase,
3391
+ orientation: sidebar ? "vertical" : "horizontal",
3392
+ active,
3393
+ onChange: (id) => go(id),
3394
+ tabs: shown.map((id) => ({
3395
+ id,
3396
+ label: t.account.tabs[id],
3397
+ // Icons ride the rail only. In the horizontal strip they cost
3398
+ // horizontal room, which is the scarce axis there.
3399
+ icon: sidebar ? ACCOUNT_ICONS[id] : void 0
3400
+ }))
3401
+ }
3402
+ );
3403
+ const panel = /* @__PURE__ */ jsxs31(
3404
+ "div",
3405
+ {
3406
+ ...a.el("accountDashboard.panel", sidebar ? "throttle-account__content" : "throttle-tabs__panel"),
3407
+ role: "tabpanel",
3408
+ id: `${idBase}-panel-${active}`,
3409
+ "aria-labelledby": `${idBase}-tab-${active}`,
3410
+ tabIndex: 0,
3411
+ children: [
3412
+ active === "profile" ? /* @__PURE__ */ jsx40(ProfilePanel, { ...shared }) : null,
3413
+ active === "addresses" ? /* @__PURE__ */ jsx40(AddressesPanel, { ...shared, variant: props.panelVariant }) : null,
3414
+ active === "paymentMethods" ? /* @__PURE__ */ jsx40(PaymentMethodsPanel, { ...shared, variant: props.panelVariant }) : null,
3415
+ active === "orders" ? /* @__PURE__ */ jsx40(OrdersPanel, { ...shared, variant: props.panelVariant, onViewSubscription: () => go("subscriptions") }) : null,
3416
+ active === "invoices" ? /* @__PURE__ */ jsx40(InvoicesPanel, { ...shared, variant: props.panelVariant }) : null,
3417
+ active === "subscriptions" ? /* @__PURE__ */ jsx40(SubscriptionsPanel, { ...shared, variant: props.panelVariant }) : null,
3418
+ active === "security" ? /* @__PURE__ */ jsx40(SecurityPanel, { ...shared, variant: props.panelVariant }) : null
3419
+ ]
3420
+ }
3421
+ );
3422
+ return /* @__PURE__ */ jsx40(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx40("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs31("div", { ...a.el("accountDashboard.root", "throttle-panel throttle-account-root"), children: [
3423
+ /* @__PURE__ */ jsx40(VerifyEmailBanner, { ...shared }),
3424
+ sidebar ? /* @__PURE__ */ jsxs31("div", { className: "throttle-account", children: [
3425
+ /* @__PURE__ */ jsx40("div", { ...a.el("accountDashboard.sidebar"), children: nav }),
3426
+ panel
3427
+ ] }) : /* @__PURE__ */ jsxs31(Fragment10, { children: [
3428
+ /* @__PURE__ */ jsx40("div", { ...a.el("accountDashboard.tabs"), children: nav }),
3429
+ panel
3430
+ ] })
3431
+ ] }) }) });
3432
+ }
3433
+
3434
+ // src/react/components/account/AccountModal.tsx
3435
+ import { jsx as jsx41, jsxs as jsxs32 } from "react/jsx-runtime";
3436
+ function AccountModal(props) {
3437
+ const a = useAppearance(props.appearance);
3438
+ const scoped = useAppearanceProviderValue(props.appearance);
3439
+ const { t } = useCopy(props.localization);
3440
+ const titleId = useId5();
3441
+ return /* @__PURE__ */ jsx41(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsxs32(
3442
+ Modal,
3443
+ {
3444
+ open: props.open,
3445
+ onClose: props.onClose,
3446
+ labelledBy: titleId,
3447
+ restoreFocusTo: props.restoreFocusTo,
3448
+ overlayKey: "accountModal.overlay",
3449
+ containerKey: "accountModal.container",
3450
+ children: [
3451
+ /* @__PURE__ */ jsx41("div", { ...a.el("accountModal.header", "throttle-modal__header"), children: /* @__PURE__ */ jsx41("h2", { id: titleId, ...a.el("accountModal.title", "throttle-modal__title"), children: t.account.title }) }),
3452
+ /* @__PURE__ */ jsx41("div", { ...a.el("accountModal.body", "throttle-modal__body"), children: /* @__PURE__ */ jsx41(AccountDashboard, { tabs: props.tabs, appearance: props.appearance, localization: props.localization }) })
3453
+ ]
3454
+ }
3455
+ ) });
3456
+ }
3457
+
3458
+ // src/react/components/provider.tsx
3459
+ import { jsx as jsx42, jsxs as jsxs33 } from "react/jsx-runtime";
3460
+ var warned = /* @__PURE__ */ new Set();
3461
+ function warnOnce(key, message) {
3462
+ if (process.env.NODE_ENV === "production") return;
3463
+ if (warned.has(key)) return;
3464
+ warned.add(key);
3465
+ console.warn(`[@usethrottle/auth] ${message}`);
3466
+ }
3467
+ var ThrottleUIContext = createContext5(null);
3468
+ function useThrottle() {
3469
+ const ctx = useContext5(ThrottleUIContext);
3470
+ if (!ctx) {
3471
+ warnOnce(
3472
+ "use-throttle-no-provider",
3473
+ "useThrottle() was called without a <ThrottleProvider> above it in the tree. openSignIn/openSignUp/openAccount will do nothing \u2014 wrap your app in <ThrottleProvider> to use them."
3474
+ );
3475
+ return { openSignIn: () => {
3476
+ }, openSignUp: () => {
3477
+ }, openAccount: () => {
3478
+ }, close: () => {
3479
+ }, isOpen: false };
3480
+ }
3481
+ return {
3482
+ openSignIn: () => ctx.openSignIn(),
3483
+ openSignUp: () => ctx.openSignUp(),
3484
+ openAccount: ctx.openAccount,
3485
+ close: ctx.close,
3486
+ isOpen: ctx.overlay !== null
3487
+ };
3488
+ }
3489
+ function ThrottleProvider(props) {
3490
+ const {
3491
+ publishableKey,
3492
+ baseUrl,
3493
+ storage,
3494
+ appearance,
3495
+ localization,
3496
+ logo,
3497
+ injectStyles = true,
3498
+ claimCartOnSignIn,
3499
+ auth: injected,
3500
+ children,
3501
+ __captureStepUp
3502
+ } = props;
3503
+ useInjectStyles(injectStyles);
3504
+ const [stepUpOpen, setStepUpOpen] = useState19(false);
3505
+ const resolveStepUp = useRef6(null);
3506
+ const claimRef = useRef6(claimCartOnSignIn);
3507
+ claimRef.current = claimCartOnSignIn;
3508
+ const [overlay, setOverlay] = useState19(null);
3509
+ const overlayRestoreFocus = useRef6(null);
3510
+ const [accountTabs, setAccountTabs] = useState19(void 0);
3511
+ const onOverlaySuccess = useRef6(null);
3512
+ const openSignIn = useCallback3((onSuccess) => {
3513
+ onOverlaySuccess.current = onSuccess ?? null;
3514
+ setOverlay("signIn");
3515
+ }, []);
3516
+ const openSignUp = useCallback3((onSuccess) => {
3517
+ onOverlaySuccess.current = onSuccess ?? null;
3518
+ setOverlay("signUp");
3519
+ }, []);
3520
+ const openAccount = useCallback3((opts) => {
3521
+ onOverlaySuccess.current = null;
3522
+ overlayRestoreFocus.current = opts?.restoreFocusTo ?? null;
3523
+ setAccountTabs(opts?.tabs);
3524
+ setOverlay("account");
3525
+ }, []);
3526
+ const closeOverlay = useCallback3(() => {
3527
+ onOverlaySuccess.current = null;
3528
+ overlayRestoreFocus.current = null;
3529
+ setOverlay(null);
3530
+ }, []);
3531
+ const handleOverlaySuccess = useCallback3(() => {
3532
+ const fn = onOverlaySuccess.current;
3533
+ onOverlaySuccess.current = null;
3534
+ setOverlay(null);
3535
+ fn?.();
3536
+ }, []);
3537
+ const ui = useMemo3(
3538
+ () => ({ overlay, openSignIn, openSignUp, openAccount, close: closeOverlay }),
3539
+ [overlay, openSignIn, openSignUp, openAccount, closeOverlay]
3540
+ );
3541
+ const askForStepUp = useCallback3(
3542
+ () => new Promise((resolve) => {
3543
+ resolveStepUp.current = resolve;
3544
+ setStepUpOpen(true);
3545
+ }),
3546
+ []
3547
+ );
3548
+ const auth = useMemo3(() => {
3549
+ __captureStepUp?.(askForStepUp);
3550
+ const client = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired: askForStepUp });
3551
+ const account = createAccount(client);
3552
+ const claim = async () => {
3553
+ const cartId = claimRef.current?.();
3554
+ if (!cartId) return;
3555
+ try {
3556
+ await account.claimCart(cartId);
3557
+ } catch (err) {
3558
+ warnOnce("claim-cart", `claimCartOnSignIn failed: ${err?.message ?? err}`);
3559
+ }
3560
+ };
3561
+ return {
3562
+ ...client,
3563
+ async signIn(email, password) {
3564
+ await client.signIn(email, password);
3565
+ await claim();
3566
+ },
3567
+ async register(email, password) {
3568
+ const result = await client.register(email, password);
3569
+ if (result.status === "active") await claim();
3570
+ return result;
3571
+ }
3572
+ };
3573
+ }, [injected, publishableKey, baseUrl]);
3574
+ const finishStepUp = (password) => {
3575
+ setStepUpOpen(false);
3576
+ const resolve = resolveStepUp.current;
3577
+ resolveStepUp.current = null;
3578
+ resolve?.(password);
3579
+ };
3580
+ const authOverlayOpen = overlay === "signIn" || overlay === "signUp";
3581
+ return /* @__PURE__ */ jsx42(AppearanceProvider, { value: appearance ?? {}, children: /* @__PURE__ */ jsx42(LocalizationProvider, { value: localization ?? {}, children: /* @__PURE__ */ jsx42(BrandLogoProvider, { value: logo ?? null, children: /* @__PURE__ */ jsx42(ThrottleAuthProvider, { auth, publishableKey, children: /* @__PURE__ */ jsxs33(ThrottleUIContext.Provider, { value: ui, children: [
3582
+ children,
3583
+ /* @__PURE__ */ jsx42(StepUpDialog, { open: stepUpOpen, onResolve: finishStepUp }),
3584
+ /* @__PURE__ */ jsx42(
3585
+ AuthModal,
3586
+ {
3587
+ open: authOverlayOpen,
3588
+ onClose: closeOverlay,
3589
+ initialView: overlay === "signUp" ? "signUp" : "signIn",
3590
+ afterSignIn: handleOverlaySuccess,
3591
+ afterSignUp: handleOverlaySuccess
3592
+ },
3593
+ overlay === "signUp" ? "signUp" : "signIn"
3594
+ ),
3595
+ /* @__PURE__ */ jsx42(AccountModal, { open: overlay === "account", onClose: closeOverlay, tabs: accountTabs, restoreFocusTo: overlayRestoreFocus.current })
3596
+ ] }) }) }) }) });
3597
+ }
3598
+
3599
+ // src/react/components/auth/SignInButton.tsx
3600
+ import { cloneElement, isValidElement, useContext as useContext6, useState as useState20 } from "react";
3601
+ import { jsx as jsx43, jsxs as jsxs34 } from "react/jsx-runtime";
3602
+ function SignInButton(props) {
3603
+ const a = useAppearance(props.appearance);
3604
+ const scoped = useAppearanceProviderValue(props.appearance);
3605
+ const { t } = useCopy(props.localization);
3606
+ const ui = useContext6(ThrottleUIContext);
3607
+ const [localOpen, setLocalOpen] = useState20(false);
3608
+ const useShared = ui !== null && props.initialView === void 0;
3609
+ const label = props.children ?? t.signIn.submitButton;
3610
+ if (props.mode === "redirect") {
3611
+ return /* @__PURE__ */ jsx43(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx43("span", { ...a.rootProps, children: /* @__PURE__ */ jsx43("a", { ...a.el("signInButton.root", "throttle-button throttle-button--primary"), href: props.url ?? "#", children: label }) }) });
3612
+ }
3613
+ const openModal = () => {
3614
+ if (useShared) ui.openSignIn(props.afterSignIn);
3615
+ else setLocalOpen(true);
3616
+ };
3617
+ const closeModal = () => {
3618
+ if (useShared) ui.close();
3619
+ else setLocalOpen(false);
3620
+ };
3621
+ const trigger = props.asChild && isValidElement(props.children) ? cloneElement(props.children, { onClick: openModal }) : /* @__PURE__ */ jsx43(Button, { elementKey: "signInButton.root", onClick: openModal, children: label });
3622
+ return /* @__PURE__ */ jsxs34(AppearanceProvider, { value: scoped, children: [
3623
+ /* @__PURE__ */ jsx43("span", { ...a.rootProps, children: trigger }),
3624
+ !useShared ? /* @__PURE__ */ jsx43(
3625
+ AuthModal,
3626
+ {
3627
+ open: localOpen,
3628
+ onClose: closeModal,
3629
+ initialView: props.initialView,
3630
+ appearance: props.appearance,
3631
+ localization: props.localization,
3632
+ afterSignIn: () => {
3633
+ closeModal();
3634
+ props.afterSignIn?.();
3635
+ }
3636
+ }
3637
+ ) : null
3638
+ ] });
3639
+ }
3640
+
3641
+ // src/react/components/auth/SignUpButton.tsx
3642
+ import { cloneElement as cloneElement2, isValidElement as isValidElement2, useContext as useContext7, useState as useState21 } from "react";
3643
+ import { jsx as jsx44, jsxs as jsxs35 } from "react/jsx-runtime";
3644
+ function SignUpButton(props) {
3645
+ const a = useAppearance(props.appearance);
3646
+ const scoped = useAppearanceProviderValue(props.appearance);
3647
+ const { t } = useCopy(props.localization);
3648
+ const ui = useContext7(ThrottleUIContext);
3649
+ const [localOpen, setLocalOpen] = useState21(false);
3650
+ const useShared = ui !== null && props.initialView === void 0;
3651
+ const label = props.children ?? t.signUp.submitButton;
3652
+ if (props.mode === "redirect") {
3653
+ return /* @__PURE__ */ jsx44(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx44("span", { ...a.rootProps, children: /* @__PURE__ */ jsx44("a", { ...a.el("signUpButton.root", "throttle-button throttle-button--primary"), href: props.url ?? "#", children: label }) }) });
3654
+ }
3655
+ const openModal = () => {
3656
+ if (useShared) ui.openSignUp(props.afterSignUp);
3657
+ else setLocalOpen(true);
3658
+ };
3659
+ const closeModal = () => {
3660
+ if (useShared) ui.close();
3661
+ else setLocalOpen(false);
3662
+ };
3663
+ const trigger = props.asChild && isValidElement2(props.children) ? cloneElement2(props.children, { onClick: openModal }) : /* @__PURE__ */ jsx44(Button, { elementKey: "signUpButton.root", onClick: openModal, children: label });
3664
+ return /* @__PURE__ */ jsxs35(AppearanceProvider, { value: scoped, children: [
3665
+ /* @__PURE__ */ jsx44("span", { ...a.rootProps, children: trigger }),
3666
+ !useShared ? /* @__PURE__ */ jsx44(
3667
+ AuthModal,
3668
+ {
3669
+ open: localOpen,
3670
+ onClose: closeModal,
3671
+ initialView: props.initialView ?? "signUp",
3672
+ appearance: props.appearance,
3673
+ localization: props.localization,
3674
+ afterSignUp: () => {
3675
+ closeModal();
3676
+ props.afterSignUp?.();
3677
+ }
3678
+ }
3679
+ ) : null
3680
+ ] });
3681
+ }
3682
+
3683
+ // src/react/components/auth/SignOutButton.tsx
3684
+ import { jsx as jsx45 } from "react/jsx-runtime";
3685
+ function SignOutButton(props) {
3686
+ const a = useAppearance();
3687
+ const { auth } = useAuth();
3688
+ const { t } = useCopy(props.localization);
3689
+ const { submitting, run } = useAction();
3690
+ const label = props.children ?? (props.everywhere ? t.userButton.signOutAll : t.userButton.signOut);
3691
+ return /* @__PURE__ */ jsx45("span", { ...a.rootProps, children: /* @__PURE__ */ jsx45(
3692
+ Button,
3693
+ {
3694
+ variant: "secondary",
3695
+ loading: submitting,
3696
+ elementKey: "signOutButton.root",
3697
+ onClick: () => void run(async () => {
3698
+ if (props.everywhere) await auth.signOutEverywhere();
3699
+ else await auth.signOut();
3700
+ props.onSignedOut?.();
3701
+ }),
3702
+ children: label
3703
+ }
3704
+ ) });
3705
+ }
3706
+
3707
+ // src/react/components/auth/SignInPage.tsx
3708
+ import { jsx as jsx46, jsxs as jsxs36 } from "react/jsx-runtime";
3709
+ function SignInPage(props) {
3710
+ const a = useAppearance(props.appearance);
3711
+ const scoped = useAppearanceProviderValue(props.appearance);
3712
+ return /* @__PURE__ */ jsx46(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx46("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs36("div", { ...a.el("signInPage.root", "throttle-page"), children: [
3713
+ props.logo ? /* @__PURE__ */ jsx46("div", { ...a.el("signInPage.logo", "throttle-page__logo"), children: props.logo }) : null,
3714
+ /* @__PURE__ */ jsx46(
3715
+ SignIn,
3716
+ {
3717
+ onSuccess: props.onSuccess,
3718
+ onForgotPassword: props.onForgotPassword,
3719
+ onSignUp: props.onSignUp,
3720
+ appearance: props.appearance,
3721
+ localization: props.localization
3722
+ }
3723
+ ),
3724
+ props.footer ? /* @__PURE__ */ jsx46("div", { ...a.el("signInPage.footer", "throttle-page__footer"), children: props.footer }) : null
3725
+ ] }) }) });
3726
+ }
3727
+
3728
+ // src/react/components/auth/SignUpPage.tsx
3729
+ import { jsx as jsx47, jsxs as jsxs37 } from "react/jsx-runtime";
3730
+ function SignUpPage(props) {
3731
+ const a = useAppearance(props.appearance);
3732
+ const scoped = useAppearanceProviderValue(props.appearance);
3733
+ return /* @__PURE__ */ jsx47(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx47("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs37("div", { ...a.el("signUpPage.root", "throttle-page"), children: [
3734
+ props.logo ? /* @__PURE__ */ jsx47("div", { ...a.el("signUpPage.logo", "throttle-page__logo"), children: props.logo }) : null,
3735
+ /* @__PURE__ */ jsx47(
3736
+ SignUp,
3737
+ {
3738
+ onSuccess: props.onSuccess,
3739
+ onSignIn: props.onSignIn,
3740
+ appearance: props.appearance,
3741
+ localization: props.localization
3742
+ }
3743
+ ),
3744
+ props.footer ? /* @__PURE__ */ jsx47("div", { ...a.el("signUpPage.footer", "throttle-page__footer"), children: props.footer }) : null
3745
+ ] }) }) });
3746
+ }
3747
+
3748
+ // src/react/components/guards.tsx
3749
+ import { Fragment as Fragment11, jsx as jsx48 } from "react/jsx-runtime";
3750
+ function SignedIn(props) {
3751
+ const { status } = useAuth();
3752
+ return status === "authenticated" ? /* @__PURE__ */ jsx48(Fragment11, { children: props.children }) : null;
3753
+ }
3754
+ function SignedOut(props) {
3755
+ const { status } = useAuth();
3756
+ return status === "unauthenticated" ? /* @__PURE__ */ jsx48(Fragment11, { children: props.children }) : null;
3757
+ }
3758
+ function Protect(props) {
3759
+ const { status, emailVerified } = useAuth();
3760
+ if (status === "loading") return null;
3761
+ if (status !== "authenticated") return /* @__PURE__ */ jsx48(Fragment11, { children: props.fallback ?? null });
3762
+ if (props.requireVerified && !emailVerified) return /* @__PURE__ */ jsx48(Fragment11, { children: props.fallback ?? null });
3763
+ return /* @__PURE__ */ jsx48(Fragment11, { children: props.children });
3764
+ }
3765
+
3766
+ // src/react/components/UserButton.tsx
3767
+ import { useContext as useContext8, useId as useId6, useRef as useRef7, useState as useState22 } from "react";
3768
+ import { jsx as jsx49, jsxs as jsxs38 } from "react/jsx-runtime";
3769
+ function UserButtonRoot(props) {
3770
+ const a = useAppearance(props.appearance);
3771
+ const scoped = useAppearanceProviderValue(props.appearance);
3772
+ const { t } = useCopy(props.localization);
3773
+ const { auth, status, customer, emailVerified } = useAuth();
3774
+ const ui = useContext8(ThrottleUIContext);
3775
+ const [localOpen, setLocalOpen] = useState22(false);
3776
+ const accountOpen = ui ? false : localOpen;
3777
+ const titleId = useId6();
3778
+ const rootRef = useRef7(null);
3779
+ const openerRef = useRef7(null);
3780
+ const openAccount = () => {
3781
+ const opener = rootRef.current?.querySelector('button[aria-haspopup="menu"]') ?? null;
3782
+ openerRef.current = opener;
3783
+ if (ui) ui.openAccount({ tabs: props.tabs, restoreFocusTo: opener });
3784
+ else setLocalOpen(true);
3785
+ };
3786
+ if (status !== "authenticated" || !customer) return null;
3787
+ const name = [customer.firstName, customer.lastName].filter(Boolean).join(" ");
3788
+ return /* @__PURE__ */ jsx49(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx49("span", { ...a.rootProps, children: /* @__PURE__ */ jsxs38("span", { ref: rootRef, ...a.el("userButton.root"), children: [
3789
+ /* @__PURE__ */ jsxs38(
3790
+ Dropdown,
3791
+ {
3792
+ label: t.userButton.openMenu,
3793
+ triggerKey: "userButton.trigger",
3794
+ menuKey: "userButton.menu",
3795
+ trigger: /* @__PURE__ */ jsx49(Avatar, { name, email: customer.email }),
3796
+ children: [
3797
+ /* @__PURE__ */ jsx49("div", { ...a.el("userButton.identity", "throttle-dropdown__item"), children: /* @__PURE__ */ jsxs38("span", { children: [
3798
+ /* @__PURE__ */ jsx49("span", { ...a.el("userButton.email"), children: customer.email }),
3799
+ /* @__PURE__ */ jsx49("br", {}),
3800
+ /* @__PURE__ */ jsx49(Badge, { tone: emailVerified ? "success" : "warning", elementKey: "userButton.badge", children: emailVerified ? t.userButton.verified : t.userButton.unverified })
3801
+ ] }) }),
3802
+ /* @__PURE__ */ jsx49(DropdownSeparator, {}),
3803
+ props.mode === "navigate" ? /* @__PURE__ */ jsx49(DropdownItem, { itemKey: "userButton.manageButton", href: props.accountUrl ?? "#", children: t.userButton.manage }) : /* @__PURE__ */ jsx49(DropdownItem, { itemKey: "userButton.manageButton", onClick: openAccount, children: t.userButton.manage }),
3804
+ props.children,
3805
+ /* @__PURE__ */ jsx49(DropdownSeparator, {}),
3806
+ /* @__PURE__ */ jsx49(
3807
+ DropdownItem,
3808
+ {
3809
+ itemKey: "userButton.signOutButton",
3810
+ onClick: () => void auth.signOut().then(() => props.afterSignOut?.()),
3811
+ children: t.userButton.signOut
3812
+ }
3813
+ ),
3814
+ /* @__PURE__ */ jsx49(
3815
+ DropdownItem,
3816
+ {
3817
+ itemKey: "userButton.signOutAllButton",
3818
+ onClick: () => void auth.signOutEverywhere().then(() => props.afterSignOut?.()),
3819
+ children: t.userButton.signOutAll
3820
+ }
3821
+ )
3822
+ ]
3823
+ }
3824
+ ),
3825
+ props.mode !== "navigate" && !ui ? /* @__PURE__ */ jsxs38(
3826
+ Modal,
3827
+ {
3828
+ open: accountOpen,
3829
+ onClose: () => setLocalOpen(false),
3830
+ labelledBy: titleId,
3831
+ restoreFocusTo: openerRef.current,
3832
+ children: [
3833
+ /* @__PURE__ */ jsx49("h2", { id: titleId, style: { padding: "16px 16px 0", margin: 0 }, children: t.account.title }),
3834
+ /* @__PURE__ */ jsx49("div", { style: { padding: 16 }, children: /* @__PURE__ */ jsx49(
3835
+ AccountDashboard,
3836
+ {
3837
+ tabs: props.tabs,
3838
+ appearance: props.appearance,
3839
+ localization: props.localization
3840
+ }
3841
+ ) })
3842
+ ]
3843
+ }
3844
+ ) : null
3845
+ ] }) }) });
3846
+ }
3847
+ function UserButtonLink(props) {
3848
+ return /* @__PURE__ */ jsxs38(DropdownItem, { itemKey: "userButton.item", href: props.href, children: [
3849
+ props.icon,
3850
+ props.label
3851
+ ] });
3852
+ }
3853
+ function UserButtonAction(props) {
3854
+ return /* @__PURE__ */ jsxs38(DropdownItem, { itemKey: "userButton.item", onClick: props.onClick, children: [
3855
+ props.icon,
3856
+ props.label
3857
+ ] });
3858
+ }
3859
+ var UserButton = Object.assign(UserButtonRoot, {
3860
+ Link: UserButtonLink,
3861
+ Action: UserButtonAction
3862
+ });
3863
+ export {
3864
+ ACCOUNT_TABS,
3865
+ AccountDashboard,
3866
+ AccountModal,
3867
+ AddressesPanel,
3868
+ AuthFlow,
3869
+ AuthModal,
3870
+ ForgotPassword,
3871
+ InvoiceDetail,
3872
+ InvoicesPanel,
3873
+ OrderDetail,
3874
+ OrdersPanel,
3875
+ PaymentMethodsPanel,
3876
+ ProfilePanel,
3877
+ Protect,
3878
+ ResetPassword,
3879
+ SecurityPanel,
3880
+ SignIn,
3881
+ SignInButton,
3882
+ SignInPage,
3883
+ SignOutButton,
3884
+ SignUp,
3885
+ SignUpButton,
3886
+ SignUpPage,
3887
+ SignedIn,
3888
+ SignedOut,
3889
+ SubscriptionsPanel,
3890
+ THROTTLE_ELEMENT_KEYS,
3891
+ THROTTLE_STYLES,
3892
+ THROTTLE_STYLE_ID,
3893
+ ThrottleAuthError,
3894
+ ThrottleAuthProvider,
3895
+ ThrottleProvider,
3896
+ UserButton,
3897
+ VerifyEmail,
3898
+ VerifyEmailBanner,
3899
+ createAccount,
3900
+ createThrottleAuth,
3901
+ enUS,
3902
+ formatDate,
3903
+ formatMoney,
3904
+ localStorageAdapter,
3905
+ memoryStorage,
3906
+ useAddresses,
3907
+ useAuth,
3908
+ useAuthContext,
3909
+ useCustomer,
3910
+ useInvoice,
3911
+ useInvoices,
3912
+ useOrder,
3913
+ useOrders,
3914
+ useSavedCards,
3915
+ useSubscriptions,
3916
+ useThrottle
3917
+ };
3918
+ //# sourceMappingURL=index.js.map