@usethrottle/auth 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +52 -0
  2. package/dist/AccountDashboard-V63ghvjY.d.ts +90 -0
  3. package/dist/AccountModal-KA54WUFW.js +13 -0
  4. package/dist/AccountModal-KA54WUFW.js.map +1 -0
  5. package/dist/AccountModal-pIMgledL.d.ts +141 -0
  6. package/dist/AuthModal-T2GYI6ZF.js +13 -0
  7. package/dist/AuthModal-T2GYI6ZF.js.map +1 -0
  8. package/dist/SignUpPage-BJcMUb2V.d.ts +160 -0
  9. package/dist/chunk-3B4DNX5T.js +2 -0
  10. package/dist/chunk-3B4DNX5T.js.map +1 -0
  11. package/dist/chunk-5EUZYJJL.js +29 -0
  12. package/dist/chunk-5EUZYJJL.js.map +1 -0
  13. package/dist/chunk-6WN3J5MM.js +2075 -0
  14. package/dist/chunk-6WN3J5MM.js.map +1 -0
  15. package/dist/chunk-CUNMJ2PH.js +1320 -0
  16. package/dist/chunk-CUNMJ2PH.js.map +1 -0
  17. package/dist/chunk-ETYOOYYV.js +2 -0
  18. package/dist/chunk-ETYOOYYV.js.map +1 -0
  19. package/dist/chunk-JSDZMPCV.js +520 -0
  20. package/dist/chunk-JSDZMPCV.js.map +1 -0
  21. package/dist/chunk-QPVS6JXP.js +39 -0
  22. package/dist/chunk-QPVS6JXP.js.map +1 -0
  23. package/dist/chunk-RCNUWITB.js +747 -0
  24. package/dist/chunk-RCNUWITB.js.map +1 -0
  25. package/dist/{chunk-YY6YATXT.js → chunk-TDQC3LMQ.js} +17 -3
  26. package/dist/chunk-TDQC3LMQ.js.map +1 -0
  27. package/dist/{chunk-EFMKBOTL.js → chunk-UFGW76AZ.js} +2 -2
  28. package/dist/chunk-VDK23T6L.js +230 -0
  29. package/dist/chunk-VDK23T6L.js.map +1 -0
  30. package/dist/chunk-X6D35EAX.js +19 -0
  31. package/dist/chunk-X6D35EAX.js.map +1 -0
  32. package/dist/client-7LVKfuXO.d.ts +319 -0
  33. package/dist/hooks-DkpLrEiC.d.ts +148 -0
  34. package/dist/index.cjs +16 -2
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +34 -3
  37. package/dist/index.d.ts +34 -3
  38. package/dist/index.js +16 -2
  39. package/dist/index.js.map +1 -1
  40. package/dist/localization-SnIAZXJn.d.ts +389 -0
  41. package/dist/react/components/account/index.d.ts +37 -0
  42. package/dist/react/components/account/index.js +44 -0
  43. package/dist/react/components/account/index.js.map +1 -0
  44. package/dist/react/components/auth/index.d.ts +17 -0
  45. package/dist/react/components/auth/index.js +40 -0
  46. package/dist/react/components/auth/index.js.map +1 -0
  47. package/dist/react/components/guards.d.ts +21 -0
  48. package/dist/react/components/guards.js +13 -0
  49. package/dist/react/components/guards.js.map +1 -0
  50. package/dist/react/components/index.cjs +3561 -2805
  51. package/dist/react/components/index.cjs.map +1 -1
  52. package/dist/react/components/index.d.cts +14 -2
  53. package/dist/react/components/index.d.ts +14 -821
  54. package/dist/react/components/index.js +90 -4680
  55. package/dist/react/components/index.js.map +1 -1
  56. package/dist/react/components/provider.d.ts +79 -0
  57. package/dist/react/components/provider.js +18 -0
  58. package/dist/react/components/provider.js.map +1 -0
  59. package/dist/react/forms/index.js +1 -1
  60. package/dist/react/index.cjs +16 -2
  61. package/dist/react/index.cjs.map +1 -1
  62. package/dist/react/index.d.cts +34 -3
  63. package/dist/react/index.d.ts +5 -433
  64. package/dist/react/index.js +3 -2
  65. package/dist/styles.css +7 -4
  66. package/package.json +4 -1
  67. package/dist/chunk-YY6YATXT.js.map +0 -1
  68. /package/dist/{chunk-EFMKBOTL.js.map → chunk-UFGW76AZ.js.map} +0 -0
@@ -1,4 +1,43 @@
1
1
  "use client";
2
+ import "../../chunk-ETYOOYYV.js";
3
+ import {
4
+ Protect,
5
+ SignedIn,
6
+ SignedOut
7
+ } from "../../chunk-5EUZYJJL.js";
8
+ import {
9
+ SignInButton,
10
+ SignInPage,
11
+ SignOutButton,
12
+ SignUpButton,
13
+ SignUpPage
14
+ } from "../../chunk-VDK23T6L.js";
15
+ import {
16
+ THROTTLE_STYLES,
17
+ THROTTLE_STYLE_ID,
18
+ ThrottleProvider,
19
+ ThrottleUIContext,
20
+ useThrottle
21
+ } from "../../chunk-RCNUWITB.js";
22
+ import "../../chunk-3B4DNX5T.js";
23
+ import {
24
+ ACCOUNT_TABS,
25
+ AccountDashboard,
26
+ AccountModal,
27
+ AddCardDialog,
28
+ AddressesPanel,
29
+ InvoiceDetail,
30
+ InvoicesPanel,
31
+ OrderDetail,
32
+ OrdersPanel,
33
+ PaymentMethodsPanel,
34
+ ProfilePanel,
35
+ SecurityPanel,
36
+ SubscriptionsPanel,
37
+ VerifyEmailBanner,
38
+ formatDate,
39
+ formatMoney
40
+ } from "../../chunk-6WN3J5MM.js";
2
41
  import {
3
42
  useAddresses,
4
43
  useCustomer,
@@ -8,10 +47,33 @@ import {
8
47
  useOrders,
9
48
  useSavedCards,
10
49
  useSubscriptions
11
- } from "../../chunk-EFMKBOTL.js";
50
+ } from "../../chunk-UFGW76AZ.js";
12
51
  import {
13
- messageForError
14
- } from "../../chunk-K7RCWADI.js";
52
+ AuthFlow,
53
+ AuthModal,
54
+ ForgotPassword,
55
+ ResetPassword,
56
+ SignIn,
57
+ SignUp,
58
+ VerifyEmail
59
+ } from "../../chunk-JSDZMPCV.js";
60
+ import "../../chunk-X6D35EAX.js";
61
+ import "../../chunk-QPVS6JXP.js";
62
+ import {
63
+ AppearanceProvider,
64
+ Avatar,
65
+ Badge,
66
+ Dropdown,
67
+ DropdownItem,
68
+ DropdownSeparator,
69
+ Modal,
70
+ THROTTLE_ELEMENT_KEYS,
71
+ enUS,
72
+ useAppearance,
73
+ useAppearanceProviderValue,
74
+ useCopy
75
+ } from "../../chunk-CUNMJ2PH.js";
76
+ import "../../chunk-K7RCWADI.js";
15
77
  import {
16
78
  ThrottleAuthError,
17
79
  ThrottleAuthProvider,
@@ -21,4674 +83,22 @@ import {
21
83
  memoryStorage,
22
84
  useAuth,
23
85
  useAuthContext
24
- } from "../../chunk-YY6YATXT.js";
25
-
26
- // src/react/components/provider.tsx
27
- import { createContext as createContext6, useCallback as useCallback4, useContext as useContext6, useMemo as useMemo4, useRef as useRef9, useState as useState22 } 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
- colorBorderControl: "--throttle-color-border-control",
129
- colorDanger: "--throttle-color-danger",
130
- colorDangerSurface: "--throttle-color-danger-surface",
131
- colorDangerBorder: "--throttle-color-danger-border",
132
- colorSuccess: "--throttle-color-success",
133
- colorSuccessSurface: "--throttle-color-success-surface",
134
- colorSuccessBorder: "--throttle-color-success-border",
135
- colorWarning: "--throttle-color-warning",
136
- colorWarningSurface: "--throttle-color-warning-surface",
137
- colorWarningBorder: "--throttle-color-warning-border",
138
- fontFamily: "--throttle-font-family",
139
- fontSize: "--throttle-font-size",
140
- borderRadius: "--throttle-radius",
141
- borderWidth: "--throttle-border-width",
142
- spacing: "--throttle-spacing",
143
- // The account dashboard's own surface and outer padding. Separate from
144
- // colorBackground/colorSurface because a merchant embedding the dashboard
145
- // in their page usually wants it to paint nothing at all, while still
146
- // theming the cards inside it.
147
- // How wide the auth card (sign-in, sign-up, forgot-password) is allowed
148
- // to grow. It was pinned at 400px, which is narrow for a page that is
149
- // nothing but that card.
150
- cardWidth: "--throttle-card-width",
151
- accountSurface: "--throttle-color-account-surface",
152
- accountPadding: "--throttle-account-padding",
153
- shadowCard: "--throttle-shadow-card",
154
- shadowPopover: "--throttle-shadow-popover",
155
- transition: "--throttle-transition",
156
- zModal: "--throttle-z-modal",
157
- zPopover: "--throttle-z-popover"
158
- };
159
- var THROTTLE_ELEMENT_KEYS = [
160
- // shared primitives
161
- "card",
162
- "card.header",
163
- "card.title",
164
- "card.description",
165
- "card.body",
166
- "card.footer",
167
- "card.logo",
168
- "field",
169
- "field.label",
170
- "field.input",
171
- "field.hint",
172
- "field.error",
173
- "field.group",
174
- "button",
175
- "alert",
176
- "spinner",
177
- "skeleton",
178
- "avatar",
179
- "badge",
180
- "emptyState",
181
- "emptyState.title",
182
- "emptyState.description",
183
- "modal.overlay",
184
- "modal.container",
185
- "modal.closeButton",
186
- "dropdown.trigger",
187
- "dropdown.menu",
188
- "dropdown.item",
189
- "dropdown.separator",
190
- "tabs.list",
191
- "tabs.tab",
192
- "table",
193
- "table.head",
194
- "table.row",
195
- "table.cell",
196
- // auth
197
- "signIn.root",
198
- "signIn.title",
199
- "signIn.logo",
200
- "signIn.emailField",
201
- "signIn.passwordField",
202
- "signIn.submitButton",
203
- "signIn.forgotLink",
204
- "signIn.switchLink",
205
- "signUp.root",
206
- "signUp.title",
207
- "signUp.logo",
208
- "signUp.emailField",
209
- "signUp.passwordField",
210
- "signUp.submitButton",
211
- "signUp.switchLink",
212
- "forgotPassword.root",
213
- "forgotPassword.title",
214
- "forgotPassword.logo",
215
- "forgotPassword.emailField",
216
- "forgotPassword.submitButton",
217
- "forgotPassword.backLink",
218
- "resetPassword.root",
219
- "resetPassword.title",
220
- "resetPassword.logo",
221
- "resetPassword.passwordField",
222
- "resetPassword.submitButton",
223
- "verifyEmail.root",
224
- "verifyEmail.title",
225
- "verifyEmail.logo",
226
- "verifyEmail.resendButton",
227
- "authFlow.root",
228
- "authModal.overlay",
229
- "authModal.container",
230
- "signInButton.root",
231
- "signUpButton.root",
232
- "signOutButton.root",
233
- "stepUp.root",
234
- "stepUp.title",
235
- "stepUp.passwordField",
236
- "stepUp.submitButton",
237
- "stepUp.cancelButton",
238
- "signInPage.root",
239
- "signInPage.logo",
240
- "signInPage.footer",
241
- "signUpPage.root",
242
- "signUpPage.logo",
243
- "signUpPage.footer",
244
- // user button
245
- "userButton.root",
246
- "userButton.trigger",
247
- "userButton.menu",
248
- "userButton.identity",
249
- "userButton.email",
250
- "userButton.badge",
251
- "userButton.item",
252
- "userButton.manageButton",
253
- "userButton.signOutButton",
254
- "userButton.signOutAllButton",
255
- // account
256
- "accountDashboard.root",
257
- "accountDashboard.tabs",
258
- "accountDashboard.sidebar",
259
- "accountDashboard.panel",
260
- "accountModal.overlay",
261
- "accountModal.container",
262
- "accountModal.header",
263
- "accountModal.title",
264
- "accountModal.body",
265
- "verifyBanner.root",
266
- "verifyBanner.message",
267
- "verifyBanner.resendButton",
268
- "verifyBanner.dismissButton",
269
- "profilePanel.root",
270
- "profilePanel.form",
271
- "profilePanel.submitButton",
272
- "addressesPanel.root",
273
- "addressesPanel.list",
274
- "addressesPanel.item",
275
- "addressesPanel.addButton",
276
- "addressesPanel.form",
277
- "addressesPanel.defaultButton",
278
- "addressesPanel.editButton",
279
- "addressesPanel.deleteButton",
280
- "addressesPanel.table",
281
- "addressesPanel.row",
282
- "paymentMethodsPanel.root",
283
- "paymentMethodsPanel.list",
284
- "paymentMethodsPanel.item",
285
- "paymentMethodsPanel.defaultButton",
286
- "paymentMethodsPanel.deleteButton",
287
- "paymentMethodsPanel.addButton",
288
- "paymentMethodsPanel.addDialog",
289
- "orderDetail.addresses",
290
- "orderDetail.shippingAddress",
291
- "orderDetail.billingAddress",
292
- "orderDetail.timeline",
293
- "orderDetail.payButton",
294
- "orderDetail.trackButton",
295
- "orderDetail.reorderButton",
296
- // Two-column detail layout, shared by the order and invoice pages.
297
- "orderDetail.layout",
298
- "orderDetail.main",
299
- "orderDetail.rail",
300
- "orderDetail.cancelButton",
301
- "orderDetail.returnButton",
302
- "orderDetail.invoiceButton",
303
- "orderDetail.subscriptionButton",
304
- "orderDetail.actions",
305
- "invoiceDetail.layout",
306
- "invoiceDetail.main",
307
- "invoiceDetail.rail",
308
- "invoiceDetail.lineItems",
309
- "invoiceDetail.lineItem",
310
- "invoiceDetail.orderLink",
311
- "invoiceDetail.subscriptionLink",
312
- "invoiceDetail.payButton",
313
- "invoiceDetail.actions",
314
- "fieldRow",
315
- "confirm.header",
316
- "confirm.title",
317
- "confirm.body",
318
- "confirm.confirmButton",
319
- "confirm.cancelButton",
320
- "paymentMethodsPanel.table",
321
- "paymentMethodsPanel.row",
322
- "ordersPanel.root",
323
- "ordersPanel.table",
324
- "ordersPanel.row",
325
- "ordersPanel.list",
326
- "ordersPanel.item",
327
- "ordersPanel.viewButton",
328
- "orderDetail.root",
329
- "orderDetail.backButton",
330
- "orderDetail.lineItems",
331
- "orderDetail.lineItem",
332
- "orderDetail.totals",
333
- "invoicesPanel.root",
334
- "invoicesPanel.table",
335
- "invoicesPanel.row",
336
- "invoicesPanel.list",
337
- "invoicesPanel.item",
338
- "invoicesPanel.viewButton",
339
- "invoiceDetail.root",
340
- "invoiceDetail.backButton",
341
- "invoiceDetail.downloadButton",
342
- "invoiceDetail.totals",
343
- "subscriptionsPanel.root",
344
- "subscriptionsPanel.list",
345
- "subscriptionsPanel.item",
346
- "subscriptionsPanel.cancelButton",
347
- "subscriptionsPanel.cancelNowButton",
348
- "subscriptionsPanel.pauseButton",
349
- "subscriptionsPanel.resumeButton",
350
- "subscriptionsPanel.changeAddressButton",
351
- "subscriptionsPanel.addressForm",
352
- "subscriptionsPanel.table",
353
- "subscriptionsPanel.row",
354
- "securityPanel.root",
355
- "securityPanel.passwordForm",
356
- "securityPanel.passwordButton",
357
- "securityPanel.sessionList",
358
- "securityPanel.sessionItem",
359
- "securityPanel.revokeButton",
360
- "securityPanel.revokeAllButton",
361
- "securityPanel.table",
362
- "securityPanel.row"
363
- ];
364
- var Ctx = createContext(null);
365
- function AppearanceProvider(props) {
366
- return /* @__PURE__ */ jsx(Ctx.Provider, { value: props.value, children: props.children });
367
- }
368
- function useAppearance(local) {
369
- const provider = useContext(Ctx);
370
- return useMemo(() => {
371
- const theme = local?.theme ?? provider?.theme ?? "light";
372
- const variables = { ...provider?.variables, ...local?.variables };
373
- const elements = { ...provider?.elements, ...local?.elements };
374
- const slots = { ...provider?.components, ...local?.components };
375
- const style = {};
376
- for (const [key, value] of Object.entries(variables)) {
377
- const property = VARIABLE_PROPERTY[key];
378
- if (property && value != null) style[property] = value;
379
- }
380
- warnOnUnreadablePrimary(variables, theme);
381
- return {
382
- theme,
383
- rootProps: { className: "throttle-root", style, "data-throttle-theme": theme },
384
- el(key, base) {
385
- const extra = elements[key];
386
- return {
387
- className: [base, extra].filter(Boolean).join(" ") || void 0,
388
- "data-throttle-element": key
389
- };
390
- },
391
- slots
392
- };
393
- }, [provider, local]);
394
- }
395
- function useAppearanceProviderValue(local) {
396
- const provider = useContext(Ctx);
397
- return useMemo(
398
- () => ({
399
- theme: local?.theme ?? provider?.theme,
400
- variables: { ...provider?.variables, ...local?.variables },
401
- elements: { ...provider?.elements, ...local?.elements },
402
- components: { ...provider?.components, ...local?.components }
403
- }),
404
- [provider, local]
405
- );
406
- }
407
-
408
- // src/react/components/branding.tsx
409
- import { createContext as createContext2, useContext as useContext2 } from "react";
410
- import { jsx as jsx2 } from "react/jsx-runtime";
411
- var Ctx2 = createContext2(null);
412
- function BrandLogoProvider(props) {
413
- return /* @__PURE__ */ jsx2(Ctx2.Provider, { value: props.value, children: props.children });
414
- }
415
- function useBrandLogo(local) {
416
- const provider = useContext2(Ctx2);
417
- return local ?? provider ?? null;
418
- }
419
-
420
- // src/react/components/localization.tsx
421
- import { createContext as createContext3, useContext as useContext3, useMemo as useMemo2 } from "react";
422
- import { jsx as jsx3 } from "react/jsx-runtime";
423
- var enUS = {
424
- locale: "en-US",
425
- common: {
426
- save: "Save",
427
- cancel: "Cancel",
428
- delete: "Delete",
429
- edit: "Edit",
430
- back: "Back",
431
- retry: "Try again",
432
- loading: "Loading\u2026",
433
- close: "Close",
434
- confirm: "Are you sure?",
435
- // The dismissive half of a confirmation. "Cancel" is ambiguous next to
436
- // "Cancel subscription" — one dismisses the dialog, the other ends the plan.
437
- keepIt: "Keep it",
438
- somethingWentWrong: "Something went wrong.",
439
- actions: "Actions",
440
- view: "View"
441
- },
442
- signIn: {
443
- title: "Sign in",
444
- description: "Sign in to your account.",
445
- email: "Email",
446
- password: "Password",
447
- submitButton: "Sign in",
448
- forgotLink: "Forgot your password?",
449
- switchPrompt: "Don't have an account?",
450
- switchLink: "Sign up"
451
- },
452
- signUp: {
453
- title: "Create your account",
454
- description: "",
455
- email: "Email",
456
- password: "Password",
457
- submitButton: "Create account",
458
- switchPrompt: "Already have an account?",
459
- switchLink: "Sign in",
460
- // Deliberately conditional. Registering onto an address that already has
461
- // an account returns the same `pending` status and sends no email, so
462
- // asserting one was sent would be false for that buyer.
463
- pendingNotice: "If that email is new, check your inbox for a link to finish signing up."
464
- },
465
- forgotPassword: {
466
- title: "Reset your password",
467
- description: "We will email you a link if an account exists.",
468
- email: "Email",
469
- submitButton: "Send reset link",
470
- sentNotice: "If an account exists for that email, a reset link is on its way.",
471
- backLink: "Back to sign in"
472
- },
473
- resetPassword: {
474
- title: "Choose a new password",
475
- password: "New password",
476
- submitButton: "Update password",
477
- doneNotice: "Your password has been updated.",
478
- missingToken: "That link is missing its token. Request a new one."
479
- },
480
- verifyEmail: {
481
- title: "Verify your email",
482
- verifying: "Confirming your email\u2026",
483
- success: "Your email is verified.",
484
- resendButton: "Resend the link",
485
- resentNotice: "We sent a new link.",
486
- instructions: "Check your inbox for a verification link."
487
- },
488
- stepUp: {
489
- title: "Confirm your password",
490
- description: "For your security, please re-enter your password to continue.",
491
- password: "Password",
492
- submitButton: "Confirm",
493
- cancelButton: "Cancel"
494
- },
495
- userButton: {
496
- manage: "Manage account",
497
- signOut: "Sign out",
498
- signOutAll: "Sign out of all devices",
499
- verified: "Verified",
500
- unverified: "Unverified",
501
- openMenu: "Account menu"
502
- },
503
- verifyBanner: {
504
- message: "Verify your email to see your full account history.",
505
- resendButton: "Resend link",
506
- dismissButton: "Dismiss"
507
- },
508
- account: {
509
- title: "Your account",
510
- tabs: {
511
- profile: "Profile",
512
- addresses: "Addresses",
513
- paymentMethods: "Saved cards",
514
- orders: "Orders",
515
- invoices: "Invoices",
516
- subscriptions: "Subscriptions",
517
- security: "Security"
518
- }
519
- },
520
- profile: {
521
- firstName: "First name",
522
- lastName: "Last name",
523
- phone: "Phone",
524
- company: "Company",
525
- acceptsMarketing: "Send me product news and offers",
526
- email: "Email",
527
- emailReadOnly: "Your email address cannot be changed here.",
528
- saveButton: "Save changes",
529
- savedNotice: "Profile updated."
530
- },
531
- addresses: {
532
- addButton: "Add address",
533
- defaultBadge: "Default",
534
- makeDefault: "Make default",
535
- deleteConfirmAction: "Delete address",
536
- empty: "No saved addresses",
537
- emptyDescription: "Addresses you use at checkout can be saved here.",
538
- label: "Label",
539
- firstName: "First name",
540
- lastName: "Last name",
541
- company: "Company",
542
- addressLine1: "Address",
543
- addressLine2: "Apartment, suite, etc.",
544
- city: "City",
545
- stateProvince: "State / Province",
546
- postalCode: "Postal code",
547
- countryCode: "Country code",
548
- phone: "Phone",
549
- deleteConfirm: "Delete this address?",
550
- columnAddress: "Address"
551
- },
552
- paymentMethods: {
553
- empty: "No saved cards",
554
- emptyDescription: "Add a card here, or one will be saved when you check out.",
555
- addButton: "Add card",
556
- addTitle: "Add a card",
557
- // Shown whenever the panel is empty with no error and the buyer is not
558
- // verified — including simply being signed out — matching the copy
559
- // orders/invoices/subscriptions already show in that state.
560
- gatedDescription: "Verify your email to see your saved cards.",
561
- defaultBadge: "Default",
562
- makeDefault: "Make default",
563
- expires: "Expires",
564
- deleteConfirm: "Remove this card?",
565
- deleteConfirmAction: "Remove card",
566
- columnCard: "Card"
567
- },
568
- orders: {
569
- shipTo: "Ship to",
570
- billTo: "Bill to",
571
- history: "History",
572
- placed: "Order placed",
573
- completed: "Completed",
574
- cancelledAt: "Cancelled",
575
- closedAt: "Closed",
576
- payNow: "Pay now",
577
- track: "Track shipment",
578
- reorder: "Order again",
579
- cancelOrder: "Cancel order",
580
- cancelOrderConfirm: "Cancel this order?",
581
- cancelOrderBody: "This cannot be undone. Nothing has been charged, so there is nothing to refund.",
582
- cancelOrderAction: "Cancel order",
583
- requestReturn: "Request a return",
584
- requestReturnConfirm: "Request a return for this order?",
585
- requestReturnBody: "The merchant reviews your request and will be in touch. Nothing is refunded until they approve it.",
586
- requestReturnAction: "Send request",
587
- returnRequested: "Return requested. The merchant will be in touch.",
588
- downloadInvoice: "Download invoice",
589
- summary: "Summary",
590
- empty: "No orders yet",
591
- emptyDescription: "Orders you place will appear here.",
592
- gatedDescription: "Verify your email to see your order history.",
593
- number: "Order",
594
- date: "Date",
595
- status: "Status",
596
- total: "Total",
597
- /**
598
- * Display copy for `order.status`. Mirrors ORDER_STATUS_META in
599
- * `@commerce/orders`, which is the server's single source of status copy —
600
- * copied rather than imported because this package ships with no runtime
601
- * dependencies and is a client for the wire API, not for server packages.
602
- * If a status is added there, add it here.
603
- */
604
- statusLabels: {
605
- draft: "Draft",
606
- pending: "Pending",
607
- processing: "Processing",
608
- partiallyFulfilled: "Partially fulfilled",
609
- fulfilled: "Fulfilled",
610
- cancelled: "Cancelled",
611
- closed: "Closed"
612
- },
613
- /**
614
- * The money half of the label, shown as a second badge beside the
615
- * lifecycle one. `captured` is deliberately empty: paid in full is the
616
- * assumed happy path, and announcing it on every settled order is noise.
617
- */
618
- paymentLabels: {
619
- pending: "Awaiting payment",
620
- authorized: "Authorized",
621
- captured: "",
622
- partiallyPaid: "Partially paid",
623
- partiallyRefunded: "Partially refunded",
624
- refunded: "Refunded",
625
- failed: "Payment failed",
626
- voided: "Voided",
627
- processing: "Payment processing",
628
- disputed: "Disputed",
629
- expired: "Authorization expired"
630
- },
631
- items: "Items",
632
- subtotal: "Subtotal",
633
- discount: "Discount",
634
- shipping: "Shipping",
635
- tax: "Tax",
636
- quantity: "Qty",
637
- shippingAddress: "Shipping address",
638
- billingAddress: "Billing address",
639
- viewSubscription: "View subscription",
640
- viewSubscriptionFor: "View subscription for"
641
- },
642
- invoices: {
643
- empty: "No invoices yet",
644
- emptyDescription: "Invoices will appear here.",
645
- gatedDescription: "Verify your email to see your invoices.",
646
- number: "Invoice",
647
- issued: "Issued",
648
- due: "Due",
649
- status: "Status",
650
- total: "Total",
651
- refunded: "Refunded",
652
- period: "Period",
653
- downloadButton: "Download PDF",
654
- items: "Items",
655
- summary: "Summary",
656
- viewOrder: "View order",
657
- viewSubscription: "View subscription",
658
- payButton: "Pay this invoice",
659
- billedTo: "Billed to",
660
- /** Shown when an invoice bills less than the order, matching the PDF. */
661
- orderTotal: "Order total",
662
- amountPaid: "Amount paid",
663
- waived: "Waived",
664
- amountDue: "Amount due",
665
- balanceDue: "Balance due",
666
- /** Display copy for `invoice.status` — the wire values are paid|open|void. */
667
- statusLabels: { paid: "Paid", open: "Open", void: "Void" },
668
- downloadUnavailable: "This invoice is not available to download right now."
669
- },
670
- subscriptions: {
671
- cancelConfirmAction: "Cancel plan",
672
- pauseConfirm: "Pause this subscription?",
673
- resumeConfirm: "Resume this subscription?",
674
- // Distinct from cancelConfirm: ending it now is not the same promise as
675
- // letting it run to the end of a period the buyer already paid for.
676
- cancelNowConfirm: "End this subscription immediately?",
677
- empty: "No subscriptions",
678
- emptyDescription: "Subscriptions will appear here.",
679
- gatedDescription: "Verify your email to see your subscriptions.",
680
- renewsOn: "Renews on",
681
- endsOn: "Ends on",
682
- cancelledOn: "Cancelled on",
683
- trialEnds: "Trial ends",
684
- installmentProgress: "Payment {paid} of {total}",
685
- installmentTotal: "{amount} total",
686
- installmentNext: "Next {amount} on {date}",
687
- /** Spec §5: a varied schedule's dated list; dates are estimates, hence "~". */
688
- upcomingPayments: "Upcoming payments",
689
- upcomingPayment: "{amount} \xB7 ~{date}",
690
- cancelButton: "Cancel",
691
- cancelAtPeriodEnd: "Cancel at period end",
692
- cancelNow: "Cancel immediately",
693
- pauseButton: "Pause",
694
- resumeButton: "Resume",
695
- /** A physical subscription's ship-to, and the control that changes it. */
696
- shipsTo: "Ships to",
697
- changeAddress: "Change address",
698
- changeAddressTitle: "Change shipping address",
699
- changeAddressHint: "Your next box ships here. Orders already placed keep their address.",
700
- cancelConfirm: "Cancel this subscription?",
701
- cancelScheduled: "Cancels at the end of the period",
702
- columnPlan: "Plan",
703
- columnPrice: "Price",
704
- columnRenews: "Renews",
705
- columnStatus: "Status",
706
- /**
707
- * Display copy for `subscription.status`. Mirrors SubscriptionStatus in
708
- * `@commerce/subscriptions`; copied rather than imported because this
709
- * package has no runtime dependencies. Without it a buyer in dunning saw
710
- * the literal pill `past_due`. `completed` is an installment plan that
711
- * has taken its last payment.
712
- */
713
- statusLabels: {
714
- active: "Active",
715
- paused: "Paused",
716
- cancelled: "Cancelled",
717
- pastDue: "Payment overdue",
718
- trialing: "Trial",
719
- unpaid: "Unpaid",
720
- completed: "Paid in full"
721
- }
722
- },
723
- security: {
724
- passwordTitle: "Change password",
725
- newPassword: "New password",
726
- changeButton: "Update password",
727
- changedNotice: "Password updated.",
728
- sessionsTitle: "Devices",
729
- thisDevice: "This device",
730
- lastActive: "Last active",
731
- revokeButton: "Sign out",
732
- revokeAllButton: "Sign out of all other devices",
733
- sessionsEmpty: "No other devices",
734
- columnDevice: "Device"
735
- },
736
- errors: {}
737
- };
738
- function isPlainObject(value) {
739
- return typeof value === "object" && value !== null && !Array.isArray(value);
740
- }
741
- function deepMerge(base, over, path = "") {
742
- if (!over) return base;
743
- const out = Array.isArray(base) ? [...base] : { ...base };
744
- for (const [key, value] of Object.entries(over)) {
745
- if (value == null) continue;
746
- const current = base[key];
747
- const keyPath = path ? `${path}.${key}` : key;
748
- const currentIsObject = isPlainObject(current);
749
- const valueIsObject = isPlainObject(value);
750
- if (current !== void 0 && currentIsObject !== valueIsObject) {
751
- if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
752
- console.warn(
753
- `[useCopy] ignoring localization override at "${keyPath}": expected ${currentIsObject ? "an object" : "a string"}, got ${valueIsObject ? "an object" : typeof value}`
754
- );
755
- }
756
- continue;
757
- }
758
- out[key] = currentIsObject && valueIsObject ? deepMerge(current, value, keyPath) : value;
759
- }
760
- return out;
761
- }
762
- var Ctx3 = createContext3(null);
763
- function LocalizationProvider(props) {
764
- return /* @__PURE__ */ jsx3(Ctx3.Provider, { value: props.value, children: props.children });
765
- }
766
- function useCopy(local) {
767
- const provider = useContext3(Ctx3);
768
- return useMemo2(() => {
769
- const t = deepMerge(deepMerge(enUS, provider), local);
770
- return {
771
- t,
772
- locale: t.locale,
773
- /**
774
- * A merchant's `errors` map wins; otherwise the shipped mapper decides.
775
- * `messageForError` stays the floor because it encodes two deliberate
776
- * non-passthroughs — `invalid_credentials` stays vague to avoid account
777
- * enumeration, and unknown codes stay generic.
778
- */
779
- errorMessage(err) {
780
- const code = err?.code;
781
- const override = code ? t.errors[code] : void 0;
782
- return override ?? messageForError(err);
783
- }
784
- };
785
- }, [provider, local]);
786
- }
787
-
788
- // src/react/components/styles.ts
789
- import { useInsertionEffect } from "react";
790
- var THROTTLE_STYLE_ID = "throttle-auth-styles";
791
- var THROTTLE_STYLES = `
792
- .throttle-root {
793
- --throttle-color-primary: #18181b;
794
- --throttle-color-primary-foreground: #ffffff;
795
- --throttle-color-background: #ffffff;
796
- --throttle-color-surface: #f8fafc;
797
- --throttle-color-foreground: #0e1116;
798
- --throttle-color-muted: #eceef2;
799
- --throttle-color-muted-foreground: #5b6472;
800
- --throttle-color-border: #e2e8f0;
801
- /* The boundary of an INTERACTIVE control, which WCAG 1.4.11 requires to
802
- reach 3:1 against its adjacent colour \u2014 an input identified only by a
803
- 1.23:1 hairline is not identifiable at all. Decorative separators (card
804
- edges, table rules, dividers) keep --throttle-color-border: they do not
805
- identify a control, so the requirement does not apply and darkening
806
- them would only add noise. */
807
- /* The account dashboard paints nothing by default so it can drop into a
808
- merchant's page without fighting their background. The accountSurface and
809
- accountPadding appearance variables opt back into a card.
810
- No backticks in this comment: the sheet is a TS template literal. */
811
- --throttle-card-width: 400px;
812
- --throttle-color-account-surface: transparent;
813
- --throttle-account-padding: 0;
814
- --throttle-color-border-control: #8b93a1;
815
- --throttle-color-danger: #c2321f;
816
- --throttle-color-danger-surface: #fef2f2;
817
- --throttle-color-danger-border: #fecaca;
818
- --throttle-color-success: #127a4a;
819
- --throttle-color-success-surface: #ecfdf5;
820
- --throttle-color-success-border: #a7f3d0;
821
- --throttle-color-warning: #92400e;
822
- --throttle-color-warning-surface: #fffbeb;
823
- --throttle-color-warning-border: #fde68a;
824
- --throttle-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
825
- --throttle-font-size: 16px;
826
- --throttle-font-size-xs: 12px;
827
- --throttle-font-size-sm: 14px;
828
- --throttle-font-size-lg: 20px;
829
- --throttle-font-size-xl: 24px;
830
- --throttle-line-height: 1.5;
831
- --throttle-line-height-tight: 1.25;
832
- --throttle-radius: 12px;
833
- --throttle-radius-sm: 8px;
834
- --throttle-radius-full: 999px;
835
- --throttle-border-width: 1px;
836
- --throttle-spacing: 8px;
837
- --throttle-space-1: 4px;
838
- --throttle-space-2: var(--throttle-spacing);
839
- --throttle-space-3: 12px;
840
- --throttle-space-4: 16px;
841
- --throttle-space-5: 24px;
842
- --throttle-space-6: 32px;
843
- --throttle-control-height: 48px;
844
- --throttle-control-padding-x: 16px;
845
- --throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);
846
- --throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);
847
- --throttle-transition: 150ms cubic-bezier(.2,0,.2,1);
848
- --throttle-z-modal: 2147483000;
849
- --throttle-z-popover: 2147482000;
850
- font-family: var(--throttle-font-family);
851
- font-size: var(--throttle-font-size);
852
- line-height: var(--throttle-line-height);
853
- color: var(--throttle-color-foreground);
854
- box-sizing: border-box;
855
- }
856
- .throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }
857
-
858
- .throttle-root[data-throttle-theme="dark"] {
859
- --throttle-color-primary: #e6edf3;
860
- --throttle-color-primary-foreground: #0d1117;
861
- --throttle-color-background: #0d1117;
862
- --throttle-color-surface: #1c2129;
863
- --throttle-color-foreground: #e6edf3;
864
- --throttle-color-muted: #21262d;
865
- --throttle-color-muted-foreground: #9aa4b2;
866
- --throttle-color-border: #30363d;
867
- --throttle-color-border-control: #5c6570;
868
- --throttle-color-danger: #ff6b58;
869
- --throttle-color-danger-surface: #2a1513;
870
- --throttle-color-danger-border: #5c2b25;
871
- --throttle-color-success: #45c08a;
872
- --throttle-color-success-surface: #0f2419;
873
- --throttle-color-success-border: #1f4d36;
874
- --throttle-color-warning: #e0a53a;
875
- --throttle-color-warning-surface: #2a2113;
876
- --throttle-color-warning-border: #5c4a25;
877
- --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
878
- --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
879
- }
880
- @media (prefers-color-scheme: dark) {
881
- .throttle-root[data-throttle-theme="auto"] {
882
- --throttle-color-primary: #e6edf3;
883
- --throttle-color-primary-foreground: #0d1117;
884
- --throttle-color-background: #0d1117;
885
- --throttle-color-surface: #1c2129;
886
- --throttle-color-foreground: #e6edf3;
887
- --throttle-color-muted: #21262d;
888
- --throttle-color-muted-foreground: #9aa4b2;
889
- --throttle-color-border: #30363d;
890
- --throttle-color-border-control: #5c6570;
891
- --throttle-color-danger: #ff6b58;
892
- --throttle-color-danger-surface: #2a1513;
893
- --throttle-color-danger-border: #5c2b25;
894
- --throttle-color-success: #45c08a;
895
- --throttle-color-success-surface: #0f2419;
896
- --throttle-color-success-border: #1f4d36;
897
- --throttle-color-warning: #e0a53a;
898
- --throttle-color-warning-surface: #2a2113;
899
- --throttle-color-warning-border: #5c4a25;
900
- --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);
901
- --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);
902
- }
903
- }
904
-
905
- .throttle-card {
906
- background: var(--throttle-color-background);
907
- border: var(--throttle-border-width) solid var(--throttle-color-border);
908
- border-radius: var(--throttle-radius);
909
- box-shadow: var(--throttle-shadow-card);
910
- padding: var(--throttle-space-5);
911
- width: 100%;
912
- max-width: var(--throttle-card-width);
913
- margin-inline: auto;
914
- }
915
- /* Inside a Modal the dialog already provides the surface \u2014 background, radius
916
- and elevation. A card that draws its own on top is the "box inside a box":
917
- two shadowed rounded rectangles nested, with the card's max-width stranding
918
- it in the middle of a much wider dialog. */
919
- .throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }
920
- .throttle-card__header { margin-bottom: var(--throttle-space-4); }
921
- .throttle-card__header--branded { text-align: center; }
922
- .throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }
923
- .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; }
924
- .throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }
925
- .throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }
926
- .throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
927
- .throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }
928
- .throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }
929
-
930
- .throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
931
- .throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }
932
- .throttle-field__label { font-weight: 500; }
933
- .throttle-field__input {
934
- font: inherit;
935
- font-size: var(--throttle-font-size);
936
- color: var(--throttle-color-foreground);
937
- background: var(--throttle-color-background);
938
- border: var(--throttle-border-width) solid var(--throttle-color-border-control);
939
- border-radius: var(--throttle-radius-sm);
940
- /* padding-block stays 0 so min-height alone owns the height, exactly as
941
- .throttle-button does. With 12px vertical padding the box computed to
942
- 50px against the button's 48px \u2014 a 2px mismatch, measured in a browser
943
- across 19 controls. An input element centres its own text vertically. */
944
- padding-block: 0;
945
- padding-inline: var(--throttle-control-padding-x);
946
- min-height: var(--throttle-control-height);
947
- width: 100%;
948
- transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);
949
- }
950
- .throttle-field__input:focus-visible {
951
- outline: none;
952
- border-color: var(--throttle-color-primary);
953
- box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);
954
- }
955
- .throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }
956
- .throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }
957
- .throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }
958
- .throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }
959
- /* Always mounted, so it reserves its line whether or not there is an error:
960
- an error appearing on submit must not push the submit button out from
961
- under the buyer's pointer. */
962
- .throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); line-height: 1rem; }
963
- /* Reserved a full line under EVERY input even with nothing to say, which
964
- with the stack gap put 28px of dead space beneath each field, on every
965
- form and all seven account panels. The element stays mounted when empty
966
- because role=alert only announces changes to a live region that was
967
- already there; it just stops occupying a line. */
968
- .throttle-field__error:empty { display: none; }
969
-
970
- .throttle-field-group {
971
- display: flex;
972
- flex-direction: column;
973
- border: var(--throttle-border-width) solid var(--throttle-color-border);
974
- border-radius: var(--throttle-radius-sm);
975
- overflow: hidden;
976
- }
977
- .throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }
978
- .throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }
979
- .throttle-field-group__forgot { margin-left: auto; }
980
-
981
- .throttle-button {
982
- font: inherit;
983
- font-weight: 500;
984
- border-radius: var(--throttle-radius-sm);
985
- border: var(--throttle-border-width) solid transparent;
986
- padding-inline: var(--throttle-control-padding-x);
987
- padding-block: 0;
988
- min-height: var(--throttle-control-height);
989
- display: inline-flex;
990
- align-items: center;
991
- justify-content: center;
992
- cursor: pointer;
993
- /* A label that wraps turns a control into a two-line block of text. */
994
- white-space: nowrap;
995
- transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);
996
- }
997
- .throttle-button:disabled { opacity: .55; cursor: not-allowed; }
998
- .throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }
999
- .throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: var(--throttle-button-width, 100%); }
1000
- .throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }
1001
- .throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border-control); }
1002
- .throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }
1003
- .throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }
1004
- .throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }
1005
- /* Link-style actions are the rows panel default, and they carry Delete,
1006
- Remove, Cancel and Sign out. Zero padding with min-height 0 made those
1007
- roughly 20px tall and ~8px apart \u2014 a mis-tap on a phone lands on something
1008
- irreversible. The target is expanded with vertical padding and pulled back
1009
- with an equal negative margin, so the hit box clears 44px while the text
1010
- sits exactly where it did. Collapsing this back to zero padding shrinks the
1011
- target again. */
1012
- .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; }
1013
- .throttle-button--link:hover:not(:disabled) { opacity: .8; }
1014
- .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; }
1015
- .throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }
1016
- .throttle-button--xs { font-size: var(--throttle-font-size-xs); }
1017
-
1018
- .throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }
1019
- .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); }
1020
- .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); }
1021
- .throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }
1022
-
1023
- .throttle-spinner {
1024
- display: inline-block; width: 1em; height: 1em;
1025
- border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
1026
- animation: throttle-spin 700ms linear infinite;
1027
- }
1028
- @keyframes throttle-spin { to { transform: rotate(360deg); } }
1029
- .throttle-button--loading { position: relative; }
1030
- .throttle-button__label { display: inline-flex; align-items: center; justify-content: center; gap: var(--throttle-space-2); }
1031
- .throttle-button__label--hidden { visibility: hidden; }
1032
- .throttle-spinner--centred { position: absolute; top: 50%; left: 50%; margin: -0.5em 0 0 -0.5em; }
1033
-
1034
- @media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }
1035
-
1036
- .throttle-skeleton {
1037
- background: var(--throttle-color-muted);
1038
- border-radius: var(--throttle-radius-sm);
1039
- animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }
1040
- @keyframes throttle-pulse { 50% { opacity: .5; } }
1041
- @media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }
1042
-
1043
- .throttle-modal__overlay {
1044
- position: fixed; inset: 0; z-index: var(--throttle-z-modal);
1045
- background: rgba(14,17,22,.45);
1046
- display: flex; align-items: center; justify-content: center;
1047
- padding: var(--throttle-space-4);
1048
- overflow-y: auto;
1049
- }
1050
- .throttle-modal__container {
1051
- background: var(--throttle-color-background);
1052
- border-radius: var(--throttle-radius);
1053
- box-shadow: var(--throttle-shadow-popover);
1054
- max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;
1055
- position: relative;
1056
- }
1057
- /* After the base rule on purpose: both are single-class, so equal specificity
1058
- means source order decides. Declared before it, this silently lost. */
1059
- .throttle-modal__container--compact { max-width: 440px; }
1060
- .throttle-modal__close {
1061
- position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);
1062
- background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);
1063
- color: var(--throttle-color-muted-foreground);
1064
- transition: color var(--throttle-transition);
1065
- }
1066
- .throttle-modal__close:hover { color: var(--throttle-color-foreground); }
1067
- .throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }
1068
- .throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }
1069
- .throttle-modal__body { padding: var(--throttle-space-4); }
1070
-
1071
- .throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }
1072
- .throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }
1073
- .throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }
1074
- .throttle-dropdown__menu {
1075
- position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));
1076
- z-index: var(--throttle-z-popover);
1077
- min-width: 260px;
1078
- background: var(--throttle-color-background);
1079
- border: var(--throttle-border-width) solid var(--throttle-color-border);
1080
- border-radius: var(--throttle-radius);
1081
- box-shadow: var(--throttle-shadow-popover);
1082
- padding: var(--throttle-space-1);
1083
- }
1084
- .throttle-dropdown__item {
1085
- display: flex; align-items: center; gap: var(--throttle-space-2);
1086
- width: 100%; text-align: left; font: inherit; color: inherit;
1087
- background: none; border: none; cursor: pointer;
1088
- padding: var(--throttle-space-3);
1089
- border-radius: var(--throttle-radius-sm);
1090
- text-decoration: none;
1091
- transition: background-color var(--throttle-transition);
1092
- }
1093
- .throttle-dropdown__item:hover { background: var(--throttle-color-surface); }
1094
- .throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1095
- .throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }
1096
-
1097
- .throttle-tabs__list {
1098
- display: flex; gap: var(--throttle-space-1);
1099
- border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);
1100
- overflow-x: auto; scrollbar-width: thin;
1101
- }
1102
- .throttle-tabs__tab {
1103
- font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;
1104
- padding: var(--throttle-space-3);
1105
- color: var(--throttle-color-muted-foreground);
1106
- border-bottom: 2px solid transparent;
1107
- transition: color var(--throttle-transition), border-color var(--throttle-transition);
1108
- }
1109
- .throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }
1110
- .throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }
1111
- .throttle-tabs__panel { padding-top: var(--throttle-space-4); }
1112
-
1113
- /* Paints its own ground. A UserButton in a navbar should stay transparent, but
1114
- the dashboard is a large surface of themed text: in dark theme its foreground
1115
- is near-white, so on a light host page it was rendering white-on-white. */
1116
- .throttle-cardframe { width: 100%; min-height: 420px; border: 0; display: block; }
1117
- .throttle-centre { display: flex; align-items: center; justify-content: center; min-height: 160px; }
1118
- .throttle-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-4); align-items: start; }
1119
- .throttle-section__title { font-size: var(--throttle-font-size-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--throttle-color-muted-foreground); margin: 0 0 var(--throttle-space-2); font-weight: 600; }
1120
- .throttle-addresses { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-5); }
1121
- .throttle-addressblock__line { margin: 0 0 2px; }
1122
- .throttle-timeline { display: flex; flex-direction: column; gap: var(--throttle-space-2); }
1123
- .throttle-timeline__row { display: flex; align-items: center; gap: var(--throttle-space-2); }
1124
- .throttle-timeline__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--throttle-color-border-control); flex: none; }
1125
- .throttle-timeline__label { flex: 1; }
1126
- .throttle-account-root { container-type: inline-size; background: var(--throttle-color-account-surface); color: var(--throttle-color-foreground); padding: var(--throttle-account-padding); }
1127
- .throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }
1128
- .throttle-account__content { flex: 1; min-width: 0; }
1129
- .throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }
1130
- .throttle-tabs__railitem {
1131
- font: inherit;
1132
- font-size: var(--throttle-font-size-sm);
1133
- display: flex;
1134
- align-items: center;
1135
- gap: var(--throttle-space-3);
1136
- width: 100%;
1137
- text-align: left;
1138
- background: none;
1139
- border: none;
1140
- border-radius: var(--throttle-radius-sm);
1141
- padding: 0 var(--throttle-space-3);
1142
- min-height: var(--throttle-control-height);
1143
- color: var(--throttle-color-muted-foreground);
1144
- cursor: pointer;
1145
- transition: background var(--throttle-transition), color var(--throttle-transition);
1146
- }
1147
- .throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }
1148
- .throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1149
- .throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1150
- .throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }
1151
- .throttle-tabs__railicon { flex: none; opacity: .75; }
1152
-
1153
- /* A CONTAINER query, not a viewport one: these components render inside a
1154
- merchant's page, so the viewport says nothing about how much room the
1155
- dashboard actually has \u2014 it could sit in a 400px column on a 1920px screen.
1156
- Below ~560px of container the rail becomes a full-width list above the
1157
- panel: all seven still visible, each still a 44px target. */
1158
- @container (max-width: 560px) {
1159
- /* align-items: flex-start is right in row direction and wrong in column \u2014
1160
- it shrinks the rail to its content width, so width:100% alone cannot make
1161
- it span. Stretch it explicitly when stacked. */
1162
- .throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }
1163
- .throttle-addresses { grid-template-columns: 1fr; }
1164
- .throttle-fieldrow { grid-template-columns: 1fr; }
1165
- .throttle-tabs__rail { width: 100%; }
1166
- /* One column, and the rail stops sticking \u2014 on a phone a sticky summary
1167
- covers the very line items it is summarising. */
1168
- .throttle-detail { grid-template-columns: 1fr; }
1169
- .throttle-detail__rail { position: static; }
1170
- }
1171
-
1172
- .throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
1173
- .throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }
1174
- .throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }
1175
- .throttle-table__row:hover { background: var(--throttle-color-surface); }
1176
- .throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }
1177
- .throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }
1178
- .throttle-table__scroll { overflow-x: auto; }
1179
-
1180
- .throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }
1181
- .throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }
1182
- .throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }
1183
-
1184
- .throttle-avatar {
1185
- display: inline-flex; align-items: center; justify-content: center;
1186
- width: 32px; height: 32px; border-radius: var(--throttle-radius-full);
1187
- background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);
1188
- font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;
1189
- }
1190
-
1191
- .throttle-badge {
1192
- display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;
1193
- padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);
1194
- background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);
1195
- }
1196
- .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); }
1197
- .throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }
1198
- .throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }
1199
- .throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }
1200
-
1201
- .throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }
1202
- .throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }
1203
- .throttle-muted { color: var(--throttle-color-muted-foreground); }
1204
- .throttle-banner {
1205
- display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);
1206
- flex-wrap: wrap;
1207
- background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);
1208
- color: var(--throttle-color-warning);
1209
- border-radius: var(--throttle-radius-sm);
1210
- padding: var(--throttle-space-3);
1211
- margin-bottom: var(--throttle-space-4);
1212
- }
1213
- .throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }
1214
- /* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the
1215
- dynamic address/toolbar, so the page would be taller than the visible
1216
- viewport and the card would sit off-centre until the toolbar collapsed. */
1217
- .throttle-page {
1218
- min-height: 100dvh;
1219
- width: 100%;
1220
- background: var(--throttle-color-surface);
1221
- display: flex;
1222
- flex-direction: column;
1223
- align-items: center;
1224
- justify-content: center;
1225
- gap: var(--throttle-space-5);
1226
- padding: var(--throttle-space-5);
1227
- }
1228
- .throttle-page__logo { display: flex; justify-content: center; }
1229
- .throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }
1230
- .throttle-listitem {
1231
- display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
1232
- flex-wrap: wrap;
1233
- border: var(--throttle-border-width) solid var(--throttle-color-border);
1234
- border-radius: var(--throttle-radius-sm);
1235
- padding: var(--throttle-space-3);
1236
- }
1237
- /* 'rows' variant: hairline rules between items instead of each item carrying
1238
- its own border+radius, so a panel does not read as a card inside a card. */
1239
- .throttle-rows { display: flex; flex-direction: column; }
1240
- .throttle-rowitem {
1241
- display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);
1242
- flex-wrap: wrap;
1243
- border-top: var(--throttle-border-width) solid var(--throttle-color-border);
1244
- padding: var(--throttle-space-3) 0;
1245
- }
1246
- .throttle-rowitem:first-child { border-top: none; padding-top: 0; }
1247
- .throttle-actions {
1248
- display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; align-items: center;
1249
- /* Primary buttons are full-width so a form's submit fills its card. In a row
1250
- of sibling actions that same rule made every button claim 100% and then
1251
- shrink to share the line, squeezing Resume to 59px so its label wrapped to
1252
- two lines inside a 48px box. A custom property INHERITS to the buttons, so
1253
- the row opts them back to content width without a descendant selector \u2014
1254
- those outrank the merchant overrides in appearance.elements. */
1255
- --throttle-button-width: auto;
1256
- }
1257
- .throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }
1258
- .throttle-lineitem { display: flex; align-items: flex-start; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }
1259
- /* The UA's 1em paragraph margins pushed a two-line item to 96px, so five
1260
- items filled a screen with mostly whitespace. These carry the spacing
1261
- instead. Separate classes, not a ".throttle-lineitem p" descendant
1262
- selector -- those outrank the merchant's appearance.elements overrides. */
1263
- .throttle-lineitem__desc { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); }
1264
- .throttle-lineitem__meta { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-sm); }
1265
- .throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }
1266
- .throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }
1267
-
1268
- /* Order and invoice detail: the record on the left, what it costs and what
1269
- you can do about it on the right. The rail sticks so the total and the
1270
- actions stay reachable on an order with thirty lines, which is exactly when
1271
- a buyer most wants them. Collapses below, where a sticky rail would eat a
1272
- phone screen. */
1273
- .throttle-detail { display: grid; grid-template-columns: minmax(0, 1fr) 17rem; gap: var(--throttle-space-6); align-items: start; }
1274
- .throttle-detail__main { display: flex; flex-direction: column; gap: var(--throttle-space-5); min-width: 0; }
1275
- .throttle-detail__rail {
1276
- display: flex; flex-direction: column; gap: var(--throttle-space-4);
1277
- position: sticky; top: var(--throttle-space-4);
1278
- padding: var(--throttle-space-4);
1279
- border: var(--throttle-border-width) solid var(--throttle-color-border);
1280
- border-radius: var(--throttle-radius-md);
1281
- background: var(--throttle-color-surface);
1282
- }
1283
- /* The one number the buyer came for. */
1284
- .throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }
1285
- .throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }
1286
- .throttle-detail__label { font-size: var(--throttle-font-size-sm); font-weight: 500; color: var(--throttle-color-muted-foreground); letter-spacing: .04em; text-transform: uppercase; }
1287
- /* A rail action column: full width, stacked, so the primary reads as primary. */
1288
- .throttle-detail__actions { display: flex; flex-direction: column; gap: var(--throttle-space-2); --throttle-button-width: 100%; }
1289
- .throttle-detail__link { display: inline-flex; align-items: center; gap: var(--throttle-space-1); }
1290
-
1291
- @media (prefers-reduced-motion: reduce) {
1292
- .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {
1293
- transition: none;
1294
- }
1295
- }
1296
- `;
1297
- function useInjectStyles(enabled) {
1298
- useInsertionEffect(() => {
1299
- if (!enabled) return;
1300
- if (typeof document === "undefined") return;
1301
- if (document.getElementById(THROTTLE_STYLE_ID)) return;
1302
- const tag = document.createElement("style");
1303
- tag.id = THROTTLE_STYLE_ID;
1304
- tag.textContent = THROTTLE_STYLES;
1305
- document.head.appendChild(tag);
1306
- }, [enabled]);
1307
- }
1308
-
1309
- // src/react/components/primitives/Card.tsx
1310
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
1311
- function CardHeader(props) {
1312
- const a = useAppearance();
1313
- const headerClass = `throttle-card__header${props.branded ? " throttle-card__header--branded" : ""}`;
1314
- const titleClass = `throttle-card__title${props.branded ? " throttle-card__title--branded" : ""}`;
1315
- return /* @__PURE__ */ jsxs("div", { ...a.el("card.header", headerClass), children: [
1316
- props.logo ? /* @__PURE__ */ jsx4("div", { ...a.el(props.logoKey ?? "card.logo", "throttle-card__logo"), children: props.logo }) : null,
1317
- /* @__PURE__ */ jsx4("h2", { ...a.el(props.titleKey ?? "card.title", titleClass), children: props.title }),
1318
- props.description ? /* @__PURE__ */ jsx4("p", { ...a.el("card.description", "throttle-card__description"), children: props.description }) : null
1319
- ] });
1320
- }
1321
- function CardBody(props) {
1322
- const a = useAppearance();
1323
- return /* @__PURE__ */ jsx4("div", { ...a.el("card.body", "throttle-card__body"), children: props.children });
1324
- }
1325
- function CardFooter(props) {
1326
- const a = useAppearance();
1327
- const className = `throttle-card__footer${props.ruled ? " throttle-card__footer--ruled" : ""}`;
1328
- return /* @__PURE__ */ jsx4("div", { ...a.el("card.footer", className), children: props.children });
1329
- }
1330
-
1331
- // src/react/components/primitives/Spinner.tsx
1332
- import { jsx as jsx5 } from "react/jsx-runtime";
1333
- function Spinner({ centred } = {}) {
1334
- const a = useAppearance();
1335
- const Slot = a.slots.Spinner;
1336
- if (Slot) return /* @__PURE__ */ jsx5(Slot, {});
1337
- const cls = `throttle-spinner${centred ? " throttle-spinner--centred" : ""}`;
1338
- return /* @__PURE__ */ jsx5("span", { ...a.el("spinner", cls), "aria-hidden": "true" });
1339
- }
1340
-
1341
- // src/react/components/primitives/Button.tsx
1342
- import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1343
- function Button({ variant = "primary", size = "default", elementKey, loading, children, ...rest }) {
1344
- const a = useAppearance();
1345
- const Slot = a.slots.Button;
1346
- if (Slot) return /* @__PURE__ */ jsx6(Slot, { variant, loading, ...rest, children });
1347
- const base = `throttle-button throttle-button--${variant}${size === "xs" ? " throttle-button--xs" : ""}${loading ? " throttle-button--loading" : ""}`;
1348
- return /* @__PURE__ */ jsxs2(
1349
- "button",
1350
- {
1351
- type: "button",
1352
- ...rest,
1353
- ...a.el(elementKey ?? "button", base),
1354
- disabled: rest.disabled || loading,
1355
- "aria-busy": loading ? true : void 0,
1356
- children: [
1357
- loading ? /* @__PURE__ */ jsx6(Spinner, { centred: true }) : null,
1358
- /* @__PURE__ */ jsx6("span", { className: `throttle-button__label${loading ? " throttle-button__label--hidden" : ""}`, children })
1359
- ]
1360
- }
1361
- );
1362
- }
1363
-
1364
- // src/react/components/primitives/Field.tsx
1365
- import { Children, Fragment, useId } from "react";
1366
- import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1367
- function Field(props) {
1368
- const a = useAppearance();
1369
- const base = useId();
1370
- const inputId = `${base}-input`;
1371
- const hintId = `${base}-hint`;
1372
- const errorId = `${base}-error`;
1373
- const describedBy = [props.hint ? hintId : null, props.error ? errorId : null].filter(Boolean).join(" ");
1374
- const label = /* @__PURE__ */ jsx7("label", { ...a.el("field.label", "throttle-field__label"), htmlFor: inputId, children: props.label });
1375
- return /* @__PURE__ */ jsxs3("div", { ...a.el(props.elementKey ?? "field", "throttle-field"), children: [
1376
- props.labelExtra ? /* @__PURE__ */ jsxs3("div", { className: "throttle-field__labelrow", children: [
1377
- label,
1378
- props.labelExtra
1379
- ] }) : label,
1380
- /* @__PURE__ */ jsx7(
1381
- "input",
1382
- {
1383
- ...a.el("field.input", `throttle-field__input${props.grouped ? " throttle-field__input--grouped" : ""}`),
1384
- id: inputId,
1385
- name: props.name,
1386
- type: props.type ?? "text",
1387
- value: props.value,
1388
- required: props.required,
1389
- disabled: props.disabled,
1390
- autoComplete: props.autoComplete,
1391
- "aria-describedby": describedBy || void 0,
1392
- "aria-invalid": props.error ? true : void 0,
1393
- onChange: (e) => props.onChange(e.target.value)
1394
- }
1395
- ),
1396
- props.hint ? /* @__PURE__ */ jsx7("span", { ...a.el("field.hint", "throttle-field__hint"), id: hintId, children: props.hint }) : null,
1397
- /* @__PURE__ */ jsx7("span", { ...a.el("field.error", "throttle-field__error"), id: errorId, role: "alert", children: props.error ?? "" })
1398
- ] });
1399
- }
1400
- function FieldGroup(props) {
1401
- const a = useAppearance();
1402
- const items = Children.toArray(props.children).filter(Boolean);
1403
- return /* @__PURE__ */ jsx7("div", { ...a.el(props.elementKey ?? "field.group", "throttle-field-group"), children: items.map((child, i) => /* @__PURE__ */ jsxs3(Fragment, { children: [
1404
- i > 0 ? /* @__PURE__ */ jsx7("div", { className: "throttle-field-group__divider" }) : null,
1405
- /* @__PURE__ */ jsx7("div", { className: "throttle-field-group__item", children: child })
1406
- ] }, i)) });
1407
- }
1408
-
1409
- // src/react/components/primitives/FieldRow.tsx
1410
- import { jsx as jsx8 } from "react/jsx-runtime";
1411
- function FieldRow({ children }) {
1412
- const a = useAppearance();
1413
- return /* @__PURE__ */ jsx8("div", { ...a.el("fieldRow", "throttle-fieldrow"), children });
1414
- }
1415
-
1416
- // src/react/components/primitives/Alert.tsx
1417
- import { jsx as jsx9 } from "react/jsx-runtime";
1418
- function Alert(props) {
1419
- const a = useAppearance();
1420
- const Slot = a.slots.Alert;
1421
- if (Slot) return /* @__PURE__ */ jsx9(Slot, { tone: props.tone, children: props.children });
1422
- return /* @__PURE__ */ jsx9(
1423
- "p",
1424
- {
1425
- ...a.el("alert", `throttle-alert throttle-alert--${props.tone}`),
1426
- role: props.tone === "error" ? "alert" : "status",
1427
- children: props.children
1428
- }
1429
- );
1430
- }
1431
-
1432
- // src/react/components/primitives/Skeleton.tsx
1433
- import { jsx as jsx10 } from "react/jsx-runtime";
1434
- function Skeleton(props) {
1435
- const a = useAppearance();
1436
- const rows = Array.from({ length: props.count ?? 1 });
1437
- return /* @__PURE__ */ jsx10("div", { "aria-hidden": "true", className: "throttle-stack", children: rows.map((_, i) => /* @__PURE__ */ jsx10("span", { ...a.el("skeleton", "throttle-skeleton") }, i)) });
1438
- }
1439
-
1440
- // src/react/components/primitives/Modal.tsx
1441
- import { useEffect, useRef, useState } from "react";
1442
- import { createPortal } from "react-dom";
1443
-
1444
- // src/react/components/surface.tsx
1445
- import { createContext as createContext4, useContext as useContext4 } from "react";
1446
- import { jsx as jsx11 } from "react/jsx-runtime";
1447
- var Ctx4 = createContext4("standalone");
1448
- function SurfaceProvider(props) {
1449
- return /* @__PURE__ */ jsx11(Ctx4.Provider, { value: props.value, children: props.children });
1450
- }
1451
- function useSurface() {
1452
- return useContext4(Ctx4);
1453
- }
1454
- function useCardClass() {
1455
- return useSurface() === "modal" ? "throttle-card throttle-card--bare" : "throttle-card";
1456
- }
1457
-
1458
- // src/react/components/primitives/Modal.tsx
1459
- import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
1460
- var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
1461
- function Modal(props) {
1462
- const a = useAppearance();
1463
- const { t } = useCopy();
1464
- const panel = useRef(null);
1465
- const returnFocusTo = useRef(null);
1466
- const [mounted, setMounted] = useState(false);
1467
- useEffect(() => {
1468
- setMounted(true);
1469
- }, []);
1470
- const onCloseRef = useRef(props.onClose);
1471
- useEffect(() => {
1472
- onCloseRef.current = props.onClose;
1473
- });
1474
- useEffect(() => {
1475
- if (!props.open || !mounted) return;
1476
- returnFocusTo.current = props.restoreFocusTo ?? document.activeElement;
1477
- const previousOverflow = document.body.style.overflow;
1478
- document.body.style.overflow = "hidden";
1479
- panel.current?.focus();
1480
- const onKeyDown = (e) => {
1481
- if (e.key === "Escape") {
1482
- onCloseRef.current();
1483
- return;
1484
- }
1485
- if (e.key !== "Tab" || !panel.current) return;
1486
- const items = Array.from(panel.current.querySelectorAll(FOCUSABLE));
1487
- if (items.length === 0) {
1488
- e.preventDefault();
1489
- return;
1490
- }
1491
- const first2 = items[0];
1492
- const last = items[items.length - 1];
1493
- if (e.shiftKey && document.activeElement === first2) {
1494
- e.preventDefault();
1495
- last.focus();
1496
- } else if (!e.shiftKey && document.activeElement === last) {
1497
- e.preventDefault();
1498
- first2.focus();
1499
- }
1500
- };
1501
- document.addEventListener("keydown", onKeyDown);
1502
- return () => {
1503
- document.removeEventListener("keydown", onKeyDown);
1504
- document.body.style.overflow = previousOverflow;
1505
- returnFocusTo.current?.focus?.();
1506
- };
1507
- }, [props.open, mounted]);
1508
- if (!props.open || !mounted) return null;
1509
- return createPortal(
1510
- /* @__PURE__ */ jsx12("div", { ...a.rootProps, children: /* @__PURE__ */ jsx12(
1511
- "div",
1512
- {
1513
- ...a.el(props.overlayKey ?? "modal.overlay", "throttle-modal__overlay"),
1514
- "data-testid": "throttle-modal-overlay",
1515
- onClick: (e) => {
1516
- if (e.target === e.currentTarget) props.onClose();
1517
- },
1518
- children: /* @__PURE__ */ jsxs4(
1519
- "div",
1520
- {
1521
- ...a.el(props.containerKey ?? "modal.container", props.size === "compact" ? "throttle-modal__container throttle-modal__container--compact" : "throttle-modal__container"),
1522
- ref: panel,
1523
- role: "dialog",
1524
- "aria-modal": "true",
1525
- "aria-labelledby": props.labelledBy,
1526
- tabIndex: -1,
1527
- children: [
1528
- /* @__PURE__ */ jsx12(
1529
- "button",
1530
- {
1531
- ...a.el("modal.closeButton", "throttle-modal__close"),
1532
- type: "button",
1533
- "aria-label": t.common.close,
1534
- onClick: props.onClose,
1535
- children: "\xD7"
1536
- }
1537
- ),
1538
- /* @__PURE__ */ jsx12(SurfaceProvider, { value: "modal", children: props.children })
1539
- ]
1540
- }
1541
- )
1542
- }
1543
- ) }),
1544
- document.body
1545
- );
1546
- }
1547
-
1548
- // src/react/components/primitives/Confirm.tsx
1549
- import { createContext as createContext5, useCallback, useContext as useContext5, useEffect as useEffect2, useId as useId2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
1550
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
1551
- var ConfirmContext = createContext5(null);
1552
- function ConfirmProvider({
1553
- children,
1554
- stepUpOpen = false,
1555
- onStepUpResolve
1556
- }) {
1557
- const [request, setRequest] = useState2(null);
1558
- const [password, setPassword] = useState2("");
1559
- const { t } = useCopy();
1560
- const a = useAppearance();
1561
- const titleId = useId2();
1562
- const settle = useRef2(null);
1563
- useEffect2(() => {
1564
- if (!stepUpOpen) setPassword("");
1565
- }, [stepUpOpen]);
1566
- const answer = useCallback((value) => {
1567
- settle.current?.(value);
1568
- settle.current = null;
1569
- setRequest(null);
1570
- }, []);
1571
- const ask = useCallback((next) => {
1572
- settle.current?.(false);
1573
- setRequest(next);
1574
- return new Promise((resolve) => {
1575
- settle.current = resolve;
1576
- });
1577
- }, []);
1578
- const showStepUp = stepUpOpen && Boolean(onStepUpResolve);
1579
- const open = showStepUp || request !== null;
1580
- const close = () => {
1581
- if (showStepUp) onStepUpResolve(null);
1582
- else answer(false);
1583
- };
1584
- return /* @__PURE__ */ jsxs5(ConfirmContext.Provider, { value: ask, children: [
1585
- children,
1586
- open ? /* @__PURE__ */ jsx13(Modal, { open: true, onClose: close, labelledBy: titleId, size: "compact", children: showStepUp ? /* @__PURE__ */ jsxs5("form", { onSubmit: (e) => {
1587
- e.preventDefault();
1588
- onStepUpResolve(password);
1589
- }, children: [
1590
- /* @__PURE__ */ jsx13("div", { ...a.el("confirm.header", "throttle-modal__header"), children: /* @__PURE__ */ jsx13("h2", { id: titleId, ...a.el("stepUp.title", "throttle-modal__title"), children: t.stepUp.title }) }),
1591
- /* @__PURE__ */ jsxs5("div", { ...a.el("confirm.body", "throttle-modal__body"), children: [
1592
- /* @__PURE__ */ jsx13("p", { className: "throttle-muted", children: t.stepUp.description }),
1593
- /* @__PURE__ */ jsx13(
1594
- Field,
1595
- {
1596
- label: t.stepUp.password,
1597
- type: "password",
1598
- autoComplete: "current-password",
1599
- value: password,
1600
- onChange: setPassword,
1601
- required: true,
1602
- elementKey: "stepUp.passwordField"
1603
- }
1604
- ),
1605
- /* @__PURE__ */ jsxs5("div", { className: "throttle-actions", children: [
1606
- /* @__PURE__ */ jsx13(Button, { type: "submit", elementKey: "stepUp.submitButton", children: t.stepUp.submitButton }),
1607
- /* @__PURE__ */ jsx13(
1608
- Button,
1609
- {
1610
- variant: "secondary",
1611
- elementKey: "stepUp.cancelButton",
1612
- onClick: () => onStepUpResolve(null),
1613
- children: t.stepUp.cancelButton
1614
- }
1615
- )
1616
- ] })
1617
- ] })
1618
- ] }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
1619
- /* @__PURE__ */ jsx13("div", { ...a.el("confirm.header", "throttle-modal__header"), children: /* @__PURE__ */ jsx13("h2", { id: titleId, ...a.el("confirm.title", "throttle-modal__title"), children: request.title }) }),
1620
- /* @__PURE__ */ jsxs5("div", { ...a.el("confirm.body", "throttle-modal__body"), children: [
1621
- request.body ? /* @__PURE__ */ jsx13("p", { className: "throttle-muted", children: request.body }) : null,
1622
- /* @__PURE__ */ jsxs5("div", { className: "throttle-actions", children: [
1623
- /* @__PURE__ */ jsx13(
1624
- Button,
1625
- {
1626
- elementKey: "confirm.confirmButton",
1627
- variant: request.tone === "danger" ? "danger" : "primary",
1628
- onClick: () => answer(true),
1629
- children: request.confirmLabel ?? t.common.delete
1630
- }
1631
- ),
1632
- /* @__PURE__ */ jsx13(Button, { elementKey: "confirm.cancelButton", variant: "secondary", onClick: () => answer(false), children: request.cancelLabel ?? t.common.keepIt })
1633
- ] })
1634
- ] })
1635
- ] }) }) : null
1636
- ] });
1637
- }
1638
- function useConfirm() {
1639
- const ask = useContext5(ConfirmContext);
1640
- const { t } = useCopy();
1641
- return useMemo3(
1642
- () => ask ?? (async (request) => window.confirm(request.title || t.common.confirm)),
1643
- [ask, t]
1644
- );
1645
- }
1646
-
1647
- // src/react/components/primitives/Dropdown.tsx
1648
- import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1649
- import { jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
1650
- function Dropdown(props) {
1651
- const a = useAppearance();
1652
- const [open, setOpen] = useState3(false);
1653
- const root = useRef3(null);
1654
- const trigger = useRef3(null);
1655
- const menu = useRef3(null);
1656
- useEffect3(() => {
1657
- if (!open) return;
1658
- menu.current?.querySelector('[role="menuitem"]')?.focus();
1659
- const onDown = (e) => {
1660
- if (root.current && !root.current.contains(e.target)) setOpen(false);
1661
- };
1662
- const onKey = (e) => {
1663
- if (e.key === "Escape") {
1664
- setOpen(false);
1665
- trigger.current?.focus();
1666
- }
1667
- };
1668
- document.addEventListener("mousedown", onDown);
1669
- document.addEventListener("keydown", onKey);
1670
- return () => {
1671
- document.removeEventListener("mousedown", onDown);
1672
- document.removeEventListener("keydown", onKey);
1673
- };
1674
- }, [open]);
1675
- const move = (delta) => {
1676
- const items = Array.from(menu.current?.querySelectorAll('[role="menuitem"]') ?? []);
1677
- if (items.length === 0) return;
1678
- const at = items.indexOf(document.activeElement);
1679
- const next = (at + delta + items.length) % items.length;
1680
- items[next < 0 ? items.length - 1 : next].focus();
1681
- };
1682
- return /* @__PURE__ */ jsxs6("div", { ref: root, style: { position: "relative", display: "inline-block" }, children: [
1683
- /* @__PURE__ */ jsx14(
1684
- "button",
1685
- {
1686
- ref: trigger,
1687
- type: "button",
1688
- ...a.el(props.triggerKey ?? "dropdown.trigger", "throttle-dropdown__trigger"),
1689
- "aria-haspopup": "menu",
1690
- "aria-expanded": open,
1691
- "aria-label": props.label,
1692
- onClick: () => setOpen((v) => !v),
1693
- children: props.trigger
1694
- }
1695
- ),
1696
- open ? /* @__PURE__ */ jsx14(
1697
- "div",
1698
- {
1699
- ref: menu,
1700
- ...a.el(props.menuKey ?? "dropdown.menu", "throttle-dropdown__menu"),
1701
- role: "menu",
1702
- onClick: () => setOpen(false),
1703
- onKeyDown: (e) => {
1704
- if (e.key === "ArrowDown") {
1705
- e.preventDefault();
1706
- move(1);
1707
- }
1708
- if (e.key === "ArrowUp") {
1709
- e.preventDefault();
1710
- move(-1);
1711
- }
1712
- },
1713
- children: props.children
1714
- }
1715
- ) : null
1716
- ] });
1717
- }
1718
- function DropdownItem(props) {
1719
- const a = useAppearance();
1720
- const attrs = a.el(props.itemKey ?? "dropdown.item", "throttle-dropdown__item");
1721
- if (props.href) {
1722
- return /* @__PURE__ */ jsx14("a", { ...attrs, role: "menuitem", href: props.href, tabIndex: -1, children: props.children });
1723
- }
1724
- return /* @__PURE__ */ jsx14("button", { ...attrs, role: "menuitem", type: "button", tabIndex: -1, onClick: props.onClick, children: props.children });
1725
- }
1726
- function DropdownSeparator() {
1727
- const a = useAppearance();
1728
- return /* @__PURE__ */ jsx14("hr", { ...a.el("dropdown.separator", "throttle-dropdown__separator") });
1729
- }
1730
-
1731
- // src/react/components/primitives/Tabs.tsx
1732
- import { jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
1733
- function Tabs(props) {
1734
- const a = useAppearance();
1735
- const at = props.tabs.findIndex((t) => t.id === props.active);
1736
- const vertical = props.orientation === "vertical";
1737
- const listClass = vertical ? "throttle-tabs__rail" : "throttle-tabs__list";
1738
- const itemClass = vertical ? "throttle-tabs__railitem" : "throttle-tabs__tab";
1739
- return /* @__PURE__ */ jsx15(
1740
- "div",
1741
- {
1742
- ...a.el("tabs.list", listClass),
1743
- role: "tablist",
1744
- "aria-orientation": vertical ? "vertical" : void 0,
1745
- onKeyDown: (e) => {
1746
- const next = vertical ? "ArrowDown" : "ArrowRight";
1747
- const prev = vertical ? "ArrowUp" : "ArrowLeft";
1748
- const delta = e.key === next ? 1 : e.key === prev ? -1 : 0;
1749
- if (!delta) return;
1750
- e.preventDefault();
1751
- const nextIndex = (at + delta + props.tabs.length) % props.tabs.length;
1752
- props.onChange(props.tabs[nextIndex].id);
1753
- e.currentTarget.querySelectorAll('[role="tab"]')[nextIndex]?.focus();
1754
- },
1755
- children: props.tabs.map((tab) => {
1756
- const selected = tab.id === props.active;
1757
- const { className, ...rest } = a.el("tabs.tab", itemClass);
1758
- return /* @__PURE__ */ jsxs7(
1759
- "button",
1760
- {
1761
- ...rest,
1762
- className: [className, selected ? `${itemClass}--active` : null].filter(Boolean).join(" "),
1763
- role: "tab",
1764
- type: "button",
1765
- id: props.idBase ? `${props.idBase}-tab-${tab.id}` : void 0,
1766
- "aria-controls": props.idBase ? `${props.idBase}-panel-${tab.id}` : void 0,
1767
- "aria-selected": selected,
1768
- tabIndex: selected ? 0 : -1,
1769
- onClick: () => props.onChange(tab.id),
1770
- children: [
1771
- tab.icon,
1772
- tab.label
1773
- ]
1774
- },
1775
- tab.id
1776
- );
1777
- })
1778
- }
1779
- );
1780
- }
1781
-
1782
- // src/react/components/primitives/DataTable.tsx
1783
- import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
1784
- function DataTable(props) {
1785
- const a = useAppearance();
1786
- const clickable = Boolean(props.onRowClick);
1787
- return (
1788
- // Tables are the one thing allowed to be wider than the panel; it scrolls
1789
- // rather than forcing the whole page to.
1790
- /* @__PURE__ */ jsx16("div", { className: "throttle-table__scroll", children: /* @__PURE__ */ jsxs8("table", { ...a.el(props.tableKey ?? "table", "throttle-table"), children: [
1791
- /* @__PURE__ */ jsx16("thead", { children: /* @__PURE__ */ jsx16("tr", { children: props.columns.map((col) => /* @__PURE__ */ jsx16("th", { ...a.el("table.head", "throttle-table__head"), scope: "col", children: col.label }, col.key)) }) }),
1792
- /* @__PURE__ */ jsx16("tbody", { children: props.rows.map((row) => /* @__PURE__ */ jsx16(
1793
- "tr",
1794
- {
1795
- ...a.el(props.rowElementKey ?? "table.row", "throttle-table__row"),
1796
- tabIndex: clickable ? 0 : void 0,
1797
- onClick: clickable ? () => props.onRowClick(row) : void 0,
1798
- onKeyDown: clickable ? (e) => {
1799
- if (e.key === "Enter" || e.key === " ") {
1800
- e.preventDefault();
1801
- props.onRowClick(row);
1802
- }
1803
- } : void 0,
1804
- children: props.columns.map((col) => /* @__PURE__ */ jsx16("td", { ...a.el("table.cell", "throttle-table__cell"), children: col.render ? col.render(row) : String(row[col.key] ?? "") }, col.key))
1805
- },
1806
- props.rowKey(row)
1807
- )) })
1808
- ] }) })
1809
- );
1810
- }
1811
-
1812
- // src/react/components/primitives/EmptyState.tsx
1813
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
1814
- function EmptyState(props) {
1815
- const a = useAppearance();
1816
- const Slot = a.slots.EmptyState;
1817
- if (Slot) return /* @__PURE__ */ jsx17(Slot, { title: props.title, description: props.description });
1818
- return /* @__PURE__ */ jsxs9("div", { ...a.el("emptyState", "throttle-empty"), children: [
1819
- /* @__PURE__ */ jsx17("p", { ...a.el("emptyState.title", "throttle-empty__title"), children: props.title }),
1820
- props.description ? /* @__PURE__ */ jsx17("p", { ...a.el("emptyState.description", "throttle-empty__description"), children: props.description }) : null
1821
- ] });
1822
- }
1823
-
1824
- // src/react/components/primitives/Avatar.tsx
1825
- import { jsx as jsx18 } from "react/jsx-runtime";
1826
- function Avatar(props) {
1827
- const a = useAppearance();
1828
- const Slot = a.slots.Avatar;
1829
- if (Slot) return /* @__PURE__ */ jsx18(Slot, { name: props.name, email: props.email });
1830
- const initials = props.name.split(" ").map((part) => part.trim()[0]).filter(Boolean).slice(0, 2).join("") || props.email.trim()[0] || "?";
1831
- return /* @__PURE__ */ jsx18("span", { ...a.el("avatar", "throttle-avatar"), title: props.email, children: initials.toUpperCase() });
1832
- }
1833
-
1834
- // src/react/components/primitives/Badge.tsx
1835
- import { jsx as jsx19 } from "react/jsx-runtime";
1836
- function Badge(props) {
1837
- const a = useAppearance();
1838
- const tone = props.tone ?? "neutral";
1839
- const base = tone === "neutral" ? "throttle-badge" : `throttle-badge throttle-badge--${tone}`;
1840
- return /* @__PURE__ */ jsx19("span", { ...a.el(props.elementKey ?? "badge", base), children: props.children });
1841
- }
1842
-
1843
- // src/react/components/auth/AuthModal.tsx
1844
- import { useId as useId3 } from "react";
1845
-
1846
- // src/react/components/auth/AuthFlow.tsx
1847
- import { useEffect as useEffect5, useState as useState10 } from "react";
1848
-
1849
- // src/react/components/auth/SignIn.tsx
1850
- import { useState as useState5 } from "react";
1851
-
1852
- // src/react/components/useAction.ts
1853
- import { useCallback as useCallback2, useRef as useRef4, useState as useState4 } from "react";
1854
- function useAction() {
1855
- const { errorMessage } = useCopy();
1856
- const [submitting, setSubmitting] = useState4(false);
1857
- const [error, setError] = useState4(null);
1858
- const [notice, setNotice] = useState4(null);
1859
- const inFlight = useRef4(false);
1860
- const run = useCallback2(
1861
- async (fn) => {
1862
- if (inFlight.current) return;
1863
- inFlight.current = true;
1864
- setSubmitting(true);
1865
- setError(null);
1866
- setNotice(null);
1867
- try {
1868
- const message = await fn();
1869
- if (typeof message === "string") setNotice(message);
1870
- } catch (err) {
1871
- setError(errorMessage(err));
1872
- } finally {
1873
- inFlight.current = false;
1874
- setSubmitting(false);
1875
- }
1876
- },
1877
- [errorMessage]
1878
- );
1879
- return { submitting, error, notice, run };
1880
- }
1881
-
1882
- // src/react/components/auth/SignIn.tsx
1883
- import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1884
- function SignIn(props) {
1885
- const a = useAppearance(props.appearance);
1886
- const cardClass = useCardClass();
1887
- const scoped = useAppearanceProviderValue(props.appearance);
1888
- const { t } = useCopy(props.localization);
1889
- const { auth } = useAuth();
1890
- const { submitting, error, run } = useAction();
1891
- const [email, setEmail] = useState5("");
1892
- const [password, setPassword] = useState5("");
1893
- const variant = props.variant ?? "quiet";
1894
- const branded = variant === "branded";
1895
- const grouped = variant === "grouped";
1896
- const logo = useBrandLogo(props.logo);
1897
- const forgotLink = props.onForgotPassword ? /* @__PURE__ */ jsx20(Button, { variant: "link", size: "xs", elementKey: "signIn.forgotLink", onClick: props.onForgotPassword, children: t.signIn.forgotLink }) : null;
1898
- const emailField = /* @__PURE__ */ jsx20(
1899
- Field,
1900
- {
1901
- label: t.signIn.email,
1902
- type: "email",
1903
- autoComplete: "email",
1904
- required: true,
1905
- value: email,
1906
- onChange: setEmail,
1907
- elementKey: "signIn.emailField",
1908
- grouped
1909
- }
1910
- );
1911
- const passwordField = /* @__PURE__ */ jsx20(
1912
- Field,
1913
- {
1914
- label: t.signIn.password,
1915
- type: "password",
1916
- autoComplete: "current-password",
1917
- required: true,
1918
- value: password,
1919
- onChange: setPassword,
1920
- elementKey: "signIn.passwordField",
1921
- grouped,
1922
- labelExtra: grouped ? void 0 : forgotLink
1923
- }
1924
- );
1925
- return /* @__PURE__ */ jsx20(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx20("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs10(
1926
- "form",
1927
- {
1928
- ...a.el("signIn.root", cardClass),
1929
- onSubmit: (e) => {
1930
- e.preventDefault();
1931
- void run(async () => {
1932
- await auth.signIn(email, password);
1933
- props.onSuccess?.();
1934
- });
1935
- },
1936
- children: [
1937
- /* @__PURE__ */ jsx20(
1938
- CardHeader,
1939
- {
1940
- title: t.signIn.title,
1941
- description: branded ? t.signIn.description : void 0,
1942
- titleKey: "signIn.title",
1943
- logo: branded ? logo : null,
1944
- logoKey: "signIn.logo",
1945
- branded
1946
- }
1947
- ),
1948
- /* @__PURE__ */ jsxs10(CardBody, { children: [
1949
- grouped ? /* @__PURE__ */ jsxs10(FieldGroup, { children: [
1950
- emailField,
1951
- passwordField
1952
- ] }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
1953
- emailField,
1954
- passwordField
1955
- ] }),
1956
- grouped && forgotLink ? /* @__PURE__ */ jsx20("div", { className: "throttle-field-group__forgot", children: forgotLink }) : null,
1957
- error ? /* @__PURE__ */ jsx20(Alert, { tone: "error", children: error }) : null,
1958
- /* @__PURE__ */ jsx20(Button, { type: "submit", loading: submitting, elementKey: "signIn.submitButton", children: t.signIn.submitButton })
1959
- ] }),
1960
- props.onSignUp ? /* @__PURE__ */ jsxs10(CardFooter, { ruled: !branded, children: [
1961
- /* @__PURE__ */ jsxs10("span", { className: "throttle-muted", children: [
1962
- t.signIn.switchPrompt,
1963
- " "
1964
- ] }),
1965
- /* @__PURE__ */ jsx20(Button, { variant: "link", elementKey: "signIn.switchLink", onClick: props.onSignUp, children: t.signIn.switchLink })
1966
- ] }) : null
1967
- ]
1968
- }
1969
- ) }) });
1970
- }
1971
-
1972
- // src/react/components/auth/SignUp.tsx
1973
- import { useState as useState6 } from "react";
1974
- import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
1975
- function SignUp(props) {
1976
- const a = useAppearance(props.appearance);
1977
- const cardClass = useCardClass();
1978
- const scoped = useAppearanceProviderValue(props.appearance);
1979
- const { t } = useCopy(props.localization);
1980
- const { auth } = useAuth();
1981
- const { submitting, error, notice, run } = useAction();
1982
- const [email, setEmail] = useState6("");
1983
- const [password, setPassword] = useState6("");
1984
- const variant = props.variant ?? "quiet";
1985
- const branded = variant === "branded";
1986
- const grouped = variant === "grouped";
1987
- const logo = useBrandLogo(props.logo);
1988
- const emailField = /* @__PURE__ */ jsx21(
1989
- Field,
1990
- {
1991
- label: t.signUp.email,
1992
- type: "email",
1993
- autoComplete: "email",
1994
- required: true,
1995
- value: email,
1996
- onChange: setEmail,
1997
- elementKey: "signUp.emailField",
1998
- grouped
1999
- }
2000
- );
2001
- const passwordField = /* @__PURE__ */ jsx21(
2002
- Field,
2003
- {
2004
- label: t.signUp.password,
2005
- type: "password",
2006
- autoComplete: "new-password",
2007
- required: true,
2008
- value: password,
2009
- onChange: setPassword,
2010
- elementKey: "signUp.passwordField",
2011
- grouped
2012
- }
2013
- );
2014
- return /* @__PURE__ */ jsx21(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx21("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs11(
2015
- "form",
2016
- {
2017
- ...a.el("signUp.root", cardClass),
2018
- onSubmit: (e) => {
2019
- e.preventDefault();
2020
- void run(async () => {
2021
- const result = await auth.register(email, password);
2022
- if (result.status === "active") {
2023
- props.onSuccess?.();
2024
- return;
2025
- }
2026
- return t.signUp.pendingNotice;
2027
- });
2028
- },
2029
- children: [
2030
- /* @__PURE__ */ jsx21(
2031
- CardHeader,
2032
- {
2033
- title: t.signUp.title,
2034
- description: branded ? t.signUp.description : void 0,
2035
- titleKey: "signUp.title",
2036
- logo: branded ? logo : null,
2037
- logoKey: "signUp.logo",
2038
- branded
2039
- }
2040
- ),
2041
- /* @__PURE__ */ jsxs11(CardBody, { children: [
2042
- grouped ? /* @__PURE__ */ jsxs11(FieldGroup, { children: [
2043
- emailField,
2044
- passwordField
2045
- ] }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
2046
- emailField,
2047
- passwordField
2048
- ] }),
2049
- error ? /* @__PURE__ */ jsx21(Alert, { tone: "error", children: error }) : null,
2050
- notice ? /* @__PURE__ */ jsx21(Alert, { tone: "info", children: notice }) : null,
2051
- /* @__PURE__ */ jsx21(Button, { type: "submit", loading: submitting, elementKey: "signUp.submitButton", children: t.signUp.submitButton })
2052
- ] }),
2053
- props.onSignIn ? /* @__PURE__ */ jsxs11(CardFooter, { ruled: !branded, children: [
2054
- /* @__PURE__ */ jsxs11("span", { className: "throttle-muted", children: [
2055
- t.signUp.switchPrompt,
2056
- " "
2057
- ] }),
2058
- /* @__PURE__ */ jsx21(Button, { variant: "link", elementKey: "signUp.switchLink", onClick: props.onSignIn, children: t.signUp.switchLink })
2059
- ] }) : null
2060
- ]
2061
- }
2062
- ) }) });
2063
- }
2064
-
2065
- // src/react/components/auth/ForgotPassword.tsx
2066
- import { useState as useState7 } from "react";
2067
- import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
2068
- function ForgotPassword(props) {
2069
- const a = useAppearance(props.appearance);
2070
- const cardClass = useCardClass();
2071
- const scoped = useAppearanceProviderValue(props.appearance);
2072
- const { t } = useCopy(props.localization);
2073
- const { auth } = useAuth();
2074
- const { submitting, error, notice, run } = useAction();
2075
- const [email, setEmail] = useState7("");
2076
- const branded = (props.variant ?? "quiet") === "branded";
2077
- const logo = useBrandLogo(props.logo);
2078
- return /* @__PURE__ */ jsx22(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx22("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs12(
2079
- "form",
2080
- {
2081
- ...a.el("forgotPassword.root", cardClass),
2082
- onSubmit: (e) => {
2083
- e.preventDefault();
2084
- void run(async () => {
2085
- await auth.forgotPassword(email);
2086
- return t.forgotPassword.sentNotice;
2087
- });
2088
- },
2089
- children: [
2090
- /* @__PURE__ */ jsx22(
2091
- CardHeader,
2092
- {
2093
- title: t.forgotPassword.title,
2094
- description: branded ? t.forgotPassword.description : void 0,
2095
- titleKey: "forgotPassword.title",
2096
- logo: branded ? logo : null,
2097
- logoKey: "forgotPassword.logo",
2098
- branded
2099
- }
2100
- ),
2101
- /* @__PURE__ */ jsxs12(CardBody, { children: [
2102
- /* @__PURE__ */ jsx22(
2103
- Field,
2104
- {
2105
- label: t.forgotPassword.email,
2106
- type: "email",
2107
- autoComplete: "email",
2108
- required: true,
2109
- value: email,
2110
- onChange: setEmail,
2111
- elementKey: "forgotPassword.emailField"
2112
- }
2113
- ),
2114
- error ? /* @__PURE__ */ jsx22(Alert, { tone: "error", children: error }) : null,
2115
- notice ? /* @__PURE__ */ jsx22(Alert, { tone: "success", children: notice }) : null,
2116
- /* @__PURE__ */ jsx22(Button, { type: "submit", loading: submitting, elementKey: "forgotPassword.submitButton", children: t.forgotPassword.submitButton })
2117
- ] }),
2118
- props.onBack ? /* @__PURE__ */ jsx22(CardFooter, { ruled: !branded, children: /* @__PURE__ */ jsx22(Button, { variant: "link", elementKey: "forgotPassword.backLink", onClick: props.onBack, children: t.forgotPassword.backLink }) }) : null
2119
- ]
2120
- }
2121
- ) }) });
2122
- }
2123
-
2124
- // src/react/components/auth/ResetPassword.tsx
2125
- import { useState as useState8 } from "react";
2126
- import { jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
2127
- function ResetPassword(props) {
2128
- const a = useAppearance(props.appearance);
2129
- const cardClass = useCardClass();
2130
- const scoped = useAppearanceProviderValue(props.appearance);
2131
- const { t } = useCopy(props.localization);
2132
- const { auth } = useAuth();
2133
- const { submitting, error, notice, run } = useAction();
2134
- const [password, setPassword] = useState8("");
2135
- const branded = (props.variant ?? "quiet") === "branded";
2136
- const logo = useBrandLogo(props.logo);
2137
- return /* @__PURE__ */ jsx23(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx23("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs13("div", { ...a.el("resetPassword.root", cardClass), children: [
2138
- /* @__PURE__ */ jsx23(
2139
- CardHeader,
2140
- {
2141
- title: t.resetPassword.title,
2142
- titleKey: "resetPassword.title",
2143
- logo: branded ? logo : null,
2144
- logoKey: "resetPassword.logo",
2145
- branded
2146
- }
2147
- ),
2148
- /* @__PURE__ */ jsx23(CardBody, { children: !props.token ? (
2149
- // No form at all without a token. A submit button that can only
2150
- // fail is worse than an explanation.
2151
- /* @__PURE__ */ jsx23(Alert, { tone: "error", children: t.resetPassword.missingToken })
2152
- ) : /* @__PURE__ */ jsx23(
2153
- "form",
2154
- {
2155
- onSubmit: (e) => {
2156
- e.preventDefault();
2157
- void run(async () => {
2158
- await auth.resetPassword(props.token, password);
2159
- props.onSuccess?.();
2160
- return t.resetPassword.doneNotice;
2161
- });
2162
- },
2163
- children: /* @__PURE__ */ jsxs13("div", { className: "throttle-stack", children: [
2164
- /* @__PURE__ */ jsx23(
2165
- Field,
2166
- {
2167
- label: t.resetPassword.password,
2168
- type: "password",
2169
- autoComplete: "new-password",
2170
- required: true,
2171
- value: password,
2172
- onChange: setPassword,
2173
- elementKey: "resetPassword.passwordField"
2174
- }
2175
- ),
2176
- error ? /* @__PURE__ */ jsx23(Alert, { tone: "error", children: error }) : null,
2177
- notice ? /* @__PURE__ */ jsx23(Alert, { tone: "success", children: notice }) : null,
2178
- /* @__PURE__ */ jsx23(Button, { type: "submit", loading: submitting, elementKey: "resetPassword.submitButton", children: t.resetPassword.submitButton })
2179
- ] })
2180
- }
2181
- ) })
2182
- ] }) }) });
2183
- }
2184
-
2185
- // src/react/components/auth/VerifyEmail.tsx
2186
- import { useEffect as useEffect4, useRef as useRef5, useState as useState9 } from "react";
2187
- import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
2188
- function VerifyEmail(props) {
2189
- const a = useAppearance(props.appearance);
2190
- const cardClass = useCardClass();
2191
- const scoped = useAppearanceProviderValue(props.appearance);
2192
- const { t, errorMessage } = useCopy(props.localization);
2193
- const { auth } = useAuth();
2194
- const { submitting, notice, run } = useAction();
2195
- const [state, setState] = useState9(props.token ? "verifying" : "idle");
2196
- const [error, setError] = useState9(null);
2197
- const branded = (props.variant ?? "quiet") === "branded";
2198
- const logo = useBrandLogo(props.logo);
2199
- const attempted = useRef5(null);
2200
- useEffect4(() => {
2201
- const token = props.token;
2202
- if (!token || attempted.current === token) return;
2203
- attempted.current = token;
2204
- setState("verifying");
2205
- auth.verifyEmail(token).then(() => {
2206
- setState("done");
2207
- props.onSuccess?.();
2208
- }).catch((err) => {
2209
- setError(errorMessage(err));
2210
- setState("failed");
2211
- });
2212
- }, [props.token, auth]);
2213
- return /* @__PURE__ */ jsx24(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx24("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs14("div", { ...a.el("verifyEmail.root", cardClass), children: [
2214
- /* @__PURE__ */ jsx24(
2215
- CardHeader,
2216
- {
2217
- title: t.verifyEmail.title,
2218
- titleKey: "verifyEmail.title",
2219
- logo: branded ? logo : null,
2220
- logoKey: "verifyEmail.logo",
2221
- branded
2222
- }
2223
- ),
2224
- /* @__PURE__ */ jsxs14(CardBody, { children: [
2225
- state === "verifying" ? /* @__PURE__ */ jsxs14("p", { children: [
2226
- /* @__PURE__ */ jsx24(Spinner, {}),
2227
- " ",
2228
- t.verifyEmail.verifying
2229
- ] }) : null,
2230
- state === "done" ? /* @__PURE__ */ jsx24(Alert, { tone: "success", children: t.verifyEmail.success }) : null,
2231
- state === "failed" && error ? /* @__PURE__ */ jsx24(Alert, { tone: "error", children: error }) : null,
2232
- state === "idle" ? /* @__PURE__ */ jsx24("p", { className: "throttle-muted", children: t.verifyEmail.instructions }) : null,
2233
- notice ? /* @__PURE__ */ jsx24(Alert, { tone: "success", children: notice }) : null,
2234
- state !== "done" ? /* @__PURE__ */ jsx24(
2235
- Button,
2236
- {
2237
- variant: "secondary",
2238
- loading: submitting,
2239
- elementKey: "verifyEmail.resendButton",
2240
- onClick: () => void run(async () => {
2241
- await auth.resendVerification();
2242
- return t.verifyEmail.resentNotice;
2243
- }),
2244
- children: t.verifyEmail.resendButton
2245
- }
2246
- ) : null
2247
- ] })
2248
- ] }) }) });
2249
- }
2250
-
2251
- // src/react/components/auth/AuthFlow.tsx
2252
- import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
2253
- var HASH_FOR = {
2254
- signIn: "#/sign-in",
2255
- signUp: "#/sign-up",
2256
- forgotPassword: "#/forgot-password",
2257
- resetPassword: "#/reset-password",
2258
- verifyEmail: "#/verify-email"
2259
- };
2260
- var VIEW_FOR_HASH = Object.fromEntries(
2261
- Object.entries(HASH_FOR).map(([view, hash]) => [hash, view])
2262
- );
2263
- function readLocation() {
2264
- if (typeof window === "undefined") return { view: null };
2265
- const params = new URLSearchParams(window.location.search);
2266
- const token = params.get("token") ?? void 0;
2267
- const hashView = VIEW_FOR_HASH[window.location.hash];
2268
- if (hashView) return { view: hashView, token };
2269
- if (!token) return { view: null };
2270
- const path = window.location.pathname.toLowerCase();
2271
- if (path.includes("reset")) return { view: "resetPassword", token };
2272
- if (path.includes("verif")) return { view: "verifyEmail", token };
2273
- return { view: null, token };
2274
- }
2275
- function AuthFlow(props) {
2276
- const a = useAppearance(props.appearance);
2277
- const controlled = props.view !== void 0;
2278
- const [internal, setInternal] = useState10(props.initialView ?? "signIn");
2279
- const [urlToken, setUrlToken] = useState10(void 0);
2280
- useEffect5(() => {
2281
- const found = readLocation();
2282
- if (found.token) setUrlToken(found.token);
2283
- if (found.view && !controlled && props.initialView === void 0) setInternal(found.view);
2284
- }, []);
2285
- const view = controlled ? props.view : internal;
2286
- const token = props.token ?? urlToken;
2287
- const go = (next) => {
2288
- props.onViewChange?.(next);
2289
- if (!controlled) setInternal(next);
2290
- if (props.routing === "hash" && typeof window !== "undefined") {
2291
- window.location.hash = HASH_FOR[next];
2292
- }
2293
- };
2294
- useEffect5(() => {
2295
- if (props.routing !== "hash" || typeof window === "undefined") return;
2296
- const onHashChange = () => {
2297
- if (controlled) return;
2298
- const hash = window.location.hash;
2299
- if (hash === "") {
2300
- setInternal(props.initialView ?? "signIn");
2301
- return;
2302
- }
2303
- const next = VIEW_FOR_HASH[hash];
2304
- if (next) setInternal(next);
2305
- };
2306
- window.addEventListener("hashchange", onHashChange);
2307
- return () => window.removeEventListener("hashchange", onHashChange);
2308
- }, [props.routing, controlled]);
2309
- const shared = { appearance: props.appearance, localization: props.localization };
2310
- return /* @__PURE__ */ jsx25("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs15("div", { ...a.el("authFlow.root"), children: [
2311
- view === "signIn" ? /* @__PURE__ */ jsx25(
2312
- SignIn,
2313
- {
2314
- ...shared,
2315
- onSuccess: props.afterSignIn,
2316
- onForgotPassword: () => go("forgotPassword"),
2317
- onSignUp: () => go("signUp")
2318
- }
2319
- ) : null,
2320
- view === "signUp" ? /* @__PURE__ */ jsx25(SignUp, { ...shared, onSuccess: props.afterSignUp, onSignIn: () => go("signIn") }) : null,
2321
- view === "forgotPassword" ? /* @__PURE__ */ jsx25(ForgotPassword, { ...shared, onBack: () => go("signIn") }) : null,
2322
- view === "resetPassword" ? /* @__PURE__ */ jsx25(ResetPassword, { ...shared, token, onSuccess: () => go("signIn") }) : null,
2323
- view === "verifyEmail" ? /* @__PURE__ */ jsx25(VerifyEmail, { ...shared, token }) : null
2324
- ] }) });
2325
- }
2326
-
2327
- // src/react/components/auth/AuthModal.tsx
2328
- import { jsx as jsx26, jsxs as jsxs16 } from "react/jsx-runtime";
2329
- function AuthModal(props) {
2330
- const scoped = useAppearanceProviderValue(props.appearance);
2331
- const titleId = useId3();
2332
- return /* @__PURE__ */ jsx26(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsxs16(
2333
- Modal,
2334
- {
2335
- open: props.open,
2336
- onClose: props.onClose,
2337
- labelledBy: titleId,
2338
- size: "compact",
2339
- overlayKey: "authModal.overlay",
2340
- containerKey: "authModal.container",
2341
- children: [
2342
- /* @__PURE__ */ jsx26("h2", { id: titleId, style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: "Account" }),
2343
- /* @__PURE__ */ jsx26(
2344
- AuthFlow,
2345
- {
2346
- initialView: props.initialView,
2347
- afterSignIn: props.afterSignIn,
2348
- afterSignUp: props.afterSignUp,
2349
- appearance: props.appearance,
2350
- localization: props.localization
2351
- }
2352
- )
2353
- ]
2354
- }
2355
- ) });
2356
- }
2357
-
2358
- // src/react/components/account/AccountModal.tsx
2359
- import { useId as useId6 } from "react";
2360
-
2361
- // src/react/components/account/AccountDashboard.tsx
2362
- import { useEffect as useEffect11, useId as useId5, useState as useState21 } from "react";
2363
-
2364
- // src/react/components/account/VerifyEmailBanner.tsx
2365
- import { useState as useState11 } from "react";
2366
- import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
2367
- var DISMISS_KEY = "throttle-auth:verify-banner-dismissed";
2368
- function readDismissed() {
2369
- try {
2370
- return sessionStorage.getItem(DISMISS_KEY) === "1";
2371
- } catch {
2372
- return false;
2373
- }
2374
- }
2375
- function writeDismissed() {
2376
- try {
2377
- sessionStorage.setItem(DISMISS_KEY, "1");
2378
- } catch {
2379
- }
2380
- }
2381
- function VerifyEmailBanner(props) {
2382
- const a = useAppearance(props.appearance);
2383
- const scoped = useAppearanceProviderValue(props.appearance);
2384
- const { t } = useCopy(props.localization);
2385
- const { auth, status, emailVerified } = useAuth();
2386
- const { submitting, notice, error, run } = useAction();
2387
- const [dismissed, setDismissed] = useState11(readDismissed);
2388
- if (status !== "authenticated" || emailVerified || dismissed) return null;
2389
- return /* @__PURE__ */ jsx27(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx27("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs17("div", { ...a.el("verifyBanner.root", "throttle-banner"), children: [
2390
- /* @__PURE__ */ jsx27("span", { ...a.el("verifyBanner.message"), children: t.verifyBanner.message }),
2391
- notice ? /* @__PURE__ */ jsx27(Alert, { tone: "success", children: notice }) : null,
2392
- error ? /* @__PURE__ */ jsx27(Alert, { tone: "error", children: error }) : null,
2393
- /* @__PURE__ */ jsxs17("span", { className: "throttle-actions", children: [
2394
- /* @__PURE__ */ jsx27(
2395
- Button,
2396
- {
2397
- variant: "secondary",
2398
- loading: submitting,
2399
- elementKey: "verifyBanner.resendButton",
2400
- onClick: () => void run(async () => {
2401
- await auth.resendVerification();
2402
- return t.verifyEmail.resentNotice;
2403
- }),
2404
- children: t.verifyBanner.resendButton
2405
- }
2406
- ),
2407
- /* @__PURE__ */ jsx27(
2408
- Button,
2409
- {
2410
- variant: "link",
2411
- elementKey: "verifyBanner.dismissButton",
2412
- onClick: () => {
2413
- writeDismissed();
2414
- setDismissed(true);
2415
- },
2416
- children: t.verifyBanner.dismissButton
2417
- }
2418
- )
2419
- ] })
2420
- ] }) }) });
2421
- }
2422
-
2423
- // src/react/components/account/icons.tsx
2424
- import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
2425
- function Glyph(props) {
2426
- return /* @__PURE__ */ jsx28(
2427
- "svg",
2428
- {
2429
- className: "throttle-tabs__railicon",
2430
- width: "18",
2431
- height: "18",
2432
- viewBox: "0 0 24 24",
2433
- fill: "none",
2434
- stroke: "currentColor",
2435
- strokeWidth: "1.6",
2436
- strokeLinecap: "round",
2437
- strokeLinejoin: "round",
2438
- "aria-hidden": "true",
2439
- focusable: "false",
2440
- children: props.children
2441
- }
2442
- );
2443
- }
2444
- var ACCOUNT_ICONS = {
2445
- profile: /* @__PURE__ */ jsxs18(Glyph, { children: [
2446
- /* @__PURE__ */ jsx28("circle", { cx: "12", cy: "8", r: "4" }),
2447
- /* @__PURE__ */ jsx28("path", { d: "M4 21c0-4 3.6-6 8-6s8 2 8 6" })
2448
- ] }),
2449
- addresses: /* @__PURE__ */ jsxs18(Glyph, { children: [
2450
- /* @__PURE__ */ jsx28("path", { d: "M12 21s7-5.3 7-11a7 7 0 1 0-14 0c0 5.7 7 11 7 11Z" }),
2451
- /* @__PURE__ */ jsx28("circle", { cx: "12", cy: "10", r: "2.5" })
2452
- ] }),
2453
- paymentMethods: /* @__PURE__ */ jsxs18(Glyph, { children: [
2454
- /* @__PURE__ */ jsx28("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
2455
- /* @__PURE__ */ jsx28("path", { d: "M2.5 10h19" })
2456
- ] }),
2457
- orders: /* @__PURE__ */ jsxs18(Glyph, { children: [
2458
- /* @__PURE__ */ jsx28("path", { d: "M4 7h16v13H4z" }),
2459
- /* @__PURE__ */ jsx28("path", { d: "M8 7V4h8v3" }),
2460
- /* @__PURE__ */ jsx28("path", { d: "M8 12h8" })
2461
- ] }),
2462
- invoices: /* @__PURE__ */ jsxs18(Glyph, { children: [
2463
- /* @__PURE__ */ jsx28("path", { d: "M6 3h12v18l-3-2-3 2-3-2-3 2Z" }),
2464
- /* @__PURE__ */ jsx28("path", { d: "M9 8h6M9 12h6" })
2465
- ] }),
2466
- subscriptions: /* @__PURE__ */ jsxs18(Glyph, { children: [
2467
- /* @__PURE__ */ jsx28("path", { d: "M3 12a9 9 0 0 1 15.5-6.2" }),
2468
- /* @__PURE__ */ jsx28("path", { d: "M21 12a9 9 0 0 1-15.5 6.2" }),
2469
- /* @__PURE__ */ jsx28("path", { d: "M18 3v4h-4M6 21v-4h4" })
2470
- ] }),
2471
- security: /* @__PURE__ */ jsxs18(Glyph, { children: [
2472
- /* @__PURE__ */ jsx28("path", { d: "M12 3 4 6.5V12c0 5 3.4 8.3 8 9 4.6-.7 8-4 8-9V6.5Z" }),
2473
- /* @__PURE__ */ jsx28("path", { d: "m9 12 2 2 4-4" })
2474
- ] })
2475
- };
2476
-
2477
- // src/react/components/account/ProfilePanel.tsx
2478
- import { useEffect as useEffect6, useState as useState12, useRef as useRef6 } from "react";
2479
-
2480
- // src/react/components/account/PanelState.tsx
2481
- import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
2482
- function PanelState(props) {
2483
- const { t, errorMessage } = useCopy();
2484
- const { data, isLoading, error, refetch } = props.resource;
2485
- const empty = props.isEmpty(data);
2486
- if (isLoading && empty) return /* @__PURE__ */ jsx29(Skeleton, { count: props.skeletonRows ?? 3 });
2487
- const errorBanner = error ? /* @__PURE__ */ jsxs19("div", { className: "throttle-stack", children: [
2488
- /* @__PURE__ */ jsx29(Alert, { tone: "error", children: errorMessage(error) }),
2489
- /* @__PURE__ */ jsx29(Button, { variant: "secondary", onClick: () => void refetch(), children: t.common.retry })
2490
- ] }) : null;
2491
- const gatedByVerification = error?.code === "verification_required";
2492
- if (gatedByVerification && empty) {
2493
- return /* @__PURE__ */ jsx29(EmptyState, { title: props.emptyTitle, description: props.gatedDescription ?? props.emptyDescription });
2494
- }
2495
- if (error && empty) return errorBanner;
2496
- if (empty) {
2497
- return props.gated ? /* @__PURE__ */ jsx29(EmptyState, { title: props.emptyTitle, description: props.gatedDescription }) : /* @__PURE__ */ jsx29(EmptyState, { title: props.emptyTitle, description: props.emptyDescription });
2498
- }
2499
- return /* @__PURE__ */ jsxs19(Fragment5, { children: [
2500
- errorBanner,
2501
- props.children(data)
2502
- ] });
2503
- }
2504
-
2505
- // src/react/components/account/ProfilePanel.tsx
2506
- import { jsx as jsx30, jsxs as jsxs20 } from "react/jsx-runtime";
2507
- function ProfilePanel(props) {
2508
- const a = useAppearance(props.appearance);
2509
- const scoped = useAppearanceProviderValue(props.appearance);
2510
- const { t } = useCopy(props.localization);
2511
- const { status } = useAuth();
2512
- const resource = useCustomer();
2513
- const { submitting, error, notice, run } = useAction();
2514
- const [form, setForm] = useState12({ firstName: "", lastName: "", phone: "", company: "" });
2515
- const edited = useRef6(false);
2516
- useEffect6(() => {
2517
- const c = resource.data;
2518
- if (!c || edited.current) return;
2519
- setForm({
2520
- firstName: c.firstName ?? "",
2521
- lastName: c.lastName ?? "",
2522
- phone: c.phone ?? "",
2523
- company: c.company ?? ""
2524
- });
2525
- }, [resource.data]);
2526
- if (status === "unauthenticated") return null;
2527
- return /* @__PURE__ */ jsx30(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx30("div", { ...a.rootProps, children: /* @__PURE__ */ jsx30("div", { ...a.el("profilePanel.root", "throttle-panel"), children: /* @__PURE__ */ jsx30(
2528
- PanelState,
2529
- {
2530
- resource,
2531
- isEmpty: (c) => c === null,
2532
- emptyTitle: t.common.somethingWentWrong,
2533
- skeletonRows: 4,
2534
- children: (customer) => /* @__PURE__ */ jsxs20(
2535
- "form",
2536
- {
2537
- ...a.el("profilePanel.form", "throttle-stack"),
2538
- onSubmit: (e) => {
2539
- e.preventDefault();
2540
- void run(async () => {
2541
- edited.current = false;
2542
- await resource.update({
2543
- firstName: form.firstName,
2544
- lastName: form.lastName,
2545
- phone: form.phone,
2546
- company: form.company || null
2547
- });
2548
- return t.profile.savedNotice;
2549
- });
2550
- },
2551
- children: [
2552
- /* @__PURE__ */ jsx30(
2553
- Field,
2554
- {
2555
- label: t.profile.email,
2556
- type: "email",
2557
- value: customer.email,
2558
- onChange: () => {
2559
- },
2560
- disabled: true,
2561
- hint: t.profile.emailReadOnly
2562
- }
2563
- ),
2564
- /* @__PURE__ */ jsxs20(FieldRow, { children: [
2565
- /* @__PURE__ */ jsx30(
2566
- Field,
2567
- {
2568
- label: t.profile.firstName,
2569
- value: form.firstName,
2570
- autoComplete: "given-name",
2571
- onChange: (v) => {
2572
- edited.current = true;
2573
- setForm((f) => ({ ...f, firstName: v }));
2574
- }
2575
- }
2576
- ),
2577
- /* @__PURE__ */ jsx30(
2578
- Field,
2579
- {
2580
- label: t.profile.lastName,
2581
- value: form.lastName,
2582
- autoComplete: "family-name",
2583
- onChange: (v) => {
2584
- edited.current = true;
2585
- setForm((f) => ({ ...f, lastName: v }));
2586
- }
2587
- }
2588
- )
2589
- ] }),
2590
- /* @__PURE__ */ jsxs20(FieldRow, { children: [
2591
- /* @__PURE__ */ jsx30(
2592
- Field,
2593
- {
2594
- label: t.profile.phone,
2595
- type: "tel",
2596
- value: form.phone,
2597
- autoComplete: "tel",
2598
- onChange: (v) => {
2599
- edited.current = true;
2600
- setForm((f) => ({ ...f, phone: v }));
2601
- }
2602
- }
2603
- ),
2604
- /* @__PURE__ */ jsx30(
2605
- Field,
2606
- {
2607
- label: t.profile.company,
2608
- value: form.company,
2609
- autoComplete: "organization",
2610
- onChange: (v) => {
2611
- edited.current = true;
2612
- setForm((f) => ({ ...f, company: v }));
2613
- }
2614
- }
2615
- )
2616
- ] }),
2617
- error ? /* @__PURE__ */ jsx30(Alert, { tone: "error", children: error }) : null,
2618
- notice ? /* @__PURE__ */ jsx30(Alert, { tone: "success", children: notice }) : null,
2619
- /* @__PURE__ */ jsx30(Button, { type: "submit", loading: submitting, elementKey: "profilePanel.submitButton", children: t.profile.saveButton })
2620
- ]
2621
- }
2622
- )
2623
- }
2624
- ) }) }) });
2625
- }
2626
-
2627
- // src/react/components/account/AddressesPanel.tsx
2628
- import { useState as useState14 } from "react";
2629
-
2630
- // src/react/components/account/AddressForm.tsx
2631
- import { useState as useState13 } from "react";
2632
- import { jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
2633
- function AddressForm(props) {
2634
- const a = useAppearance();
2635
- const { t } = useCopy();
2636
- const i = props.initial;
2637
- const [form, setForm] = useState13({
2638
- label: i?.label ?? "",
2639
- firstName: i?.firstName ?? "",
2640
- lastName: i?.lastName ?? "",
2641
- company: i?.company ?? "",
2642
- addressLine1: i?.addressLine1 ?? "",
2643
- addressLine2: i?.addressLine2 ?? "",
2644
- city: i?.city ?? "",
2645
- stateProvince: i?.stateProvince ?? "",
2646
- postalCode: i?.postalCode ?? "",
2647
- countryCode: i?.countryCode ?? "",
2648
- phone: i?.phone ?? ""
2649
- });
2650
- const set = (key) => (value) => setForm((f) => ({ ...f, [key]: value }));
2651
- return /* @__PURE__ */ jsxs21(
2652
- "form",
2653
- {
2654
- ...a.el(props.elementKey ?? "addressesPanel.form", "throttle-stack"),
2655
- onSubmit: (e) => {
2656
- e.preventDefault();
2657
- props.onSubmit({
2658
- label: form.label || void 0,
2659
- firstName: form.firstName || void 0,
2660
- lastName: form.lastName || void 0,
2661
- company: form.company || void 0,
2662
- addressLine1: form.addressLine1,
2663
- addressLine2: form.addressLine2 || void 0,
2664
- city: form.city,
2665
- stateProvince: form.stateProvince || void 0,
2666
- postalCode: form.postalCode || void 0,
2667
- // The API wants ISO-3166 alpha-2; uppercase so "gb" is not rejected.
2668
- countryCode: form.countryCode.toUpperCase(),
2669
- phone: form.phone || void 0
2670
- });
2671
- },
2672
- children: [
2673
- /* @__PURE__ */ jsxs21(FieldRow, { children: [
2674
- props.hideLabel ? null : /* @__PURE__ */ jsx31(Field, { label: t.addresses.label, value: form.label, onChange: set("label") }),
2675
- /* @__PURE__ */ jsx31(Field, { label: t.addresses.company, value: form.company, onChange: set("company"), autoComplete: "organization" })
2676
- ] }),
2677
- /* @__PURE__ */ jsxs21(FieldRow, { children: [
2678
- /* @__PURE__ */ jsx31(Field, { label: t.addresses.firstName, value: form.firstName, onChange: set("firstName"), autoComplete: "given-name" }),
2679
- /* @__PURE__ */ jsx31(Field, { label: t.addresses.lastName, value: form.lastName, onChange: set("lastName"), autoComplete: "family-name" })
2680
- ] }),
2681
- /* @__PURE__ */ jsx31(
2682
- Field,
2683
- {
2684
- label: t.addresses.addressLine1,
2685
- value: form.addressLine1,
2686
- onChange: set("addressLine1"),
2687
- required: true,
2688
- autoComplete: "address-line1"
2689
- }
2690
- ),
2691
- /* @__PURE__ */ jsx31(
2692
- Field,
2693
- {
2694
- label: t.addresses.addressLine2,
2695
- value: form.addressLine2,
2696
- onChange: set("addressLine2"),
2697
- autoComplete: "address-line2"
2698
- }
2699
- ),
2700
- /* @__PURE__ */ jsxs21(FieldRow, { children: [
2701
- /* @__PURE__ */ jsx31(Field, { label: t.addresses.city, value: form.city, onChange: set("city"), required: true, autoComplete: "address-level2" }),
2702
- /* @__PURE__ */ jsx31(
2703
- Field,
2704
- {
2705
- label: t.addresses.stateProvince,
2706
- value: form.stateProvince,
2707
- onChange: set("stateProvince"),
2708
- autoComplete: "address-level1"
2709
- }
2710
- )
2711
- ] }),
2712
- /* @__PURE__ */ jsxs21(FieldRow, { children: [
2713
- /* @__PURE__ */ jsx31(
2714
- Field,
2715
- {
2716
- label: t.addresses.postalCode,
2717
- value: form.postalCode,
2718
- onChange: set("postalCode"),
2719
- autoComplete: "postal-code"
2720
- }
2721
- ),
2722
- /* @__PURE__ */ jsx31(
2723
- Field,
2724
- {
2725
- label: t.addresses.countryCode,
2726
- value: form.countryCode,
2727
- onChange: set("countryCode"),
2728
- required: true,
2729
- autoComplete: "country",
2730
- hint: "ISO 3166-1 alpha-2, e.g. GB"
2731
- }
2732
- )
2733
- ] }),
2734
- /* @__PURE__ */ jsx31(Field, { label: t.addresses.phone, type: "tel", value: form.phone, onChange: set("phone"), autoComplete: "tel" }),
2735
- props.error ? /* @__PURE__ */ jsx31(Alert, { tone: "error", children: props.error }) : null,
2736
- /* @__PURE__ */ jsxs21("div", { className: "throttle-actions", children: [
2737
- /* @__PURE__ */ jsx31(Button, { type: "submit", loading: props.submitting, children: t.common.save }),
2738
- /* @__PURE__ */ jsx31(Button, { variant: "link", onClick: props.onCancel, children: t.common.cancel })
2739
- ] })
2740
- ]
2741
- }
2742
- );
2743
- }
2744
-
2745
- // src/react/components/account/AddressesPanel.tsx
2746
- import { Fragment as Fragment6, jsx as jsx32, jsxs as jsxs22 } from "react/jsx-runtime";
2747
- function AddressesPanel(props) {
2748
- const a = useAppearance(props.appearance);
2749
- const scoped = useAppearanceProviderValue(props.appearance);
2750
- const { t } = useCopy(props.localization);
2751
- const resource = useAddresses();
2752
- const { submitting, error, run } = useAction();
2753
- const confirm = useConfirm();
2754
- const [editing, setEditing] = useState14(null);
2755
- const variant = props.variant ?? "rows";
2756
- const rowsVariant = variant === "rows";
2757
- const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
2758
- const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
2759
- const actionVariant = (base) => rowsVariant ? base === "danger" ? "linkDanger" : "link" : base;
2760
- const close = () => setEditing(null);
2761
- const formatAddress = (address) => [address.addressLine2, address.city, address.stateProvince, address.postalCode, address.countryCode].filter(Boolean).join(", ");
2762
- return /* @__PURE__ */ jsx32(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx32("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs22("div", { ...a.el("addressesPanel.root", "throttle-panel"), children: [
2763
- !editing && error ? /* @__PURE__ */ jsx32(Alert, { tone: "error", children: error }) : null,
2764
- editing ? /* @__PURE__ */ jsx32(
2765
- AddressForm,
2766
- {
2767
- initial: editing === "new" ? void 0 : editing,
2768
- submitting,
2769
- error,
2770
- onCancel: close,
2771
- onSubmit: (input) => void run(async () => {
2772
- if (editing === "new") await resource.create(input);
2773
- else await resource.update(editing.id, input);
2774
- close();
2775
- })
2776
- }
2777
- ) : /* @__PURE__ */ jsxs22(Fragment6, { children: [
2778
- /* @__PURE__ */ jsx32("div", { children: /* @__PURE__ */ jsx32(Button, { elementKey: "addressesPanel.addButton", variant: "secondary", onClick: () => setEditing("new"), children: t.addresses.addButton }) }),
2779
- /* @__PURE__ */ jsx32(
2780
- PanelState,
2781
- {
2782
- resource,
2783
- isEmpty: (rows) => rows.length === 0,
2784
- emptyTitle: t.addresses.empty,
2785
- emptyDescription: t.addresses.emptyDescription,
2786
- children: (rows) => variant === "table" ? /* @__PURE__ */ jsx32(
2787
- DataTable,
2788
- {
2789
- tableKey: "addressesPanel.table",
2790
- rowElementKey: "addressesPanel.row",
2791
- rowKey: (address) => address.id,
2792
- rows,
2793
- columns: [
2794
- { key: "label", label: t.addresses.label, render: (address) => address.label || address.addressLine1 },
2795
- { key: "address", label: t.addresses.columnAddress, render: (address) => /* @__PURE__ */ jsxs22(Fragment6, { children: [
2796
- address.addressLine1,
2797
- formatAddress(address) ? `, ${formatAddress(address)}` : ""
2798
- ] }) },
2799
- { key: "default", label: t.addresses.defaultBadge, render: (address) => address.isDefault ? /* @__PURE__ */ jsx32(Badge, { tone: "success", children: t.addresses.defaultBadge }) : null },
2800
- { key: "actions", label: t.common.actions, render: (address) => /* @__PURE__ */ jsxs22("div", { className: "throttle-actions", children: [
2801
- !address.isDefault ? /* @__PURE__ */ jsx32(
2802
- Button,
2803
- {
2804
- variant: "secondary",
2805
- elementKey: "addressesPanel.defaultButton",
2806
- onClick: () => void run(() => resource.update(address.id, { isDefault: true })),
2807
- children: t.addresses.makeDefault
2808
- }
2809
- ) : null,
2810
- /* @__PURE__ */ jsx32(
2811
- Button,
2812
- {
2813
- variant: "secondary",
2814
- elementKey: "addressesPanel.editButton",
2815
- onClick: () => setEditing(address),
2816
- children: t.common.edit
2817
- }
2818
- ),
2819
- /* @__PURE__ */ jsx32(
2820
- Button,
2821
- {
2822
- variant: "danger",
2823
- elementKey: "addressesPanel.deleteButton",
2824
- disabled: submitting,
2825
- onClick: async () => {
2826
- if (!await confirm({ title: t.addresses.deleteConfirm, confirmLabel: t.addresses.deleteConfirmAction, tone: "danger" })) return;
2827
- void run(() => resource.remove(address.id));
2828
- },
2829
- children: t.common.delete
2830
- }
2831
- )
2832
- ] }) }
2833
- ]
2834
- }
2835
- ) : /* @__PURE__ */ jsx32("div", { ...a.el("addressesPanel.list", listBase), children: rows.map((address) => /* @__PURE__ */ jsxs22("div", { ...a.el("addressesPanel.item", itemBase), children: [
2836
- /* @__PURE__ */ jsxs22("div", { children: [
2837
- /* @__PURE__ */ jsxs22("div", { className: "throttle-row", children: [
2838
- /* @__PURE__ */ jsx32("strong", { children: address.label || address.addressLine1 }),
2839
- address.isDefault ? /* @__PURE__ */ jsx32(Badge, { tone: "success", children: t.addresses.defaultBadge }) : null
2840
- ] }),
2841
- /* @__PURE__ */ jsx32("p", { className: "throttle-muted", children: address.addressLine1 }),
2842
- /* @__PURE__ */ jsx32("p", { className: "throttle-muted", children: formatAddress(address) })
2843
- ] }),
2844
- /* @__PURE__ */ jsxs22("div", { className: "throttle-actions", children: [
2845
- !address.isDefault ? /* @__PURE__ */ jsx32(
2846
- Button,
2847
- {
2848
- variant: actionVariant("secondary"),
2849
- elementKey: "addressesPanel.defaultButton",
2850
- onClick: () => void run(() => resource.update(address.id, { isDefault: true })),
2851
- children: t.addresses.makeDefault
2852
- }
2853
- ) : null,
2854
- /* @__PURE__ */ jsx32(
2855
- Button,
2856
- {
2857
- variant: actionVariant("secondary"),
2858
- elementKey: "addressesPanel.editButton",
2859
- onClick: () => setEditing(address),
2860
- children: t.common.edit
2861
- }
2862
- ),
2863
- /* @__PURE__ */ jsx32(
2864
- Button,
2865
- {
2866
- variant: actionVariant("danger"),
2867
- elementKey: "addressesPanel.deleteButton",
2868
- disabled: submitting,
2869
- onClick: async () => {
2870
- if (!await confirm({ title: t.addresses.deleteConfirm, confirmLabel: t.addresses.deleteConfirmAction, tone: "danger" })) return;
2871
- void run(() => resource.remove(address.id));
2872
- },
2873
- children: t.common.delete
2874
- }
2875
- )
2876
- ] })
2877
- ] }, address.id)) })
2878
- }
2879
- )
2880
- ] })
2881
- ] }) }) });
2882
- }
2883
-
2884
- // src/react/components/account/PaymentMethodsPanel.tsx
2885
- import { useState as useState16 } from "react";
2886
-
2887
- // src/react/components/account/AddCardDialog.tsx
2888
- import { useEffect as useEffect7, useId as useId4, useRef as useRef7, useState as useState15 } from "react";
2889
- import { jsx as jsx33, jsxs as jsxs23 } from "react/jsx-runtime";
2890
- function AddCardDialog(props) {
2891
- const { account } = useAuth();
2892
- const titleId = useId4();
2893
- const { t } = useCopy(props.localization);
2894
- const [src, setSrc] = useState15(null);
2895
- const [error, setError] = useState15(null);
2896
- const [busy, setBusy] = useState15(false);
2897
- const onAdded = useRef7(props.onAdded);
2898
- const onClose = useRef7(props.onClose);
2899
- onAdded.current = props.onAdded;
2900
- onClose.current = props.onClose;
2901
- useEffect7(() => {
2902
- if (!props.open) {
2903
- setSrc(null);
2904
- setError(null);
2905
- return;
2906
- }
2907
- let live = true;
2908
- setError(null);
2909
- account.startCardSetup().then(
2910
- (session) => {
2911
- if (!live) return;
2912
- let url = session.embedUrl;
2913
- try {
2914
- const u = new URL(session.embedUrl, window.location.origin);
2915
- u.searchParams.set("parentOrigin", window.location.origin);
2916
- url = u.toString();
2917
- } catch {
2918
- }
2919
- setSrc(url);
2920
- },
2921
- (err) => {
2922
- if (live) setError(messageForError(err));
2923
- }
2924
- );
2925
- return () => {
2926
- live = false;
2927
- };
2928
- }, [props.open, account]);
2929
- useEffect7(() => {
2930
- if (!src) return;
2931
- const frameOrigin = (() => {
2932
- try {
2933
- return new URL(src).origin;
2934
- } catch {
2935
- return null;
2936
- }
2937
- })();
2938
- if (!frameOrigin) return;
2939
- async function onMessage(event) {
2940
- if (event.origin !== frameOrigin) return;
2941
- const data = event.data;
2942
- if (!data || typeof data.type !== "string") return;
2943
- if (data.type === "throttle.completed" && data.paymentId) {
2944
- setBusy(true);
2945
- try {
2946
- await account.confirmCardSetup(data.paymentId);
2947
- onAdded.current();
2948
- onClose.current();
2949
- } catch (err) {
2950
- setError(messageForError(err));
2951
- } finally {
2952
- setBusy(false);
2953
- }
2954
- } else if (data.type === "throttle.error") {
2955
- setError(data.message || t.common.somethingWentWrong);
2956
- } else if (data.type === "throttle.cancelled") {
2957
- onClose.current();
2958
- }
2959
- }
2960
- window.addEventListener("message", onMessage);
2961
- return () => window.removeEventListener("message", onMessage);
2962
- }, [src, account, t]);
2963
- if (!props.open) return null;
2964
- return /* @__PURE__ */ jsxs23(Modal, { open: true, onClose: props.onClose, labelledBy: titleId, children: [
2965
- /* @__PURE__ */ jsx33("div", { className: "throttle-modal__header", children: /* @__PURE__ */ jsx33("h2", { id: titleId, className: "throttle-modal__title", children: t.paymentMethods.addTitle }) }),
2966
- /* @__PURE__ */ jsxs23("div", { className: "throttle-modal__body", children: [
2967
- error ? /* @__PURE__ */ jsx33(Alert, { tone: "error", children: error }) : null,
2968
- busy ? /* @__PURE__ */ jsx33("div", { className: "throttle-centre", children: /* @__PURE__ */ jsx33(Spinner, {}) }) : src ? /* @__PURE__ */ jsx33(
2969
- "iframe",
2970
- {
2971
- className: "throttle-cardframe",
2972
- src,
2973
- title: t.paymentMethods.addTitle,
2974
- sandbox: "allow-scripts allow-forms allow-same-origin"
2975
- }
2976
- ) : !error ? /* @__PURE__ */ jsx33("div", { className: "throttle-centre", children: /* @__PURE__ */ jsx33(Spinner, {}) }) : null
2977
- ] })
2978
- ] });
2979
- }
2980
-
2981
- // src/react/components/account/PaymentMethodsPanel.tsx
2982
- import { jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
2983
- function PaymentMethodsPanel(props) {
2984
- const a = useAppearance(props.appearance);
2985
- const scoped = useAppearanceProviderValue(props.appearance);
2986
- const { t } = useCopy(props.localization);
2987
- const { emailVerified } = useAuth();
2988
- const resource = useSavedCards();
2989
- const { submitting, error, run } = useAction();
2990
- const confirm = useConfirm();
2991
- const [adding, setAdding] = useState16(false);
2992
- const variant = props.variant ?? "rows";
2993
- const rowsVariant = variant === "rows";
2994
- const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
2995
- const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
2996
- const actionVariant = (base) => rowsVariant ? base === "danger" ? "linkDanger" : "link" : base;
2997
- const cardLabel = (card) => `${(card.cardBrand ?? card.methodType).toUpperCase()} \u2022\u2022\u2022\u2022 ${card.cardLastFour ?? "____"}`;
2998
- const expiryLabel = (card) => card.cardExpMonth && card.cardExpYear ? `${String(card.cardExpMonth).padStart(2, "0")}/${card.cardExpYear}` : "";
2999
- return /* @__PURE__ */ jsx34(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx34("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs24("div", { ...a.el("paymentMethodsPanel.root", "throttle-panel"), children: [
3000
- error ? /* @__PURE__ */ jsx34(Alert, { tone: "error", children: error }) : null,
3001
- emailVerified ? /* @__PURE__ */ jsx34("div", { children: /* @__PURE__ */ jsx34(Button, { elementKey: "paymentMethodsPanel.addButton", variant: "secondary", onClick: () => setAdding(true), children: t.paymentMethods.addButton }) }) : null,
3002
- /* @__PURE__ */ jsx34(
3003
- AddCardDialog,
3004
- {
3005
- open: adding,
3006
- onClose: () => setAdding(false),
3007
- onAdded: () => {
3008
- void resource.refetch();
3009
- },
3010
- localization: props.localization
3011
- }
3012
- ),
3013
- /* @__PURE__ */ jsx34(
3014
- PanelState,
3015
- {
3016
- resource,
3017
- gated: !emailVerified,
3018
- gatedDescription: t.paymentMethods.gatedDescription,
3019
- isEmpty: (rows) => rows.length === 0,
3020
- emptyTitle: t.paymentMethods.empty,
3021
- emptyDescription: t.paymentMethods.emptyDescription,
3022
- children: (rows) => variant === "table" ? /* @__PURE__ */ jsx34(
3023
- DataTable,
3024
- {
3025
- tableKey: "paymentMethodsPanel.table",
3026
- rowElementKey: "paymentMethodsPanel.row",
3027
- rowKey: (card) => card.id,
3028
- rows,
3029
- columns: [
3030
- { key: "card", label: t.paymentMethods.columnCard, render: (card) => cardLabel(card) },
3031
- { key: "expires", label: t.paymentMethods.expires, render: (card) => expiryLabel(card) },
3032
- { key: "default", label: t.paymentMethods.defaultBadge, render: (card) => card.isDefault ? /* @__PURE__ */ jsx34(Badge, { tone: "success", children: t.paymentMethods.defaultBadge }) : null },
3033
- { key: "actions", label: t.common.actions, render: (card) => /* @__PURE__ */ jsxs24("div", { className: "throttle-actions", children: [
3034
- !card.isDefault ? /* @__PURE__ */ jsx34(
3035
- Button,
3036
- {
3037
- variant: "secondary",
3038
- loading: submitting,
3039
- elementKey: "paymentMethodsPanel.defaultButton",
3040
- onClick: () => void run(() => resource.setDefault(card.id)),
3041
- children: t.paymentMethods.makeDefault
3042
- }
3043
- ) : null,
3044
- /* @__PURE__ */ jsx34(
3045
- Button,
3046
- {
3047
- variant: "danger",
3048
- elementKey: "paymentMethodsPanel.deleteButton",
3049
- disabled: submitting,
3050
- onClick: async () => {
3051
- if (!await confirm({ title: t.paymentMethods.deleteConfirm, confirmLabel: t.paymentMethods.deleteConfirmAction, tone: "danger" })) return;
3052
- void run(() => resource.remove(card.id));
3053
- },
3054
- children: t.common.delete
3055
- }
3056
- )
3057
- ] }) }
3058
- ]
3059
- }
3060
- ) : /* @__PURE__ */ jsx34("div", { ...a.el("paymentMethodsPanel.list", listBase), children: rows.map((card) => /* @__PURE__ */ jsxs24("div", { ...a.el("paymentMethodsPanel.item", itemBase), children: [
3061
- /* @__PURE__ */ jsxs24("div", { children: [
3062
- /* @__PURE__ */ jsxs24("div", { className: "throttle-row", children: [
3063
- /* @__PURE__ */ jsx34("strong", { children: cardLabel(card) }),
3064
- card.isDefault ? /* @__PURE__ */ jsx34(Badge, { tone: "success", children: t.paymentMethods.defaultBadge }) : null
3065
- ] }),
3066
- card.cardExpMonth && card.cardExpYear ? /* @__PURE__ */ jsxs24("p", { className: "throttle-muted", children: [
3067
- t.paymentMethods.expires,
3068
- " ",
3069
- String(card.cardExpMonth).padStart(2, "0"),
3070
- "/",
3071
- card.cardExpYear
3072
- ] }) : null
3073
- ] }),
3074
- /* @__PURE__ */ jsxs24("div", { className: "throttle-actions", children: [
3075
- !card.isDefault ? /* @__PURE__ */ jsx34(
3076
- Button,
3077
- {
3078
- variant: actionVariant("secondary"),
3079
- loading: submitting,
3080
- elementKey: "paymentMethodsPanel.defaultButton",
3081
- onClick: () => void run(() => resource.setDefault(card.id)),
3082
- children: t.paymentMethods.makeDefault
3083
- }
3084
- ) : null,
3085
- /* @__PURE__ */ jsx34(
3086
- Button,
3087
- {
3088
- variant: actionVariant("danger"),
3089
- elementKey: "paymentMethodsPanel.deleteButton",
3090
- disabled: submitting,
3091
- onClick: async () => {
3092
- if (!await confirm({ title: t.paymentMethods.deleteConfirm, confirmLabel: t.paymentMethods.deleteConfirmAction, tone: "danger" })) return;
3093
- void run(() => resource.remove(card.id));
3094
- },
3095
- children: t.common.delete
3096
- }
3097
- )
3098
- ] })
3099
- ] }, card.id)) })
3100
- }
3101
- )
3102
- ] }) }) });
3103
- }
3104
-
3105
- // src/react/components/account/OrdersPanel.tsx
3106
- import { useEffect as useEffect8, useState as useState17 } from "react";
3107
-
3108
- // src/react/components/format.ts
3109
- function formatMoney(minorUnits, currency, locale) {
3110
- const code = (currency || "USD").toUpperCase();
3111
- let digits = 2;
3112
- try {
3113
- digits = new Intl.NumberFormat("en-US", { style: "currency", currency: code }).resolvedOptions().maximumFractionDigits ?? 2;
3114
- } catch {
3115
- }
3116
- const major = minorUnits / 10 ** digits;
3117
- try {
3118
- return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(major);
3119
- } catch {
3120
- try {
3121
- return new Intl.NumberFormat("en-US", { style: "currency", currency: code }).format(major);
3122
- } catch {
3123
- return `${code} ${major.toFixed(digits)}`;
3124
- }
3125
- }
3126
- }
3127
- function formatDate(value, locale) {
3128
- const date = value instanceof Date ? value : new Date(value);
3129
- if (Number.isNaN(date.getTime())) return "";
3130
- try {
3131
- return new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(date);
3132
- } catch {
3133
- return date.toISOString().slice(0, 10);
3134
- }
3135
- }
3136
- function scheduleDueLabel(row, locale) {
3137
- if (row.intervalUnit !== "hour") return formatDate(row.dueAt, locale);
3138
- const date = new Date(row.dueAt);
3139
- if (Number.isNaN(date.getTime())) return "";
3140
- const opts = {
3141
- dateStyle: "medium",
3142
- timeStyle: "short",
3143
- timeZone: "UTC"
3144
- };
3145
- try {
3146
- return `${new Intl.DateTimeFormat(locale, opts).format(date)} UTC`;
3147
- } catch {
3148
- return `${new Intl.DateTimeFormat("en-US", opts).format(date)} UTC`;
3149
- }
3150
- }
3151
-
3152
- // src/react/components/account/OrderStatus.tsx
3153
- import { Fragment as Fragment7, jsx as jsx35, jsxs as jsxs25 } from "react/jsx-runtime";
3154
- function copyKey(wire) {
3155
- return wire.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
3156
- }
3157
- var ATTENTION_PAYMENT_STATES = /* @__PURE__ */ new Set(["pending", "failed", "disputed", "expired"]);
3158
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["cancelled", "closed", "void", "expired", "voided"]);
3159
- function StatusBadge(props) {
3160
- const label = props.labels[copyKey(props.status)] ?? props.status.replace(/_/g, " ");
3161
- return /* @__PURE__ */ jsx35(Badge, { tone: TERMINAL_STATUSES.has(props.status) ? "muted" : "neutral", children: label });
3162
- }
3163
- function OrderStatus(props) {
3164
- const labels = props.t.orders.statusLabels;
3165
- const payLabels = props.t.orders.paymentLabels;
3166
- const lifecycle = labels[copyKey(props.status)] ?? props.status.replace(/_/g, " ");
3167
- const money = props.paymentStatus ? payLabels[copyKey(props.paymentStatus)] : void 0;
3168
- return /* @__PURE__ */ jsxs25(Fragment7, { children: [
3169
- /* @__PURE__ */ jsx35(Badge, { tone: TERMINAL_STATUSES.has(props.status) ? "muted" : "neutral", children: lifecycle }),
3170
- money ? /* @__PURE__ */ jsx35(Badge, { tone: ATTENTION_PAYMENT_STATES.has(props.paymentStatus) ? "warning" : "neutral", children: money }) : null
3171
- ] });
3172
- }
3173
-
3174
- // src/react/components/account/AddressBlock.tsx
3175
- import { jsx as jsx36, jsxs as jsxs26 } from "react/jsx-runtime";
3176
- function addressLines(address) {
3177
- if (!address || typeof address !== "object") return [];
3178
- const a = address;
3179
- const str = (k) => typeof a[k] === "string" && a[k].trim() ? a[k].trim() : null;
3180
- const name = [str("firstName"), str("lastName")].filter(Boolean).join(" ");
3181
- const cityLine = [str("city"), str("stateProvince"), str("postalCode")].filter(Boolean).join(", ");
3182
- return [
3183
- name || null,
3184
- str("company"),
3185
- str("addressLine1") ?? str("line1"),
3186
- str("addressLine2") ?? str("line2"),
3187
- cityLine || null,
3188
- str("countryCode") ?? str("country")
3189
- ].filter((l) => Boolean(l));
3190
- }
3191
- function AddressBlock(props) {
3192
- const a = useAppearance();
3193
- const lines = addressLines(props.address);
3194
- if (lines.length === 0) return null;
3195
- return /* @__PURE__ */ jsxs26("div", { ...a.el(props.elementKey ?? "orderDetail.address", "throttle-addressblock"), children: [
3196
- /* @__PURE__ */ jsx36("h4", { className: "throttle-section__title", children: props.heading }),
3197
- lines.map((l) => /* @__PURE__ */ jsx36("p", { className: "throttle-addressblock__line throttle-muted", children: l }, l))
3198
- ] });
3199
- }
3200
-
3201
- // src/react/components/account/OrderTimeline.tsx
3202
- import { jsx as jsx37, jsxs as jsxs27 } from "react/jsx-runtime";
3203
- function orderTimeline(order, labels) {
3204
- const entries = [{ label: labels.placed, at: order.createdAt }];
3205
- if (order.completedAt) entries.push({ label: labels.completed, at: order.completedAt });
3206
- if (order.cancelledAt) entries.push({ label: labels.cancelled, at: order.cancelledAt });
3207
- if (order.closedAt) entries.push({ label: labels.closed, at: order.closedAt });
3208
- return entries.sort((x, y) => new Date(x.at).getTime() - new Date(y.at).getTime());
3209
- }
3210
- function OrderTimeline(props) {
3211
- const a = useAppearance();
3212
- if (props.entries.length <= 1) return null;
3213
- return /* @__PURE__ */ jsxs27("div", { ...a.el("orderDetail.timeline", "throttle-timeline"), children: [
3214
- /* @__PURE__ */ jsx37("h4", { className: "throttle-section__title", children: props.heading }),
3215
- props.entries.map((e) => /* @__PURE__ */ jsxs27("div", { className: "throttle-timeline__row", children: [
3216
- /* @__PURE__ */ jsx37("span", { className: "throttle-timeline__dot", "aria-hidden": "true" }),
3217
- /* @__PURE__ */ jsx37("span", { className: "throttle-timeline__label", children: e.label }),
3218
- /* @__PURE__ */ jsx37("span", { className: "throttle-muted", children: formatDate(e.at, props.locale) })
3219
- ] }, `${e.label}-${e.at}`))
3220
- ] });
3221
- }
3222
-
3223
- // src/react/components/account/order-subscription-links.ts
3224
- function orderSubscriptionLinks(order, labels) {
3225
- const ids = order.subscriptionIds?.length ? order.subscriptionIds : order.subscriptionId ? [order.subscriptionId] : [];
3226
- if (ids.length === 1) return [{ id: ids[0], label: labels.single }];
3227
- return ids.map((id, index) => {
3228
- const name = order.lineItems.find((li) => li.subscriptionId === id)?.name;
3229
- return { id, label: name ? `${labels.named} ${name}` : `${labels.single} ${index + 1}` };
3230
- });
3231
- }
3232
-
3233
- // src/react/components/account/OrderDetail.tsx
3234
- import { jsx as jsx38, jsxs as jsxs28 } from "react/jsx-runtime";
3235
- var CAN_PAY = /* @__PURE__ */ new Set(["draft", "pending"]);
3236
- var CAN_REORDER = /* @__PURE__ */ new Set(["fulfilled", "cancelled", "closed"]);
3237
- var CAN_CANCEL = /* @__PURE__ */ new Set(["draft", "pending", "processing"]);
3238
- var SETTLED_PAYMENT = /* @__PURE__ */ new Set(["captured", "paid", "partially_refunded", "refunded"]);
3239
- var CAN_RETURN = /* @__PURE__ */ new Set(["fulfilled", "partially_fulfilled", "closed"]);
3240
- function Line(props) {
3241
- return /* @__PURE__ */ jsxs28("div", { className: "throttle-row", children: [
3242
- /* @__PURE__ */ jsx38("span", { className: "throttle-muted", children: props.label }),
3243
- /* @__PURE__ */ jsx38("span", { children: props.strong ? /* @__PURE__ */ jsx38("strong", { children: props.value }) : props.value })
3244
- ] });
3245
- }
3246
- function OrderDetail(props) {
3247
- const a = useAppearance(props.appearance);
3248
- const scoped = useAppearanceProviderValue(props.appearance);
3249
- const { t, locale } = useCopy(props.localization);
3250
- const { account } = useAuth();
3251
- const resource = useOrder(props.orderId);
3252
- const { submitting, error, run } = useAction();
3253
- const confirm = useConfirm();
3254
- const allowCancel = props.allowCancel ?? true;
3255
- const allowReturn = props.allowReturn ?? true;
3256
- return /* @__PURE__ */ jsx38(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx38("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.root", "throttle-panel"), children: [
3257
- props.onBack ? (
3258
- // Wrapped on purpose. The panel is a column flex container, so a
3259
- // direct child stretches to its full width however `width: auto`
3260
- // the link variant is — the control rendered 748px wide with a
3261
- // centred label. The wrapper takes the stretch; the button keeps
3262
- // its own width. A CSS fix would need a compound selector, and
3263
- // those outrank the merchant overrides in `appearance.elements`.
3264
- /* @__PURE__ */ jsx38("div", { children: /* @__PURE__ */ jsx38(Button, { variant: "link", elementKey: "orderDetail.backButton", onClick: props.onBack, children: t.common.back }) })
3265
- ) : null,
3266
- error ? /* @__PURE__ */ jsx38(Alert, { tone: "error", children: error }) : null,
3267
- /* @__PURE__ */ jsx38(
3268
- PanelState,
3269
- {
3270
- resource,
3271
- isEmpty: (o) => o === null,
3272
- emptyTitle: t.orders.empty,
3273
- skeletonRows: 5,
3274
- children: (order) => {
3275
- const target = {
3276
- id: order.id,
3277
- orderNumber: order.orderNumber,
3278
- currency: order.currency,
3279
- lineItems: order.lineItems.map((li) => ({
3280
- name: li.name,
3281
- unitPrice: li.unitPrice,
3282
- quantity: li.quantity
3283
- }))
3284
- };
3285
- const money = (v) => formatMoney(v, order.currency, locale);
3286
- const paid = SETTLED_PAYMENT.has(order.paymentStatus);
3287
- const showCancel = allowCancel && CAN_CANCEL.has(order.status) && !paid;
3288
- const showReturn = allowReturn && CAN_RETURN.has(order.status);
3289
- return /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.layout", "throttle-detail"), children: [
3290
- /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.main", "throttle-detail__main"), children: [
3291
- /* @__PURE__ */ jsxs28("div", { className: "throttle-detail__section", children: [
3292
- /* @__PURE__ */ jsxs28("div", { className: "throttle-row", children: [
3293
- /* @__PURE__ */ jsx38("h3", { children: order.orderNumber }),
3294
- /* @__PURE__ */ jsx38(OrderStatus, { status: order.status, paymentStatus: order.paymentStatus, t })
3295
- ] }),
3296
- /* @__PURE__ */ jsx38("p", { className: "throttle-muted", children: formatDate(order.createdAt, locale) })
3297
- ] }),
3298
- /* @__PURE__ */ jsxs28("div", { className: "throttle-detail__section", children: [
3299
- /* @__PURE__ */ jsx38("span", { className: "throttle-detail__label", children: t.orders.items }),
3300
- /* @__PURE__ */ jsx38("div", { ...a.el("orderDetail.lineItems", "throttle-stack"), children: order.lineItems.map((item) => /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.lineItem", "throttle-lineitem"), children: [
3301
- item.imageUrl ? /* @__PURE__ */ jsx38("img", { className: "throttle-lineitem__image", src: item.imageUrl, alt: "" }) : null,
3302
- /* @__PURE__ */ jsxs28("div", { style: { flex: 1 }, children: [
3303
- /* @__PURE__ */ jsx38("strong", { children: item.name }),
3304
- item.description ? /* @__PURE__ */ jsx38("p", { className: "throttle-lineitem__desc", children: item.description }) : null,
3305
- /* @__PURE__ */ jsxs28("p", { className: "throttle-lineitem__meta", children: [
3306
- t.orders.quantity,
3307
- " ",
3308
- item.quantity,
3309
- " \xD7 ",
3310
- money(item.unitPrice)
3311
- ] })
3312
- ] }),
3313
- /* @__PURE__ */ jsx38("span", { children: money(item.total) })
3314
- ] }, item.id)) })
3315
- ] }),
3316
- /* @__PURE__ */ jsx38(
3317
- OrderTimeline,
3318
- {
3319
- heading: t.orders.history,
3320
- locale,
3321
- entries: orderTimeline(order, {
3322
- placed: t.orders.placed,
3323
- completed: t.orders.completed,
3324
- cancelled: t.orders.cancelledAt,
3325
- closed: t.orders.closedAt
3326
- })
3327
- }
3328
- ),
3329
- /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.addresses", "throttle-addresses"), children: [
3330
- /* @__PURE__ */ jsx38(
3331
- AddressBlock,
3332
- {
3333
- heading: t.orders.shipTo,
3334
- address: order.shippingAddress,
3335
- elementKey: "orderDetail.shippingAddress"
3336
- }
3337
- ),
3338
- /* @__PURE__ */ jsx38(
3339
- AddressBlock,
3340
- {
3341
- heading: t.orders.billTo,
3342
- address: order.billingAddress,
3343
- elementKey: "orderDetail.billingAddress"
3344
- }
3345
- )
3346
- ] })
3347
- ] }),
3348
- /* @__PURE__ */ jsxs28("aside", { ...a.el("orderDetail.rail", "throttle-detail__rail"), children: [
3349
- /* @__PURE__ */ jsxs28("div", { className: "throttle-detail__section", children: [
3350
- /* @__PURE__ */ jsx38("span", { className: "throttle-detail__label", children: t.orders.total }),
3351
- /* @__PURE__ */ jsx38("span", { className: "throttle-detail__headline", children: money(order.total) })
3352
- ] }),
3353
- /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.totals", "throttle-totals"), children: [
3354
- /* @__PURE__ */ jsx38(Line, { label: t.orders.subtotal, value: money(order.subtotal) }),
3355
- order.discountTotal ? /* @__PURE__ */ jsx38(Line, { label: t.orders.discount, value: money(-order.discountTotal) }) : null,
3356
- /* @__PURE__ */ jsx38(Line, { label: t.orders.shipping, value: money(order.shippingTotal) }),
3357
- /* @__PURE__ */ jsx38(Line, { label: t.orders.tax, value: money(order.taxTotal) })
3358
- ] }),
3359
- /* @__PURE__ */ jsxs28("div", { ...a.el("orderDetail.actions", "throttle-detail__actions"), children: [
3360
- props.onPayNow && CAN_PAY.has(order.status) ? /* @__PURE__ */ jsx38(Button, { elementKey: "orderDetail.payButton", onClick: () => props.onPayNow(target), children: t.orders.payNow }) : null,
3361
- order.invoiceId ? /* @__PURE__ */ jsx38(
3362
- Button,
3363
- {
3364
- variant: "secondary",
3365
- loading: submitting,
3366
- elementKey: "orderDetail.invoiceButton",
3367
- onClick: () => void run(async () => {
3368
- const { url } = await account.getInvoicePdfUrl(order.invoiceId);
3369
- const opened = window.open(url, "_blank", "noopener,noreferrer");
3370
- if (!opened) window.location.assign(url);
3371
- }),
3372
- children: t.orders.downloadInvoice
3373
- }
3374
- ) : null,
3375
- props.onTrackShipment && order.status === "partially_fulfilled" ? /* @__PURE__ */ jsx38(
3376
- Button,
3377
- {
3378
- variant: "secondary",
3379
- elementKey: "orderDetail.trackButton",
3380
- onClick: () => props.onTrackShipment(target),
3381
- children: t.orders.track
3382
- }
3383
- ) : null,
3384
- props.onReorder && CAN_REORDER.has(order.status) ? /* @__PURE__ */ jsx38(
3385
- Button,
3386
- {
3387
- variant: "secondary",
3388
- elementKey: "orderDetail.reorderButton",
3389
- onClick: () => props.onReorder(target),
3390
- children: t.orders.reorder
3391
- }
3392
- ) : null,
3393
- props.onViewSubscription ? orderSubscriptionLinks(order, {
3394
- single: t.orders.viewSubscription,
3395
- named: t.orders.viewSubscriptionFor
3396
- }).map((link) => /* @__PURE__ */ jsx38(
3397
- Button,
3398
- {
3399
- variant: "secondary",
3400
- elementKey: "orderDetail.subscriptionButton",
3401
- onClick: () => props.onViewSubscription(link.id),
3402
- children: link.label
3403
- },
3404
- link.id
3405
- )) : null,
3406
- showReturn ? /* @__PURE__ */ jsx38(
3407
- Button,
3408
- {
3409
- variant: "secondary",
3410
- disabled: submitting,
3411
- elementKey: "orderDetail.returnButton",
3412
- onClick: () => void (async () => {
3413
- if (!await confirm({
3414
- title: t.orders.requestReturnConfirm,
3415
- body: t.orders.requestReturnBody,
3416
- confirmLabel: t.orders.requestReturnAction
3417
- })) return;
3418
- await run(async () => {
3419
- await account.requestOrderReturn(order.id, {
3420
- items: order.lineItems.map((li) => ({
3421
- lineItemId: li.id,
3422
- quantity: li.quantity
3423
- }))
3424
- });
3425
- await resource.refetch();
3426
- });
3427
- })(),
3428
- children: t.orders.requestReturn
3429
- }
3430
- ) : null,
3431
- showCancel ? /* @__PURE__ */ jsx38(
3432
- Button,
3433
- {
3434
- variant: "danger",
3435
- disabled: submitting,
3436
- elementKey: "orderDetail.cancelButton",
3437
- onClick: () => void (async () => {
3438
- if (!await confirm({
3439
- title: t.orders.cancelOrderConfirm,
3440
- body: t.orders.cancelOrderBody,
3441
- confirmLabel: t.orders.cancelOrderAction,
3442
- tone: "danger"
3443
- })) return;
3444
- await run(async () => {
3445
- await account.cancelOrder(order.id);
3446
- await resource.refetch();
3447
- });
3448
- })(),
3449
- children: t.orders.cancelOrder
3450
- }
3451
- ) : null
3452
- ] })
3453
- ] })
3454
- ] });
3455
- }
3456
- }
3457
- )
3458
- ] }) }) });
3459
- }
3460
-
3461
- // src/react/components/account/OrdersPanel.tsx
3462
- import { jsx as jsx39, jsxs as jsxs29 } from "react/jsx-runtime";
3463
- function OrdersPanel(props) {
3464
- const a = useAppearance(props.appearance);
3465
- const scoped = useAppearanceProviderValue(props.appearance);
3466
- const { t, locale } = useCopy(props.localization);
3467
- const { emailVerified } = useAuth();
3468
- const resource = useOrders();
3469
- const [openId, setOpenId] = useState17(props.initialOpenId ?? null);
3470
- useEffect8(() => {
3471
- if (props.initialOpenId) setOpenId(props.initialOpenId);
3472
- }, [props.initialOpenId]);
3473
- const variant = props.variant ?? "table";
3474
- const rowsVariant = variant === "rows";
3475
- const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
3476
- const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
3477
- if (openId) {
3478
- return /* @__PURE__ */ jsx39(
3479
- OrderDetail,
3480
- {
3481
- orderId: openId,
3482
- onBack: () => setOpenId(null),
3483
- onPayNow: props.onPayNow,
3484
- onReorder: props.onReorder,
3485
- onTrackShipment: props.onTrackShipment,
3486
- onViewSubscription: props.onViewSubscription,
3487
- allowCancel: props.allowCancel,
3488
- allowReturn: props.allowReturn,
3489
- appearance: props.appearance,
3490
- localization: props.localization
3491
- }
3492
- );
3493
- }
3494
- return /* @__PURE__ */ jsx39(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx39("div", { ...a.rootProps, children: /* @__PURE__ */ jsx39("div", { ...a.el("ordersPanel.root", "throttle-panel"), children: /* @__PURE__ */ jsx39(
3495
- PanelState,
3496
- {
3497
- resource,
3498
- gated: !emailVerified,
3499
- gatedDescription: t.orders.gatedDescription,
3500
- isEmpty: (rows) => rows.length === 0,
3501
- emptyTitle: t.orders.empty,
3502
- emptyDescription: t.orders.emptyDescription,
3503
- children: (rows) => variant === "table" ? /* @__PURE__ */ jsx39(
3504
- DataTable,
3505
- {
3506
- tableKey: "ordersPanel.table",
3507
- rowElementKey: "ordersPanel.row",
3508
- rowKey: (o) => o.id,
3509
- onRowClick: (o) => setOpenId(o.id),
3510
- rows,
3511
- columns: [
3512
- { key: "orderNumber", label: t.orders.number },
3513
- { key: "createdAt", label: t.orders.date, render: (o) => formatDate(o.createdAt, locale) },
3514
- {
3515
- key: "status",
3516
- label: t.orders.status,
3517
- render: (o) => /* @__PURE__ */ jsx39(OrderStatus, { status: o.status, paymentStatus: o.paymentStatus, t })
3518
- },
3519
- {
3520
- key: "total",
3521
- label: t.orders.total,
3522
- render: (o) => /* @__PURE__ */ jsx39("span", { className: "throttle-table__money", children: formatMoney(o.total, o.currency, locale) })
3523
- }
3524
- ]
3525
- }
3526
- ) : /* @__PURE__ */ jsx39("div", { ...a.el("ordersPanel.list", listBase), children: rows.map((o) => /* @__PURE__ */ jsxs29("div", { ...a.el("ordersPanel.item", itemBase), children: [
3527
- /* @__PURE__ */ jsxs29("div", { children: [
3528
- /* @__PURE__ */ jsxs29("div", { className: "throttle-row", children: [
3529
- /* @__PURE__ */ jsx39("strong", { children: o.orderNumber }),
3530
- /* @__PURE__ */ jsx39(OrderStatus, { status: o.status, paymentStatus: o.paymentStatus, t })
3531
- ] }),
3532
- /* @__PURE__ */ jsx39("p", { className: "throttle-muted", children: formatDate(o.createdAt, locale) }),
3533
- /* @__PURE__ */ jsx39("p", { className: "throttle-muted", children: formatMoney(o.total, o.currency, locale) })
3534
- ] }),
3535
- /* @__PURE__ */ jsx39("div", { className: "throttle-actions", children: /* @__PURE__ */ jsx39(
3536
- Button,
3537
- {
3538
- variant: rowsVariant ? "link" : "secondary",
3539
- elementKey: "ordersPanel.viewButton",
3540
- onClick: () => setOpenId(o.id),
3541
- children: t.common.view
3542
- }
3543
- ) })
3544
- ] }, o.id)) })
3545
- }
3546
- ) }) }) });
3547
- }
3548
-
3549
- // src/react/components/account/InvoicesPanel.tsx
3550
- import { useEffect as useEffect9, useState as useState18 } from "react";
3551
-
3552
- // src/react/components/account/InvoiceDetail.tsx
3553
- import { jsx as jsx40, jsxs as jsxs30 } from "react/jsx-runtime";
3554
- var PAYABLE = /* @__PURE__ */ new Set(["open"]);
3555
- function Line2(props) {
3556
- return /* @__PURE__ */ jsxs30("div", { className: "throttle-row", children: [
3557
- /* @__PURE__ */ jsx40("span", { className: "throttle-muted", children: props.label }),
3558
- /* @__PURE__ */ jsx40("span", { children: props.value })
3559
- ] });
3560
- }
3561
- function InvoiceDetail(props) {
3562
- const a = useAppearance(props.appearance);
3563
- const scoped = useAppearanceProviderValue(props.appearance);
3564
- const { t, locale } = useCopy(props.localization);
3565
- const { account } = useAuth();
3566
- const resource = useInvoice(props.invoiceId);
3567
- const { submitting, error, run } = useAction();
3568
- return /* @__PURE__ */ jsx40(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx40("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs30("div", { ...a.el("invoiceDetail.root", "throttle-panel"), children: [
3569
- props.onBack ? (
3570
- // Wrapped on purpose — see OrderDetail's back button for why a
3571
- // direct flex child stretched to 748px.
3572
- /* @__PURE__ */ jsx40("div", { children: /* @__PURE__ */ jsx40(Button, { variant: "link", elementKey: "invoiceDetail.backButton", onClick: props.onBack, children: t.common.back }) })
3573
- ) : null,
3574
- error ? /* @__PURE__ */ jsx40(Alert, { tone: "error", children: error }) : null,
3575
- /* @__PURE__ */ jsx40(PanelState, { resource, isEmpty: (i) => i === null, emptyTitle: t.invoices.empty, skeletonRows: 4, children: (invoice) => {
3576
- const money = (v) => formatMoney(v, invoice.currency, locale);
3577
- const lines = invoice.lineItems ?? [];
3578
- const partOfOrder = invoice.orderTotal != null;
3579
- const isDepositReceipt = partOfOrder && invoice.balanceDue != null;
3580
- const headlineLabel = !partOfOrder ? t.invoices.total : isDepositReceipt ? t.invoices.amountPaid : t.invoices.amountDue;
3581
- return /* @__PURE__ */ jsxs30("div", { ...a.el("invoiceDetail.layout", "throttle-detail"), children: [
3582
- /* @__PURE__ */ jsxs30("div", { ...a.el("invoiceDetail.main", "throttle-detail__main"), children: [
3583
- /* @__PURE__ */ jsxs30("div", { className: "throttle-detail__section", children: [
3584
- /* @__PURE__ */ jsxs30("div", { className: "throttle-row", children: [
3585
- /* @__PURE__ */ jsx40("h3", { children: invoice.number }),
3586
- /* @__PURE__ */ jsx40(StatusBadge, { status: invoice.status, labels: t.invoices.statusLabels })
3587
- ] }),
3588
- /* @__PURE__ */ jsxs30("p", { className: "throttle-muted", children: [
3589
- t.invoices.issued,
3590
- " ",
3591
- formatDate(invoice.issuedAt, locale)
3592
- ] })
3593
- ] }),
3594
- lines.length > 0 ? /* @__PURE__ */ jsxs30("div", { className: "throttle-detail__section", children: [
3595
- /* @__PURE__ */ jsx40("span", { className: "throttle-detail__label", children: t.invoices.items }),
3596
- /* @__PURE__ */ jsx40("div", { ...a.el("invoiceDetail.lineItems", "throttle-stack"), children: lines.map((item, i) => /* @__PURE__ */ jsxs30("div", { ...a.el("invoiceDetail.lineItem", "throttle-lineitem"), children: [
3597
- /* @__PURE__ */ jsxs30("div", { style: { flex: 1 }, children: [
3598
- /* @__PURE__ */ jsx40("strong", { children: item.name }),
3599
- item.description ? /* @__PURE__ */ jsx40("p", { className: "throttle-lineitem__desc", children: item.description }) : null,
3600
- /* @__PURE__ */ jsxs30("p", { className: "throttle-lineitem__meta", children: [
3601
- t.orders.quantity,
3602
- " ",
3603
- item.quantity,
3604
- " \xD7 ",
3605
- money(item.unitPrice)
3606
- ] })
3607
- ] }),
3608
- /* @__PURE__ */ jsx40("span", { children: money(item.total) })
3609
- ] }, i)) })
3610
- ] }) : null
3611
- ] }),
3612
- /* @__PURE__ */ jsxs30("aside", { ...a.el("invoiceDetail.rail", "throttle-detail__rail"), children: [
3613
- /* @__PURE__ */ jsxs30("div", { className: "throttle-detail__section", children: [
3614
- /* @__PURE__ */ jsx40("span", { className: "throttle-detail__label", children: headlineLabel }),
3615
- /* @__PURE__ */ jsx40("span", { className: "throttle-detail__headline", children: money(invoice.total) })
3616
- ] }),
3617
- /* @__PURE__ */ jsxs30("div", { ...a.el("invoiceDetail.totals", "throttle-totals"), children: [
3618
- partOfOrder ? /* @__PURE__ */ jsx40(Line2, { label: t.invoices.orderTotal, value: money(invoice.orderTotal) }) : null,
3619
- partOfOrder && invoice.amountPaid ? /* @__PURE__ */ jsx40(Line2, { label: t.invoices.amountPaid, value: `-${money(invoice.amountPaid)}` }) : null,
3620
- partOfOrder && invoice.compWaived ? /* @__PURE__ */ jsx40(Line2, { label: t.invoices.waived, value: `-${money(invoice.compWaived)}` }) : null,
3621
- isDepositReceipt && invoice.balanceDue ? /* @__PURE__ */ jsx40(Line2, { label: t.invoices.balanceDue, value: money(invoice.balanceDue) }) : null,
3622
- invoice.dueAt ? /* @__PURE__ */ jsx40(Line2, { label: t.invoices.due, value: formatDate(invoice.dueAt, locale) }) : null,
3623
- invoice.periodStart && invoice.periodEnd ? /* @__PURE__ */ jsx40(
3624
- Line2,
3625
- {
3626
- label: t.invoices.period,
3627
- value: `${formatDate(invoice.periodStart, locale)} \u2013 ${formatDate(invoice.periodEnd, locale)}`
3628
- }
3629
- ) : null,
3630
- invoice.amountRefunded ? /* @__PURE__ */ jsx40(Line2, { label: t.invoices.refunded, value: money(invoice.amountRefunded) }) : null
3631
- ] }),
3632
- /* @__PURE__ */ jsxs30("div", { ...a.el("invoiceDetail.actions", "throttle-detail__actions"), children: [
3633
- props.onPay && PAYABLE.has(invoice.status) ? /* @__PURE__ */ jsx40(
3634
- Button,
3635
- {
3636
- elementKey: "invoiceDetail.payButton",
3637
- onClick: () => props.onPay({
3638
- id: invoice.id,
3639
- number: invoice.number,
3640
- total: invoice.total,
3641
- currency: invoice.currency
3642
- }),
3643
- children: t.invoices.payButton
3644
- }
3645
- ) : null,
3646
- /* @__PURE__ */ jsx40(
3647
- Button,
3648
- {
3649
- variant: "secondary",
3650
- loading: submitting,
3651
- elementKey: "invoiceDetail.downloadButton",
3652
- onClick: () => void run(async () => {
3653
- const { url } = await account.getInvoicePdfUrl(invoice.id);
3654
- const opened = window.open(url, "_blank", "noopener,noreferrer");
3655
- if (!opened) window.location.assign(url);
3656
- }),
3657
- children: t.invoices.downloadButton
3658
- }
3659
- ),
3660
- invoice.orderId && props.onViewOrder ? /* @__PURE__ */ jsx40(
3661
- Button,
3662
- {
3663
- variant: "secondary",
3664
- elementKey: "invoiceDetail.orderLink",
3665
- onClick: () => props.onViewOrder(invoice.orderId),
3666
- children: invoice.orderNumber ? `${t.invoices.viewOrder} ${invoice.orderNumber}` : t.invoices.viewOrder
3667
- }
3668
- ) : null,
3669
- invoice.subscriptionId && props.onViewSubscription ? /* @__PURE__ */ jsx40(
3670
- Button,
3671
- {
3672
- variant: "secondary",
3673
- elementKey: "invoiceDetail.subscriptionLink",
3674
- onClick: () => props.onViewSubscription(invoice.subscriptionId),
3675
- children: t.invoices.viewSubscription
3676
- }
3677
- ) : null
3678
- ] })
3679
- ] })
3680
- ] });
3681
- } })
3682
- ] }) }) });
3683
- }
3684
-
3685
- // src/react/components/account/InvoicesPanel.tsx
3686
- import { jsx as jsx41, jsxs as jsxs31 } from "react/jsx-runtime";
3687
- function InvoicesPanel(props) {
3688
- const a = useAppearance(props.appearance);
3689
- const scoped = useAppearanceProviderValue(props.appearance);
3690
- const { t, locale } = useCopy(props.localization);
3691
- const { emailVerified } = useAuth();
3692
- const resource = useInvoices();
3693
- const [openId, setOpenId] = useState18(props.initialOpenId ?? null);
3694
- useEffect9(() => {
3695
- if (props.initialOpenId) setOpenId(props.initialOpenId);
3696
- }, [props.initialOpenId]);
3697
- const variant = props.variant ?? "table";
3698
- const rowsVariant = variant === "rows";
3699
- const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
3700
- const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
3701
- if (openId) {
3702
- return /* @__PURE__ */ jsx41(
3703
- InvoiceDetail,
3704
- {
3705
- invoiceId: openId,
3706
- onBack: () => setOpenId(null),
3707
- onViewOrder: props.onViewOrder,
3708
- onViewSubscription: props.onViewSubscription,
3709
- onPay: props.onPay,
3710
- appearance: props.appearance,
3711
- localization: props.localization
3712
- }
3713
- );
3714
- }
3715
- return /* @__PURE__ */ jsx41(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx41("div", { ...a.rootProps, children: /* @__PURE__ */ jsx41("div", { ...a.el("invoicesPanel.root", "throttle-panel"), children: /* @__PURE__ */ jsx41(
3716
- PanelState,
3717
- {
3718
- resource,
3719
- gated: !emailVerified,
3720
- gatedDescription: t.invoices.gatedDescription,
3721
- isEmpty: (rows) => rows.length === 0,
3722
- emptyTitle: t.invoices.empty,
3723
- emptyDescription: t.invoices.emptyDescription,
3724
- children: (rows) => variant === "table" ? /* @__PURE__ */ jsx41(
3725
- DataTable,
3726
- {
3727
- tableKey: "invoicesPanel.table",
3728
- rowElementKey: "invoicesPanel.row",
3729
- rowKey: (i) => i.id,
3730
- onRowClick: (i) => setOpenId(i.id),
3731
- rows,
3732
- columns: [
3733
- { key: "number", label: t.invoices.number },
3734
- { key: "issuedAt", label: t.invoices.issued, render: (i) => formatDate(i.issuedAt, locale) },
3735
- { key: "status", label: t.invoices.status, render: (i) => /* @__PURE__ */ jsx41(StatusBadge, { status: i.status, labels: t.invoices.statusLabels }) },
3736
- {
3737
- key: "total",
3738
- label: t.invoices.total,
3739
- render: (i) => /* @__PURE__ */ jsx41("span", { className: "throttle-table__money", children: formatMoney(i.total, i.currency, locale) })
3740
- }
3741
- ]
3742
- }
3743
- ) : /* @__PURE__ */ jsx41("div", { ...a.el("invoicesPanel.list", listBase), children: rows.map((i) => /* @__PURE__ */ jsxs31("div", { ...a.el("invoicesPanel.item", itemBase), children: [
3744
- /* @__PURE__ */ jsxs31("div", { children: [
3745
- /* @__PURE__ */ jsxs31("div", { className: "throttle-row", children: [
3746
- /* @__PURE__ */ jsx41("strong", { children: i.number }),
3747
- /* @__PURE__ */ jsx41(StatusBadge, { status: i.status, labels: t.invoices.statusLabels })
3748
- ] }),
3749
- /* @__PURE__ */ jsx41("p", { className: "throttle-muted", children: formatDate(i.issuedAt, locale) }),
3750
- /* @__PURE__ */ jsx41("p", { className: "throttle-muted", children: formatMoney(i.total, i.currency, locale) })
3751
- ] }),
3752
- /* @__PURE__ */ jsx41("div", { className: "throttle-actions", children: /* @__PURE__ */ jsx41(
3753
- Button,
3754
- {
3755
- variant: rowsVariant ? "link" : "secondary",
3756
- elementKey: "invoicesPanel.viewButton",
3757
- onClick: () => setOpenId(i.id),
3758
- children: t.common.view
3759
- }
3760
- ) })
3761
- ] }, i.id)) })
3762
- }
3763
- ) }) }) });
3764
- }
3765
-
3766
- // src/react/components/account/SubscriptionsPanel.tsx
3767
- import { useState as useState19 } from "react";
3768
-
3769
- // ../core-cadence/src/cadence.ts
3770
- var INTERVAL_UNITS = ["hour", "day", "week", "month"];
3771
- var NAMED_INTERVALS = ["weekly", "biweekly", "monthly", "quarterly", "yearly"];
3772
- var INTERVAL_VALUES = [...NAMED_INTERVALS, "custom"];
3773
- var HOUR_MS = 36e5;
3774
- var DAY_MS = 24 * HOUR_MS;
3775
- var WEEK_MS = 7 * DAY_MS;
3776
- function isNamedInterval(value) {
3777
- return typeof value === "string" && NAMED_INTERVALS.includes(value);
3778
- }
3779
- function isIntervalUnit(value) {
3780
- return typeof value === "string" && INTERVAL_UNITS.includes(value);
3781
- }
3782
-
3783
- // ../core-cadence/src/schedule.ts
3784
- function isVariedSchedule(rows) {
3785
- if (!rows || rows.length === 0) return false;
3786
- return rows.some(
3787
- (r, i) => r.discount !== 0 || r.amount !== rows[0].amount || i > 1 && (r.intervalUnit !== rows[1].intervalUnit || r.intervalCount !== rows[1].intervalCount)
3788
- );
3789
- }
3790
- function upcomingSchedulePayments(rows) {
3791
- if (!rows || !isVariedSchedule(rows)) return [];
3792
- return rows.filter((r) => r.status !== "paid" && r.dueAt != null);
3793
- }
3794
-
3795
- // ../core-cadence/src/labels.ts
3796
- var NAMED_NOUN = {
3797
- weekly: "week",
3798
- biweekly: "2 weeks",
3799
- monthly: "month",
3800
- quarterly: "quarter",
3801
- yearly: "year"
3802
- };
3803
- function cadenceNoun(cadence) {
3804
- if (isNamedInterval(cadence.interval)) return NAMED_NOUN[cadence.interval];
3805
- if (isIntervalUnit(cadence.intervalUnit) && cadence.intervalCount != null) {
3806
- return cadence.intervalCount === 1 ? cadence.intervalUnit : `${cadence.intervalCount} ${cadence.intervalUnit}s`;
3807
- }
3808
- return cadence.interval;
3809
- }
3810
- function scheduleDeal(sub) {
3811
- if (!sub.paymentSchedule || sub.scheduleTotal == null || !isVariedSchedule(sub.paymentSchedule)) return null;
3812
- return { total: sub.scheduleTotal, payments: sub.paymentSchedule.length };
3813
- }
3814
-
3815
- // src/react/components/account/SubscriptionsPanel.tsx
3816
- import { Fragment as Fragment8, jsx as jsx42, jsxs as jsxs32 } from "react/jsx-runtime";
3817
- var CAN_PAUSE = /* @__PURE__ */ new Set(["active"]);
3818
- var CAN_RESUME = /* @__PURE__ */ new Set(["paused"]);
3819
- var CAN_CANCEL2 = /* @__PURE__ */ new Set(["active", "trialing", "paused", "past_due", "unpaid"]);
3820
- var isInstallment = (sub) => sub.totalPayments != null;
3821
- var canChangeAddress = (sub) => sub.deliveryMethod === "shipment" && sub.status !== "cancelled" && sub.status !== "completed";
3822
- function oneLine(address) {
3823
- if (!address) return null;
3824
- const parts = [address.addressLine1, address.addressLine2, address.city, address.stateProvince, address.postalCode, address.countryCode];
3825
- return parts.filter(Boolean).join(", ") || null;
3826
- }
3827
- function SubscriptionsPanel(props) {
3828
- const a = useAppearance(props.appearance);
3829
- const scoped = useAppearanceProviderValue(props.appearance);
3830
- const { t, locale } = useCopy(props.localization);
3831
- const { emailVerified } = useAuth();
3832
- const resource = useSubscriptions();
3833
- const { submitting, error, run } = useAction();
3834
- const confirm = useConfirm();
3835
- const [editingAddress, setEditingAddress] = useState19(null);
3836
- const variant = props.variant ?? "rows";
3837
- const rowsVariant = variant === "rows";
3838
- const listBase = rowsVariant ? "throttle-rows" : "throttle-stack";
3839
- const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
3840
- const actionVariant = (base) => base;
3841
- const confirmThen = (fn, ask) => () => {
3842
- void (async () => {
3843
- if (!await confirm({ ...ask, tone: ask.tone ?? "neutral" })) return;
3844
- await run(fn);
3845
- })();
3846
- };
3847
- const priceLabel = (sub) => {
3848
- const deal = scheduleDeal(sub);
3849
- return deal ? t.subscriptions.installmentTotal.replace("{amount}", formatMoney(deal.total, sub.currency, locale)) : `${formatMoney(sub.amount, sub.currency, locale)} / ${cadenceNoun(sub)}${sub.quantity > 1 ? ` \xD7 ${sub.quantity}` : ""}`;
3850
- };
3851
- const nextPaymentLabel = (sub) => {
3852
- if (!isVariedSchedule(sub.paymentSchedule) || !sub.paymentSchedule || sub.nextPaymentAmount == null) return null;
3853
- const upcoming = sub.paymentSchedule.find((r) => r.status !== "paid");
3854
- return t.subscriptions.installmentNext.replace("{amount}", formatMoney(sub.nextPaymentAmount, sub.currency, locale)).replace(
3855
- "{date}",
3856
- upcoming?.dueAt ? scheduleDueLabel({ dueAt: upcoming.dueAt, intervalUnit: upcoming.intervalUnit }, locale) : formatDate(sub.currentPeriodEnd, locale)
3857
- );
3858
- };
3859
- const upcomingList = (sub) => {
3860
- const upcoming = upcomingSchedulePayments(sub.paymentSchedule);
3861
- if (upcoming.length === 0) return null;
3862
- return /* @__PURE__ */ jsx42("ul", { className: "throttle-muted", "aria-label": t.subscriptions.upcomingPayments, children: upcoming.map((r) => /* @__PURE__ */ jsx42("li", { children: t.subscriptions.upcomingPayment.replace("{amount}", formatMoney(r.net, sub.currency, locale)).replace("{date}", scheduleDueLabel(r, locale)) }, r.dueAt)) });
3863
- };
3864
- const shipsTo = (sub) => sub.deliveryMethod === "shipment" && oneLine(sub.shippingAddress) ? /* @__PURE__ */ jsxs32("p", { className: "throttle-muted", children: [
3865
- t.subscriptions.shipsTo,
3866
- " ",
3867
- oneLine(sub.shippingAddress)
3868
- ] }) : null;
3869
- const addressAction = (sub, variantFor) => canChangeAddress(sub) ? /* @__PURE__ */ jsx42(
3870
- Button,
3871
- {
3872
- variant: variantFor("secondary"),
3873
- elementKey: "subscriptionsPanel.changeAddressButton",
3874
- disabled: submitting,
3875
- onClick: () => setEditingAddress(sub.id),
3876
- children: t.subscriptions.changeAddress
3877
- }
3878
- ) : null;
3879
- const renewsLabel = (sub) => formatDate(
3880
- sub.status === "completed" ? sub.completedAt ?? sub.currentPeriodEnd : sub.status === "cancelled" ? sub.cancelledAt ?? sub.currentPeriodEnd : sub.currentPeriodEnd,
3881
- locale
3882
- );
3883
- const lifecycleActions = (sub, variantFor) => isInstallment(sub) ? null : /* @__PURE__ */ jsxs32("div", { className: "throttle-actions", children: [
3884
- CAN_PAUSE.has(sub.status) ? /* @__PURE__ */ jsx42(
3885
- Button,
3886
- {
3887
- variant: variantFor("secondary"),
3888
- loading: submitting,
3889
- elementKey: "subscriptionsPanel.pauseButton",
3890
- onClick: confirmThen(() => resource.pause(sub.id), {
3891
- title: t.subscriptions.pauseConfirm,
3892
- confirmLabel: t.subscriptions.pauseButton
3893
- }),
3894
- children: t.subscriptions.pauseButton
3895
- }
3896
- ) : null,
3897
- CAN_RESUME.has(sub.status) ? /* @__PURE__ */ jsx42(
3898
- Button,
3899
- {
3900
- variant: variantFor("secondary"),
3901
- loading: submitting,
3902
- elementKey: "subscriptionsPanel.resumeButton",
3903
- onClick: confirmThen(() => resource.resume(sub.id), {
3904
- title: t.subscriptions.resumeConfirm,
3905
- confirmLabel: t.subscriptions.resumeButton
3906
- }),
3907
- children: t.subscriptions.resumeButton
3908
- }
3909
- ) : null,
3910
- CAN_CANCEL2.has(sub.status) && !sub.cancelAtPeriodEnd ? /* @__PURE__ */ jsx42(
3911
- Button,
3912
- {
3913
- variant: variantFor("secondary"),
3914
- elementKey: "subscriptionsPanel.cancelButton",
3915
- disabled: submitting,
3916
- onClick: confirmThen(() => resource.cancel(sub.id, { atPeriodEnd: true }), {
3917
- title: t.subscriptions.cancelConfirm,
3918
- confirmLabel: t.subscriptions.cancelConfirmAction,
3919
- tone: "danger"
3920
- }),
3921
- children: t.subscriptions.cancelAtPeriodEnd
3922
- }
3923
- ) : null,
3924
- CAN_CANCEL2.has(sub.status) ? /* @__PURE__ */ jsx42(
3925
- Button,
3926
- {
3927
- variant: variantFor("danger"),
3928
- elementKey: "subscriptionsPanel.cancelNowButton",
3929
- disabled: submitting,
3930
- onClick: confirmThen(() => resource.cancel(sub.id, { atPeriodEnd: false }), {
3931
- title: t.subscriptions.cancelNowConfirm,
3932
- confirmLabel: t.subscriptions.cancelConfirmAction,
3933
- tone: "danger"
3934
- }),
3935
- children: t.subscriptions.cancelNow
3936
- }
3937
- ) : null
3938
- ] });
3939
- return /* @__PURE__ */ jsx42(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx42("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs32("div", { ...a.el("subscriptionsPanel.root", "throttle-panel"), children: [
3940
- error && !editingAddress ? /* @__PURE__ */ jsx42(Alert, { tone: "error", children: error }) : null,
3941
- editingAddress ? (() => {
3942
- const sub = resource.data.find((row) => row.id === editingAddress);
3943
- const current = sub?.shippingAddress;
3944
- return /* @__PURE__ */ jsxs32("div", { className: "throttle-stack", children: [
3945
- /* @__PURE__ */ jsx42("strong", { children: t.subscriptions.changeAddressTitle }),
3946
- /* @__PURE__ */ jsx42("p", { className: "throttle-muted", children: t.subscriptions.changeAddressHint }),
3947
- /* @__PURE__ */ jsx42(
3948
- AddressForm,
3949
- {
3950
- elementKey: "subscriptionsPanel.addressForm",
3951
- hideLabel: true,
3952
- initial: current ? { ...current, label: null } : void 0,
3953
- submitting,
3954
- error,
3955
- onCancel: () => setEditingAddress(null),
3956
- onSubmit: ({ label: _label, isDefault: _isDefault, ...address }) => {
3957
- void run(async () => {
3958
- await resource.updateShippingAddress(editingAddress, address);
3959
- setEditingAddress(null);
3960
- });
3961
- }
3962
- }
3963
- )
3964
- ] });
3965
- })() : null,
3966
- /* @__PURE__ */ jsx42(
3967
- PanelState,
3968
- {
3969
- resource,
3970
- gated: !emailVerified,
3971
- gatedDescription: t.subscriptions.gatedDescription,
3972
- isEmpty: (rows) => rows.length === 0,
3973
- emptyTitle: t.subscriptions.empty,
3974
- emptyDescription: t.subscriptions.emptyDescription,
3975
- children: (rows) => variant === "table" ? /* @__PURE__ */ jsx42(
3976
- DataTable,
3977
- {
3978
- tableKey: "subscriptionsPanel.table",
3979
- rowElementKey: "subscriptionsPanel.row",
3980
- rowKey: (sub) => sub.id,
3981
- rows,
3982
- columns: [
3983
- { key: "plan", label: t.subscriptions.columnPlan, render: (sub) => /* @__PURE__ */ jsxs32(Fragment8, { children: [
3984
- sub.planName ?? sub.planReference,
3985
- sub.totalPayments != null ? /* @__PURE__ */ jsx42("div", { className: "throttle-muted", children: t.subscriptions.installmentProgress.replace("{paid}", String(Math.min(sub.paymentsMade ?? 0, sub.totalPayments))).replace("{total}", String(sub.totalPayments)) }) : null,
3986
- nextPaymentLabel(sub) ? /* @__PURE__ */ jsx42("div", { className: "throttle-muted", children: nextPaymentLabel(sub) }) : null,
3987
- upcomingList(sub),
3988
- shipsTo(sub)
3989
- ] }) },
3990
- { key: "price", label: t.subscriptions.columnPrice, render: (sub) => /* @__PURE__ */ jsx42("span", { className: "throttle-table__money", children: priceLabel(sub) }) },
3991
- { key: "renews", label: t.subscriptions.columnRenews, render: (sub) => renewsLabel(sub) },
3992
- { key: "status", label: t.subscriptions.columnStatus, render: (sub) => /* @__PURE__ */ jsxs32("div", { className: "throttle-row", children: [
3993
- /* @__PURE__ */ jsx42(StatusBadge, { status: sub.status, labels: t.subscriptions.statusLabels }),
3994
- sub.cancelAtPeriodEnd ? /* @__PURE__ */ jsx42(Badge, { tone: "warning", children: t.subscriptions.cancelScheduled }) : null
3995
- ] }) },
3996
- { key: "actions", label: t.common.actions, render: (sub) => /* @__PURE__ */ jsxs32(Fragment8, { children: [
3997
- lifecycleActions(sub, (base) => base),
3998
- addressAction(sub, (base) => base)
3999
- ] }) }
4000
- ]
4001
- }
4002
- ) : /* @__PURE__ */ jsx42("div", { ...a.el("subscriptionsPanel.list", listBase), children: rows.map((sub) => /* @__PURE__ */ jsxs32("div", { ...a.el("subscriptionsPanel.item", itemBase), children: [
4003
- /* @__PURE__ */ jsxs32("div", { children: [
4004
- /* @__PURE__ */ jsxs32("div", { className: "throttle-row", children: [
4005
- /* @__PURE__ */ jsx42("strong", { children: sub.planName ?? sub.planReference }),
4006
- /* @__PURE__ */ jsx42(StatusBadge, { status: sub.status, labels: t.subscriptions.statusLabels })
4007
- ] }),
4008
- sub.totalPayments != null ? /* @__PURE__ */ jsx42("div", { className: "throttle-muted", children: t.subscriptions.installmentProgress.replace("{paid}", String(Math.min(sub.paymentsMade ?? 0, sub.totalPayments))).replace("{total}", String(sub.totalPayments)) }) : null,
4009
- nextPaymentLabel(sub) ? /* @__PURE__ */ jsx42("div", { className: "throttle-muted", children: nextPaymentLabel(sub) }) : null,
4010
- upcomingList(sub),
4011
- /* @__PURE__ */ jsx42("p", { className: "throttle-muted", children: priceLabel(sub) }),
4012
- shipsTo(sub),
4013
- sub.status === "completed" ? /* @__PURE__ */ jsxs32("p", { className: "throttle-muted", children: [
4014
- t.subscriptions.statusLabels.completed,
4015
- " ",
4016
- formatDate(sub.completedAt ?? sub.currentPeriodEnd, locale)
4017
- ] }) : sub.status === "cancelled" ? /* @__PURE__ */ jsxs32("p", { className: "throttle-muted", children: [
4018
- t.subscriptions.cancelledOn,
4019
- " ",
4020
- formatDate(sub.cancelledAt ?? sub.currentPeriodEnd, locale)
4021
- ] }) : /* @__PURE__ */ jsxs32("p", { className: "throttle-muted", children: [
4022
- sub.cancelAtPeriodEnd ? t.subscriptions.endsOn : t.subscriptions.renewsOn,
4023
- " ",
4024
- formatDate(sub.currentPeriodEnd, locale)
4025
- ] }),
4026
- sub.trialEnd ? /* @__PURE__ */ jsxs32("p", { className: "throttle-muted", children: [
4027
- t.subscriptions.trialEnds,
4028
- " ",
4029
- formatDate(sub.trialEnd, locale)
4030
- ] }) : null,
4031
- sub.cancelAtPeriodEnd ? /* @__PURE__ */ jsx42(Badge, { tone: "warning", children: t.subscriptions.cancelScheduled }) : null
4032
- ] }),
4033
- lifecycleActions(sub, actionVariant),
4034
- addressAction(sub, actionVariant)
4035
- ] }, sub.id)) })
4036
- }
4037
- )
4038
- ] }) }) });
4039
- }
4040
-
4041
- // src/react/components/account/SecurityPanel.tsx
4042
- import { useCallback as useCallback3, useEffect as useEffect10, useRef as useRef8, useState as useState20 } from "react";
4043
-
4044
- // src/react/components/account/device.ts
4045
- var BROWSERS = [
4046
- [/\bEdg(?:e|A|iOS)?\//, "Edge"],
4047
- [/\bOPR\/|\bOpera\b/, "Opera"],
4048
- // No leading \b: real agents ship prefixed builds (HeadlessChrome), and a
4049
- // word boundary there sends them to the Safari rule instead.
4050
- [/Chrome\/|CriOS\//, "Chrome"],
4051
- [/\bFirefox\/|\bFxiOS\//, "Firefox"],
4052
- [/\bSafari\//, "Safari"]
4053
- ];
4054
- var PLATFORMS = [
4055
- [/\biPhone\b|\biPad\b|\biPod\b/, "iOS"],
4056
- [/\bAndroid\b/, "Android"],
4057
- [/\bMac OS X\b|\bMacintosh\b/, "macOS"],
4058
- [/\bWindows\b/, "Windows"],
4059
- [/\bLinux\b|\bX11\b/, "Linux"]
4060
- ];
4061
- function first(ua, table) {
4062
- for (const [pattern, label] of table) if (pattern.test(ua)) return label;
4063
- return null;
4064
- }
4065
- function describeDevice(userAgent) {
4066
- const ua = (userAgent ?? "").trim();
4067
- if (!ua) return "";
4068
- const browser = first(ua, BROWSERS);
4069
- if (!browser) return ua;
4070
- const platform = first(ua, PLATFORMS);
4071
- return platform ? `${browser} on ${platform}` : browser;
4072
- }
4073
-
4074
- // src/react/components/account/SecurityPanel.tsx
4075
- import { Fragment as Fragment9, jsx as jsx43, jsxs as jsxs33 } from "react/jsx-runtime";
4076
- function SecurityPanel(props) {
4077
- const a = useAppearance(props.appearance);
4078
- const scoped = useAppearanceProviderValue(props.appearance);
4079
- const { t, locale, errorMessage } = useCopy(props.localization);
4080
- const { auth } = useAuth();
4081
- const variant = props.variant ?? "rows";
4082
- const rowsVariant = variant === "rows";
4083
- const itemBase = rowsVariant ? "throttle-rowitem" : "throttle-listitem";
4084
- const revokeVariant = rowsVariant ? "linkDanger" : "danger";
4085
- const [password, setPassword] = useState20("");
4086
- const passwordAction = useAction();
4087
- const sessionAction = useAction();
4088
- const [sessions, setSessions] = useState20(null);
4089
- const [listError, setListError] = useState20(null);
4090
- const errorMessageRef = useRef8(errorMessage);
4091
- useEffect10(() => {
4092
- errorMessageRef.current = errorMessage;
4093
- });
4094
- const load = useCallback3(async () => {
4095
- try {
4096
- setSessions(await auth.listSessions());
4097
- setListError(null);
4098
- } catch (err) {
4099
- setListError(errorMessageRef.current(err));
4100
- }
4101
- }, [auth]);
4102
- useEffect10(() => {
4103
- void load();
4104
- }, [load]);
4105
- const others = (sessions ?? []).filter((s) => !s.current);
4106
- return /* @__PURE__ */ jsx43(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx43("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs33("div", { ...a.el("securityPanel.root", "throttle-panel"), children: [
4107
- /* @__PURE__ */ jsxs33(
4108
- "form",
4109
- {
4110
- ...a.el("securityPanel.passwordForm", "throttle-stack"),
4111
- onSubmit: (e) => {
4112
- e.preventDefault();
4113
- void passwordAction.run(async () => {
4114
- await auth.changePassword(password);
4115
- setPassword("");
4116
- return t.security.changedNotice;
4117
- });
4118
- },
4119
- children: [
4120
- /* @__PURE__ */ jsx43("h3", { children: t.security.passwordTitle }),
4121
- /* @__PURE__ */ jsx43(
4122
- Field,
4123
- {
4124
- label: t.security.newPassword,
4125
- type: "password",
4126
- autoComplete: "new-password",
4127
- required: true,
4128
- value: password,
4129
- onChange: setPassword
4130
- }
4131
- ),
4132
- passwordAction.error ? /* @__PURE__ */ jsx43(Alert, { tone: "error", children: passwordAction.error }) : null,
4133
- passwordAction.notice ? /* @__PURE__ */ jsx43(Alert, { tone: "success", children: passwordAction.notice }) : null,
4134
- /* @__PURE__ */ jsx43(Button, { type: "submit", loading: passwordAction.submitting, elementKey: "securityPanel.passwordButton", children: t.security.changeButton })
4135
- ]
4136
- }
4137
- ),
4138
- /* @__PURE__ */ jsxs33("div", { ...a.el("securityPanel.sessionList", "throttle-stack"), children: [
4139
- /* @__PURE__ */ jsx43("h3", { children: t.security.sessionsTitle }),
4140
- listError ? /* @__PURE__ */ jsxs33(Fragment9, { children: [
4141
- /* @__PURE__ */ jsx43(Alert, { tone: "error", children: listError }),
4142
- /* @__PURE__ */ jsx43(Button, { variant: "secondary", onClick: () => void load(), children: t.common.retry })
4143
- ] }) : null,
4144
- sessionAction.error ? /* @__PURE__ */ jsx43(Alert, { tone: "error", children: sessionAction.error }) : null,
4145
- sessions === null && !listError ? /* @__PURE__ */ jsx43(Skeleton, { count: 2 }) : null,
4146
- sessions !== null && !listError && variant === "table" ? /* @__PURE__ */ jsx43(
4147
- DataTable,
4148
- {
4149
- tableKey: "securityPanel.table",
4150
- rowElementKey: "securityPanel.row",
4151
- rowKey: (session) => session.id,
4152
- rows: sessions,
4153
- columns: [
4154
- { key: "device", label: t.security.columnDevice, render: (session) => /* @__PURE__ */ jsxs33("div", { className: "throttle-row", children: [
4155
- /* @__PURE__ */ jsx43("span", { children: describeDevice(session.userAgent) || session.ipPrefix || session.id }),
4156
- session.current ? /* @__PURE__ */ jsx43(Badge, { tone: "success", children: t.security.thisDevice }) : null
4157
- ] }) },
4158
- { key: "lastActive", label: t.security.lastActive, render: (session) => formatDate(session.lastUsedAt, locale) },
4159
- { key: "actions", label: t.common.actions, render: (session) => !session.current ? /* @__PURE__ */ jsx43(
4160
- Button,
4161
- {
4162
- variant: "danger",
4163
- loading: sessionAction.submitting,
4164
- elementKey: "securityPanel.revokeButton",
4165
- onClick: () => void sessionAction.run(async () => {
4166
- await auth.revokeSession(session.id);
4167
- await load();
4168
- }),
4169
- children: t.security.revokeButton
4170
- }
4171
- ) : null }
4172
- ]
4173
- }
4174
- ) : (sessions ?? []).map((session) => /* @__PURE__ */ jsxs33("div", { ...a.el("securityPanel.sessionItem", itemBase), children: [
4175
- /* @__PURE__ */ jsxs33("div", { children: [
4176
- /* @__PURE__ */ jsxs33("div", { className: "throttle-row", children: [
4177
- /* @__PURE__ */ jsx43("strong", { children: describeDevice(session.userAgent) || session.ipPrefix || session.id }),
4178
- session.current ? /* @__PURE__ */ jsx43(Badge, { tone: "success", children: t.security.thisDevice }) : null
4179
- ] }),
4180
- /* @__PURE__ */ jsxs33("p", { className: "throttle-muted", children: [
4181
- t.security.lastActive,
4182
- " ",
4183
- formatDate(session.lastUsedAt, locale)
4184
- ] })
4185
- ] }),
4186
- !session.current ? /* @__PURE__ */ jsx43(
4187
- Button,
4188
- {
4189
- variant: revokeVariant,
4190
- loading: sessionAction.submitting,
4191
- elementKey: "securityPanel.revokeButton",
4192
- onClick: () => void sessionAction.run(async () => {
4193
- await auth.revokeSession(session.id);
4194
- await load();
4195
- }),
4196
- children: t.security.revokeButton
4197
- }
4198
- ) : null
4199
- ] }, session.id)),
4200
- sessions !== null && others.length === 0 ? /* @__PURE__ */ jsx43("p", { className: "throttle-muted", children: t.security.sessionsEmpty }) : null,
4201
- others.length > 0 ? /* @__PURE__ */ jsx43(
4202
- Button,
4203
- {
4204
- variant: "danger",
4205
- loading: sessionAction.submitting,
4206
- elementKey: "securityPanel.revokeAllButton",
4207
- onClick: () => void sessionAction.run(async () => {
4208
- for (const session of others) {
4209
- await auth.revokeSession(session.id);
4210
- }
4211
- await load();
4212
- }),
4213
- children: t.security.revokeAllButton
4214
- }
4215
- ) : null
4216
- ] })
4217
- ] }) }) });
4218
- }
4219
-
4220
- // src/react/components/account/AccountDashboard.tsx
4221
- import { Fragment as Fragment10, jsx as jsx44, jsxs as jsxs34 } from "react/jsx-runtime";
4222
- var ACCOUNT_TABS = [
4223
- "profile",
4224
- "addresses",
4225
- "paymentMethods",
4226
- "orders",
4227
- "invoices",
4228
- "subscriptions",
4229
- "security"
4230
- ];
4231
- var HASH_FOR2 = {
4232
- profile: "#/profile",
4233
- addresses: "#/addresses",
4234
- paymentMethods: "#/payment-methods",
4235
- orders: "#/orders",
4236
- invoices: "#/invoices",
4237
- subscriptions: "#/subscriptions",
4238
- security: "#/security"
4239
- };
4240
- var TAB_FOR_HASH = Object.fromEntries(
4241
- Object.entries(HASH_FOR2).map(([tab, hash]) => [hash, tab])
4242
- );
4243
- function AccountDashboard(props) {
4244
- const a = useAppearance(props.appearance);
4245
- const scoped = useAppearanceProviderValue(props.appearance);
4246
- const { t } = useCopy(props.localization);
4247
- const { status } = useAuth();
4248
- const shown = props.tabs ?? [...ACCOUNT_TABS];
4249
- const controlled = props.tab !== void 0;
4250
- const [internal, setInternal] = useState21(shown[0] ?? "profile");
4251
- const [openOrderId, setOpenOrderId] = useState21(null);
4252
- const idBase = useId5();
4253
- useEffect11(() => {
4254
- if (props.routing !== "hash" || typeof window === "undefined") return;
4255
- const apply = () => {
4256
- const next = TAB_FOR_HASH[window.location.hash] ?? shown[0];
4257
- if (!next || !shown.includes(next)) return;
4258
- if (controlled) props.onTabChange?.(next);
4259
- else setInternal(next);
4260
- };
4261
- apply();
4262
- window.addEventListener("hashchange", apply);
4263
- return () => window.removeEventListener("hashchange", apply);
4264
- }, [props.routing, controlled, shown.join(",")]);
4265
- if (status !== "authenticated") return null;
4266
- const active = controlled ? props.tab : internal;
4267
- const go = (next) => {
4268
- props.onTabChange?.(next);
4269
- if (!controlled) setInternal(next);
4270
- if (props.routing === "hash" && typeof window !== "undefined") window.location.hash = HASH_FOR2[next];
4271
- };
4272
- const shared = { appearance: props.appearance, localization: props.localization };
4273
- const sidebar = (props.layout ?? "sidebar") === "sidebar";
4274
- const nav = /* @__PURE__ */ jsx44(
4275
- Tabs,
4276
- {
4277
- idBase,
4278
- orientation: sidebar ? "vertical" : "horizontal",
4279
- active,
4280
- onChange: (id) => go(id),
4281
- tabs: shown.map((id) => ({
4282
- id,
4283
- label: t.account.tabs[id],
4284
- // Icons ride the rail only. In the horizontal strip they cost
4285
- // horizontal room, which is the scarce axis there.
4286
- icon: sidebar ? ACCOUNT_ICONS[id] : void 0
4287
- }))
4288
- }
4289
- );
4290
- const panel = /* @__PURE__ */ jsxs34(
4291
- "div",
4292
- {
4293
- ...a.el("accountDashboard.panel", sidebar ? "throttle-account__content" : "throttle-tabs__panel"),
4294
- role: "tabpanel",
4295
- id: `${idBase}-panel-${active}`,
4296
- "aria-labelledby": `${idBase}-tab-${active}`,
4297
- tabIndex: 0,
4298
- children: [
4299
- active === "profile" ? /* @__PURE__ */ jsx44(ProfilePanel, { ...shared }) : null,
4300
- active === "addresses" ? /* @__PURE__ */ jsx44(AddressesPanel, { ...shared, variant: props.panelVariant }) : null,
4301
- active === "paymentMethods" ? /* @__PURE__ */ jsx44(PaymentMethodsPanel, { ...shared, variant: props.panelVariant }) : null,
4302
- active === "orders" ? /* @__PURE__ */ jsx44(
4303
- OrdersPanel,
4304
- {
4305
- ...shared,
4306
- variant: props.panelVariant,
4307
- initialOpenId: openOrderId,
4308
- onViewSubscription: () => go("subscriptions"),
4309
- onPayNow: props.onPayNow,
4310
- onReorder: props.onReorder,
4311
- onTrackShipment: props.onTrackShipment
4312
- }
4313
- ) : null,
4314
- active === "invoices" ? /* @__PURE__ */ jsx44(
4315
- InvoicesPanel,
4316
- {
4317
- ...shared,
4318
- variant: props.panelVariant,
4319
- onViewOrder: (orderId) => {
4320
- setOpenOrderId(orderId);
4321
- go("orders");
4322
- },
4323
- onViewSubscription: () => go("subscriptions")
4324
- }
4325
- ) : null,
4326
- active === "subscriptions" ? /* @__PURE__ */ jsx44(SubscriptionsPanel, { ...shared, variant: props.panelVariant }) : null,
4327
- active === "security" ? /* @__PURE__ */ jsx44(SecurityPanel, { ...shared, variant: props.panelVariant }) : null
4328
- ]
4329
- }
4330
- );
4331
- return /* @__PURE__ */ jsx44(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx44("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs34("div", { ...a.el("accountDashboard.root", "throttle-panel throttle-account-root"), children: [
4332
- /* @__PURE__ */ jsx44(VerifyEmailBanner, { ...shared }),
4333
- sidebar ? /* @__PURE__ */ jsxs34("div", { className: "throttle-account", children: [
4334
- /* @__PURE__ */ jsx44("div", { ...a.el("accountDashboard.sidebar"), children: nav }),
4335
- panel
4336
- ] }) : /* @__PURE__ */ jsxs34(Fragment10, { children: [
4337
- /* @__PURE__ */ jsx44("div", { ...a.el("accountDashboard.tabs"), children: nav }),
4338
- panel
4339
- ] })
4340
- ] }) }) });
4341
- }
4342
-
4343
- // src/react/components/account/AccountModal.tsx
4344
- import { jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
4345
- function AccountModal(props) {
4346
- const a = useAppearance(props.appearance);
4347
- const scoped = useAppearanceProviderValue(props.appearance);
4348
- const { t } = useCopy(props.localization);
4349
- const titleId = useId6();
4350
- return /* @__PURE__ */ jsx45(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsxs35(
4351
- Modal,
4352
- {
4353
- open: props.open,
4354
- onClose: props.onClose,
4355
- labelledBy: titleId,
4356
- restoreFocusTo: props.restoreFocusTo,
4357
- overlayKey: "accountModal.overlay",
4358
- containerKey: "accountModal.container",
4359
- children: [
4360
- /* @__PURE__ */ jsx45("div", { ...a.el("accountModal.header", "throttle-modal__header"), children: /* @__PURE__ */ jsx45("h2", { id: titleId, ...a.el("accountModal.title", "throttle-modal__title"), children: t.account.title }) }),
4361
- /* @__PURE__ */ jsx45("div", { ...a.el("accountModal.body", "throttle-modal__body"), children: /* @__PURE__ */ jsx45(AccountDashboard, { tabs: props.tabs, appearance: props.appearance, localization: props.localization }) })
4362
- ]
4363
- }
4364
- ) });
4365
- }
4366
-
4367
- // src/react/components/provider.tsx
4368
- import { jsx as jsx46, jsxs as jsxs36 } from "react/jsx-runtime";
4369
- var warned = /* @__PURE__ */ new Set();
4370
- function warnOnce(key, message) {
4371
- if (process.env.NODE_ENV === "production") return;
4372
- if (warned.has(key)) return;
4373
- warned.add(key);
4374
- console.warn(`[@usethrottle/auth] ${message}`);
4375
- }
4376
- var ThrottleUIContext = createContext6(null);
4377
- function useThrottle() {
4378
- const ctx = useContext6(ThrottleUIContext);
4379
- if (!ctx) {
4380
- warnOnce(
4381
- "use-throttle-no-provider",
4382
- "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."
4383
- );
4384
- return { openSignIn: () => {
4385
- }, openSignUp: () => {
4386
- }, openAccount: () => {
4387
- }, close: () => {
4388
- }, isOpen: false };
4389
- }
4390
- return {
4391
- openSignIn: () => ctx.openSignIn(),
4392
- openSignUp: () => ctx.openSignUp(),
4393
- openAccount: ctx.openAccount,
4394
- close: ctx.close,
4395
- isOpen: ctx.overlay !== null
4396
- };
4397
- }
4398
- function ThrottleProvider(props) {
4399
- const {
4400
- publishableKey,
4401
- baseUrl,
4402
- storage,
4403
- appearance,
4404
- localization,
4405
- logo,
4406
- injectStyles = true,
4407
- claimCartOnSignIn,
4408
- auth: injected,
4409
- children,
4410
- __captureStepUp
4411
- } = props;
4412
- useInjectStyles(injectStyles);
4413
- const [stepUpOpen, setStepUpOpen] = useState22(false);
4414
- const resolveStepUp = useRef9(null);
4415
- const claimRef = useRef9(claimCartOnSignIn);
4416
- claimRef.current = claimCartOnSignIn;
4417
- const [overlay, setOverlay] = useState22(null);
4418
- const overlayRestoreFocus = useRef9(null);
4419
- const [accountTabs, setAccountTabs] = useState22(void 0);
4420
- const onOverlaySuccess = useRef9(null);
4421
- const openSignIn = useCallback4((onSuccess) => {
4422
- onOverlaySuccess.current = onSuccess ?? null;
4423
- setOverlay("signIn");
4424
- }, []);
4425
- const openSignUp = useCallback4((onSuccess) => {
4426
- onOverlaySuccess.current = onSuccess ?? null;
4427
- setOverlay("signUp");
4428
- }, []);
4429
- const openAccount = useCallback4((opts) => {
4430
- onOverlaySuccess.current = null;
4431
- overlayRestoreFocus.current = opts?.restoreFocusTo ?? null;
4432
- setAccountTabs(opts?.tabs);
4433
- setOverlay("account");
4434
- }, []);
4435
- const closeOverlay = useCallback4(() => {
4436
- onOverlaySuccess.current = null;
4437
- overlayRestoreFocus.current = null;
4438
- setOverlay(null);
4439
- }, []);
4440
- const handleOverlaySuccess = useCallback4(() => {
4441
- const fn = onOverlaySuccess.current;
4442
- onOverlaySuccess.current = null;
4443
- setOverlay(null);
4444
- fn?.();
4445
- }, []);
4446
- const ui = useMemo4(
4447
- () => ({ overlay, openSignIn, openSignUp, openAccount, close: closeOverlay }),
4448
- [overlay, openSignIn, openSignUp, openAccount, closeOverlay]
4449
- );
4450
- const askForStepUp = useCallback4(
4451
- () => new Promise((resolve) => {
4452
- resolveStepUp.current = resolve;
4453
- setStepUpOpen(true);
4454
- }),
4455
- []
4456
- );
4457
- const auth = useMemo4(() => {
4458
- __captureStepUp?.(askForStepUp);
4459
- const client = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired: askForStepUp });
4460
- const account = createAccount(client);
4461
- const claim = async () => {
4462
- const cartId = claimRef.current?.();
4463
- if (!cartId) return;
4464
- try {
4465
- await account.claimCart(cartId);
4466
- } catch (err) {
4467
- warnOnce("claim-cart", `claimCartOnSignIn failed: ${err?.message ?? err}`);
4468
- }
4469
- };
4470
- return {
4471
- ...client,
4472
- async signIn(email, password) {
4473
- await client.signIn(email, password);
4474
- await claim();
4475
- },
4476
- async register(email, password) {
4477
- const result = await client.register(email, password);
4478
- if (result.status === "active") await claim();
4479
- return result;
4480
- }
4481
- };
4482
- }, [injected, publishableKey, baseUrl]);
4483
- const finishStepUp = (password) => {
4484
- setStepUpOpen(false);
4485
- const resolve = resolveStepUp.current;
4486
- resolveStepUp.current = null;
4487
- resolve?.(password);
4488
- };
4489
- const authOverlayOpen = overlay === "signIn" || overlay === "signUp";
4490
- return /* @__PURE__ */ jsx46(AppearanceProvider, { value: appearance ?? {}, children: /* @__PURE__ */ jsx46(LocalizationProvider, { value: localization ?? {}, children: /* @__PURE__ */ jsx46(BrandLogoProvider, { value: logo ?? null, children: /* @__PURE__ */ jsx46(ThrottleAuthProvider, { auth, publishableKey, children: /* @__PURE__ */ jsx46(ConfirmProvider, { stepUpOpen, onStepUpResolve: finishStepUp, children: /* @__PURE__ */ jsxs36(ThrottleUIContext.Provider, { value: ui, children: [
4491
- children,
4492
- /* @__PURE__ */ jsx46(
4493
- AuthModal,
4494
- {
4495
- open: authOverlayOpen,
4496
- onClose: closeOverlay,
4497
- initialView: overlay === "signUp" ? "signUp" : "signIn",
4498
- afterSignIn: handleOverlaySuccess,
4499
- afterSignUp: handleOverlaySuccess
4500
- },
4501
- overlay === "signUp" ? "signUp" : "signIn"
4502
- ),
4503
- /* @__PURE__ */ jsx46(AccountModal, { open: overlay === "account", onClose: closeOverlay, tabs: accountTabs, restoreFocusTo: overlayRestoreFocus.current })
4504
- ] }) }) }) }) }) });
4505
- }
4506
-
4507
- // src/react/components/auth/StepUpDialog.tsx
4508
- import { useEffect as useEffect12, useId as useId7, useState as useState23 } from "react";
4509
- import { jsx as jsx47, jsxs as jsxs37 } from "react/jsx-runtime";
4510
-
4511
- // src/react/components/auth/SignInButton.tsx
4512
- import { cloneElement, isValidElement, useContext as useContext7, useState as useState24 } from "react";
4513
- import { jsx as jsx48, jsxs as jsxs38 } from "react/jsx-runtime";
4514
- function SignInButton(props) {
4515
- const a = useAppearance(props.appearance);
4516
- const scoped = useAppearanceProviderValue(props.appearance);
4517
- const { t } = useCopy(props.localization);
4518
- const ui = useContext7(ThrottleUIContext);
4519
- const [localOpen, setLocalOpen] = useState24(false);
4520
- const useShared = ui !== null && props.initialView === void 0;
4521
- const label = props.children ?? t.signIn.submitButton;
4522
- if (props.mode === "redirect") {
4523
- return /* @__PURE__ */ jsx48(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx48("span", { ...a.rootProps, children: /* @__PURE__ */ jsx48("a", { ...a.el("signInButton.root", "throttle-button throttle-button--primary"), href: props.url ?? "#", children: label }) }) });
4524
- }
4525
- const openModal = () => {
4526
- if (useShared) ui.openSignIn(props.afterSignIn);
4527
- else setLocalOpen(true);
4528
- };
4529
- const closeModal = () => {
4530
- if (useShared) ui.close();
4531
- else setLocalOpen(false);
4532
- };
4533
- const trigger = props.asChild && isValidElement(props.children) ? cloneElement(props.children, { onClick: openModal }) : /* @__PURE__ */ jsx48(Button, { elementKey: "signInButton.root", onClick: openModal, children: label });
4534
- return /* @__PURE__ */ jsxs38(AppearanceProvider, { value: scoped, children: [
4535
- /* @__PURE__ */ jsx48("span", { ...a.rootProps, children: trigger }),
4536
- !useShared ? /* @__PURE__ */ jsx48(
4537
- AuthModal,
4538
- {
4539
- open: localOpen,
4540
- onClose: closeModal,
4541
- initialView: props.initialView,
4542
- appearance: props.appearance,
4543
- localization: props.localization,
4544
- afterSignIn: () => {
4545
- closeModal();
4546
- props.afterSignIn?.();
4547
- }
4548
- }
4549
- ) : null
4550
- ] });
4551
- }
4552
-
4553
- // src/react/components/auth/SignUpButton.tsx
4554
- import { cloneElement as cloneElement2, isValidElement as isValidElement2, useContext as useContext8, useState as useState25 } from "react";
4555
- import { jsx as jsx49, jsxs as jsxs39 } from "react/jsx-runtime";
4556
- function SignUpButton(props) {
4557
- const a = useAppearance(props.appearance);
4558
- const scoped = useAppearanceProviderValue(props.appearance);
4559
- const { t } = useCopy(props.localization);
4560
- const ui = useContext8(ThrottleUIContext);
4561
- const [localOpen, setLocalOpen] = useState25(false);
4562
- const useShared = ui !== null && props.initialView === void 0;
4563
- const label = props.children ?? t.signUp.submitButton;
4564
- if (props.mode === "redirect") {
4565
- return /* @__PURE__ */ jsx49(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx49("span", { ...a.rootProps, children: /* @__PURE__ */ jsx49("a", { ...a.el("signUpButton.root", "throttle-button throttle-button--primary"), href: props.url ?? "#", children: label }) }) });
4566
- }
4567
- const openModal = () => {
4568
- if (useShared) ui.openSignUp(props.afterSignUp);
4569
- else setLocalOpen(true);
4570
- };
4571
- const closeModal = () => {
4572
- if (useShared) ui.close();
4573
- else setLocalOpen(false);
4574
- };
4575
- const trigger = props.asChild && isValidElement2(props.children) ? cloneElement2(props.children, { onClick: openModal }) : /* @__PURE__ */ jsx49(Button, { elementKey: "signUpButton.root", onClick: openModal, children: label });
4576
- return /* @__PURE__ */ jsxs39(AppearanceProvider, { value: scoped, children: [
4577
- /* @__PURE__ */ jsx49("span", { ...a.rootProps, children: trigger }),
4578
- !useShared ? /* @__PURE__ */ jsx49(
4579
- AuthModal,
4580
- {
4581
- open: localOpen,
4582
- onClose: closeModal,
4583
- initialView: props.initialView ?? "signUp",
4584
- appearance: props.appearance,
4585
- localization: props.localization,
4586
- afterSignUp: () => {
4587
- closeModal();
4588
- props.afterSignUp?.();
4589
- }
4590
- }
4591
- ) : null
4592
- ] });
4593
- }
4594
-
4595
- // src/react/components/auth/SignOutButton.tsx
4596
- import { jsx as jsx50 } from "react/jsx-runtime";
4597
- function SignOutButton(props) {
4598
- const a = useAppearance();
4599
- const { auth } = useAuth();
4600
- const { t } = useCopy(props.localization);
4601
- const { submitting, run } = useAction();
4602
- const label = props.children ?? (props.everywhere ? t.userButton.signOutAll : t.userButton.signOut);
4603
- return /* @__PURE__ */ jsx50("span", { ...a.rootProps, children: /* @__PURE__ */ jsx50(
4604
- Button,
4605
- {
4606
- variant: "secondary",
4607
- loading: submitting,
4608
- elementKey: "signOutButton.root",
4609
- onClick: () => void run(async () => {
4610
- if (props.everywhere) await auth.signOutEverywhere();
4611
- else await auth.signOut();
4612
- props.onSignedOut?.();
4613
- }),
4614
- children: label
4615
- }
4616
- ) });
4617
- }
4618
-
4619
- // src/react/components/auth/SignInPage.tsx
4620
- import { jsx as jsx51, jsxs as jsxs40 } from "react/jsx-runtime";
4621
- function SignInPage(props) {
4622
- const a = useAppearance(props.appearance);
4623
- const scoped = useAppearanceProviderValue(props.appearance);
4624
- return /* @__PURE__ */ jsx51(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx51("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs40("div", { ...a.el("signInPage.root", "throttle-page"), children: [
4625
- props.logo ? /* @__PURE__ */ jsx51("div", { ...a.el("signInPage.logo", "throttle-page__logo"), children: props.logo }) : null,
4626
- /* @__PURE__ */ jsx51(
4627
- SignIn,
4628
- {
4629
- onSuccess: props.onSuccess,
4630
- onForgotPassword: props.onForgotPassword,
4631
- onSignUp: props.onSignUp,
4632
- appearance: props.appearance,
4633
- localization: props.localization
4634
- }
4635
- ),
4636
- props.footer ? /* @__PURE__ */ jsx51("div", { ...a.el("signInPage.footer", "throttle-page__footer"), children: props.footer }) : null
4637
- ] }) }) });
4638
- }
4639
-
4640
- // src/react/components/auth/SignUpPage.tsx
4641
- import { jsx as jsx52, jsxs as jsxs41 } from "react/jsx-runtime";
4642
- function SignUpPage(props) {
4643
- const a = useAppearance(props.appearance);
4644
- const scoped = useAppearanceProviderValue(props.appearance);
4645
- return /* @__PURE__ */ jsx52(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx52("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs41("div", { ...a.el("signUpPage.root", "throttle-page"), children: [
4646
- props.logo ? /* @__PURE__ */ jsx52("div", { ...a.el("signUpPage.logo", "throttle-page__logo"), children: props.logo }) : null,
4647
- /* @__PURE__ */ jsx52(
4648
- SignUp,
4649
- {
4650
- onSuccess: props.onSuccess,
4651
- onSignIn: props.onSignIn,
4652
- appearance: props.appearance,
4653
- localization: props.localization
4654
- }
4655
- ),
4656
- props.footer ? /* @__PURE__ */ jsx52("div", { ...a.el("signUpPage.footer", "throttle-page__footer"), children: props.footer }) : null
4657
- ] }) }) });
4658
- }
4659
-
4660
- // src/react/components/guards.tsx
4661
- import { Fragment as Fragment11, jsx as jsx53 } from "react/jsx-runtime";
4662
- function SignedIn(props) {
4663
- const { status } = useAuth();
4664
- return status === "authenticated" ? /* @__PURE__ */ jsx53(Fragment11, { children: props.children }) : null;
4665
- }
4666
- function SignedOut(props) {
4667
- const { status } = useAuth();
4668
- return status === "unauthenticated" ? /* @__PURE__ */ jsx53(Fragment11, { children: props.children }) : null;
4669
- }
4670
- function Protect(props) {
4671
- const { status, emailVerified } = useAuth();
4672
- if (status === "loading") return null;
4673
- if (status !== "authenticated") return /* @__PURE__ */ jsx53(Fragment11, { children: props.fallback ?? null });
4674
- if (props.requireVerified && !emailVerified) return /* @__PURE__ */ jsx53(Fragment11, { children: props.fallback ?? null });
4675
- return /* @__PURE__ */ jsx53(Fragment11, { children: props.children });
4676
- }
86
+ } from "../../chunk-TDQC3LMQ.js";
4677
87
 
4678
88
  // src/react/components/UserButton.tsx
4679
- import { useContext as useContext9, useId as useId8, useRef as useRef10, useState as useState26 } from "react";
4680
- import { jsx as jsx54, jsxs as jsxs42 } from "react/jsx-runtime";
89
+ import { useContext, useId, useRef, useState } from "react";
90
+ import { jsx, jsxs } from "react/jsx-runtime";
4681
91
  function UserButtonRoot(props) {
4682
92
  const a = useAppearance(props.appearance);
4683
93
  const scoped = useAppearanceProviderValue(props.appearance);
4684
94
  const { t } = useCopy(props.localization);
4685
95
  const { auth, status, customer, emailVerified } = useAuth();
4686
- const ui = useContext9(ThrottleUIContext);
4687
- const [localOpen, setLocalOpen] = useState26(false);
96
+ const ui = useContext(ThrottleUIContext);
97
+ const [localOpen, setLocalOpen] = useState(false);
4688
98
  const accountOpen = ui ? false : localOpen;
4689
- const titleId = useId8();
4690
- const rootRef = useRef10(null);
4691
- const openerRef = useRef10(null);
99
+ const titleId = useId();
100
+ const rootRef = useRef(null);
101
+ const openerRef = useRef(null);
4692
102
  const openAccount = () => {
4693
103
  const opener = rootRef.current?.querySelector('button[aria-haspopup="menu"]') ?? null;
4694
104
  openerRef.current = opener;
@@ -4697,25 +107,25 @@ function UserButtonRoot(props) {
4697
107
  };
4698
108
  if (status !== "authenticated" || !customer) return null;
4699
109
  const name = [customer.firstName, customer.lastName].filter(Boolean).join(" ");
4700
- return /* @__PURE__ */ jsx54(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx54("span", { ...a.rootProps, children: /* @__PURE__ */ jsxs42("span", { ref: rootRef, ...a.el("userButton.root"), children: [
4701
- /* @__PURE__ */ jsxs42(
110
+ return /* @__PURE__ */ jsx(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx("span", { ...a.rootProps, children: /* @__PURE__ */ jsxs("span", { ref: rootRef, ...a.el("userButton.root"), children: [
111
+ /* @__PURE__ */ jsxs(
4702
112
  Dropdown,
4703
113
  {
4704
114
  label: t.userButton.openMenu,
4705
115
  triggerKey: "userButton.trigger",
4706
116
  menuKey: "userButton.menu",
4707
- trigger: /* @__PURE__ */ jsx54(Avatar, { name, email: customer.email }),
117
+ trigger: /* @__PURE__ */ jsx(Avatar, { name, email: customer.email }),
4708
118
  children: [
4709
- /* @__PURE__ */ jsx54("div", { ...a.el("userButton.identity", "throttle-dropdown__item"), children: /* @__PURE__ */ jsxs42("span", { children: [
4710
- /* @__PURE__ */ jsx54("span", { ...a.el("userButton.email"), children: customer.email }),
4711
- /* @__PURE__ */ jsx54("br", {}),
4712
- /* @__PURE__ */ jsx54(Badge, { tone: emailVerified ? "success" : "warning", elementKey: "userButton.badge", children: emailVerified ? t.userButton.verified : t.userButton.unverified })
119
+ /* @__PURE__ */ jsx("div", { ...a.el("userButton.identity", "throttle-dropdown__item"), children: /* @__PURE__ */ jsxs("span", { children: [
120
+ /* @__PURE__ */ jsx("span", { ...a.el("userButton.email"), children: customer.email }),
121
+ /* @__PURE__ */ jsx("br", {}),
122
+ /* @__PURE__ */ jsx(Badge, { tone: emailVerified ? "success" : "warning", elementKey: "userButton.badge", children: emailVerified ? t.userButton.verified : t.userButton.unverified })
4713
123
  ] }) }),
4714
- /* @__PURE__ */ jsx54(DropdownSeparator, {}),
4715
- props.mode === "navigate" ? /* @__PURE__ */ jsx54(DropdownItem, { itemKey: "userButton.manageButton", href: props.accountUrl ?? "#", children: t.userButton.manage }) : /* @__PURE__ */ jsx54(DropdownItem, { itemKey: "userButton.manageButton", onClick: openAccount, children: t.userButton.manage }),
124
+ /* @__PURE__ */ jsx(DropdownSeparator, {}),
125
+ props.mode === "navigate" ? /* @__PURE__ */ jsx(DropdownItem, { itemKey: "userButton.manageButton", href: props.accountUrl ?? "#", children: t.userButton.manage }) : /* @__PURE__ */ jsx(DropdownItem, { itemKey: "userButton.manageButton", onClick: openAccount, children: t.userButton.manage }),
4716
126
  props.children,
4717
- /* @__PURE__ */ jsx54(DropdownSeparator, {}),
4718
- /* @__PURE__ */ jsx54(
127
+ /* @__PURE__ */ jsx(DropdownSeparator, {}),
128
+ /* @__PURE__ */ jsx(
4719
129
  DropdownItem,
4720
130
  {
4721
131
  itemKey: "userButton.signOutButton",
@@ -4723,7 +133,7 @@ function UserButtonRoot(props) {
4723
133
  children: t.userButton.signOut
4724
134
  }
4725
135
  ),
4726
- /* @__PURE__ */ jsx54(
136
+ /* @__PURE__ */ jsx(
4727
137
  DropdownItem,
4728
138
  {
4729
139
  itemKey: "userButton.signOutAllButton",
@@ -4734,7 +144,7 @@ function UserButtonRoot(props) {
4734
144
  ]
4735
145
  }
4736
146
  ),
4737
- props.mode !== "navigate" && !ui ? /* @__PURE__ */ jsxs42(
147
+ props.mode !== "navigate" && !ui ? /* @__PURE__ */ jsxs(
4738
148
  Modal,
4739
149
  {
4740
150
  open: accountOpen,
@@ -4742,8 +152,8 @@ function UserButtonRoot(props) {
4742
152
  labelledBy: titleId,
4743
153
  restoreFocusTo: openerRef.current,
4744
154
  children: [
4745
- /* @__PURE__ */ jsx54("h2", { id: titleId, style: { padding: "16px 16px 0", margin: 0 }, children: t.account.title }),
4746
- /* @__PURE__ */ jsx54("div", { style: { padding: 16 }, children: /* @__PURE__ */ jsx54(
155
+ /* @__PURE__ */ jsx("h2", { id: titleId, style: { padding: "16px 16px 0", margin: 0 }, children: t.account.title }),
156
+ /* @__PURE__ */ jsx("div", { style: { padding: 16 }, children: /* @__PURE__ */ jsx(
4747
157
  AccountDashboard,
4748
158
  {
4749
159
  tabs: props.tabs,
@@ -4757,13 +167,13 @@ function UserButtonRoot(props) {
4757
167
  ] }) }) });
4758
168
  }
4759
169
  function UserButtonLink(props) {
4760
- return /* @__PURE__ */ jsxs42(DropdownItem, { itemKey: "userButton.item", href: props.href, children: [
170
+ return /* @__PURE__ */ jsxs(DropdownItem, { itemKey: "userButton.item", href: props.href, children: [
4761
171
  props.icon,
4762
172
  props.label
4763
173
  ] });
4764
174
  }
4765
175
  function UserButtonAction(props) {
4766
- return /* @__PURE__ */ jsxs42(DropdownItem, { itemKey: "userButton.item", onClick: props.onClick, children: [
176
+ return /* @__PURE__ */ jsxs(DropdownItem, { itemKey: "userButton.item", onClick: props.onClick, children: [
4767
177
  props.icon,
4768
178
  props.label
4769
179
  ] });