@base316/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1233 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ Base316Provider: () => Base316Provider,
25
+ Branding: () => Branding,
26
+ Invoices: () => Invoices,
27
+ PasskeyManager: () => PasskeyManager,
28
+ PricingTable: () => PricingTable,
29
+ SignIn: () => SignIn,
30
+ SignUp: () => SignUp,
31
+ SubscriptionManager: () => SubscriptionManager,
32
+ UserProfile: () => UserProfile,
33
+ useBase316: () => useBase316,
34
+ useSession: () => useSession
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/provider.tsx
39
+ var import_sdk = require("@base316/sdk");
40
+ var import_react = require("react");
41
+
42
+ // src/appearance.ts
43
+ var VARIABLES = {
44
+ colorPrimary: "--b316-color-primary",
45
+ colorPrimaryText: "--b316-color-primary-text",
46
+ colorText: "--b316-color-text",
47
+ colorMuted: "--b316-color-muted",
48
+ colorBackground: "--b316-color-background",
49
+ colorBorder: "--b316-color-border",
50
+ colorDanger: "--b316-color-danger",
51
+ colorDangerBackground: "--b316-color-danger-bg",
52
+ colorSuccess: "--b316-color-success",
53
+ borderRadius: "--b316-radius",
54
+ fontFamily: "--b316-font",
55
+ fontSize: "--b316-font-size",
56
+ spacing: "--b316-spacing"
57
+ };
58
+ function appearanceToStyle(appearance) {
59
+ if (!appearance) {
60
+ return {};
61
+ }
62
+ const style = {};
63
+ for (const [key, variable] of Object.entries(VARIABLES)) {
64
+ const value = appearance[key];
65
+ if (value !== void 0) {
66
+ style[variable] = value;
67
+ }
68
+ }
69
+ return style;
70
+ }
71
+ var STYLE_ELEMENT_ID = "base316-components";
72
+ var STYLESHEET = `
73
+ .b316-root {
74
+ --b316-color-primary: #111827;
75
+ --b316-color-primary-text: #ffffff;
76
+ --b316-color-text: #111827;
77
+ --b316-color-muted: #6b7280;
78
+ --b316-color-background: #ffffff;
79
+ --b316-color-border: #e5e7eb;
80
+ --b316-color-danger: #991b1b;
81
+ --b316-color-danger-bg: #fef2f2;
82
+ --b316-color-success: #166534;
83
+ --b316-radius: 12px;
84
+ --b316-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
85
+ --b316-font-size: 15px;
86
+ --b316-spacing: 12px;
87
+
88
+ font-family: var(--b316-font);
89
+ font-size: var(--b316-font-size);
90
+ line-height: 1.5;
91
+ color: var(--b316-color-text);
92
+ }
93
+
94
+ .b316-card {
95
+ border: 1px solid var(--b316-color-border);
96
+ border-radius: var(--b316-radius);
97
+ padding: calc(var(--b316-spacing) * 1.6);
98
+ background: var(--b316-color-background);
99
+ }
100
+
101
+ .b316-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--b316-spacing); }
102
+ .b316-label { font-size: 0.87em; color: var(--b316-color-muted); }
103
+
104
+ .b316-input {
105
+ padding: 9px 11px;
106
+ border: 1px solid var(--b316-color-border);
107
+ border-radius: calc(var(--b316-radius) / 1.6);
108
+ font: inherit;
109
+ color: inherit;
110
+ background: var(--b316-color-background);
111
+ }
112
+ .b316-input:focus-visible { outline: 2px solid var(--b316-color-primary); outline-offset: 1px; }
113
+
114
+ .b316-button {
115
+ width: 100%;
116
+ padding: 10px 14px;
117
+ border: 0;
118
+ border-radius: calc(var(--b316-radius) / 1.6);
119
+ background: var(--b316-color-primary);
120
+ color: var(--b316-color-primary-text);
121
+ font: inherit;
122
+ font-weight: 600;
123
+ cursor: pointer;
124
+ }
125
+ .b316-button:hover:not([disabled]) { filter: brightness(1.12); }
126
+ .b316-button:active:not([disabled]) { transform: translateY(1px); }
127
+ .b316-button:focus-visible { outline: 2px solid var(--b316-color-primary); outline-offset: 2px; }
128
+ .b316-button[disabled] { opacity: .55; cursor: not-allowed; }
129
+
130
+ .b316-button-secondary {
131
+ width: auto;
132
+ transition: background-color .12s ease;
133
+ background: var(--b316-color-background);
134
+ color: var(--b316-color-text);
135
+ border: 1px solid var(--b316-color-border);
136
+ font-weight: 500;
137
+ }
138
+
139
+ .b316-button-link {
140
+ width: auto;
141
+ background: none;
142
+ border: 0;
143
+ padding: 0;
144
+ color: var(--b316-color-primary);
145
+ font: inherit;
146
+ cursor: pointer;
147
+ text-decoration: underline;
148
+ }
149
+
150
+ .b316-error {
151
+ background: var(--b316-color-danger-bg);
152
+ color: var(--b316-color-danger);
153
+ padding: 8px 10px;
154
+ border-radius: calc(var(--b316-radius) / 2);
155
+ font-size: 0.9em;
156
+ margin-bottom: var(--b316-spacing);
157
+ }
158
+
159
+ .b316-muted { color: var(--b316-color-muted); font-size: 0.87em; }
160
+ .b316-success { color: var(--b316-color-success); }
161
+
162
+ .b316-plans {
163
+ display: grid;
164
+ gap: calc(var(--b316-spacing) * 1.3);
165
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
166
+ align-items: stretch;
167
+ }
168
+
169
+ .b316-plan {
170
+ display: flex;
171
+ flex-direction: column;
172
+ padding: 0;
173
+ overflow: hidden;
174
+ }
175
+
176
+ /* The recommended plan. Lifted rather than merely tinted: on a pricing table
177
+ the eye needs one obvious default, and colour alone does not survive being
178
+ viewed in greyscale or by someone who cannot distinguish the accent. */
179
+ .b316-plan-featured {
180
+ border-color: var(--b316-color-primary);
181
+ box-shadow: 0 0 0 1px var(--b316-color-primary);
182
+ }
183
+
184
+ .b316-plan-badge {
185
+ display: inline-block;
186
+ background: var(--b316-color-primary);
187
+ color: var(--b316-color-primary-text);
188
+ border-radius: 999px;
189
+ padding: 2px 9px;
190
+ font-size: 0.75em;
191
+ font-weight: 600;
192
+ letter-spacing: .02em;
193
+ margin-bottom: 8px;
194
+ }
195
+
196
+ /* Occupies the badge's height without drawing anything, so a featured card
197
+ does not push its own price and features out of line with the others. */
198
+ .b316-plan-badge-spacer {
199
+ display: inline-block;
200
+ padding: 2px 9px;
201
+ font-size: 0.75em;
202
+ margin-bottom: 8px;
203
+ visibility: hidden;
204
+ }
205
+
206
+ .b316-plan-head { padding: calc(var(--b316-spacing) * 1.6); padding-bottom: 0; }
207
+ .b316-plan-name { font-weight: 600; font-size: 1.06em; }
208
+ .b316-plan-desc { color: var(--b316-color-muted); font-size: 0.87em; margin-top: 2px; }
209
+
210
+ .b316-plan-pricing { padding: 0 calc(var(--b316-spacing) * 1.6) calc(var(--b316-spacing) * 1.4); }
211
+
212
+ .b316-plan-price {
213
+ font-size: 2.1em;
214
+ font-weight: 640;
215
+ letter-spacing: -.02em;
216
+ margin-top: calc(var(--b316-spacing) * 0.7);
217
+ /* Lining figures of even width, so prices do not shimmer between cards. */
218
+ font-variant-numeric: tabular-nums lining-nums;
219
+ }
220
+
221
+ .b316-plan-cadence { color: var(--b316-color-muted); font-size: 0.87em; min-height: 1.35em; }
222
+ .b316-plan-trial { color: var(--b316-color-success); font-size: 0.87em; min-height: 1.35em; }
223
+
224
+ .b316-plan-features {
225
+ list-style: none;
226
+ padding: calc(var(--b316-spacing) * 1.2) calc(var(--b316-spacing) * 1.6);
227
+ margin: 0;
228
+ flex: 1;
229
+ border-top: 1px solid var(--b316-color-border);
230
+ }
231
+ .b316-plan-feature {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ gap: 12px;
235
+ padding: 5px 0;
236
+ font-size: 0.94em;
237
+ }
238
+ .b316-plan-feature-value { color: var(--b316-color-muted); font-variant-numeric: tabular-nums; }
239
+
240
+ .b316-plan-action {
241
+ padding: calc(var(--b316-spacing) * 1.2) calc(var(--b316-spacing) * 1.6);
242
+ border-top: 1px solid var(--b316-color-border);
243
+ }
244
+
245
+ /* The plan already held. A faded primary button reads as broken rather than
246
+ as "you are here", so this is a flat, quiet, genuinely inert surface. */
247
+ .b316-button-current,
248
+ .b316-button-current[disabled] {
249
+ background: transparent;
250
+ color: var(--b316-color-muted);
251
+ border: 1px solid var(--b316-color-border);
252
+ font-weight: 500;
253
+ opacity: 1;
254
+ cursor: default;
255
+ }
256
+
257
+ .b316-row { display: flex; justify-content: space-between; gap: 16px; align-items: center; }
258
+ `;
259
+ function cls(base, extra) {
260
+ return extra ? `${base} ${extra}` : base;
261
+ }
262
+ var STYLESHEET_EXTRA = `
263
+ .b316-branding { display: flex; flex-direction: column; gap: 4px; }
264
+ .b316-branding-logo { max-height: 40px; width: auto; align-self: flex-start; }
265
+ .b316-branding-name { font-size: 1.5em; font-weight: 650; letter-spacing: -.01em; }
266
+ .b316-branding-tagline { color: var(--b316-color-muted); }
267
+
268
+ .b316-invoice-table { width: 100%; border-collapse: collapse; font-size: 0.94em; }
269
+ .b316-invoice-table th {
270
+ text-align: left;
271
+ font-weight: 500;
272
+ color: var(--b316-color-muted);
273
+ font-size: 0.85em;
274
+ padding: 0 0 6px;
275
+ border-bottom: 1px solid var(--b316-color-border);
276
+ }
277
+ .b316-invoice-table td { padding: 9px 0; border-bottom: 1px solid var(--b316-color-border); }
278
+ /* Columns need breathing room or the amount runs straight into the action \u2014
279
+ "$49.00View" \u2014 which is what happens when only the row has padding. */
280
+ .b316-invoice-table th + th,
281
+ .b316-invoice-table td + td { padding-left: 16px; }
282
+ .b316-invoice-table td:last-child,
283
+ .b316-invoice-table th:last-child { text-align: right; width: 1%; white-space: nowrap; }
284
+ .b316-invoice-table tr:last-child td { border-bottom: 0; }
285
+ .b316-num { text-align: right; font-variant-numeric: tabular-nums; }
286
+
287
+ .b316-invoice-view {
288
+ margin-top: calc(var(--b316-spacing) * 1.4);
289
+ border-top: 1px solid var(--b316-color-border);
290
+ padding-top: calc(var(--b316-spacing) * 1.4);
291
+ }
292
+ .b316-invoice-actions { display: flex; gap: 8px; justify-content: flex-end; margin-bottom: var(--b316-spacing); }
293
+ .b316-invoice-doc { display: flex; flex-direction: column; gap: calc(var(--b316-spacing) * 1.4); }
294
+ .b316-invoice-meta { display: grid; grid-template-columns: 1fr 1fr; gap: var(--b316-spacing); }
295
+ .b316-invoice-totals { display: flex; flex-direction: column; gap: 6px; margin-left: auto; min-width: 240px; }
296
+ .b316-invoice-total { font-weight: 650; border-top: 1px solid var(--b316-color-border); padding-top: 6px; }
297
+
298
+ /* Printing emits the invoice alone. Without this the browser would paginate the
299
+ whole application around it, which is not something anyone wants to file. */
300
+ @media print {
301
+ body * { visibility: hidden; }
302
+ .b316-invoice-doc, .b316-invoice-doc * { visibility: visible; }
303
+ .b316-invoice-doc {
304
+ position: absolute;
305
+ inset: 0 auto auto 0;
306
+ width: 100%;
307
+ padding: 24px;
308
+ }
309
+ .b316-invoice-actions { display: none; }
310
+ }
311
+ `;
312
+
313
+ // src/provider.tsx
314
+ var import_jsx_runtime = require("react/jsx-runtime");
315
+ var Base316Context = (0, import_react.createContext)(null);
316
+ var STORAGE_KEY = "base316.session";
317
+ function readStoredToken() {
318
+ try {
319
+ return window.localStorage.getItem(STORAGE_KEY);
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+ function writeStoredToken(token) {
325
+ try {
326
+ if (token === null) {
327
+ window.localStorage.removeItem(STORAGE_KEY);
328
+ } else {
329
+ window.localStorage.setItem(STORAGE_KEY, token);
330
+ }
331
+ } catch {
332
+ }
333
+ }
334
+ function Base316Provider({
335
+ publishableKey,
336
+ baseUrl,
337
+ token: controlledToken,
338
+ onTokenChange,
339
+ appearance,
340
+ className,
341
+ children
342
+ }) {
343
+ const client = (0, import_react.useMemo)(
344
+ () => new import_sdk.Base316Browser({ publishableKey, baseUrl }),
345
+ [publishableKey, baseUrl]
346
+ );
347
+ (0, import_react.useEffect)(() => {
348
+ if (document.getElementById(STYLE_ELEMENT_ID)) {
349
+ return;
350
+ }
351
+ const element = document.createElement("style");
352
+ element.id = STYLE_ELEMENT_ID;
353
+ element.textContent = STYLESHEET + STYLESHEET_EXTRA;
354
+ document.head.appendChild(element);
355
+ }, []);
356
+ const controlled = controlledToken !== void 0;
357
+ const [internalToken, setInternalToken] = (0, import_react.useState)(null);
358
+ const [session, setSession] = (0, import_react.useState)(null);
359
+ const [branding, setBranding] = (0, import_react.useState)(null);
360
+ const [loading, setLoading] = (0, import_react.useState)(true);
361
+ const token = controlled ? controlledToken : internalToken;
362
+ (0, import_react.useEffect)(() => {
363
+ if (controlled) {
364
+ return;
365
+ }
366
+ setInternalToken(readStoredToken());
367
+ }, [controlled]);
368
+ (0, import_react.useEffect)(() => {
369
+ let cancelled = false;
370
+ client.billing.branding().then((result) => {
371
+ if (!cancelled) setBranding(result);
372
+ }).catch(() => {
373
+ });
374
+ return () => {
375
+ cancelled = true;
376
+ };
377
+ }, [client]);
378
+ const setToken = (0, import_react.useCallback)(
379
+ (next) => {
380
+ if (controlled) {
381
+ onTokenChange?.(next);
382
+ return;
383
+ }
384
+ writeStoredToken(next);
385
+ setInternalToken(next);
386
+ onTokenChange?.(next);
387
+ },
388
+ [controlled, onTokenChange]
389
+ );
390
+ const refresh = (0, import_react.useCallback)(async () => {
391
+ if (!token) {
392
+ setSession(null);
393
+ setLoading(false);
394
+ return;
395
+ }
396
+ try {
397
+ setSession(await client.auth.session(token));
398
+ } catch {
399
+ setSession(null);
400
+ setToken(null);
401
+ } finally {
402
+ setLoading(false);
403
+ }
404
+ }, [client, token, setToken]);
405
+ (0, import_react.useEffect)(() => {
406
+ setLoading(true);
407
+ void refresh();
408
+ }, [refresh]);
409
+ const signOut = (0, import_react.useCallback)(async () => {
410
+ if (token) {
411
+ await client.auth.signOut(token).catch(() => {
412
+ });
413
+ }
414
+ setToken(null);
415
+ setSession(null);
416
+ }, [client, token, setToken]);
417
+ const value = (0, import_react.useMemo)(
418
+ () => ({ client, branding, token, setToken, session, loading, refresh, signOut }),
419
+ [client, branding, token, setToken, session, loading, refresh, signOut]
420
+ );
421
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Base316Context.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
422
+ "div",
423
+ {
424
+ className: className ? `b316-root ${className}` : "b316-root",
425
+ style: appearanceToStyle({
426
+ // Whatever was set in the sign-in designer arrives here, so a
427
+ // customer styles their login once on Base316 and the embedded
428
+ // components follow — rather than configuring the same colours
429
+ // twice and watching them drift apart.
430
+ //
431
+ // An explicit `appearance` still wins, because a host that has
432
+ // gone to the trouble of passing tokens means them.
433
+ ...branding ? {
434
+ colorPrimary: branding.primaryColor,
435
+ colorBackground: branding.backgroundColor
436
+ } : {},
437
+ ...appearance
438
+ }),
439
+ children
440
+ }
441
+ ) });
442
+ }
443
+ function useBase316() {
444
+ const value = (0, import_react.useContext)(Base316Context);
445
+ if (!value) {
446
+ throw new Error("Base316 components must be rendered inside <Base316Provider>.");
447
+ }
448
+ return value;
449
+ }
450
+ function useSession() {
451
+ const { session, loading } = useBase316();
452
+ return { session, loading };
453
+ }
454
+
455
+ // src/Branding.tsx
456
+ var import_jsx_runtime2 = require("react/jsx-runtime");
457
+ function Branding({
458
+ showTagline = true,
459
+ className,
460
+ style
461
+ }) {
462
+ const { branding } = useBase316();
463
+ if (!branding) {
464
+ return null;
465
+ }
466
+ const showLogo = branding.logoUrl && branding.brandDisplay !== "NAME";
467
+ const showName = branding.brandDisplay !== "LOGO" || !branding.logoUrl;
468
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cls("b316-branding", className), style, children: [
469
+ showLogo ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { className: "b316-branding-logo", src: branding.logoUrl, alt: branding.brandName }) : null,
470
+ showName ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "b316-branding-name", children: branding.brandName }) : null,
471
+ showTagline && branding.tagline ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "b316-branding-tagline", children: branding.tagline }) : null
472
+ ] });
473
+ }
474
+
475
+ // src/Invoices.tsx
476
+ var import_react2 = require("react");
477
+ var import_jsx_runtime3 = require("react/jsx-runtime");
478
+ function money(amount, currency) {
479
+ const value = Number(amount);
480
+ if (!Number.isFinite(value)) {
481
+ return `${amount} ${currency}`;
482
+ }
483
+ try {
484
+ return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(value);
485
+ } catch {
486
+ return `${value} ${currency}`;
487
+ }
488
+ }
489
+ function day(value) {
490
+ return value ? new Date(value).toLocaleDateString() : "\u2014";
491
+ }
492
+ function Invoices({ className, style }) {
493
+ const { client, token, loading: sessionLoading } = useBase316();
494
+ const [invoices, setInvoices] = (0, import_react2.useState)(null);
495
+ const [open, setOpen] = (0, import_react2.useState)(null);
496
+ const [error, setError] = (0, import_react2.useState)(null);
497
+ (0, import_react2.useEffect)(() => {
498
+ if (!token) {
499
+ setInvoices([]);
500
+ return;
501
+ }
502
+ let cancelled = false;
503
+ client.billing.invoices(token).then((result) => {
504
+ if (!cancelled) setInvoices(result.data);
505
+ }).catch((cause) => {
506
+ console.error("[base316] could not load invoices", cause);
507
+ if (!cancelled) setError("Could not load your invoices.");
508
+ });
509
+ return () => {
510
+ cancelled = true;
511
+ };
512
+ }, [client, token]);
513
+ const view = (0, import_react2.useCallback)(
514
+ async (id) => {
515
+ if (!token) return;
516
+ try {
517
+ setOpen(await client.billing.invoice(token, id));
518
+ } catch (cause) {
519
+ console.error("[base316] could not load the invoice", cause);
520
+ setError("Could not open that invoice.");
521
+ }
522
+ },
523
+ [client, token]
524
+ );
525
+ if (sessionLoading || invoices === null) {
526
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cls("b316-card", className), style, children: "Loading\u2026" });
527
+ }
528
+ if (!token) {
529
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cls("b316-card", className), style, children: "Sign in to see your invoices." });
530
+ }
531
+ if (invoices.length === 0) {
532
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: cls("b316-card", className), style, children: "No invoices yet." });
533
+ }
534
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cls("b316-card b316-invoices", className), style, children: [
535
+ error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-error", children: error }) : null,
536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "b316-invoice-table", children: [
537
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
538
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Invoice" }),
539
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Period" }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Status" }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "b316-num", children: "Total" }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", {})
543
+ ] }) }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: invoices.map((invoice) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
545
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: invoice.number }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("td", { className: "b316-muted", children: [
547
+ day(invoice.periodStart),
548
+ " \u2013 ",
549
+ day(invoice.periodEnd)
550
+ ] }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "b316-muted", children: invoice.status.toLowerCase() }),
552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "b316-num", children: money(invoice.total, invoice.currency) }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ className: "b316-button b316-button-link",
558
+ onClick: () => void view(invoice.id),
559
+ children: "View"
560
+ }
561
+ ) })
562
+ ] }, invoice.id)) })
563
+ ] }),
564
+ open ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(InvoiceView, { invoice: open, onClose: () => setOpen(null) }) : null
565
+ ] });
566
+ }
567
+ function InvoiceView({ invoice, onClose }) {
568
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-invoice-view", children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-invoice-actions", children: [
570
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "b316-button b316-button-secondary", onClick: onClose, children: "Close" }),
571
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
572
+ "button",
573
+ {
574
+ type: "button",
575
+ className: "b316-button b316-button-secondary",
576
+ onClick: () => window.print(),
577
+ children: "Download PDF"
578
+ }
579
+ )
580
+ ] }),
581
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-invoice-doc", children: [
582
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-row", children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
584
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-plan-name", children: invoice.issuer.brandName }),
585
+ invoice.issuer.supportEmail ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-muted", children: invoice.issuer.supportEmail }) : null
586
+ ] }),
587
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "right" }, children: [
588
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-plan-name", children: invoice.number }),
589
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-muted", children: invoice.status.toLowerCase() })
590
+ ] })
591
+ ] }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-invoice-meta", children: [
593
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-label", children: "Billed to" }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: invoice.customer.name ?? invoice.customer.email ?? "\u2014" }),
596
+ invoice.customer.name && invoice.customer.email ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-muted", children: invoice.customer.email }) : null
597
+ ] }),
598
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
599
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "b316-label", children: "Period" }),
600
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
601
+ day(invoice.periodStart),
602
+ " \u2013 ",
603
+ day(invoice.periodEnd)
604
+ ] }),
605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-muted", children: [
606
+ "Issued ",
607
+ day(invoice.issuedAt)
608
+ ] })
609
+ ] })
610
+ ] }),
611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "b316-invoice-table", children: [
612
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
613
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Description" }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "b316-num", children: "Qty" }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "b316-num", children: "Unit" }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "b316-num", children: "Amount" })
617
+ ] }) }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: invoice.lineItems.map((line) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: line.description }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("td", { className: "b316-num b316-muted", children: [
621
+ line.billable,
622
+ line.unit ? ` ${line.unit}` : ""
623
+ ] }),
624
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "b316-num b316-muted", children: line.unitAmount === null ? "\u2014" : money(line.unitAmount, invoice.currency) }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "b316-num", children: money(line.amount, invoice.currency) })
626
+ ] }, line.id)) })
627
+ ] }),
628
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-invoice-totals", children: [
629
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-row", children: [
630
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-muted", children: "Subtotal" }),
631
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-num", children: money(invoice.subtotal, invoice.currency) })
632
+ ] }),
633
+ Number(invoice.discountTotal) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-row", children: [
634
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-muted", children: "Discount" }),
635
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "b316-num", children: [
636
+ "\u2212",
637
+ money(invoice.discountTotal, invoice.currency)
638
+ ] })
639
+ ] }) : null,
640
+ Number(invoice.taxTotal) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-row", children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-muted", children: "Tax" }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-num", children: money(invoice.taxTotal, invoice.currency) })
643
+ ] }) : null,
644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-row b316-invoice-total", children: [
645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Total" }),
646
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-num", children: money(invoice.total, invoice.currency) })
647
+ ] }),
648
+ Number(invoice.amountPaid) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "b316-row", children: [
649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-muted", children: "Paid" }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "b316-num", children: money(invoice.amountPaid, invoice.currency) })
651
+ ] }) : null
652
+ ] })
653
+ ] })
654
+ ] });
655
+ }
656
+
657
+ // src/SignIn.tsx
658
+ var import_sdk2 = require("@base316/sdk");
659
+ var import_react3 = require("react");
660
+ var import_jsx_runtime4 = require("react/jsx-runtime");
661
+ function SignIn({
662
+ onSignedIn,
663
+ passkeys = true,
664
+ className,
665
+ style
666
+ }) {
667
+ const { client, setToken } = useBase316();
668
+ const [email, setEmail] = (0, import_react3.useState)("");
669
+ const [password, setPassword] = (0, import_react3.useState)("");
670
+ const [code, setCode] = (0, import_react3.useState)("");
671
+ const [pendingUserId, setPendingUserId] = (0, import_react3.useState)(null);
672
+ const [error, setError] = (0, import_react3.useState)(null);
673
+ const [busy, setBusy] = (0, import_react3.useState)(false);
674
+ const [passkeyReady, setPasskeyReady] = (0, import_react3.useState)(false);
675
+ const abortRef = (0, import_react3.useRef)(null);
676
+ const signInWithPasskey = async (conditional, signal) => {
677
+ if (!conditional) {
678
+ setBusy(true);
679
+ setError(null);
680
+ }
681
+ try {
682
+ const result = await client.auth.passkeys.signIn({ conditional, signal });
683
+ setToken(result.token);
684
+ onSignedIn?.();
685
+ } catch (cause) {
686
+ if (cause instanceof DOMException && (cause.name === "NotAllowedError" || cause.name === "AbortError")) {
687
+ return;
688
+ }
689
+ if (!conditional) {
690
+ console.error("[base316] passkey sign-in failed", cause);
691
+ setError("That passkey did not work.");
692
+ }
693
+ } finally {
694
+ if (!conditional) {
695
+ setBusy(false);
696
+ }
697
+ }
698
+ };
699
+ (0, import_react3.useEffect)(() => {
700
+ if (!passkeys || !(0, import_sdk2.isPasskeySupported)() || pendingUserId) {
701
+ return;
702
+ }
703
+ setPasskeyReady(true);
704
+ const controller = new AbortController();
705
+ abortRef.current = controller;
706
+ void (0, import_sdk2.isPasskeyAutofillAvailable)().then((available) => {
707
+ if (available && !controller.signal.aborted) {
708
+ void signInWithPasskey(true, controller.signal);
709
+ }
710
+ });
711
+ return () => {
712
+ controller.abort();
713
+ };
714
+ }, [passkeys, pendingUserId]);
715
+ async function submit(event) {
716
+ event.preventDefault();
717
+ setBusy(true);
718
+ setError(null);
719
+ try {
720
+ if (pendingUserId) {
721
+ const verified = await client.auth.verifyMfa({ userId: pendingUserId, code });
722
+ setToken(verified.token);
723
+ onSignedIn?.();
724
+ return;
725
+ }
726
+ const result = await client.auth.signIn({ email, password });
727
+ if ((0, import_sdk2.needsSecondFactor)(result)) {
728
+ setPendingUserId(result.userId);
729
+ return;
730
+ }
731
+ setToken(result.token);
732
+ onSignedIn?.();
733
+ } catch (cause) {
734
+ console.error("[base316] sign-in failed", cause);
735
+ setError(
736
+ pendingUserId ? "That code was not accepted." : "That email and password did not match."
737
+ );
738
+ } finally {
739
+ setBusy(false);
740
+ }
741
+ }
742
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: submit, className: cls("b316-card", className), style, children: [
743
+ error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "b316-error", children: error }) : null,
744
+ pendingUserId ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-code", children: "Authentication code" }),
746
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
747
+ "input",
748
+ {
749
+ id: "b316-code",
750
+ className: "b316-input",
751
+ value: code,
752
+ onChange: (event) => setCode(event.target.value),
753
+ autoComplete: "one-time-code",
754
+ inputMode: "numeric",
755
+ required: true
756
+ }
757
+ ),
758
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "b316-muted", children: "From your authenticator app, or one of your recovery codes." })
759
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
761
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-email", children: "Email" }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
763
+ "input",
764
+ {
765
+ id: "b316-email",
766
+ className: "b316-input",
767
+ type: "email",
768
+ value: email,
769
+ onChange: (event) => setEmail(event.target.value),
770
+ autoComplete: passkeyReady ? "email webauthn" : "email",
771
+ required: true
772
+ }
773
+ )
774
+ ] }),
775
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "b316-field", children: [
776
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "b316-label", htmlFor: "b316-password", children: "Password" }),
777
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
778
+ "input",
779
+ {
780
+ id: "b316-password",
781
+ className: "b316-input",
782
+ type: "password",
783
+ value: password,
784
+ onChange: (event) => setPassword(event.target.value),
785
+ autoComplete: "current-password",
786
+ required: true
787
+ }
788
+ )
789
+ ] })
790
+ ] }),
791
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "b316-button", type: "submit", disabled: busy, children: busy ? "Working\u2026" : pendingUserId ? "Verify" : "Sign in" }),
792
+ passkeyReady && !pendingUserId ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
793
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "b316-muted", style: { textAlign: "center", margin: "10px 0" }, children: "or" }),
794
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
795
+ "button",
796
+ {
797
+ type: "button",
798
+ className: "b316-button b316-button-secondary",
799
+ disabled: busy,
800
+ onClick: () => {
801
+ abortRef.current?.abort();
802
+ void signInWithPasskey(false);
803
+ },
804
+ children: "Sign in with a passkey"
805
+ }
806
+ )
807
+ ] }) : null
808
+ ] });
809
+ }
810
+
811
+ // src/SignUp.tsx
812
+ var import_react4 = require("react");
813
+ var import_jsx_runtime5 = require("react/jsx-runtime");
814
+ function SignUp({
815
+ onSignedUp,
816
+ className,
817
+ style
818
+ }) {
819
+ const { client, setToken } = useBase316();
820
+ const [name, setName] = (0, import_react4.useState)("");
821
+ const [email, setEmail] = (0, import_react4.useState)("");
822
+ const [password, setPassword] = (0, import_react4.useState)("");
823
+ const [error, setError] = (0, import_react4.useState)(null);
824
+ const [busy, setBusy] = (0, import_react4.useState)(false);
825
+ async function submit(event) {
826
+ event.preventDefault();
827
+ setBusy(true);
828
+ setError(null);
829
+ try {
830
+ await client.auth.signUp({ email, password, name: name || void 0 });
831
+ const signedIn = await client.auth.signIn({ email, password });
832
+ if ("token" in signedIn && signedIn.token) {
833
+ setToken(signedIn.token);
834
+ }
835
+ onSignedUp?.();
836
+ } catch (cause) {
837
+ console.error("[base316] sign-up failed", cause);
838
+ setError("We could not create that account. The email may already be registered.");
839
+ } finally {
840
+ setBusy(false);
841
+ }
842
+ }
843
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: submit, className: cls("b316-card", className), style, children: [
844
+ error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "b316-error", children: error }) : null,
845
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "b316-field", children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "b316-label", htmlFor: "b316-name", children: "Name" }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
848
+ "input",
849
+ {
850
+ id: "b316-name",
851
+ className: "b316-input",
852
+ value: name,
853
+ onChange: (event) => setName(event.target.value),
854
+ autoComplete: "name"
855
+ }
856
+ )
857
+ ] }),
858
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "b316-field", children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "b316-label", htmlFor: "b316-signup-email", children: "Email" }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
861
+ "input",
862
+ {
863
+ id: "b316-signup-email",
864
+ className: "b316-input",
865
+ type: "email",
866
+ value: email,
867
+ onChange: (event) => setEmail(event.target.value),
868
+ autoComplete: "email",
869
+ required: true
870
+ }
871
+ )
872
+ ] }),
873
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "b316-field", children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "b316-label", htmlFor: "b316-signup-password", children: "Password" }),
875
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
876
+ "input",
877
+ {
878
+ id: "b316-signup-password",
879
+ className: "b316-input",
880
+ type: "password",
881
+ value: password,
882
+ onChange: (event) => setPassword(event.target.value),
883
+ autoComplete: "new-password",
884
+ minLength: 8,
885
+ required: true
886
+ }
887
+ ),
888
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "b316-muted", children: "At least 8 characters." })
889
+ ] }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "b316-button", type: "submit", disabled: busy, children: busy ? "Creating\u2026" : "Create account" })
891
+ ] });
892
+ }
893
+
894
+ // src/UserProfile.tsx
895
+ var import_jsx_runtime6 = require("react/jsx-runtime");
896
+ function UserProfile({ className, style }) {
897
+ const { session, loading, signOut } = useBase316();
898
+ if (loading) {
899
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cls("b316-card", className), style, children: "Loading\u2026" });
900
+ }
901
+ if (!session) {
902
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cls("b316-card", className), style, children: "Not signed in." });
903
+ }
904
+ const user = session.user ?? {};
905
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cls("b316-card", className), style, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "b316-row", children: [
906
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
907
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontWeight: 600 }, children: user.name || user.email || "Signed in" }),
908
+ user.name && user.email ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "b316-muted", children: user.email }) : null,
909
+ user.emailVerified === false ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "b316-muted", children: "Email not verified" }) : null
910
+ ] }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ className: "b316-button b316-button-secondary",
916
+ onClick: () => void signOut(),
917
+ children: "Sign out"
918
+ }
919
+ )
920
+ ] }) });
921
+ }
922
+
923
+ // src/PasskeyManager.tsx
924
+ var import_sdk3 = require("@base316/sdk");
925
+ var import_react5 = require("react");
926
+ var import_jsx_runtime7 = require("react/jsx-runtime");
927
+ function PasskeyManager({
928
+ className,
929
+ style,
930
+ title = "Passkeys"
931
+ }) {
932
+ const { client, token, session } = useBase316();
933
+ const [passkeys, setPasskeys] = (0, import_react5.useState)(null);
934
+ const [busy, setBusy] = (0, import_react5.useState)(false);
935
+ const [error, setError] = (0, import_react5.useState)(null);
936
+ const [name, setName] = (0, import_react5.useState)("");
937
+ const [supported, setSupported] = (0, import_react5.useState)(false);
938
+ const load = (0, import_react5.useCallback)(async () => {
939
+ if (!token) {
940
+ return;
941
+ }
942
+ try {
943
+ const result = await client.auth.passkeys.list(token);
944
+ setPasskeys(result.data);
945
+ } catch {
946
+ setPasskeys([]);
947
+ }
948
+ }, [client, token]);
949
+ (0, import_react5.useEffect)(() => {
950
+ if (!(0, import_sdk3.isPasskeySupported)()) {
951
+ return;
952
+ }
953
+ setSupported(true);
954
+ void load();
955
+ }, [load]);
956
+ if (!session || !token || !supported) {
957
+ return null;
958
+ }
959
+ const add = async () => {
960
+ setBusy(true);
961
+ setError(null);
962
+ try {
963
+ await client.auth.passkeys.register(token, { name: name.trim() || void 0 });
964
+ setName("");
965
+ await load();
966
+ } catch (caught) {
967
+ if (caught instanceof DOMException && caught.name === "NotAllowedError") {
968
+ setError(null);
969
+ } else {
970
+ setError(caught instanceof Error ? caught.message : "That did not work.");
971
+ }
972
+ } finally {
973
+ setBusy(false);
974
+ }
975
+ };
976
+ const remove = async (id) => {
977
+ setError(null);
978
+ try {
979
+ await client.auth.passkeys.remove(token, id);
980
+ await load();
981
+ } catch (caught) {
982
+ setError(caught instanceof Error ? caught.message : "That did not work.");
983
+ }
984
+ };
985
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: cls("b316-card", className), style, children: [
986
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: title }),
987
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "b316-muted", style: { marginBottom: 12 }, children: "Sign in with your fingerprint, face or a security key \u2014 nothing to type and nothing to remember." }),
988
+ passkeys === null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "b316-muted", children: "Loading\u2026" }) : passkeys.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "b316-muted", children: "No passkeys yet." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: passkeys.map((passkey) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { className: "b316-row", style: { padding: "8px 0" }, children: [
989
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: passkey.name || "Passkey" }),
991
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "b316-muted", children: [
992
+ passkey.lastUsedAt ? `Last used ${formatDate(passkey.lastUsedAt)}` : `Added ${formatDate(passkey.createdAt)}`,
993
+ passkey.backedUp ? "" : " \xB7 this device only"
994
+ ] })
995
+ ] }),
996
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
997
+ "button",
998
+ {
999
+ type: "button",
1000
+ className: "b316-button b316-button-secondary",
1001
+ onClick: () => void remove(passkey.id),
1002
+ children: "Remove"
1003
+ }
1004
+ )
1005
+ ] }, passkey.id)) }),
1006
+ error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "b316-error", style: { marginTop: 12 }, children: error }) : null,
1007
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "b316-row", style: { marginTop: 12, gap: 8 }, children: [
1008
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1009
+ "input",
1010
+ {
1011
+ className: "b316-input",
1012
+ placeholder: "Name this passkey (optional)",
1013
+ value: name,
1014
+ onChange: (event) => setName(event.target.value)
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1018
+ "button",
1019
+ {
1020
+ type: "button",
1021
+ className: "b316-button",
1022
+ disabled: busy,
1023
+ onClick: () => void add(),
1024
+ children: busy ? "Waiting\u2026" : "Add a passkey"
1025
+ }
1026
+ )
1027
+ ] })
1028
+ ] });
1029
+ }
1030
+ function formatDate(value) {
1031
+ const date = new Date(value);
1032
+ return Number.isNaN(date.getTime()) ? "" : date.toLocaleDateString();
1033
+ }
1034
+
1035
+ // src/PricingTable.tsx
1036
+ var import_react6 = require("react");
1037
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1038
+ function formatAmount(amount, currency) {
1039
+ if (amount === null) return "Free";
1040
+ const value = Number(amount);
1041
+ if (!Number.isFinite(value)) return "Free";
1042
+ try {
1043
+ return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(value);
1044
+ } catch {
1045
+ return `${value} ${currency}`;
1046
+ }
1047
+ }
1048
+ function cadence(plan, amount) {
1049
+ if (amount === null) {
1050
+ return "\xA0";
1051
+ }
1052
+ const unit = plan.interval.toLowerCase();
1053
+ return plan.intervalCount === 1 ? `per ${unit}` : `every ${plan.intervalCount} ${unit}s`;
1054
+ }
1055
+ function PricingTable({
1056
+ onSelectPlan,
1057
+ currentPlanVersionId,
1058
+ featuredPlanKey,
1059
+ className,
1060
+ style
1061
+ }) {
1062
+ const { client } = useBase316();
1063
+ const [plans, setPlans] = (0, import_react6.useState)(null);
1064
+ const [error, setError] = (0, import_react6.useState)(false);
1065
+ const [busy, setBusy] = (0, import_react6.useState)(null);
1066
+ (0, import_react6.useEffect)(() => {
1067
+ let cancelled = false;
1068
+ client.billing.plans().then((result) => {
1069
+ if (!cancelled) setPlans(result.data);
1070
+ }).catch((cause) => {
1071
+ console.error("[base316] could not load plans", cause);
1072
+ if (!cancelled) setError(true);
1073
+ });
1074
+ return () => {
1075
+ cancelled = true;
1076
+ };
1077
+ }, [client]);
1078
+ if (error) {
1079
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cls("b316-card", className), style, children: "Plans are unavailable right now." });
1080
+ }
1081
+ if (!plans) {
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cls("b316-card", className), style, children: "Loading plans\u2026" });
1083
+ }
1084
+ if (plans.length === 0) {
1085
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cls("b316-card", className), style, children: "No plans are published yet." });
1086
+ }
1087
+ const anyTrial = plans.some((plan) => Boolean(plan.trialDays));
1088
+ const anyDescription = plans.some((plan) => Boolean(plan.description));
1089
+ const anyFeatured = plans.some((plan) => plan.key === featuredPlanKey);
1090
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cls("b316-plans", className), style, children: plans.map((plan) => {
1091
+ const base = plan.prices.find((price) => price.featureId === null) ?? plan.prices[0];
1092
+ const current = currentPlanVersionId === plan.planVersionId;
1093
+ const featured = featuredPlanKey === plan.key;
1094
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1095
+ "div",
1096
+ {
1097
+ className: featured ? "b316-card b316-plan b316-plan-featured" : "b316-card b316-plan",
1098
+ children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "b316-plan-head", children: [
1100
+ anyFeatured ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: featured ? "b316-plan-badge" : "b316-plan-badge-spacer", children: featured ? "Recommended" : "\xA0" }) : null,
1101
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "b316-plan-name", children: plan.name }),
1102
+ anyDescription ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "b316-plan-desc", children: plan.description ?? "\xA0" }) : null
1103
+ ] }),
1104
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "b316-plan-pricing", children: [
1105
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "b316-plan-price", children: formatAmount(base?.amount ?? null, plan.currency) }),
1106
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "b316-plan-cadence", children: cadence(plan, base?.amount ?? null) }),
1107
+ anyTrial ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "b316-plan-trial", children: plan.trialDays ? `${plan.trialDays}-day free trial` : "\xA0" }) : null
1108
+ ] }),
1109
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "b316-plan-features", children: plan.entitlements.filter((entitlement) => entitlement.enabled).map((entitlement) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { className: "b316-plan-feature", children: [
1110
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: entitlement.featureName }),
1111
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "b316-plan-feature-value", children: entitlement.limit === null ? "Unlimited" : entitlement.limit })
1112
+ ] }, entitlement.featureId)) }),
1113
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "b316-plan-action", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1114
+ "button",
1115
+ {
1116
+ type: "button",
1117
+ className: current ? "b316-button b316-button-current" : "b316-button",
1118
+ disabled: current || busy !== null,
1119
+ onClick: async () => {
1120
+ setBusy(plan.planVersionId);
1121
+ try {
1122
+ await onSelectPlan(plan.planVersionId, plan);
1123
+ } finally {
1124
+ setBusy(null);
1125
+ }
1126
+ },
1127
+ children: current ? "Current plan" : busy === plan.planVersionId ? "Working\u2026" : "Choose"
1128
+ }
1129
+ ) })
1130
+ ]
1131
+ },
1132
+ plan.id
1133
+ );
1134
+ }) });
1135
+ }
1136
+
1137
+ // src/SubscriptionManager.tsx
1138
+ var import_react7 = require("react");
1139
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1140
+ function statusLabel(status) {
1141
+ const words = status.toLowerCase().split("_");
1142
+ return words.map((w, i) => i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w).join(" ");
1143
+ }
1144
+ function SubscriptionManager({
1145
+ className,
1146
+ style
1147
+ }) {
1148
+ const { client, token, loading: sessionLoading } = useBase316();
1149
+ const [subscription, setSubscription] = (0, import_react7.useState)(null);
1150
+ const [loading, setLoading] = (0, import_react7.useState)(true);
1151
+ const [busy, setBusy] = (0, import_react7.useState)(false);
1152
+ const [error, setError] = (0, import_react7.useState)(null);
1153
+ const load = (0, import_react7.useCallback)(async () => {
1154
+ if (!token) {
1155
+ setSubscription(null);
1156
+ setLoading(false);
1157
+ return;
1158
+ }
1159
+ try {
1160
+ const result = await client.billing.subscription(token);
1161
+ setSubscription(result.subscription);
1162
+ } catch (cause) {
1163
+ console.error("[base316] could not load the subscription", cause);
1164
+ setError("Could not load your subscription.");
1165
+ } finally {
1166
+ setLoading(false);
1167
+ }
1168
+ }, [client, token]);
1169
+ (0, import_react7.useEffect)(() => {
1170
+ void load();
1171
+ }, [load]);
1172
+ if (sessionLoading || loading) {
1173
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cls("b316-card", className), style, children: "Loading\u2026" });
1174
+ }
1175
+ if (!token) {
1176
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cls("b316-card", className), style, children: "Sign in to manage your subscription." });
1177
+ }
1178
+ if (!subscription) {
1179
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cls("b316-card", className), style, children: "You are not subscribed to a plan." });
1180
+ }
1181
+ const renews = new Date(subscription.currentPeriodEnd).toLocaleDateString();
1182
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: cls("b316-card", className), style, children: [
1183
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "b316-error", children: error }) : null,
1184
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "b316-plan-name", children: subscription.planVersion.plan.name }),
1185
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "b316-muted", children: [
1186
+ statusLabel(subscription.status),
1187
+ " \xB7 ",
1188
+ subscription.cancelAtPeriodEnd ? `Ends ${renews}` : `Renews ${renews}`
1189
+ ] }),
1190
+ subscription.cancelAtPeriodEnd ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { className: "b316-muted", children: [
1191
+ "This subscription will not renew. You keep access until ",
1192
+ renews,
1193
+ "."
1194
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1195
+ "button",
1196
+ {
1197
+ type: "button",
1198
+ className: "b316-button b316-button-secondary",
1199
+ style: { marginTop: 14 },
1200
+ disabled: busy,
1201
+ onClick: async () => {
1202
+ setBusy(true);
1203
+ setError(null);
1204
+ try {
1205
+ await client.billing.cancel(token);
1206
+ await load();
1207
+ } catch (cause) {
1208
+ console.error("[base316] cancel failed", cause);
1209
+ setError("Could not cancel. Try again.");
1210
+ } finally {
1211
+ setBusy(false);
1212
+ }
1213
+ },
1214
+ children: busy ? "Working\u2026" : "Cancel subscription"
1215
+ }
1216
+ )
1217
+ ] });
1218
+ }
1219
+ // Annotate the CommonJS export names for ESM import in node:
1220
+ 0 && (module.exports = {
1221
+ Base316Provider,
1222
+ Branding,
1223
+ Invoices,
1224
+ PasskeyManager,
1225
+ PricingTable,
1226
+ SignIn,
1227
+ SignUp,
1228
+ SubscriptionManager,
1229
+ UserProfile,
1230
+ useBase316,
1231
+ useSession
1232
+ });
1233
+ //# sourceMappingURL=index.cjs.map