@kerne/react 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2984 @@
1
+ "use client";
2
+ import {
3
+ defaultLocalization,
4
+ fill,
5
+ fillNode,
6
+ useAccess,
7
+ useAuth,
8
+ useAuthConfig,
9
+ useCheckout,
10
+ useClient,
11
+ useEntitlements,
12
+ useErrorResolver,
13
+ useLocalization,
14
+ usePlans,
15
+ usePortal,
16
+ useSubscription,
17
+ useUser,
18
+ useWaitlist
19
+ } from "../chunk-AIUIBAYB.js";
20
+
21
+ // src/ui/AuthFlow.tsx
22
+ import { useState as useState9 } from "react";
23
+
24
+ // src/ui/useUrlToken.ts
25
+ import { useState, useEffect } from "react";
26
+ function useUrlToken(param = "token", override) {
27
+ const [token, setToken] = useState(override ?? null);
28
+ useEffect(() => {
29
+ if (override) {
30
+ setToken(override);
31
+ return;
32
+ }
33
+ if (typeof window === "undefined") return;
34
+ setToken(new URLSearchParams(window.location.search).get(param) ?? "");
35
+ }, [param, override]);
36
+ return token;
37
+ }
38
+
39
+ // src/ui/LoginForm.tsx
40
+ import { useState as useState2 } from "react";
41
+
42
+ // src/ui/primitives.tsx
43
+ import { useId, useContext, createContext } from "react";
44
+
45
+ // src/ui/styles.ts
46
+ var STYLE_ELEMENT_ID = "kerne-ui-styles";
47
+ var DARK_TOKENS = `
48
+ --kerne-fg: #f0f1f3;
49
+ --kerne-fg-muted: #9ba2ad;
50
+ --kerne-fg-subtle: #838b96;
51
+ --kerne-border: #2a2e36;
52
+ --kerne-border-strong: #363b45;
53
+ --kerne-panel: #1b1f26;
54
+ --kerne-surface: #14171c;
55
+ --kerne-surface-raised: #2a2f38;
56
+ --kerne-primary: #f0f1f3;
57
+ --kerne-primary-fg: #16181d;
58
+ --kerne-primary-hover: #d8dade;
59
+ --kerne-accent: #91a7ff;
60
+ --kerne-danger: #f0837a;
61
+ --kerne-success: #5cbd8d;
62
+ --kerne-warning: #d9ab5e;
63
+ --kerne-shadow: 0 4px 6px -2px rgba(0,0,0,.4), 0 12px 24px -8px rgba(0,0,0,.6);
64
+ `;
65
+ var CSS = `
66
+ .kerne-root {
67
+ --kerne-fg: #16171a;
68
+ --kerne-fg-muted: #6b7280;
69
+ /* Meets 4.5:1 on white - this carries placeholders and the legal footnote,
70
+ which used to sit around 2.5:1. */
71
+ --kerne-fg-subtle: #6f7681;
72
+ --kerne-border: #e3e5e9;
73
+ --kerne-border-strong: #d6dade;
74
+ --kerne-field-bg: transparent;
75
+ --kerne-panel: #f4f5f7;
76
+ /* The ground the component sits on. Transparent so an embedded instance
77
+ inherits the host's page; set it (appearance.background) when the component
78
+ owns the viewport, so text and background can never resolve independently. */
79
+ --kerne-bg: transparent;
80
+ /* An opaque surface for the things that must not show through: the card
81
+ variant, the dropdown floating over host content, a pressed segment. */
82
+ --kerne-surface: #ffffff;
83
+ --kerne-surface-raised: #ffffff;
84
+ --kerne-primary: #16171a;
85
+ --kerne-primary-fg: #ffffff;
86
+ --kerne-primary-hover: #2c2e33;
87
+ --kerne-accent: #3b5bdb;
88
+ --kerne-danger: #c0392f;
89
+ --kerne-success: #1f7a4d;
90
+ --kerne-warning: #9a6b12;
91
+ --kerne-radius: 8px;
92
+ --kerne-control-height: 44px;
93
+ --kerne-shadow: 0 4px 6px -2px rgba(0,0,0,.06), 0 12px 24px -8px rgba(0,0,0,.16);
94
+ --kerne-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
95
+
96
+ box-sizing: border-box;
97
+ font-family: var(--kerne-font);
98
+ color: var(--kerne-fg);
99
+ background: var(--kerne-bg);
100
+ -webkit-font-smoothing: antialiased;
101
+ display: flex;
102
+ justify-content: center;
103
+ width: 100%;
104
+ }
105
+ .kerne-root *, .kerne-root *::before, .kerne-root *::after { box-sizing: inherit; }
106
+
107
+ .kerne-root[data-kerne-theme="dark"] {${DARK_TOKENS}}
108
+ /* Only an explicit opt-in follows the visitor's OS. Defaulting to it meant a
109
+ light-only host got a dark form on any machine set to dark, without ever
110
+ asking for one - and with no way for the host to know it had happened. */
111
+ @media (prefers-color-scheme: dark) {
112
+ .kerne-root[data-kerne-theme="system"] {${DARK_TOKENS}}
113
+ }
114
+
115
+ .kerne-shell {
116
+ width: 100%;
117
+ max-width: 384px;
118
+ display: flex;
119
+ flex-direction: column;
120
+ }
121
+ /* Auth screens are a single column of fields; an account manager is a
122
+ navigation plus a content pane and suffocates at 384px. */
123
+ .kerne-shell[data-kerne-size="wide"] { max-width: 800px; }
124
+
125
+ .kerne-shell[data-kerne-surface="card"] {
126
+ background: var(--kerne-surface);
127
+ border: 1px solid var(--kerne-border);
128
+ border-radius: calc(var(--kerne-radius) + 6px);
129
+ box-shadow: var(--kerne-shadow);
130
+ padding: 32px 28px;
131
+ /* The card is the ground from here down, so nested surfaces stop inheriting
132
+ the page behind it. */
133
+ --kerne-bg: var(--kerne-surface);
134
+ }
135
+ @media (max-width: 480px) {
136
+ /* Full-bleed on a phone: a card with 16px of page showing on each side wastes
137
+ the only axis a login form has. */
138
+ .kerne-shell[data-kerne-surface="card"] {
139
+ border: none;
140
+ box-shadow: none;
141
+ padding: 8px 0;
142
+ background: transparent;
143
+ /* The card no longer paints, so the ground is the page again. */
144
+ --kerne-bg: transparent;
145
+ }
146
+ }
147
+
148
+ /* ---------- Tabbed account manager ---------- */
149
+ .kerne-profile { display: grid; grid-template-columns: 188px minmax(0, 1fr); gap: 40px; align-items: start; }
150
+ @media (max-width: 640px) { .kerne-profile { grid-template-columns: 1fr; gap: 20px; } }
151
+
152
+ .kerne-nav { display: flex; flex-direction: column; gap: 2px; position: sticky; top: 24px; }
153
+ @media (max-width: 640px) {
154
+ .kerne-nav {
155
+ position: static;
156
+ flex-direction: row;
157
+ overflow-x: auto;
158
+ gap: 4px;
159
+ border-bottom: 1px solid var(--kerne-border);
160
+ padding-bottom: 8px;
161
+ }
162
+ }
163
+ .kerne-nav-item {
164
+ font: inherit; font-size: 14px; text-align: left;
165
+ background: none; border: none; cursor: pointer;
166
+ padding: 8px 12px; border-radius: 6px;
167
+ color: var(--kerne-fg-muted); white-space: nowrap;
168
+ transition: background-color .12s ease, color .12s ease;
169
+ }
170
+ .kerne-nav-item:hover { color: var(--kerne-fg); }
171
+ .kerne-nav-item[aria-current="page"] { background: var(--kerne-panel); color: var(--kerne-fg); font-weight: 500; }
172
+ .kerne-nav-item:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; }
173
+
174
+ .kerne-pane { display: flex; flex-direction: column; min-width: 0; }
175
+ .kerne-pane-title { font-size: 20px; font-weight: 600; letter-spacing: -.01em; margin: 0 0 4px; }
176
+ .kerne-pane-desc { font-size: 14px; color: var(--kerne-fg-muted); margin: 0 0 8px; }
177
+
178
+ .kerne-subsection { padding: 24px 0; display: flex; flex-direction: column; gap: 16px; }
179
+ .kerne-subsection:first-of-type { padding-top: 16px; }
180
+ .kerne-subsection + .kerne-subsection { border-top: 1px solid var(--kerne-border); }
181
+ .kerne-subsection-head { display: flex; flex-direction: column; gap: 3px; }
182
+ .kerne-subsection-title { font-size: 15px; font-weight: 600; margin: 0; }
183
+ .kerne-subsection-desc { font-size: 13px; color: var(--kerne-fg-muted); margin: 0; }
184
+ /* Buttons inside a settings pane are actions, not page-wide calls to action. */
185
+ .kerne-subsection .kerne-button { width: auto; align-self: flex-start; }
186
+
187
+ /* ---------- Plan options ---------- */
188
+ .kerne-plan-list { display: flex; flex-direction: column; gap: 10px; }
189
+ .kerne-plan-option {
190
+ display: flex; justify-content: space-between; align-items: center; gap: 16px;
191
+ padding: 14px 16px; border: 1px solid var(--kerne-border); border-radius: var(--kerne-radius);
192
+ }
193
+ .kerne-plan-option[data-current="true"] { border-color: var(--kerne-fg); }
194
+ .kerne-plan-option-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
195
+ .kerne-plan-option-name { font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 8px; }
196
+ .kerne-plan-option-price { font-size: 13px; color: var(--kerne-fg-muted); font-variant-numeric: tabular-nums; }
197
+ .kerne-plan-option .kerne-button { width: auto; flex-shrink: 0; height: 36px; font-size: 13px; }
198
+
199
+ /* Segmented control for billing interval. */
200
+ .kerne-segmented { display: inline-flex; padding: 3px; gap: 2px; background: var(--kerne-panel); border-radius: var(--kerne-radius); align-self: flex-start; }
201
+ .kerne-segment {
202
+ font: inherit; font-size: 13px; border: none; background: none; cursor: pointer;
203
+ padding: 5px 12px; border-radius: calc(var(--kerne-radius) - 3px); color: var(--kerne-fg-muted);
204
+ }
205
+ .kerne-segment[aria-pressed="true"] { background: var(--kerne-surface-raised); color: var(--kerne-fg); font-weight: 500; }
206
+ .kerne-segment:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; }
207
+
208
+ /* ---------- Masthead ---------- */
209
+ .kerne-masthead {
210
+ display: flex;
211
+ flex-direction: column;
212
+ align-items: center;
213
+ gap: 16px;
214
+ text-align: center;
215
+ margin-bottom: 32px;
216
+ }
217
+ .kerne-logo { display: flex; align-items: center; justify-content: center; }
218
+ .kerne-heading { display: flex; flex-direction: column; gap: 6px; }
219
+ .kerne-title {
220
+ margin: 0;
221
+ font-size: 24px;
222
+ font-weight: 600;
223
+ letter-spacing: -.02em;
224
+ line-height: 1.2;
225
+ text-wrap: balance;
226
+ }
227
+ .kerne-subtitle { margin: 0; font-size: 14px; color: var(--kerne-fg-muted); line-height: 1.5; }
228
+
229
+ /* ---------- Stack ---------- */
230
+ .kerne-stack { display: flex; flex-direction: column; gap: 16px; }
231
+ .kerne-form { display: flex; flex-direction: column; gap: 12px; }
232
+ .kerne-field { display: flex; flex-direction: column; gap: 6px; }
233
+ .kerne-label { font-size: 14px; font-weight: 500; color: var(--kerne-fg); }
234
+ .kerne-label-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
235
+
236
+ .kerne-input {
237
+ font: inherit;
238
+ font-size: 14px;
239
+ height: var(--kerne-control-height);
240
+ color: var(--kerne-fg);
241
+ background: var(--kerne-field-bg);
242
+ border: 1px solid var(--kerne-border-strong);
243
+ border-radius: var(--kerne-radius);
244
+ padding: 0 13px;
245
+ width: 100%;
246
+ transition: border-color .12s ease, box-shadow .12s ease;
247
+ }
248
+ .kerne-input::placeholder { color: var(--kerne-fg-subtle); }
249
+ .kerne-input:hover:not(:disabled):not(:focus) { border-color: var(--kerne-fg-subtle); }
250
+ .kerne-input:focus {
251
+ outline: none;
252
+ border-color: var(--kerne-accent);
253
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--kerne-accent) 15%, transparent);
254
+ }
255
+ .kerne-input:disabled { opacity: .6; cursor: not-allowed; }
256
+ .kerne-input[aria-invalid="true"] { border-color: var(--kerne-danger); }
257
+ .kerne-input[data-kerne-code="true"] {
258
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
259
+ letter-spacing: .3em;
260
+ text-transform: uppercase;
261
+ }
262
+
263
+ /* ---------- Buttons ---------- */
264
+ .kerne-button {
265
+ font: inherit;
266
+ font-size: 14px;
267
+ font-weight: 500;
268
+ height: var(--kerne-control-height);
269
+ border-radius: var(--kerne-radius);
270
+ padding: 0 16px;
271
+ cursor: pointer;
272
+ border: 1px solid transparent;
273
+ display: inline-flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ gap: 10px;
277
+ width: 100%;
278
+ transition: background-color .12s ease, border-color .12s ease, opacity .12s ease;
279
+ }
280
+ .kerne-button:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; }
281
+ .kerne-button:disabled { opacity: .5; cursor: not-allowed; }
282
+ .kerne-button-primary { background: var(--kerne-primary); color: var(--kerne-primary-fg); }
283
+ .kerne-button-primary:hover:not(:disabled) { background: var(--kerne-primary-hover); }
284
+ .kerne-button-outline {
285
+ background: transparent;
286
+ color: var(--kerne-fg);
287
+ border-color: var(--kerne-border-strong);
288
+ }
289
+ .kerne-button-outline:hover:not(:disabled) { background: var(--kerne-panel); }
290
+
291
+ /* Method switch - a quiet text control, not a second call to action competing
292
+ with the primary button above it. */
293
+ .kerne-switch {
294
+ font: inherit;
295
+ font-size: 14px;
296
+ width: 100%;
297
+ text-align: center;
298
+ background: none;
299
+ border: none;
300
+ padding: 4px 0;
301
+ cursor: pointer;
302
+ color: var(--kerne-fg-muted);
303
+ text-underline-offset: 4px;
304
+ transition: color .12s ease;
305
+ }
306
+ .kerne-switch:hover { color: var(--kerne-fg); text-decoration: underline; }
307
+ .kerne-switch:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; border-radius: 4px; }
308
+
309
+ .kerne-link {
310
+ font-size: inherit;
311
+ color: var(--kerne-accent);
312
+ background: none;
313
+ border: none;
314
+ padding: 0;
315
+ cursor: pointer;
316
+ font-family: inherit;
317
+ text-decoration: none;
318
+ text-underline-offset: 3px;
319
+ }
320
+ .kerne-link:hover { text-decoration: underline; }
321
+ .kerne-link:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; border-radius: 2px; }
322
+
323
+ /* ---------- Divider ---------- */
324
+ .kerne-divider {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 12px;
328
+ color: var(--kerne-fg-muted);
329
+ font-size: 11px;
330
+ text-transform: uppercase;
331
+ letter-spacing: .06em;
332
+ padding: 2px 0;
333
+ }
334
+ .kerne-divider::before, .kerne-divider::after {
335
+ content: "";
336
+ flex: 1;
337
+ height: 1px;
338
+ background: var(--kerne-border);
339
+ }
340
+
341
+ /* ---------- Panels ---------- */
342
+ .kerne-panel {
343
+ display: flex;
344
+ align-items: flex-start;
345
+ gap: 10px;
346
+ font-size: 14px;
347
+ line-height: 1.5;
348
+ padding: 12px 14px;
349
+ border-radius: var(--kerne-radius);
350
+ background: var(--kerne-panel);
351
+ color: var(--kerne-fg-muted);
352
+ }
353
+ .kerne-panel-danger { color: var(--kerne-danger); }
354
+ .kerne-panel-success { color: var(--kerne-success); }
355
+ .kerne-panel svg { flex-shrink: 0; margin-top: 1px; }
356
+ .kerne-panel strong { color: var(--kerne-fg); font-weight: 500; }
357
+
358
+ .kerne-oauth-group { display: flex; flex-direction: column; gap: 10px; }
359
+
360
+ /* A closing line under a form, e.g. "Remembered it? Back to sign in". */
361
+ .kerne-footer { font-size: 14px; color: var(--kerne-fg-muted); text-align: center; margin: 4px 0 0; }
362
+
363
+ /* ---------- Footnotes ---------- */
364
+ .kerne-legal {
365
+ font-size: 12px;
366
+ line-height: 1.5;
367
+ color: var(--kerne-fg-subtle);
368
+ text-align: center;
369
+ margin: 32px 0 0;
370
+ }
371
+ .kerne-branding {
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: center;
375
+ gap: 5px;
376
+ margin-top: 24px;
377
+ font-size: 12px;
378
+ color: var(--kerne-fg-subtle);
379
+ text-decoration: none;
380
+ }
381
+ .kerne-branding:hover { color: var(--kerne-fg-muted); }
382
+
383
+ /* Centred outcome screen for the flows that resolve on their own (magic-link
384
+ landing, email verification by link) - there is no form to show, only what
385
+ happened. */
386
+ .kerne-status { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; padding: 8px 0; }
387
+ .kerne-status-icon { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 50%; background: var(--kerne-panel); }
388
+ .kerne-status-icon[data-tone="danger"] { color: var(--kerne-danger); }
389
+ .kerne-status-icon[data-tone="success"] { color: var(--kerne-success); }
390
+ .kerne-status-icon[data-tone="muted"] { color: var(--kerne-fg-muted); }
391
+ .kerne-status-text { font-size: 14px; color: var(--kerne-fg-muted); line-height: 1.5; margin: 0; max-width: 34ch; }
392
+
393
+ .kerne-spinner {
394
+ width: 20px; height: 20px;
395
+ border: 2px solid currentColor;
396
+ border-right-color: transparent;
397
+ border-radius: 50%;
398
+ animation: kerne-spin .6s linear infinite;
399
+ }
400
+ @keyframes kerne-spin { to { transform: rotate(360deg); } }
401
+
402
+ .kerne-button-danger {
403
+ background: transparent;
404
+ color: var(--kerne-danger);
405
+ border-color: color-mix(in srgb, var(--kerne-danger) 40%, transparent);
406
+ }
407
+ .kerne-button-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--kerne-danger) 8%, transparent); }
408
+
409
+ /* ---------- Usage meters ---------- */
410
+ .kerne-meters { display: flex; flex-direction: column; gap: 18px; }
411
+ .kerne-meter { display: flex; flex-direction: column; gap: 7px; }
412
+ .kerne-meter-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
413
+ .kerne-meter-name { font-size: 14px; font-weight: 500; }
414
+ .kerne-meter-value {
415
+ font-size: 13px;
416
+ color: var(--kerne-fg-muted);
417
+ /* Digits line up between rows instead of jittering as counts change. */
418
+ font-variant-numeric: tabular-nums;
419
+ }
420
+ .kerne-meter-track { height: 6px; border-radius: 999px; background: var(--kerne-panel); overflow: hidden; }
421
+ .kerne-meter-fill { height: 100%; border-radius: 999px; background: var(--kerne-fg); transition: width .3s ease; }
422
+ .kerne-meter[data-level="warning"] .kerne-meter-fill { background: var(--kerne-warning); }
423
+ .kerne-meter[data-level="critical"] .kerne-meter-fill { background: var(--kerne-danger); }
424
+ .kerne-meter[data-level="warning"] .kerne-meter-value { color: var(--kerne-warning); }
425
+ .kerne-meter[data-level="critical"] .kerne-meter-value { color: var(--kerne-danger); }
426
+ .kerne-meter-note { font-size: 12px; color: var(--kerne-fg-subtle); margin: 0; }
427
+ @media (prefers-reduced-motion: reduce) { .kerne-meter-fill { transition: none; } }
428
+
429
+ /* ---------- Sections (profile, settings) ---------- */
430
+ .kerne-section-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
431
+ .kerne-section-title { font-size: 15px; font-weight: 600; margin: 0; }
432
+
433
+ .kerne-rows { display: flex; flex-direction: column; gap: 10px; }
434
+ .kerne-row { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; font-size: 14px; }
435
+ .kerne-row dt { color: var(--kerne-fg-muted); margin: 0; }
436
+ .kerne-row dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
437
+
438
+ .kerne-badge {
439
+ display: inline-flex;
440
+ align-items: center;
441
+ font-size: 12px;
442
+ font-weight: 500;
443
+ padding: 2px 8px;
444
+ border-radius: 999px;
445
+ background: var(--kerne-panel);
446
+ color: var(--kerne-fg-muted);
447
+ white-space: nowrap;
448
+ }
449
+ .kerne-badge[data-tone="success"] { color: var(--kerne-success); }
450
+ .kerne-badge[data-tone="warning"] { color: var(--kerne-warning); }
451
+ .kerne-badge[data-tone="danger"] { color: var(--kerne-danger); }
452
+
453
+ /* ---------- Identity ---------- */
454
+ .kerne-avatar {
455
+ width: 32px; height: 32px; border-radius: 50%;
456
+ background: var(--kerne-panel); color: var(--kerne-fg-muted);
457
+ display: flex; align-items: center; justify-content: center;
458
+ font-size: 12px; font-weight: 600; letter-spacing: .02em; flex-shrink: 0;
459
+ object-fit: cover;
460
+ }
461
+ .kerne-identity { display: flex; align-items: center; gap: 12px; }
462
+ .kerne-identity-text { display: flex; flex-direction: column; min-width: 0; }
463
+ .kerne-identity-name { font-size: 14px; font-weight: 500; }
464
+ .kerne-identity-mail { font-size: 13px; color: var(--kerne-fg-muted); overflow: hidden; text-overflow: ellipsis; }
465
+
466
+ .kerne-menu-wrap { position: relative; display: inline-flex; }
467
+ .kerne-menu-trigger {
468
+ background: none; border: none; padding: 0; cursor: pointer;
469
+ border-radius: 50%; display: inline-flex; font: inherit;
470
+ }
471
+ .kerne-menu-trigger:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; }
472
+ .kerne-menu {
473
+ position: absolute; top: calc(100% + 8px); right: 0; z-index: 50;
474
+ min-width: 240px; padding: 8px;
475
+ display: flex; flex-direction: column; gap: 2px;
476
+ /* Needs an opaque ground of its own: it floats over host content. */
477
+ background: var(--kerne-surface);
478
+ border: 1px solid var(--kerne-border);
479
+ border-radius: var(--kerne-radius);
480
+ box-shadow: var(--kerne-shadow);
481
+ }
482
+ .kerne-menu-header { padding: 8px 10px 10px; border-bottom: 1px solid var(--kerne-border); margin-bottom: 6px; }
483
+ .kerne-menu-item {
484
+ font: inherit; font-size: 14px; text-align: left;
485
+ background: none; border: none; cursor: pointer;
486
+ padding: 8px 10px; border-radius: 6px; color: var(--kerne-fg);
487
+ display: block; width: 100%; text-decoration: none;
488
+ }
489
+ .kerne-menu-item:hover { background: var(--kerne-panel); }
490
+ .kerne-menu-item:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: -2px; }
491
+ .kerne-menu-item[data-tone="danger"] { color: var(--kerne-danger); }
492
+
493
+ /* ---------- Loading ---------- */
494
+ .kerne-skeleton {
495
+ background: var(--kerne-panel);
496
+ border-radius: var(--kerne-radius);
497
+ height: var(--kerne-control-height);
498
+ animation: kerne-pulse 1.6s ease-in-out infinite;
499
+ }
500
+ @keyframes kerne-pulse { 50% { opacity: .55; } }
501
+ @media (prefers-reduced-motion: reduce) {
502
+ .kerne-skeleton { animation: none; }
503
+ .kerne-spinner { animation-duration: 2s; }
504
+ .kerne-input, .kerne-button, .kerne-switch { transition: none; }
505
+ }
506
+ `;
507
+ function ensureStylesInjected() {
508
+ if (typeof document === "undefined") return;
509
+ if (document.getElementById(STYLE_ELEMENT_ID)) return;
510
+ const style = document.createElement("style");
511
+ style.id = STYLE_ELEMENT_ID;
512
+ style.textContent = CSS;
513
+ document.head.appendChild(style);
514
+ }
515
+ function parseColor(input) {
516
+ const value = input.trim();
517
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value);
518
+ if (hex) {
519
+ const digits = hex[1].length === 3 ? hex[1].split("").map((c) => c + c).join("") : hex[1];
520
+ return [
521
+ parseInt(digits.slice(0, 2), 16),
522
+ parseInt(digits.slice(2, 4), 16),
523
+ parseInt(digits.slice(4, 6), 16)
524
+ ];
525
+ }
526
+ const rgb = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(value);
527
+ if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
528
+ return null;
529
+ }
530
+ function luminance([r, g, b]) {
531
+ const channel = (v) => {
532
+ const s = Math.min(Math.max(v, 0), 255) / 255;
533
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
534
+ };
535
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
536
+ }
537
+ function toHex([r, g, b]) {
538
+ const pair = (v) => Math.round(Math.min(Math.max(v, 0), 255)).toString(16).padStart(2, "0");
539
+ return `#${pair(r)}${pair(g)}${pair(b)}`;
540
+ }
541
+ function mix(color, toward, amount) {
542
+ return color.map((c) => c + (toward - c) * amount);
543
+ }
544
+ var LABEL_CROSSOVER = 0.2;
545
+ function appearanceToStyle(appearance) {
546
+ if (!appearance) return {};
547
+ const vars = {};
548
+ if (appearance.primaryColor) {
549
+ vars["--kerne-primary"] = appearance.primaryColor;
550
+ vars["--kerne-accent"] = appearance.accentColor ?? appearance.primaryColor;
551
+ const rgb = parseColor(appearance.primaryColor);
552
+ if (rgb) {
553
+ const light = luminance(rgb) > LABEL_CROSSOVER;
554
+ vars["--kerne-primary-fg"] = appearance.primaryTextColor ?? (light ? "#16171a" : "#ffffff");
555
+ vars["--kerne-primary-hover"] = toHex(mix(rgb, light ? 0 : 255, 0.14));
556
+ } else if (appearance.primaryTextColor) {
557
+ vars["--kerne-primary-fg"] = appearance.primaryTextColor;
558
+ }
559
+ } else {
560
+ if (appearance.accentColor) vars["--kerne-accent"] = appearance.accentColor;
561
+ if (appearance.primaryTextColor) vars["--kerne-primary-fg"] = appearance.primaryTextColor;
562
+ }
563
+ if (appearance.textColor) vars["--kerne-fg"] = appearance.textColor;
564
+ if (appearance.borderColor) vars["--kerne-border-strong"] = appearance.borderColor;
565
+ if (appearance.panelColor) vars["--kerne-panel"] = appearance.panelColor;
566
+ if (appearance.background) vars["--kerne-bg"] = appearance.background;
567
+ if (appearance.surfaceColor) {
568
+ vars["--kerne-surface"] = appearance.surfaceColor;
569
+ vars["--kerne-surface-raised"] = appearance.surfaceColor;
570
+ }
571
+ if (appearance.fontFamily) vars["--kerne-font"] = appearance.fontFamily;
572
+ if (appearance.radius) vars["--kerne-radius"] = appearance.radius;
573
+ if (appearance.controlHeight) vars["--kerne-control-height"] = appearance.controlHeight;
574
+ return vars;
575
+ }
576
+ function resolveTheme(appearance) {
577
+ return appearance?.theme ?? "light";
578
+ }
579
+
580
+ // src/ui/primitives.tsx
581
+ import { jsx, jsxs } from "react/jsx-runtime";
582
+ var InsideShell = createContext(false);
583
+ function Shell({
584
+ appearance,
585
+ className,
586
+ size = "auth",
587
+ children
588
+ }) {
589
+ ensureStylesInjected();
590
+ const nested = useContext(InsideShell);
591
+ if (nested) {
592
+ return /* @__PURE__ */ jsx("div", { className, children });
593
+ }
594
+ return /* @__PURE__ */ jsx(InsideShell.Provider, { value: true, children: /* @__PURE__ */ jsx(
595
+ "div",
596
+ {
597
+ className: `kerne-root${className ? ` ${className}` : ""}`,
598
+ style: appearanceToStyle(appearance),
599
+ "data-kerne-theme": resolveTheme(appearance),
600
+ children: /* @__PURE__ */ jsx(
601
+ "div",
602
+ {
603
+ className: "kerne-shell",
604
+ "data-kerne-size": size === "wide" ? "wide" : void 0,
605
+ "data-kerne-surface": appearance?.surface === "card" ? "card" : void 0,
606
+ children
607
+ }
608
+ )
609
+ }
610
+ ) });
611
+ }
612
+ function Subsection({
613
+ title,
614
+ description,
615
+ children
616
+ }) {
617
+ return /* @__PURE__ */ jsxs("section", { className: "kerne-subsection", children: [
618
+ /* @__PURE__ */ jsxs("div", { className: "kerne-subsection-head", children: [
619
+ /* @__PURE__ */ jsx("h3", { className: "kerne-subsection-title", children: title }),
620
+ description ? /* @__PURE__ */ jsx("p", { className: "kerne-subsection-desc", children: description }) : null
621
+ ] }),
622
+ children
623
+ ] });
624
+ }
625
+ function Masthead({
626
+ logo,
627
+ title,
628
+ subtitle
629
+ }) {
630
+ return /* @__PURE__ */ jsxs("div", { className: "kerne-masthead", children: [
631
+ logo ? /* @__PURE__ */ jsx("div", { className: "kerne-logo", children: logo }) : null,
632
+ /* @__PURE__ */ jsxs("div", { className: "kerne-heading", children: [
633
+ /* @__PURE__ */ jsx("h1", { className: "kerne-title", children: title }),
634
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "kerne-subtitle", children: subtitle }) : null
635
+ ] })
636
+ ] });
637
+ }
638
+ function Field({
639
+ label,
640
+ type,
641
+ value,
642
+ onChange,
643
+ autoComplete,
644
+ placeholder,
645
+ disabled,
646
+ required,
647
+ invalid,
648
+ action,
649
+ code,
650
+ autoFocus
651
+ }) {
652
+ const id = useId();
653
+ return /* @__PURE__ */ jsxs("div", { className: "kerne-field", children: [
654
+ action ? /* @__PURE__ */ jsxs("div", { className: "kerne-label-row", children: [
655
+ /* @__PURE__ */ jsx("label", { className: "kerne-label", htmlFor: id, children: label }),
656
+ action
657
+ ] }) : /* @__PURE__ */ jsx("label", { className: "kerne-label", htmlFor: id, children: label }),
658
+ /* @__PURE__ */ jsx(
659
+ "input",
660
+ {
661
+ id,
662
+ className: "kerne-input",
663
+ type,
664
+ value,
665
+ onChange: (e) => onChange(e.target.value),
666
+ autoComplete,
667
+ placeholder,
668
+ disabled,
669
+ required,
670
+ "aria-invalid": invalid || void 0,
671
+ "data-kerne-code": code ? "true" : void 0,
672
+ autoFocus
673
+ }
674
+ )
675
+ ] });
676
+ }
677
+ function Button({
678
+ children,
679
+ variant = "primary",
680
+ disabled,
681
+ type = "submit",
682
+ onClick,
683
+ ariaLabel
684
+ }) {
685
+ return /* @__PURE__ */ jsx(
686
+ "button",
687
+ {
688
+ type,
689
+ className: `kerne-button kerne-button-${variant}`,
690
+ disabled,
691
+ onClick,
692
+ "aria-label": ariaLabel,
693
+ children
694
+ }
695
+ );
696
+ }
697
+ function MethodSwitch({
698
+ onClick,
699
+ children
700
+ }) {
701
+ return /* @__PURE__ */ jsx("button", { type: "button", className: "kerne-switch", onClick, children });
702
+ }
703
+ function AlertIcon() {
704
+ return /* @__PURE__ */ jsxs(
705
+ "svg",
706
+ {
707
+ width: "16",
708
+ height: "16",
709
+ viewBox: "0 0 24 24",
710
+ fill: "none",
711
+ stroke: "currentColor",
712
+ strokeWidth: "2",
713
+ "aria-hidden": "true",
714
+ children: [
715
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
716
+ /* @__PURE__ */ jsx("path", { d: "M12 8v4M12 16h.01", strokeLinecap: "round" })
717
+ ]
718
+ }
719
+ );
720
+ }
721
+ function MailIcon() {
722
+ return /* @__PURE__ */ jsxs(
723
+ "svg",
724
+ {
725
+ width: "16",
726
+ height: "16",
727
+ viewBox: "0 0 24 24",
728
+ fill: "none",
729
+ stroke: "currentColor",
730
+ strokeWidth: "2",
731
+ "aria-hidden": "true",
732
+ children: [
733
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
734
+ /* @__PURE__ */ jsx("path", { d: "m2 7 10 6 10-6", strokeLinecap: "round", strokeLinejoin: "round" })
735
+ ]
736
+ }
737
+ );
738
+ }
739
+ function Panel({
740
+ children,
741
+ variant = "info"
742
+ }) {
743
+ const danger = variant === "danger";
744
+ const icon = danger ? /* @__PURE__ */ jsx(AlertIcon, {}) : variant === "success" ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(MailIcon, {});
745
+ return /* @__PURE__ */ jsxs(
746
+ "div",
747
+ {
748
+ className: `kerne-panel${danger ? " kerne-panel-danger" : ""}${variant === "success" ? " kerne-panel-success" : ""}`,
749
+ ...danger ? { role: "alert" } : {},
750
+ children: [
751
+ icon,
752
+ /* @__PURE__ */ jsx("span", { children })
753
+ ]
754
+ }
755
+ );
756
+ }
757
+ function CheckIcon() {
758
+ return /* @__PURE__ */ jsx(
759
+ "svg",
760
+ {
761
+ width: "16",
762
+ height: "16",
763
+ viewBox: "0 0 24 24",
764
+ fill: "none",
765
+ stroke: "currentColor",
766
+ strokeWidth: "2",
767
+ "aria-hidden": "true",
768
+ children: /* @__PURE__ */ jsx("path", { d: "m5 13 4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" })
769
+ }
770
+ );
771
+ }
772
+ function Status({
773
+ tone,
774
+ title,
775
+ children,
776
+ action
777
+ }) {
778
+ return /* @__PURE__ */ jsxs("div", { className: "kerne-status", ...tone === "danger" ? { role: "alert" } : {}, children: [
779
+ /* @__PURE__ */ jsx("div", { className: "kerne-status-icon", "data-tone": tone === "loading" ? "muted" : tone, children: tone === "loading" ? /* @__PURE__ */ jsx("span", { className: "kerne-spinner", "aria-hidden": "true" }) : tone === "success" ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(AlertIcon, {}) }),
780
+ /* @__PURE__ */ jsxs("div", { className: "kerne-heading", children: [
781
+ /* @__PURE__ */ jsx("h1", { className: "kerne-title", children: title }),
782
+ children ? /* @__PURE__ */ jsx("p", { className: "kerne-status-text", children }) : null
783
+ ] }),
784
+ action
785
+ ] });
786
+ }
787
+ function Divider({ label }) {
788
+ return /* @__PURE__ */ jsx("div", { className: "kerne-divider", children: label });
789
+ }
790
+ function TextLink({
791
+ href,
792
+ onClick,
793
+ children
794
+ }) {
795
+ if (onClick) {
796
+ return /* @__PURE__ */ jsx("button", { type: "button", className: "kerne-link", onClick, children });
797
+ }
798
+ return /* @__PURE__ */ jsx("a", { className: "kerne-link", href: href ?? "#", children });
799
+ }
800
+ var OAUTH_LABELS = {
801
+ google: "Google",
802
+ github: "GitHub",
803
+ microsoft: "Microsoft",
804
+ apple: "Apple",
805
+ gitlab: "GitLab",
806
+ discord: "Discord"
807
+ };
808
+ function OAuthIcon({ provider }) {
809
+ const common = { width: 18, height: 18, viewBox: "0 0 24 24", "aria-hidden": true };
810
+ switch (provider) {
811
+ case "google":
812
+ return /* @__PURE__ */ jsxs("svg", { ...common, children: [
813
+ /* @__PURE__ */ jsx(
814
+ "path",
815
+ {
816
+ fill: "#4285F4",
817
+ d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
818
+ }
819
+ ),
820
+ /* @__PURE__ */ jsx(
821
+ "path",
822
+ {
823
+ fill: "#34A853",
824
+ d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
825
+ }
826
+ ),
827
+ /* @__PURE__ */ jsx(
828
+ "path",
829
+ {
830
+ fill: "#FBBC05",
831
+ d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsx(
835
+ "path",
836
+ {
837
+ fill: "#EA4335",
838
+ d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
839
+ }
840
+ )
841
+ ] });
842
+ case "github":
843
+ return /* @__PURE__ */ jsx("svg", { ...common, fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.26.8-.57v-2c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.75.08-.73.08-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18.77.84 1.23 1.91 1.23 3.22 0 4.61-2.8 5.63-5.48 5.92.43.37.81 1.1.81 2.22v3.29c0 .31.2.68.81.57A12 12 0 0 0 12 .3z" }) });
844
+ case "apple":
845
+ return /* @__PURE__ */ jsx("svg", { ...common, fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M17.05 12.75c-.03-2.6 2.12-3.85 2.22-3.91-1.21-1.77-3.09-2.01-3.76-2.04-1.6-.16-3.12.94-3.93.94-.81 0-2.06-.92-3.39-.9-1.74.03-3.35 1.01-4.25 2.57-1.81 3.14-.46 7.79 1.3 10.34.86 1.25 1.89 2.65 3.24 2.6 1.3-.05 1.79-.84 3.36-.84 1.57 0 2.01.84 3.38.81 1.4-.02 2.28-1.27 3.13-2.53.99-1.45 1.4-2.86 1.42-2.93-.03-.01-2.72-1.04-2.75-4.13zM14.5 4.9c.71-.87 1.19-2.07 1.06-3.27-1.02.04-2.26.68-3 1.55-.66.76-1.24 1.98-1.09 3.15 1.14.09 2.31-.58 3.03-1.43z" }) });
846
+ default:
847
+ return null;
848
+ }
849
+ }
850
+ function OAuthButtons({
851
+ providers,
852
+ disabled,
853
+ onSelect
854
+ }) {
855
+ const l10n = useLocalization();
856
+ if (providers.length === 0) return null;
857
+ return /* @__PURE__ */ jsx("div", { className: "kerne-oauth-group", children: providers.map((provider) => {
858
+ const label = fill(l10n.common.continueWith, {
859
+ provider: OAUTH_LABELS[provider] ?? provider
860
+ });
861
+ return /* @__PURE__ */ jsxs(
862
+ "button",
863
+ {
864
+ type: "button",
865
+ className: "kerne-button kerne-button-outline",
866
+ disabled,
867
+ onClick: () => onSelect?.(provider),
868
+ "aria-label": label,
869
+ children: [
870
+ /* @__PURE__ */ jsx(OAuthIcon, { provider }),
871
+ label
872
+ ]
873
+ },
874
+ provider
875
+ );
876
+ }) });
877
+ }
878
+ function KerneBranding({ show }) {
879
+ const l10n = useLocalization();
880
+ if (!show) return null;
881
+ return /* @__PURE__ */ jsx("a", { className: "kerne-branding", href: "https://kerne.io", target: "_blank", rel: "noopener noreferrer", children: l10n.common.securedByKerne });
882
+ }
883
+ function FormSkeleton() {
884
+ const l10n = useLocalization();
885
+ return /* @__PURE__ */ jsxs("div", { className: "kerne-form", "aria-busy": "true", "aria-label": l10n.common.loading, children: [
886
+ /* @__PURE__ */ jsx("div", { className: "kerne-skeleton" }),
887
+ /* @__PURE__ */ jsx("div", { className: "kerne-skeleton" })
888
+ ] });
889
+ }
890
+
891
+ // src/ui/LoginForm.tsx
892
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
893
+ function LoginForm({
894
+ onSuccess,
895
+ redirectUrl,
896
+ magicLinkCallbackUrl,
897
+ signUpUrl,
898
+ onSignUpClick,
899
+ forgotPasswordUrl,
900
+ onForgotPasswordClick,
901
+ onOAuthSelect,
902
+ title,
903
+ subtitle,
904
+ legal,
905
+ appearance,
906
+ logo,
907
+ className
908
+ }) {
909
+ const client = useClient();
910
+ const { login, startPasswordless } = useAuth();
911
+ const { config, isLoading: configLoading, error: configError } = useAuthConfig();
912
+ const l10n = useLocalization();
913
+ const showError = useErrorResolver();
914
+ const [email, setEmail] = useState2("");
915
+ const [password, setPassword] = useState2("");
916
+ const [submitting, setSubmitting] = useState2(false);
917
+ const [error, setError] = useState2(null);
918
+ const [linkSentTo, setLinkSentTo] = useState2(null);
919
+ const [passwordMode, setPasswordMode] = useState2(false);
920
+ const shell = (children) => /* @__PURE__ */ jsx2(Shell, { appearance, className, children });
921
+ const hasOAuth = (config?.oauth_providers.length ?? 0) > 0;
922
+ const hasCredentials = config?.enable_credentials ?? false;
923
+ const hasMagicLink = config?.enable_magic_link ?? false;
924
+ const hasEmailMethod = hasCredentials || hasMagicLink;
925
+ const usePassword = passwordMode || !hasMagicLink && hasCredentials;
926
+ const handleSubmit = async (e) => {
927
+ e.preventDefault();
928
+ setError(null);
929
+ setSubmitting(true);
930
+ try {
931
+ if (usePassword) {
932
+ await login({ email, password });
933
+ if (redirectUrl) {
934
+ await client.redirectWithSession(redirectUrl);
935
+ } else {
936
+ onSuccess?.();
937
+ }
938
+ } else {
939
+ await startPasswordless(email, magicLinkCallbackUrl);
940
+ setLinkSentTo(email);
941
+ }
942
+ } catch (err) {
943
+ setError(showError(err));
944
+ setSubmitting(false);
945
+ }
946
+ };
947
+ const appName = config?.app_name;
948
+ const heading = title ?? l10n.signIn.title;
949
+ if (configLoading) {
950
+ return shell(
951
+ /* @__PURE__ */ jsxs2(Fragment, { children: [
952
+ /* @__PURE__ */ jsx2(Masthead, { logo, title: heading }),
953
+ /* @__PURE__ */ jsx2(FormSkeleton, {})
954
+ ] })
955
+ );
956
+ }
957
+ if (configError || !config) {
958
+ return shell(
959
+ /* @__PURE__ */ jsxs2(Fragment, { children: [
960
+ /* @__PURE__ */ jsx2(Masthead, { logo, title: heading }),
961
+ /* @__PURE__ */ jsx2(Panel, { variant: "danger", children: l10n.signIn.loadFailed })
962
+ ] })
963
+ );
964
+ }
965
+ if (!hasEmailMethod && !hasOAuth) {
966
+ return shell(
967
+ /* @__PURE__ */ jsxs2(Fragment, { children: [
968
+ /* @__PURE__ */ jsx2(Masthead, { logo, title: heading }),
969
+ /* @__PURE__ */ jsx2(Panel, { variant: "danger", children: l10n.signIn.unavailable })
970
+ ] })
971
+ );
972
+ }
973
+ const canSignUp = config.registration_mode !== "CLOSED";
974
+ const signUpLine = canSignUp && (signUpUrl || onSignUpClick) ? /* @__PURE__ */ jsxs2(Fragment, { children: [
975
+ l10n.signIn.signUpPrompt,
976
+ " ",
977
+ /* @__PURE__ */ jsx2(TextLink, { href: signUpUrl, onClick: onSignUpClick, children: appName ? fill(l10n.signIn.signUpForApp, { app: appName }) : l10n.signIn.signUpLink }),
978
+ "."
979
+ ] }) : void 0;
980
+ const submitLabel = submitting ? usePassword ? l10n.signIn.submitting : l10n.signIn.magicSubmitting : usePassword ? l10n.signIn.submit : l10n.signIn.magicSubmit;
981
+ return shell(
982
+ /* @__PURE__ */ jsxs2(Fragment, { children: [
983
+ /* @__PURE__ */ jsx2(Masthead, { logo, title: heading, subtitle: subtitle ?? signUpLine }),
984
+ /* @__PURE__ */ jsxs2("div", { className: "kerne-stack", children: [
985
+ error ? /* @__PURE__ */ jsx2(Panel, { variant: "danger", children: error }) : null,
986
+ linkSentTo ? /* @__PURE__ */ jsx2(Panel, { children: fillNode(l10n.signIn.linkSent, { email: /* @__PURE__ */ jsx2("strong", { children: linkSentTo }) }) }) : hasEmailMethod ? /* @__PURE__ */ jsxs2("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
987
+ /* @__PURE__ */ jsx2(
988
+ Field,
989
+ {
990
+ label: l10n.common.email,
991
+ type: "email",
992
+ value: email,
993
+ onChange: setEmail,
994
+ autoComplete: "email",
995
+ placeholder: l10n.common.emailPlaceholder,
996
+ disabled: submitting,
997
+ required: true,
998
+ autoFocus: true
999
+ }
1000
+ ),
1001
+ usePassword ? /* @__PURE__ */ jsx2(
1002
+ Field,
1003
+ {
1004
+ label: l10n.common.password,
1005
+ type: "password",
1006
+ value: password,
1007
+ onChange: setPassword,
1008
+ autoComplete: "current-password",
1009
+ placeholder: l10n.common.passwordPlaceholder,
1010
+ disabled: submitting,
1011
+ required: true,
1012
+ action: forgotPasswordUrl || onForgotPasswordClick ? /* @__PURE__ */ jsx2(TextLink, { href: forgotPasswordUrl, onClick: onForgotPasswordClick, children: l10n.signIn.forgotPassword }) : void 0
1013
+ }
1014
+ ) : null,
1015
+ /* @__PURE__ */ jsx2(Button, { disabled: submitting || !email || usePassword && !password, children: submitLabel }),
1016
+ hasCredentials && hasMagicLink ? /* @__PURE__ */ jsx2(
1017
+ MethodSwitch,
1018
+ {
1019
+ onClick: () => {
1020
+ setPasswordMode((v) => !v);
1021
+ setError(null);
1022
+ },
1023
+ children: usePassword ? l10n.signIn.useMagicLink : l10n.signIn.usePassword
1024
+ }
1025
+ ) : null
1026
+ ] }) : null,
1027
+ hasOAuth && hasEmailMethod ? /* @__PURE__ */ jsx2(Divider, { label: l10n.common.or }) : null,
1028
+ /* @__PURE__ */ jsx2(
1029
+ OAuthButtons,
1030
+ {
1031
+ providers: config.oauth_providers,
1032
+ disabled: submitting,
1033
+ onSelect: onOAuthSelect
1034
+ }
1035
+ )
1036
+ ] }),
1037
+ legal ? /* @__PURE__ */ jsx2("p", { className: "kerne-legal", children: legal }) : null,
1038
+ /* @__PURE__ */ jsx2(KerneBranding, { show: config.show_kerne_branding })
1039
+ ] })
1040
+ );
1041
+ }
1042
+
1043
+ // src/ui/RegisterForm.tsx
1044
+ import { useState as useState3 } from "react";
1045
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1046
+ function RegisterForm({
1047
+ onSuccess,
1048
+ redirectUrl,
1049
+ magicLinkCallbackUrl,
1050
+ invitationToken: invitationTokenProp,
1051
+ signInUrl,
1052
+ onSignInClick,
1053
+ onOAuthSelect,
1054
+ title,
1055
+ subtitle,
1056
+ legal,
1057
+ collectName = true,
1058
+ appearance,
1059
+ logo,
1060
+ className
1061
+ }) {
1062
+ const client = useClient();
1063
+ const { register, startPasswordless } = useAuth();
1064
+ const { config, isLoading: configLoading, error: configError } = useAuthConfig();
1065
+ const invitationToken = useUrlToken("token", invitationTokenProp) || void 0;
1066
+ const l10n = useLocalization();
1067
+ const showError = useErrorResolver();
1068
+ const [email, setEmail] = useState3("");
1069
+ const [password, setPassword] = useState3("");
1070
+ const [name, setName] = useState3("");
1071
+ const [invitationCode, setInvitationCode] = useState3("");
1072
+ const [submitting, setSubmitting] = useState3(false);
1073
+ const [error, setError] = useState3(null);
1074
+ const [linkSentTo, setLinkSentTo] = useState3(null);
1075
+ const [passwordMode, setPasswordMode] = useState3(false);
1076
+ const shell = (children) => /* @__PURE__ */ jsx3(Shell, { appearance, className, children });
1077
+ const appName = config?.app_name;
1078
+ const heading = title ?? (appName ? fill(l10n.signUp.titleWithApp, { app: appName }) : l10n.signUp.title);
1079
+ const signInLine = signInUrl || onSignInClick ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1080
+ l10n.signUp.signInPrompt,
1081
+ " ",
1082
+ /* @__PURE__ */ jsx3(TextLink, { href: signInUrl, onClick: onSignInClick, children: l10n.signUp.signInLink }),
1083
+ "."
1084
+ ] }) : void 0;
1085
+ if (configLoading) {
1086
+ return shell(
1087
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
1088
+ /* @__PURE__ */ jsx3(Masthead, { logo, title: heading }),
1089
+ /* @__PURE__ */ jsx3(FormSkeleton, {})
1090
+ ] })
1091
+ );
1092
+ }
1093
+ if (configError || !config) {
1094
+ return shell(
1095
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
1096
+ /* @__PURE__ */ jsx3(Masthead, { logo, title: heading }),
1097
+ /* @__PURE__ */ jsx3(Panel, { variant: "danger", children: l10n.signUp.loadFailed })
1098
+ ] })
1099
+ );
1100
+ }
1101
+ if (config.registration_mode === "CLOSED") {
1102
+ return shell(
1103
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
1104
+ /* @__PURE__ */ jsx3(Masthead, { logo, title: l10n.signUp.closedTitle, subtitle: signInLine }),
1105
+ /* @__PURE__ */ jsx3(Panel, { children: fill(l10n.signUp.closedBody, { app: appName ?? l10n.common.thisApp }) }),
1106
+ /* @__PURE__ */ jsx3(KerneBranding, { show: config.show_kerne_branding })
1107
+ ] })
1108
+ );
1109
+ }
1110
+ const hasOAuth = config.oauth_providers.length > 0;
1111
+ const hasCredentials = config.enable_credentials;
1112
+ const hasMagicLink = config.enable_magic_link;
1113
+ const hasEmailMethod = hasCredentials || hasMagicLink;
1114
+ const usePassword = passwordMode || !hasMagicLink && hasCredentials;
1115
+ const needsCode = config.registration_mode === "INVITE_ONLY" && !invitationToken;
1116
+ const handleSubmit = async (e) => {
1117
+ e.preventDefault();
1118
+ setError(null);
1119
+ setSubmitting(true);
1120
+ try {
1121
+ if (usePassword) {
1122
+ await register({
1123
+ email,
1124
+ password,
1125
+ name: collectName && name ? name : void 0,
1126
+ invitationToken,
1127
+ invitationCode: invitationCode || void 0
1128
+ });
1129
+ if (redirectUrl) {
1130
+ await client.redirectWithSession(redirectUrl);
1131
+ } else {
1132
+ onSuccess?.();
1133
+ }
1134
+ } else {
1135
+ await startPasswordless(email, magicLinkCallbackUrl, {
1136
+ invitationToken,
1137
+ invitationCode: invitationCode || void 0
1138
+ });
1139
+ setLinkSentTo(email);
1140
+ }
1141
+ } catch (err) {
1142
+ setError(showError(err));
1143
+ setSubmitting(false);
1144
+ }
1145
+ };
1146
+ if (!hasEmailMethod && !hasOAuth) {
1147
+ return shell(
1148
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
1149
+ /* @__PURE__ */ jsx3(Masthead, { logo, title: heading }),
1150
+ /* @__PURE__ */ jsx3(Panel, { variant: "danger", children: l10n.signUp.unavailable })
1151
+ ] })
1152
+ );
1153
+ }
1154
+ const submitLabel = submitting ? usePassword ? l10n.signUp.submitting : l10n.signUp.magicSubmitting : usePassword ? l10n.signUp.submit : l10n.signUp.magicSubmit;
1155
+ return shell(
1156
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
1157
+ /* @__PURE__ */ jsx3(
1158
+ Masthead,
1159
+ {
1160
+ logo,
1161
+ title: heading,
1162
+ subtitle: subtitle ?? (needsCode ? l10n.signUp.invitationNeeded : signInLine)
1163
+ }
1164
+ ),
1165
+ /* @__PURE__ */ jsxs3("div", { className: "kerne-stack", children: [
1166
+ error ? /* @__PURE__ */ jsx3(Panel, { variant: "danger", children: error }) : null,
1167
+ linkSentTo ? /* @__PURE__ */ jsx3(Panel, { children: fillNode(l10n.signUp.linkSent, { email: /* @__PURE__ */ jsx3("strong", { children: linkSentTo }) }) }) : hasEmailMethod ? /* @__PURE__ */ jsxs3("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1168
+ needsCode ? /* @__PURE__ */ jsx3(
1169
+ Field,
1170
+ {
1171
+ label: l10n.signUp.invitationCode,
1172
+ type: "text",
1173
+ value: invitationCode,
1174
+ onChange: setInvitationCode,
1175
+ placeholder: "XXXXXX",
1176
+ autoComplete: "one-time-code",
1177
+ disabled: submitting,
1178
+ required: true,
1179
+ code: true,
1180
+ autoFocus: true
1181
+ }
1182
+ ) : null,
1183
+ collectName && usePassword ? /* @__PURE__ */ jsx3(
1184
+ Field,
1185
+ {
1186
+ label: l10n.common.name,
1187
+ type: "text",
1188
+ value: name,
1189
+ onChange: setName,
1190
+ autoComplete: "name",
1191
+ placeholder: l10n.common.namePlaceholder,
1192
+ disabled: submitting
1193
+ }
1194
+ ) : null,
1195
+ /* @__PURE__ */ jsx3(
1196
+ Field,
1197
+ {
1198
+ label: l10n.common.email,
1199
+ type: "email",
1200
+ value: email,
1201
+ onChange: setEmail,
1202
+ autoComplete: "email",
1203
+ placeholder: l10n.common.emailPlaceholder,
1204
+ disabled: submitting,
1205
+ required: true,
1206
+ autoFocus: !needsCode
1207
+ }
1208
+ ),
1209
+ usePassword ? /* @__PURE__ */ jsx3(
1210
+ Field,
1211
+ {
1212
+ label: l10n.common.password,
1213
+ type: "password",
1214
+ value: password,
1215
+ onChange: setPassword,
1216
+ autoComplete: "new-password",
1217
+ placeholder: l10n.common.passwordPlaceholder,
1218
+ disabled: submitting,
1219
+ required: true
1220
+ }
1221
+ ) : null,
1222
+ /* @__PURE__ */ jsx3(
1223
+ Button,
1224
+ {
1225
+ disabled: submitting || !email || needsCode && !invitationCode || usePassword && !password,
1226
+ children: submitLabel
1227
+ }
1228
+ ),
1229
+ hasCredentials && hasMagicLink ? /* @__PURE__ */ jsx3(
1230
+ MethodSwitch,
1231
+ {
1232
+ onClick: () => {
1233
+ setPasswordMode((v) => !v);
1234
+ setError(null);
1235
+ },
1236
+ children: usePassword ? l10n.signUp.useMagicLink : l10n.signUp.usePassword
1237
+ }
1238
+ ) : null
1239
+ ] }) : null,
1240
+ hasOAuth && hasEmailMethod ? /* @__PURE__ */ jsx3(Divider, { label: l10n.common.or }) : null,
1241
+ /* @__PURE__ */ jsx3(
1242
+ OAuthButtons,
1243
+ {
1244
+ providers: config.oauth_providers,
1245
+ disabled: submitting,
1246
+ onSelect: onOAuthSelect
1247
+ }
1248
+ )
1249
+ ] }),
1250
+ legal ? /* @__PURE__ */ jsx3("p", { className: "kerne-legal", children: legal }) : null,
1251
+ /* @__PURE__ */ jsx3(KerneBranding, { show: config.show_kerne_branding })
1252
+ ] })
1253
+ );
1254
+ }
1255
+
1256
+ // src/ui/ForgotPasswordForm.tsx
1257
+ import { useState as useState4 } from "react";
1258
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1259
+ function ForgotPasswordForm({
1260
+ callbackUrl,
1261
+ signInUrl,
1262
+ onSignInClick,
1263
+ onSent,
1264
+ title,
1265
+ subtitle,
1266
+ appearance,
1267
+ logo,
1268
+ className
1269
+ }) {
1270
+ const { requestPasswordReset } = useAuth();
1271
+ const { config } = useAuthConfig();
1272
+ const l10n = useLocalization();
1273
+ const showError = useErrorResolver();
1274
+ const [email, setEmail] = useState4("");
1275
+ const [submitting, setSubmitting] = useState4(false);
1276
+ const [error, setError] = useState4(null);
1277
+ const [sentTo, setSentTo] = useState4(null);
1278
+ const handleSubmit = async (e) => {
1279
+ e.preventDefault();
1280
+ setError(null);
1281
+ setSubmitting(true);
1282
+ try {
1283
+ await requestPasswordReset(email, callbackUrl);
1284
+ setSentTo(email);
1285
+ onSent?.(email);
1286
+ } catch (err) {
1287
+ setError(showError(err));
1288
+ } finally {
1289
+ setSubmitting(false);
1290
+ }
1291
+ };
1292
+ const backLine = signInUrl || onSignInClick ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1293
+ l10n.resetPassword.rememberedPrompt,
1294
+ " ",
1295
+ /* @__PURE__ */ jsx4(TextLink, { href: signInUrl, onClick: onSignInClick, children: l10n.common.backToSignIn }),
1296
+ "."
1297
+ ] }) : void 0;
1298
+ return /* @__PURE__ */ jsxs4(Shell, { appearance, className, children: [
1299
+ /* @__PURE__ */ jsx4(
1300
+ Masthead,
1301
+ {
1302
+ logo,
1303
+ title: title ?? l10n.resetPassword.requestTitle,
1304
+ subtitle: subtitle ?? (sentTo ? backLine : l10n.resetPassword.requestSubtitle)
1305
+ }
1306
+ ),
1307
+ /* @__PURE__ */ jsxs4("div", { className: "kerne-stack", children: [
1308
+ error ? /* @__PURE__ */ jsx4(Panel, { variant: "danger", children: error }) : null,
1309
+ sentTo ? /* @__PURE__ */ jsx4(Panel, { children: fillNode(l10n.resetPassword.requestSent, { email: /* @__PURE__ */ jsx4("strong", { children: sentTo }) }) }) : /* @__PURE__ */ jsxs4("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1310
+ /* @__PURE__ */ jsx4(
1311
+ Field,
1312
+ {
1313
+ label: l10n.common.email,
1314
+ type: "email",
1315
+ value: email,
1316
+ onChange: setEmail,
1317
+ autoComplete: "email",
1318
+ placeholder: l10n.common.emailPlaceholder,
1319
+ disabled: submitting,
1320
+ required: true,
1321
+ autoFocus: true
1322
+ }
1323
+ ),
1324
+ /* @__PURE__ */ jsx4(Button, { disabled: submitting || !email, children: submitting ? l10n.resetPassword.requestSubmitting : l10n.resetPassword.requestSubmit }),
1325
+ backLine ? /* @__PURE__ */ jsx4("p", { className: "kerne-footer", children: backLine }) : null
1326
+ ] })
1327
+ ] }),
1328
+ /* @__PURE__ */ jsx4(KerneBranding, { show: config?.show_kerne_branding ?? false })
1329
+ ] });
1330
+ }
1331
+
1332
+ // src/ui/ResetPasswordForm.tsx
1333
+ import { useState as useState5 } from "react";
1334
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1335
+ function ResetPasswordForm({
1336
+ token: tokenProp,
1337
+ signInUrl,
1338
+ onSignInClick,
1339
+ onSuccess,
1340
+ title,
1341
+ subtitle,
1342
+ appearance,
1343
+ logo,
1344
+ className
1345
+ }) {
1346
+ const { confirmPasswordReset } = useAuth();
1347
+ const { config } = useAuthConfig();
1348
+ const token = useUrlToken("token", tokenProp);
1349
+ const l10n = useLocalization();
1350
+ const showError = useErrorResolver();
1351
+ const [password, setPassword] = useState5("");
1352
+ const [confirm, setConfirm] = useState5("");
1353
+ const [submitting, setSubmitting] = useState5(false);
1354
+ const [error, setError] = useState5(null);
1355
+ const [done, setDone] = useState5(false);
1356
+ const heading = title ?? l10n.resetPassword.title;
1357
+ const mismatch = confirm.length > 0 && password !== confirm;
1358
+ const signInAction = signInUrl || onSignInClick ? /* @__PURE__ */ jsx5(
1359
+ Button,
1360
+ {
1361
+ type: "button",
1362
+ onClick: onSignInClick ?? (() => {
1363
+ if (signInUrl) window.location.href = signInUrl;
1364
+ }),
1365
+ children: l10n.signIn.submit
1366
+ }
1367
+ ) : void 0;
1368
+ const handleSubmit = async (e) => {
1369
+ e.preventDefault();
1370
+ if (mismatch || !token) return;
1371
+ setError(null);
1372
+ setSubmitting(true);
1373
+ try {
1374
+ await confirmPasswordReset(token, password);
1375
+ setDone(true);
1376
+ onSuccess?.();
1377
+ } catch (err) {
1378
+ setError(showError(err));
1379
+ } finally {
1380
+ setSubmitting(false);
1381
+ }
1382
+ };
1383
+ if (token === null) {
1384
+ return /* @__PURE__ */ jsxs5(Shell, { appearance, className, children: [
1385
+ /* @__PURE__ */ jsx5(Masthead, { logo, title: heading }),
1386
+ /* @__PURE__ */ jsx5(FormSkeleton, {})
1387
+ ] });
1388
+ }
1389
+ if (token === "") {
1390
+ return /* @__PURE__ */ jsxs5(Shell, { appearance, className, children: [
1391
+ logo ? /* @__PURE__ */ jsx5("div", { className: "kerne-masthead", children: logo }) : null,
1392
+ /* @__PURE__ */ jsx5(Status, { tone: "danger", title: l10n.resetPassword.incompleteTitle, action: signInAction, children: l10n.resetPassword.incompleteBody }),
1393
+ /* @__PURE__ */ jsx5(KerneBranding, { show: config?.show_kerne_branding ?? false })
1394
+ ] });
1395
+ }
1396
+ if (done) {
1397
+ return /* @__PURE__ */ jsxs5(Shell, { appearance, className, children: [
1398
+ logo ? /* @__PURE__ */ jsx5("div", { className: "kerne-masthead", children: logo }) : null,
1399
+ /* @__PURE__ */ jsx5(Status, { tone: "success", title: l10n.resetPassword.doneTitle, action: signInAction, children: l10n.resetPassword.doneBody }),
1400
+ /* @__PURE__ */ jsx5(KerneBranding, { show: config?.show_kerne_branding ?? false })
1401
+ ] });
1402
+ }
1403
+ return /* @__PURE__ */ jsxs5(Shell, { appearance, className, children: [
1404
+ /* @__PURE__ */ jsx5(Masthead, { logo, title: heading, subtitle: subtitle ?? l10n.resetPassword.subtitle }),
1405
+ /* @__PURE__ */ jsxs5("div", { className: "kerne-stack", children: [
1406
+ error ? /* @__PURE__ */ jsx5(Panel, { variant: "danger", children: error }) : null,
1407
+ /* @__PURE__ */ jsxs5("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1408
+ /* @__PURE__ */ jsx5(
1409
+ Field,
1410
+ {
1411
+ label: l10n.resetPassword.newPassword,
1412
+ type: "password",
1413
+ value: password,
1414
+ onChange: setPassword,
1415
+ autoComplete: "new-password",
1416
+ placeholder: l10n.common.passwordPlaceholder,
1417
+ disabled: submitting,
1418
+ required: true,
1419
+ autoFocus: true
1420
+ }
1421
+ ),
1422
+ /* @__PURE__ */ jsx5(
1423
+ Field,
1424
+ {
1425
+ label: l10n.resetPassword.confirmPassword,
1426
+ type: "password",
1427
+ value: confirm,
1428
+ onChange: setConfirm,
1429
+ autoComplete: "new-password",
1430
+ placeholder: l10n.common.passwordPlaceholder,
1431
+ disabled: submitting,
1432
+ required: true,
1433
+ invalid: mismatch
1434
+ }
1435
+ ),
1436
+ mismatch ? /* @__PURE__ */ jsx5("p", { className: "kerne-footer", children: l10n.resetPassword.mismatch }) : null,
1437
+ /* @__PURE__ */ jsx5(Button, { disabled: submitting || !password || !confirm || mismatch, children: submitting ? l10n.resetPassword.submitting : l10n.resetPassword.submit }),
1438
+ signInUrl || onSignInClick ? /* @__PURE__ */ jsx5("p", { className: "kerne-footer", children: /* @__PURE__ */ jsx5(TextLink, { href: signInUrl, onClick: onSignInClick, children: l10n.common.backToSignIn }) }) : null
1439
+ ] })
1440
+ ] }),
1441
+ /* @__PURE__ */ jsx5(KerneBranding, { show: config?.show_kerne_branding ?? false })
1442
+ ] });
1443
+ }
1444
+
1445
+ // src/ui/MagicLinkCallback.tsx
1446
+ import { useEffect as useEffect2, useRef, useState as useState6 } from "react";
1447
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1448
+ function MagicLinkCallback({
1449
+ token: tokenProp,
1450
+ onSuccess,
1451
+ redirectUrl,
1452
+ signInUrl,
1453
+ onSignInClick,
1454
+ appearance,
1455
+ logo,
1456
+ className
1457
+ }) {
1458
+ const client = useClient();
1459
+ const { loginWithMagicLink } = useAuth();
1460
+ const { config } = useAuthConfig();
1461
+ const token = useUrlToken("token", tokenProp);
1462
+ const l10n = useLocalization();
1463
+ const showError = useErrorResolver();
1464
+ const [state, setState] = useState6("pending");
1465
+ const [message, setMessage] = useState6(null);
1466
+ const consumed = useRef(false);
1467
+ useEffect2(() => {
1468
+ if (token === null || consumed.current) return;
1469
+ if (token === "") {
1470
+ setState("failed");
1471
+ return;
1472
+ }
1473
+ consumed.current = true;
1474
+ loginWithMagicLink(token).then(async () => {
1475
+ setState("done");
1476
+ if (redirectUrl) {
1477
+ await client.redirectWithSession(redirectUrl);
1478
+ } else {
1479
+ onSuccess?.();
1480
+ }
1481
+ }).catch((e) => {
1482
+ setState("failed");
1483
+ setMessage(showError(e));
1484
+ });
1485
+ }, [token, loginWithMagicLink, redirectUrl, client, onSuccess]);
1486
+ const retry = signInUrl || onSignInClick ? /* @__PURE__ */ jsx6(
1487
+ Button,
1488
+ {
1489
+ variant: "outline",
1490
+ type: "button",
1491
+ onClick: onSignInClick ?? (() => {
1492
+ if (signInUrl) window.location.href = signInUrl;
1493
+ }),
1494
+ children: l10n.common.backToSignIn
1495
+ }
1496
+ ) : void 0;
1497
+ return /* @__PURE__ */ jsxs6(Shell, { appearance, className, children: [
1498
+ logo ? /* @__PURE__ */ jsx6("div", { className: "kerne-masthead", children: logo }) : null,
1499
+ state === "pending" ? /* @__PURE__ */ jsx6(Status, { tone: "loading", title: l10n.magicLink.pendingTitle, children: l10n.magicLink.pendingBody }) : state === "done" ? /* @__PURE__ */ jsx6(Status, { tone: "success", title: l10n.magicLink.doneTitle, children: l10n.magicLink.doneBody }) : /* @__PURE__ */ jsx6(Status, { tone: "danger", title: l10n.magicLink.failedTitle, action: retry, children: message ?? l10n.magicLink.missingToken }),
1500
+ /* @__PURE__ */ jsx6(KerneBranding, { show: config?.show_kerne_branding ?? false })
1501
+ ] });
1502
+ }
1503
+
1504
+ // src/ui/VerifyEmailForm.tsx
1505
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState7 } from "react";
1506
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1507
+ function VerifyEmailForm({
1508
+ token: tokenProp,
1509
+ callbackUrl,
1510
+ onVerified,
1511
+ title,
1512
+ subtitle,
1513
+ appearance,
1514
+ logo,
1515
+ className
1516
+ }) {
1517
+ const { verifyEmailWithCode, verifyEmailWithLink, sendVerificationEmail, refreshToken } = useAuth();
1518
+ const { config } = useAuthConfig();
1519
+ const token = useUrlToken("token", tokenProp);
1520
+ const l10n = useLocalization();
1521
+ const showError = useErrorResolver();
1522
+ const [code, setCode] = useState7("");
1523
+ const [submitting, setSubmitting] = useState7(false);
1524
+ const [error, setError] = useState7(null);
1525
+ const [resent, setResent] = useState7(false);
1526
+ const [linkState, setLinkState] = useState7("idle");
1527
+ const [verified, setVerified] = useState7(false);
1528
+ const consumed = useRef2(false);
1529
+ const heading = title ?? l10n.verifyEmail.title;
1530
+ const settle = async (needsTokenRefresh) => {
1531
+ if (needsTokenRefresh) {
1532
+ await refreshToken().catch(() => void 0);
1533
+ }
1534
+ setVerified(true);
1535
+ onVerified?.();
1536
+ };
1537
+ useEffect3(() => {
1538
+ if (token === null || token === "" || consumed.current) return;
1539
+ consumed.current = true;
1540
+ setLinkState("pending");
1541
+ verifyEmailWithLink(token).then(async (r) => {
1542
+ await settle(r.needsTokenRefresh);
1543
+ setLinkState("done");
1544
+ }).catch((e) => {
1545
+ setLinkState("failed");
1546
+ setError(showError(e));
1547
+ });
1548
+ }, [token, verifyEmailWithLink]);
1549
+ const handleSubmit = async (e) => {
1550
+ e.preventDefault();
1551
+ setError(null);
1552
+ setSubmitting(true);
1553
+ try {
1554
+ const r = await verifyEmailWithCode(code);
1555
+ await settle(r.needsTokenRefresh);
1556
+ } catch (err) {
1557
+ setError(showError(err));
1558
+ } finally {
1559
+ setSubmitting(false);
1560
+ }
1561
+ };
1562
+ const handleResend = async () => {
1563
+ setError(null);
1564
+ try {
1565
+ await sendVerificationEmail("code", callbackUrl);
1566
+ setResent(true);
1567
+ } catch (err) {
1568
+ setError(showError(err));
1569
+ }
1570
+ };
1571
+ if (verified) {
1572
+ return /* @__PURE__ */ jsxs7(Shell, { appearance, className, children: [
1573
+ logo ? /* @__PURE__ */ jsx7("div", { className: "kerne-masthead", children: logo }) : null,
1574
+ /* @__PURE__ */ jsx7(Status, { tone: "success", title: l10n.verifyEmail.doneTitle, children: l10n.verifyEmail.doneBody }),
1575
+ /* @__PURE__ */ jsx7(KerneBranding, { show: config?.show_kerne_branding ?? false })
1576
+ ] });
1577
+ }
1578
+ if (linkState === "pending") {
1579
+ return /* @__PURE__ */ jsxs7(Shell, { appearance, className, children: [
1580
+ logo ? /* @__PURE__ */ jsx7("div", { className: "kerne-masthead", children: logo }) : null,
1581
+ /* @__PURE__ */ jsx7(Status, { tone: "loading", title: l10n.verifyEmail.verifyingTitle, children: l10n.verifyEmail.verifyingBody })
1582
+ ] });
1583
+ }
1584
+ if (linkState === "failed") {
1585
+ return /* @__PURE__ */ jsxs7(Shell, { appearance, className, children: [
1586
+ logo ? /* @__PURE__ */ jsx7("div", { className: "kerne-masthead", children: logo }) : null,
1587
+ /* @__PURE__ */ jsx7(
1588
+ Status,
1589
+ {
1590
+ tone: "danger",
1591
+ title: l10n.verifyEmail.failedTitle,
1592
+ action: /* @__PURE__ */ jsx7(Button, { variant: "outline", type: "button", onClick: handleResend, children: l10n.verifyEmail.newCode }),
1593
+ children: error
1594
+ }
1595
+ ),
1596
+ resent ? /* @__PURE__ */ jsx7(Panel, { variant: "success", children: l10n.verifyEmail.resent }) : null,
1597
+ /* @__PURE__ */ jsx7(KerneBranding, { show: config?.show_kerne_branding ?? false })
1598
+ ] });
1599
+ }
1600
+ return /* @__PURE__ */ jsxs7(Shell, { appearance, className, children: [
1601
+ /* @__PURE__ */ jsx7(Masthead, { logo, title: heading, subtitle: subtitle ?? l10n.verifyEmail.subtitle }),
1602
+ /* @__PURE__ */ jsxs7("div", { className: "kerne-stack", children: [
1603
+ error ? /* @__PURE__ */ jsx7(Panel, { variant: "danger", children: error }) : null,
1604
+ resent ? /* @__PURE__ */ jsx7(Panel, { variant: "success", children: l10n.verifyEmail.resent }) : null,
1605
+ /* @__PURE__ */ jsxs7("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1606
+ /* @__PURE__ */ jsx7(
1607
+ Field,
1608
+ {
1609
+ label: l10n.verifyEmail.code,
1610
+ type: "text",
1611
+ value: code,
1612
+ onChange: setCode,
1613
+ autoComplete: "one-time-code",
1614
+ placeholder: "000000",
1615
+ disabled: submitting,
1616
+ required: true,
1617
+ code: true,
1618
+ autoFocus: true
1619
+ }
1620
+ ),
1621
+ /* @__PURE__ */ jsx7(Button, { disabled: submitting || !code, children: submitting ? l10n.verifyEmail.submitting : l10n.verifyEmail.submit }),
1622
+ /* @__PURE__ */ jsx7(MethodSwitch, { onClick: handleResend, children: l10n.verifyEmail.resend })
1623
+ ] })
1624
+ ] }),
1625
+ /* @__PURE__ */ jsx7(KerneBranding, { show: config?.show_kerne_branding ?? false })
1626
+ ] });
1627
+ }
1628
+
1629
+ // src/ui/ActivateAccountForm.tsx
1630
+ import { useEffect as useEffect4, useState as useState8 } from "react";
1631
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1632
+ function ActivateAccountForm({
1633
+ token: tokenProp,
1634
+ onSuccess,
1635
+ redirectUrl,
1636
+ signInUrl,
1637
+ onSignInClick,
1638
+ title,
1639
+ appearance,
1640
+ logo,
1641
+ className
1642
+ }) {
1643
+ const client = useClient();
1644
+ const { getActivationContext, completeActivation } = useAuth();
1645
+ const { config } = useAuthConfig();
1646
+ const token = useUrlToken("token", tokenProp);
1647
+ const l10n = useLocalization();
1648
+ const showError = useErrorResolver();
1649
+ const [context, setContext] = useState8(null);
1650
+ const [loadError, setLoadError] = useState8(null);
1651
+ const [password, setPassword] = useState8("");
1652
+ const [confirm, setConfirm] = useState8("");
1653
+ const [submitting, setSubmitting] = useState8(false);
1654
+ const [error, setError] = useState8(null);
1655
+ const heading = title ?? l10n.activate.title;
1656
+ const mismatch = confirm.length > 0 && password !== confirm;
1657
+ useEffect4(() => {
1658
+ if (!token) return;
1659
+ let mounted = true;
1660
+ getActivationContext(token).then((c) => mounted && setContext(c)).catch((e) => mounted && setLoadError(showError(e)));
1661
+ return () => {
1662
+ mounted = false;
1663
+ };
1664
+ }, [token, getActivationContext]);
1665
+ const signInAction = signInUrl || onSignInClick ? /* @__PURE__ */ jsx8(
1666
+ Button,
1667
+ {
1668
+ type: "button",
1669
+ onClick: onSignInClick ?? (() => {
1670
+ if (signInUrl) window.location.href = signInUrl;
1671
+ }),
1672
+ children: l10n.activate.goToSignIn
1673
+ }
1674
+ ) : void 0;
1675
+ const handleSubmit = async (e) => {
1676
+ e.preventDefault();
1677
+ if (!token || mismatch) return;
1678
+ setError(null);
1679
+ setSubmitting(true);
1680
+ try {
1681
+ await completeActivation(token, context?.mode === "credentials" ? password : void 0);
1682
+ if (redirectUrl) {
1683
+ await client.redirectWithSession(redirectUrl);
1684
+ } else {
1685
+ onSuccess?.();
1686
+ }
1687
+ } catch (err) {
1688
+ setError(showError(err));
1689
+ setSubmitting(false);
1690
+ }
1691
+ };
1692
+ if (token === null) {
1693
+ return /* @__PURE__ */ jsxs8(Shell, { appearance, className, children: [
1694
+ /* @__PURE__ */ jsx8(Masthead, { logo, title: heading }),
1695
+ /* @__PURE__ */ jsx8(FormSkeleton, {})
1696
+ ] });
1697
+ }
1698
+ if (token === "" || loadError) {
1699
+ return /* @__PURE__ */ jsxs8(Shell, { appearance, className, children: [
1700
+ logo ? /* @__PURE__ */ jsx8("div", { className: "kerne-masthead", children: logo }) : null,
1701
+ /* @__PURE__ */ jsx8(Status, { tone: "danger", title: l10n.activate.failedTitle, action: signInAction, children: loadError ?? l10n.activate.missingToken }),
1702
+ /* @__PURE__ */ jsx8(KerneBranding, { show: config?.show_kerne_branding ?? false })
1703
+ ] });
1704
+ }
1705
+ if (!context) {
1706
+ return /* @__PURE__ */ jsxs8(Shell, { appearance, className, children: [
1707
+ /* @__PURE__ */ jsx8(Masthead, { logo, title: heading }),
1708
+ /* @__PURE__ */ jsx8(FormSkeleton, {})
1709
+ ] });
1710
+ }
1711
+ const credentialsMode = context.mode === "credentials";
1712
+ return /* @__PURE__ */ jsxs8(Shell, { appearance, className, children: [
1713
+ /* @__PURE__ */ jsx8(
1714
+ Masthead,
1715
+ {
1716
+ logo,
1717
+ title: heading,
1718
+ subtitle: fillNode(
1719
+ credentialsMode ? l10n.activate.credentialsSubtitle : l10n.activate.magicSubtitle,
1720
+ { email: /* @__PURE__ */ jsx8("strong", { children: context.email }) }
1721
+ )
1722
+ }
1723
+ ),
1724
+ /* @__PURE__ */ jsxs8("div", { className: "kerne-stack", children: [
1725
+ error ? /* @__PURE__ */ jsx8(Panel, { variant: "danger", children: error }) : null,
1726
+ /* @__PURE__ */ jsxs8("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1727
+ credentialsMode ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
1728
+ /* @__PURE__ */ jsx8(
1729
+ Field,
1730
+ {
1731
+ label: l10n.common.password,
1732
+ type: "password",
1733
+ value: password,
1734
+ onChange: setPassword,
1735
+ autoComplete: "new-password",
1736
+ placeholder: l10n.common.passwordPlaceholder,
1737
+ disabled: submitting,
1738
+ required: true,
1739
+ autoFocus: true
1740
+ }
1741
+ ),
1742
+ /* @__PURE__ */ jsx8(
1743
+ Field,
1744
+ {
1745
+ label: l10n.activate.confirmPassword,
1746
+ type: "password",
1747
+ value: confirm,
1748
+ onChange: setConfirm,
1749
+ autoComplete: "new-password",
1750
+ placeholder: l10n.common.passwordPlaceholder,
1751
+ disabled: submitting,
1752
+ required: true,
1753
+ invalid: mismatch
1754
+ }
1755
+ ),
1756
+ mismatch ? /* @__PURE__ */ jsx8("p", { className: "kerne-footer", children: l10n.activate.mismatch }) : null
1757
+ ] }) : null,
1758
+ /* @__PURE__ */ jsx8(Button, { disabled: submitting || credentialsMode && (!password || !confirm || mismatch), children: submitting ? l10n.activate.submitting : l10n.activate.submit })
1759
+ ] })
1760
+ ] }),
1761
+ /* @__PURE__ */ jsx8(KerneBranding, { show: config?.show_kerne_branding ?? false })
1762
+ ] });
1763
+ }
1764
+
1765
+ // src/ui/AuthFlow.tsx
1766
+ import { jsx as jsx9 } from "react/jsx-runtime";
1767
+ var TOKEN_STEPS = ["reset-password", "magic-link", "activate", "verify-email"];
1768
+ function AuthFlow({
1769
+ initialStep,
1770
+ step: controlledStep,
1771
+ onStepChange,
1772
+ onSuccess,
1773
+ redirectUrl,
1774
+ magicLinkCallbackUrl,
1775
+ resetPasswordCallbackUrl,
1776
+ verificationCallbackUrl,
1777
+ allowSignUp = true,
1778
+ legal,
1779
+ appearance,
1780
+ logo,
1781
+ className
1782
+ }) {
1783
+ const { config } = useAuthConfig();
1784
+ const token = useUrlToken("token");
1785
+ const [internalStep, setInternalStep] = useState9(null);
1786
+ const go = (next) => {
1787
+ if (controlledStep === void 0) setInternalStep(next);
1788
+ onStepChange?.(next);
1789
+ };
1790
+ const tokenStep = token && (initialStep === void 0 || TOKEN_STEPS.includes(initialStep)) ? initialStep ?? "magic-link" : null;
1791
+ const step = controlledStep ?? internalStep ?? tokenStep ?? initialStep ?? "sign-in";
1792
+ const canSignUp = allowSignUp && config?.registration_mode !== "CLOSED";
1793
+ const shared = { appearance, logo, className };
1794
+ switch (step) {
1795
+ case "sign-up":
1796
+ return /* @__PURE__ */ jsx9(
1797
+ RegisterForm,
1798
+ {
1799
+ ...shared,
1800
+ onSuccess,
1801
+ redirectUrl,
1802
+ magicLinkCallbackUrl,
1803
+ onSignInClick: () => go("sign-in"),
1804
+ legal
1805
+ }
1806
+ );
1807
+ case "forgot-password":
1808
+ return /* @__PURE__ */ jsx9(
1809
+ ForgotPasswordForm,
1810
+ {
1811
+ ...shared,
1812
+ callbackUrl: resetPasswordCallbackUrl,
1813
+ onSignInClick: () => go("sign-in")
1814
+ }
1815
+ );
1816
+ case "reset-password":
1817
+ return /* @__PURE__ */ jsx9(ResetPasswordForm, { ...shared, onSignInClick: () => go("sign-in"), onSuccess });
1818
+ case "magic-link":
1819
+ return /* @__PURE__ */ jsx9(
1820
+ MagicLinkCallback,
1821
+ {
1822
+ ...shared,
1823
+ onSuccess,
1824
+ redirectUrl,
1825
+ onSignInClick: () => go("sign-in")
1826
+ }
1827
+ );
1828
+ case "verify-email":
1829
+ return /* @__PURE__ */ jsx9(VerifyEmailForm, { ...shared, callbackUrl: verificationCallbackUrl, onVerified: onSuccess });
1830
+ case "activate":
1831
+ return /* @__PURE__ */ jsx9(
1832
+ ActivateAccountForm,
1833
+ {
1834
+ ...shared,
1835
+ onSuccess,
1836
+ redirectUrl,
1837
+ onSignInClick: () => go("sign-in")
1838
+ }
1839
+ );
1840
+ case "sign-in":
1841
+ default:
1842
+ return /* @__PURE__ */ jsx9(
1843
+ LoginForm,
1844
+ {
1845
+ ...shared,
1846
+ onSuccess,
1847
+ redirectUrl,
1848
+ magicLinkCallbackUrl,
1849
+ onSignUpClick: canSignUp ? () => go("sign-up") : void 0,
1850
+ onForgotPasswordClick: () => go("forgot-password"),
1851
+ legal
1852
+ }
1853
+ );
1854
+ }
1855
+ }
1856
+
1857
+ // src/ui/ChangePasswordForm.tsx
1858
+ import { useState as useState10 } from "react";
1859
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1860
+ function ChangePasswordForm({
1861
+ onSuccess,
1862
+ title,
1863
+ appearance,
1864
+ className
1865
+ }) {
1866
+ const { updatePassword } = useAuth();
1867
+ const l10n = useLocalization();
1868
+ const showError = useErrorResolver();
1869
+ const heading = title === null ? null : title ?? l10n.security.submit;
1870
+ const [currentPassword, setCurrentPassword] = useState10("");
1871
+ const [newPassword, setNewPassword] = useState10("");
1872
+ const [confirm, setConfirm] = useState10("");
1873
+ const [submitting, setSubmitting] = useState10(false);
1874
+ const [error, setError] = useState10(null);
1875
+ const [done, setDone] = useState10(false);
1876
+ const mismatch = confirm.length > 0 && newPassword !== confirm;
1877
+ const unchanged = newPassword.length > 0 && newPassword === currentPassword;
1878
+ const handleSubmit = async (e) => {
1879
+ e.preventDefault();
1880
+ if (mismatch || unchanged) return;
1881
+ setError(null);
1882
+ setDone(false);
1883
+ setSubmitting(true);
1884
+ try {
1885
+ await updatePassword({ currentPassword, newPassword });
1886
+ setDone(true);
1887
+ setCurrentPassword("");
1888
+ setNewPassword("");
1889
+ setConfirm("");
1890
+ onSuccess?.();
1891
+ } catch (err) {
1892
+ setError(showError(err));
1893
+ } finally {
1894
+ setSubmitting(false);
1895
+ }
1896
+ };
1897
+ return /* @__PURE__ */ jsxs9(Shell, { appearance, className, children: [
1898
+ heading ? /* @__PURE__ */ jsx10("div", { className: "kerne-heading", style: { marginBottom: 20 }, children: /* @__PURE__ */ jsx10("h1", { className: "kerne-title", children: heading }) }) : null,
1899
+ /* @__PURE__ */ jsxs9("div", { className: "kerne-stack", children: [
1900
+ error ? /* @__PURE__ */ jsx10(Panel, { variant: "danger", children: error }) : null,
1901
+ done ? /* @__PURE__ */ jsx10(Panel, { variant: "success", children: l10n.security.changed }) : null,
1902
+ /* @__PURE__ */ jsxs9("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1903
+ /* @__PURE__ */ jsx10(
1904
+ Field,
1905
+ {
1906
+ label: l10n.security.currentPassword,
1907
+ type: "password",
1908
+ value: currentPassword,
1909
+ onChange: setCurrentPassword,
1910
+ autoComplete: "current-password",
1911
+ placeholder: l10n.common.passwordPlaceholder,
1912
+ disabled: submitting,
1913
+ required: true
1914
+ }
1915
+ ),
1916
+ /* @__PURE__ */ jsx10(
1917
+ Field,
1918
+ {
1919
+ label: l10n.security.newPassword,
1920
+ type: "password",
1921
+ value: newPassword,
1922
+ onChange: setNewPassword,
1923
+ autoComplete: "new-password",
1924
+ placeholder: l10n.common.passwordPlaceholder,
1925
+ disabled: submitting,
1926
+ required: true,
1927
+ invalid: unchanged
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ jsx10(
1931
+ Field,
1932
+ {
1933
+ label: l10n.security.confirmPassword,
1934
+ type: "password",
1935
+ value: confirm,
1936
+ onChange: setConfirm,
1937
+ autoComplete: "new-password",
1938
+ placeholder: l10n.common.passwordPlaceholder,
1939
+ disabled: submitting,
1940
+ required: true,
1941
+ invalid: mismatch
1942
+ }
1943
+ ),
1944
+ unchanged ? /* @__PURE__ */ jsx10("p", { className: "kerne-footer", children: l10n.security.sameAsCurrent }) : mismatch ? /* @__PURE__ */ jsx10("p", { className: "kerne-footer", children: l10n.security.mismatch }) : null,
1945
+ /* @__PURE__ */ jsx10(
1946
+ Button,
1947
+ {
1948
+ disabled: submitting || !currentPassword || !newPassword || !confirm || mismatch || unchanged,
1949
+ children: submitting ? l10n.security.submitting : l10n.security.submit
1950
+ }
1951
+ )
1952
+ ] })
1953
+ ] })
1954
+ ] });
1955
+ }
1956
+
1957
+ // src/ui/WaitlistForm.tsx
1958
+ import { useState as useState11 } from "react";
1959
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1960
+ function WaitlistForm({
1961
+ title,
1962
+ subtitle,
1963
+ collectName = false,
1964
+ metadata,
1965
+ submitLabel,
1966
+ successMessage,
1967
+ onJoined,
1968
+ legal,
1969
+ appearance,
1970
+ logo,
1971
+ className
1972
+ }) {
1973
+ const { join } = useWaitlist();
1974
+ const { config } = useAuthConfig();
1975
+ const l10n = useLocalization();
1976
+ const showError = useErrorResolver();
1977
+ const [email, setEmail] = useState11("");
1978
+ const [name, setName] = useState11("");
1979
+ const [submitting, setSubmitting] = useState11(false);
1980
+ const [error, setError] = useState11(null);
1981
+ const [joined, setJoined] = useState11(false);
1982
+ const handleSubmit = async (e) => {
1983
+ e.preventDefault();
1984
+ setError(null);
1985
+ setSubmitting(true);
1986
+ try {
1987
+ await join({
1988
+ email,
1989
+ name: collectName && name ? name : void 0,
1990
+ metadata
1991
+ });
1992
+ setJoined(true);
1993
+ onJoined?.(email);
1994
+ } catch (err) {
1995
+ setError(showError(err));
1996
+ } finally {
1997
+ setSubmitting(false);
1998
+ }
1999
+ };
2000
+ return /* @__PURE__ */ jsxs10(Shell, { appearance, className, children: [
2001
+ /* @__PURE__ */ jsx11(
2002
+ Masthead,
2003
+ {
2004
+ logo,
2005
+ title: title ?? l10n.waitlist.title,
2006
+ subtitle: joined ? void 0 : subtitle ?? l10n.waitlist.subtitle
2007
+ }
2008
+ ),
2009
+ /* @__PURE__ */ jsxs10("div", { className: "kerne-stack", children: [
2010
+ error ? /* @__PURE__ */ jsx11(Panel, { variant: "danger", children: error }) : null,
2011
+ joined ? /* @__PURE__ */ jsx11(Panel, { variant: "success", children: successMessage ?? l10n.waitlist.joined }) : /* @__PURE__ */ jsxs10("form", { className: "kerne-form", onSubmit: handleSubmit, children: [
2012
+ collectName ? /* @__PURE__ */ jsx11(
2013
+ Field,
2014
+ {
2015
+ label: l10n.common.name,
2016
+ type: "text",
2017
+ value: name,
2018
+ onChange: setName,
2019
+ autoComplete: "name",
2020
+ placeholder: l10n.common.namePlaceholder,
2021
+ disabled: submitting
2022
+ }
2023
+ ) : null,
2024
+ /* @__PURE__ */ jsx11(
2025
+ Field,
2026
+ {
2027
+ label: l10n.common.email,
2028
+ type: "email",
2029
+ value: email,
2030
+ onChange: setEmail,
2031
+ autoComplete: "email",
2032
+ placeholder: l10n.common.emailPlaceholder,
2033
+ disabled: submitting,
2034
+ required: true,
2035
+ autoFocus: true
2036
+ }
2037
+ ),
2038
+ /* @__PURE__ */ jsx11(Button, { disabled: submitting || !email, children: submitting ? l10n.waitlist.submitting : submitLabel ?? l10n.waitlist.submit })
2039
+ ] })
2040
+ ] }),
2041
+ legal ? /* @__PURE__ */ jsx11("p", { className: "kerne-legal", children: legal }) : null,
2042
+ /* @__PURE__ */ jsx11(KerneBranding, { show: config?.show_kerne_branding ?? false })
2043
+ ] });
2044
+ }
2045
+
2046
+ // src/ui/UserButton.tsx
2047
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState12 } from "react";
2048
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2049
+ function SignOutButton({
2050
+ children,
2051
+ onSignedOut,
2052
+ redirectUrl,
2053
+ className,
2054
+ appearance
2055
+ }) {
2056
+ ensureStylesInjected();
2057
+ const { logout } = useAuth();
2058
+ const l10n = useLocalization();
2059
+ const handle = () => {
2060
+ logout();
2061
+ onSignedOut?.();
2062
+ if (redirectUrl && typeof window !== "undefined") window.location.href = redirectUrl;
2063
+ };
2064
+ return /* @__PURE__ */ jsx12(
2065
+ "div",
2066
+ {
2067
+ className: "kerne-root",
2068
+ style: { ...appearanceToStyle(appearance), display: "inline-flex", width: "auto" },
2069
+ "data-kerne-theme": resolveTheme(appearance),
2070
+ children: /* @__PURE__ */ jsx12(
2071
+ "button",
2072
+ {
2073
+ type: "button",
2074
+ className: `kerne-button kerne-button-outline${className ? ` ${className}` : ""}`,
2075
+ style: { width: "auto" },
2076
+ onClick: handle,
2077
+ children: children ?? l10n.account.signOut
2078
+ }
2079
+ )
2080
+ }
2081
+ );
2082
+ }
2083
+ function UserButton({
2084
+ items = [],
2085
+ onSignedOut,
2086
+ signOutRedirectUrl,
2087
+ signedOutFallback = null,
2088
+ appearance,
2089
+ className
2090
+ }) {
2091
+ ensureStylesInjected();
2092
+ const { logout, isAuthenticated } = useAuth();
2093
+ const { user, fullName, initials, email } = useUser();
2094
+ const l10n = useLocalization();
2095
+ const [open, setOpen] = useState12(false);
2096
+ const wrapRef = useRef3(null);
2097
+ useEffect5(() => {
2098
+ if (!open) return;
2099
+ const onPointerDown = (e) => {
2100
+ if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
2101
+ };
2102
+ const onKeyDown = (e) => {
2103
+ if (e.key === "Escape") setOpen(false);
2104
+ };
2105
+ document.addEventListener("mousedown", onPointerDown);
2106
+ document.addEventListener("keydown", onKeyDown);
2107
+ return () => {
2108
+ document.removeEventListener("mousedown", onPointerDown);
2109
+ document.removeEventListener("keydown", onKeyDown);
2110
+ };
2111
+ }, [open]);
2112
+ if (!isAuthenticated) return /* @__PURE__ */ jsx12(Fragment5, { children: signedOutFallback });
2113
+ const handleSignOut = () => {
2114
+ setOpen(false);
2115
+ logout();
2116
+ onSignedOut?.();
2117
+ if (signOutRedirectUrl && typeof window !== "undefined") {
2118
+ window.location.href = signOutRedirectUrl;
2119
+ }
2120
+ };
2121
+ return /* @__PURE__ */ jsx12(
2122
+ "div",
2123
+ {
2124
+ className: `kerne-root${className ? ` ${className}` : ""}`,
2125
+ style: { ...appearanceToStyle(appearance), display: "inline-flex", width: "auto" },
2126
+ "data-kerne-theme": resolveTheme(appearance),
2127
+ children: /* @__PURE__ */ jsxs11("div", { className: "kerne-menu-wrap", ref: wrapRef, children: [
2128
+ /* @__PURE__ */ jsx12(
2129
+ "button",
2130
+ {
2131
+ type: "button",
2132
+ className: "kerne-menu-trigger",
2133
+ "aria-haspopup": "menu",
2134
+ "aria-expanded": open,
2135
+ "aria-label": l10n.account.menuLabel,
2136
+ onClick: () => setOpen((v) => !v),
2137
+ children: user?.avatar ? /* @__PURE__ */ jsx12("img", { className: "kerne-avatar", src: user.avatar, alt: "" }) : /* @__PURE__ */ jsx12("span", { className: "kerne-avatar", children: initials || email?.[0]?.toUpperCase() || "?" })
2138
+ }
2139
+ ),
2140
+ open ? /* @__PURE__ */ jsxs11("div", { className: "kerne-menu", role: "menu", children: [
2141
+ /* @__PURE__ */ jsxs11("div", { className: "kerne-menu-header", children: [
2142
+ fullName ? /* @__PURE__ */ jsx12("div", { className: "kerne-identity-name", children: fullName }) : null,
2143
+ /* @__PURE__ */ jsx12("div", { className: "kerne-identity-mail", children: email })
2144
+ ] }),
2145
+ items.map(
2146
+ (item) => item.href ? /* @__PURE__ */ jsx12("a", { role: "menuitem", className: "kerne-menu-item", href: item.href, children: item.label }, item.label) : /* @__PURE__ */ jsx12(
2147
+ "button",
2148
+ {
2149
+ type: "button",
2150
+ role: "menuitem",
2151
+ className: "kerne-menu-item",
2152
+ onClick: () => {
2153
+ setOpen(false);
2154
+ item.onClick?.();
2155
+ },
2156
+ children: item.label
2157
+ },
2158
+ item.label
2159
+ )
2160
+ ),
2161
+ /* @__PURE__ */ jsx12(
2162
+ "button",
2163
+ {
2164
+ type: "button",
2165
+ role: "menuitem",
2166
+ className: "kerne-menu-item",
2167
+ "data-tone": "danger",
2168
+ onClick: handleSignOut,
2169
+ children: l10n.account.signOut
2170
+ }
2171
+ )
2172
+ ] }) : null
2173
+ ] })
2174
+ }
2175
+ );
2176
+ }
2177
+
2178
+ // src/ui/UserProfile.tsx
2179
+ import { useState as useState17 } from "react";
2180
+
2181
+ // src/ui/profile/ProfileSection.tsx
2182
+ import { useEffect as useEffect6, useState as useState13 } from "react";
2183
+ import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2184
+ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2185
+ const { updateProfile, sendVerificationEmail, getVerificationStatus } = useAuth();
2186
+ const { user, fullName, initials, email } = useUser();
2187
+ const l10n = useLocalization();
2188
+ const showError = useErrorResolver();
2189
+ const [firstName, setFirstName] = useState13("");
2190
+ const [lastName, setLastName] = useState13("");
2191
+ const [saving, setSaving] = useState13(false);
2192
+ const [saved, setSaved] = useState13(false);
2193
+ const [error, setError] = useState13(null);
2194
+ const [verified, setVerified] = useState13(null);
2195
+ const [sending, setSending] = useState13(false);
2196
+ const [sent, setSent] = useState13(false);
2197
+ useEffect6(() => {
2198
+ if (!user) return;
2199
+ setFirstName(user.first_name ?? "");
2200
+ setLastName(user.last_name ?? "");
2201
+ }, [user]);
2202
+ useEffect6(() => {
2203
+ let mounted = true;
2204
+ getVerificationStatus().then((s) => mounted && setVerified(s.emailVerified)).catch(() => mounted && setVerified(null));
2205
+ return () => {
2206
+ mounted = false;
2207
+ };
2208
+ }, [getVerificationStatus]);
2209
+ const handleSave = async (e) => {
2210
+ e.preventDefault();
2211
+ setError(null);
2212
+ setSaved(false);
2213
+ setSaving(true);
2214
+ try {
2215
+ await updateProfile({ first_name: firstName, last_name: lastName });
2216
+ setSaved(true);
2217
+ onSaved?.();
2218
+ } catch (err) {
2219
+ setError(showError(err));
2220
+ } finally {
2221
+ setSaving(false);
2222
+ }
2223
+ };
2224
+ const handleResend = async () => {
2225
+ setError(null);
2226
+ setSending(true);
2227
+ try {
2228
+ await sendVerificationEmail("link", verificationCallbackUrl);
2229
+ setSent(true);
2230
+ } catch (err) {
2231
+ setError(showError(err));
2232
+ } finally {
2233
+ setSending(false);
2234
+ }
2235
+ };
2236
+ return /* @__PURE__ */ jsxs12(Fragment6, { children: [
2237
+ /* @__PURE__ */ jsxs12(Subsection, { title: l10n.profile.profileTitle, children: [
2238
+ /* @__PURE__ */ jsxs12("div", { className: "kerne-identity", children: [
2239
+ user?.avatar ? /* @__PURE__ */ jsx13("img", { className: "kerne-avatar", src: user.avatar, alt: "" }) : /* @__PURE__ */ jsx13("div", { className: "kerne-avatar", "aria-hidden": "true", children: initials || email?.[0]?.toUpperCase() || "?" }),
2240
+ /* @__PURE__ */ jsxs12("div", { className: "kerne-identity-text", children: [
2241
+ /* @__PURE__ */ jsx13("span", { className: "kerne-identity-name", children: fullName || l10n.profile.unnamed }),
2242
+ /* @__PURE__ */ jsx13("span", { className: "kerne-identity-mail", children: email })
2243
+ ] })
2244
+ ] }),
2245
+ error ? /* @__PURE__ */ jsx13(Panel, { variant: "danger", children: error }) : null,
2246
+ saved ? /* @__PURE__ */ jsx13(Panel, { variant: "success", children: l10n.profile.saved }) : null,
2247
+ /* @__PURE__ */ jsxs12("form", { className: "kerne-form", onSubmit: handleSave, children: [
2248
+ /* @__PURE__ */ jsx13(
2249
+ Field,
2250
+ {
2251
+ label: l10n.profile.firstName,
2252
+ type: "text",
2253
+ value: firstName,
2254
+ onChange: setFirstName,
2255
+ autoComplete: "given-name",
2256
+ placeholder: l10n.profile.firstNamePlaceholder,
2257
+ disabled: saving
2258
+ }
2259
+ ),
2260
+ /* @__PURE__ */ jsx13(
2261
+ Field,
2262
+ {
2263
+ label: l10n.profile.lastName,
2264
+ type: "text",
2265
+ value: lastName,
2266
+ onChange: setLastName,
2267
+ autoComplete: "family-name",
2268
+ placeholder: l10n.profile.lastNamePlaceholder,
2269
+ disabled: saving
2270
+ }
2271
+ ),
2272
+ /* @__PURE__ */ jsx13(Button, { disabled: saving, children: saving ? l10n.profile.saving : l10n.profile.save })
2273
+ ] })
2274
+ ] }),
2275
+ /* @__PURE__ */ jsxs12(
2276
+ Subsection,
2277
+ {
2278
+ title: l10n.profile.emailSectionTitle,
2279
+ description: l10n.profile.emailSectionDesc,
2280
+ children: [
2281
+ /* @__PURE__ */ jsxs12("div", { className: "kerne-plan-option", children: [
2282
+ /* @__PURE__ */ jsx13("div", { className: "kerne-plan-option-text", children: /* @__PURE__ */ jsxs12("span", { className: "kerne-plan-option-name", children: [
2283
+ email,
2284
+ verified === true ? /* @__PURE__ */ jsx13("span", { className: "kerne-badge", "data-tone": "success", children: l10n.profile.verified }) : verified === false ? /* @__PURE__ */ jsx13("span", { className: "kerne-badge", "data-tone": "warning", children: l10n.profile.unverified }) : null
2285
+ ] }) }),
2286
+ verified === false ? /* @__PURE__ */ jsx13(Button, { variant: "outline", type: "button", onClick: handleResend, disabled: sending, children: sending ? l10n.common.sending : l10n.profile.verify }) : null
2287
+ ] }),
2288
+ sent ? /* @__PURE__ */ jsx13(Panel, { variant: "success", children: l10n.profile.verificationSent }) : null
2289
+ ]
2290
+ }
2291
+ )
2292
+ ] });
2293
+ }
2294
+
2295
+ // src/ui/profile/SecuritySection.tsx
2296
+ import { useState as useState14 } from "react";
2297
+ import { Fragment as Fragment7, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2298
+ function SecuritySection({ showDeleteAccount = true, onDeleted }) {
2299
+ const { requestAccountDeletion } = useAuth();
2300
+ const { config } = useAuthConfig();
2301
+ const l10n = useLocalization();
2302
+ const showError = useErrorResolver();
2303
+ const [error, setError] = useState14(null);
2304
+ const [confirmingDelete, setConfirmingDelete] = useState14(false);
2305
+ const [deleting, setDeleting] = useState14(false);
2306
+ const hasPassword = config?.enable_credentials ?? true;
2307
+ const handleDelete = async () => {
2308
+ setError(null);
2309
+ setDeleting(true);
2310
+ try {
2311
+ await requestAccountDeletion();
2312
+ onDeleted?.();
2313
+ } catch (err) {
2314
+ setError(showError(err));
2315
+ setDeleting(false);
2316
+ }
2317
+ };
2318
+ return /* @__PURE__ */ jsxs13(Fragment7, { children: [
2319
+ error ? /* @__PURE__ */ jsx14(Panel, { variant: "danger", children: error }) : null,
2320
+ hasPassword ? /* @__PURE__ */ jsx14(Subsection, { title: l10n.security.passwordTitle, description: l10n.security.passwordDesc, children: /* @__PURE__ */ jsx14(ChangePasswordForm, { title: null }) }) : null,
2321
+ showDeleteAccount ? /* @__PURE__ */ jsx14(Subsection, { title: l10n.security.deleteTitle, description: l10n.security.deleteDesc, children: confirmingDelete ? /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 10, flexWrap: "wrap" }, children: [
2322
+ /* @__PURE__ */ jsx14(Button, { variant: "danger", type: "button", onClick: handleDelete, disabled: deleting, children: deleting ? l10n.security.deleting : l10n.security.deleteConfirm }),
2323
+ /* @__PURE__ */ jsx14(
2324
+ Button,
2325
+ {
2326
+ variant: "outline",
2327
+ type: "button",
2328
+ onClick: () => setConfirmingDelete(false),
2329
+ disabled: deleting,
2330
+ children: l10n.common.cancel
2331
+ }
2332
+ )
2333
+ ] }) : /* @__PURE__ */ jsx14(Button, { variant: "danger", type: "button", onClick: () => setConfirmingDelete(true), children: l10n.security.deleteAction }) }) : null
2334
+ ] });
2335
+ }
2336
+
2337
+ // src/ui/profile/BillingSection.tsx
2338
+ import { useMemo, useState as useState16 } from "react";
2339
+
2340
+ // src/ui/format.ts
2341
+ function humanizeFeatureKey(key) {
2342
+ const words = key.replace(/[_-]+/g, " ").trim();
2343
+ return words.charAt(0).toUpperCase() + words.slice(1);
2344
+ }
2345
+ function formatMoney(amountMinorUnits, currency) {
2346
+ try {
2347
+ return new Intl.NumberFormat(void 0, {
2348
+ style: "currency",
2349
+ currency: currency.toUpperCase(),
2350
+ // Whole amounts read better without ".00" on a pricing surface, but a
2351
+ // real fractional price must never be rounded away.
2352
+ minimumFractionDigits: amountMinorUnits % 100 === 0 ? 0 : 2
2353
+ }).format(amountMinorUnits / 100);
2354
+ } catch {
2355
+ return `${(amountMinorUnits / 100).toFixed(2)} ${currency.toUpperCase()}`;
2356
+ }
2357
+ }
2358
+ function formatNumber(value) {
2359
+ return new Intl.NumberFormat().format(value);
2360
+ }
2361
+ function formatDate(iso) {
2362
+ if (!iso) return null;
2363
+ const d = new Date(iso);
2364
+ if (Number.isNaN(d.getTime())) return null;
2365
+ return new Intl.DateTimeFormat(void 0, {
2366
+ year: "numeric",
2367
+ month: "short",
2368
+ day: "numeric"
2369
+ }).format(d);
2370
+ }
2371
+ var ENCODED_INTERVAL = /^([1-9]\d*)([DMY])$/;
2372
+ function formatInterval(interval, labels) {
2373
+ const encoded = ENCODED_INTERVAL.exec(interval);
2374
+ if (encoded) {
2375
+ const count = parseInt(encoded[1], 10);
2376
+ const unitLabel = { D: labels.intervalDay, M: labels.intervalMonth, Y: labels.intervalYear }[encoded[2]];
2377
+ return count === 1 ? unitLabel : `${count} ${unitLabel}`;
2378
+ }
2379
+ switch (interval.toLowerCase()) {
2380
+ case "month":
2381
+ case "monthly":
2382
+ return labels.intervalMonth;
2383
+ case "year":
2384
+ case "yearly":
2385
+ case "annual":
2386
+ return labels.intervalYear;
2387
+ case "week":
2388
+ case "weekly":
2389
+ return labels.intervalWeek;
2390
+ case "day":
2391
+ case "daily":
2392
+ return labels.intervalDay;
2393
+ default:
2394
+ return interval;
2395
+ }
2396
+ }
2397
+
2398
+ // src/ui/UsageMeters.tsx
2399
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2400
+ function levelFor(ratio, overageAllowed) {
2401
+ if (overageAllowed) return ratio >= 1 ? "warning" : "normal";
2402
+ if (ratio >= 0.9) return "critical";
2403
+ if (ratio >= 0.75) return "warning";
2404
+ return "normal";
2405
+ }
2406
+ function UsageMeters({
2407
+ title,
2408
+ featureKeys,
2409
+ showUnlimited = false,
2410
+ limit: maxRows,
2411
+ labels,
2412
+ emptyState,
2413
+ appearance,
2414
+ className
2415
+ }) {
2416
+ const { entitlements, isLoading, error } = useEntitlements();
2417
+ const { subscription } = useSubscription();
2418
+ const l10n = useLocalization();
2419
+ const heading = title === null ? null : title ?? l10n.usage.title;
2420
+ const nameFor = (key) => {
2421
+ if (labels?.[key]) return labels[key];
2422
+ const fromPlan = subscription?.entitlements.find((e) => e.feature_key === key)?.feature_name;
2423
+ return fromPlan ?? humanizeFeatureKey(key);
2424
+ };
2425
+ const shell = (children) => /* @__PURE__ */ jsxs14(Shell, { appearance, className, children: [
2426
+ heading ? /* @__PURE__ */ jsx15("div", { className: "kerne-section-head", style: { marginBottom: 16 }, children: /* @__PURE__ */ jsx15("h2", { className: "kerne-section-title", children: heading }) }) : null,
2427
+ children
2428
+ ] });
2429
+ if (isLoading) {
2430
+ return shell(
2431
+ /* @__PURE__ */ jsxs14("div", { className: "kerne-meters", "aria-busy": "true", "aria-label": l10n.usage.loading, children: [
2432
+ /* @__PURE__ */ jsx15("div", { className: "kerne-skeleton", style: { height: 34 } }),
2433
+ /* @__PURE__ */ jsx15("div", { className: "kerne-skeleton", style: { height: 34 } })
2434
+ ] })
2435
+ );
2436
+ }
2437
+ if (error || !entitlements) {
2438
+ return shell(/* @__PURE__ */ jsx15(Panel, { variant: "danger", children: l10n.usage.loadFailed }));
2439
+ }
2440
+ const metered = entitlements.entitlements.filter((e) => {
2441
+ if (e.feature_type !== "QUOTA") return false;
2442
+ if (featureKeys && !featureKeys.includes(e.feature_key)) return false;
2443
+ if (!showUnlimited && (e.limit === null || e.limit === void 0)) return false;
2444
+ return typeof e.used === "number";
2445
+ }).sort((a, b) => {
2446
+ if (featureKeys) {
2447
+ return featureKeys.indexOf(a.feature_key) - featureKeys.indexOf(b.feature_key);
2448
+ }
2449
+ const ratio = (e) => e.limit ? e.used / e.limit : -1;
2450
+ return ratio(b) - ratio(a);
2451
+ });
2452
+ const rows = maxRows ? metered.slice(0, maxRows) : metered;
2453
+ if (rows.length === 0) {
2454
+ return shell(emptyState ?? /* @__PURE__ */ jsx15("p", { className: "kerne-meter-note", children: l10n.usage.nothingMetered }));
2455
+ }
2456
+ return shell(
2457
+ /* @__PURE__ */ jsx15("div", { className: "kerne-meters", children: rows.map((e) => {
2458
+ const unlimited = e.limit === null || e.limit === void 0;
2459
+ const overageAllowed = e.overage_behavior === "ALLOW";
2460
+ const ratio = unlimited ? 0 : Math.min(e.used / e.limit, 1);
2461
+ const level = unlimited ? "normal" : levelFor(e.used / e.limit, overageAllowed);
2462
+ const over = !unlimited && e.used > e.limit;
2463
+ return /* @__PURE__ */ jsxs14("div", { className: "kerne-meter", "data-level": level, children: [
2464
+ /* @__PURE__ */ jsxs14("div", { className: "kerne-meter-head", children: [
2465
+ /* @__PURE__ */ jsx15("span", { className: "kerne-meter-name", children: nameFor(e.feature_key) }),
2466
+ /* @__PURE__ */ jsx15("span", { className: "kerne-meter-value", children: unlimited ? fill(l10n.usage.used, { count: formatNumber(e.used) }) : `${formatNumber(e.used)} / ${formatNumber(e.limit)}` })
2467
+ ] }),
2468
+ !unlimited ? /* @__PURE__ */ jsx15(
2469
+ "div",
2470
+ {
2471
+ className: "kerne-meter-track",
2472
+ role: "progressbar",
2473
+ "aria-valuenow": e.used,
2474
+ "aria-valuemin": 0,
2475
+ "aria-valuemax": e.limit,
2476
+ "aria-label": nameFor(e.feature_key),
2477
+ children: /* @__PURE__ */ jsx15("div", { className: "kerne-meter-fill", style: { width: `${ratio * 100}%` } })
2478
+ }
2479
+ ) : null,
2480
+ over && overageAllowed ? /* @__PURE__ */ jsx15("p", { className: "kerne-meter-note", children: fill(l10n.usage.overage, {
2481
+ count: formatNumber(e.used - e.limit)
2482
+ }) }) : over ? /* @__PURE__ */ jsx15("p", { className: "kerne-meter-note", children: l10n.usage.limitReached }) : null
2483
+ ] }, e.feature_key);
2484
+ }) })
2485
+ );
2486
+ }
2487
+
2488
+ // src/ui/SubscriptionCard.tsx
2489
+ import { useState as useState15 } from "react";
2490
+ import { Fragment as Fragment8, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2491
+ var STATUS_TONES = {
2492
+ ACTIVE: "success",
2493
+ TRIALING: "success",
2494
+ PAST_DUE: "warning",
2495
+ CANCELED: void 0,
2496
+ UNPAID: "danger",
2497
+ PAUSED: "warning"
2498
+ };
2499
+ function SubscriptionCard({
2500
+ productSlug,
2501
+ title,
2502
+ portalReturnUrl,
2503
+ upgradeUrl,
2504
+ onUpgradeClick,
2505
+ hidePortalButton = false,
2506
+ appearance,
2507
+ className
2508
+ }) {
2509
+ const { subscription, isLoading, error } = useSubscription(productSlug);
2510
+ const { openPortal } = usePortal();
2511
+ const l10n = useLocalization();
2512
+ const showError = useErrorResolver();
2513
+ const heading = title === null ? null : title ?? l10n.billing.plan;
2514
+ const statusLabels = {
2515
+ ACTIVE: l10n.billing.statusActive,
2516
+ TRIALING: l10n.billing.statusTrialing,
2517
+ PAST_DUE: l10n.billing.statusPastDue,
2518
+ CANCELED: l10n.billing.statusCanceled,
2519
+ UNPAID: l10n.billing.statusUnpaid,
2520
+ PAUSED: l10n.billing.statusPaused
2521
+ };
2522
+ const [opening, setOpening] = useState15(false);
2523
+ const [portalError, setPortalError] = useState15(null);
2524
+ const handlePortal = async () => {
2525
+ setPortalError(null);
2526
+ setOpening(true);
2527
+ try {
2528
+ await openPortal(portalReturnUrl);
2529
+ } catch (e) {
2530
+ setPortalError(showError(e));
2531
+ setOpening(false);
2532
+ }
2533
+ };
2534
+ const shell = (children) => /* @__PURE__ */ jsx16(Shell, { appearance, className, children: /* @__PURE__ */ jsxs15("div", { className: "kerne-stack", children: [
2535
+ heading ? /* @__PURE__ */ jsx16("h2", { className: "kerne-section-title", children: heading }) : null,
2536
+ children
2537
+ ] }) });
2538
+ if (isLoading) {
2539
+ return shell(
2540
+ /* @__PURE__ */ jsxs15("div", { className: "kerne-rows", "aria-busy": "true", "aria-label": l10n.billing.loadingPlan, children: [
2541
+ /* @__PURE__ */ jsx16("div", { className: "kerne-skeleton", style: { height: 20 } }),
2542
+ /* @__PURE__ */ jsx16("div", { className: "kerne-skeleton", style: { height: 20 } })
2543
+ ] })
2544
+ );
2545
+ }
2546
+ if (error) {
2547
+ return shell(/* @__PURE__ */ jsx16(Panel, { variant: "danger", children: l10n.billing.loadFailed }));
2548
+ }
2549
+ if (!subscription) {
2550
+ return shell(
2551
+ /* @__PURE__ */ jsxs15(Fragment8, { children: [
2552
+ /* @__PURE__ */ jsx16("p", { className: "kerne-subsection-desc", children: l10n.billing.freePlan }),
2553
+ upgradeUrl || onUpgradeClick ? /* @__PURE__ */ jsx16(
2554
+ Button,
2555
+ {
2556
+ type: "button",
2557
+ onClick: onUpgradeClick ?? (() => {
2558
+ if (upgradeUrl) window.location.href = upgradeUrl;
2559
+ }),
2560
+ children: l10n.billing.seePlans
2561
+ }
2562
+ ) : null
2563
+ ] })
2564
+ );
2565
+ }
2566
+ const { plan, plan_price, status, current_period_end, trial_end, canceled_at } = subscription;
2567
+ const renews = formatDate(current_period_end);
2568
+ const trialEnds = formatDate(trial_end);
2569
+ return shell(
2570
+ /* @__PURE__ */ jsxs15(Fragment8, { children: [
2571
+ portalError ? /* @__PURE__ */ jsx16(Panel, { variant: "danger", children: portalError }) : null,
2572
+ /* @__PURE__ */ jsxs15("dl", { className: "kerne-rows", children: [
2573
+ /* @__PURE__ */ jsxs15("div", { className: "kerne-row", children: [
2574
+ /* @__PURE__ */ jsx16("dt", { children: l10n.billing.plan }),
2575
+ /* @__PURE__ */ jsx16("dd", { children: plan.name })
2576
+ ] }),
2577
+ /* @__PURE__ */ jsxs15("div", { className: "kerne-row", children: [
2578
+ /* @__PURE__ */ jsx16("dt", { children: l10n.billing.price }),
2579
+ /* @__PURE__ */ jsxs15("dd", { children: [
2580
+ formatMoney(plan_price.amount, plan_price.currency),
2581
+ " /",
2582
+ " ",
2583
+ formatInterval(plan_price.interval, l10n.billing)
2584
+ ] })
2585
+ ] }),
2586
+ /* @__PURE__ */ jsxs15("div", { className: "kerne-row", children: [
2587
+ /* @__PURE__ */ jsx16("dt", { children: l10n.billing.status }),
2588
+ /* @__PURE__ */ jsx16("dd", { children: /* @__PURE__ */ jsx16("span", { className: "kerne-badge", "data-tone": STATUS_TONES[status], children: statusLabels[status] ?? status }) })
2589
+ ] }),
2590
+ status === "TRIALING" && trialEnds ? /* @__PURE__ */ jsxs15("div", { className: "kerne-row", children: [
2591
+ /* @__PURE__ */ jsx16("dt", { children: l10n.billing.trialEnds }),
2592
+ /* @__PURE__ */ jsx16("dd", { children: trialEnds })
2593
+ ] }) : renews ? /* @__PURE__ */ jsxs15("div", { className: "kerne-row", children: [
2594
+ /* @__PURE__ */ jsx16("dt", { children: canceled_at ? l10n.billing.ends : l10n.billing.renews }),
2595
+ /* @__PURE__ */ jsx16("dd", { children: renews })
2596
+ ] }) : null
2597
+ ] }),
2598
+ canceled_at ? /* @__PURE__ */ jsx16(Panel, { children: l10n.billing.cancelNotice }) : null,
2599
+ !hidePortalButton ? /* @__PURE__ */ jsx16(Button, { variant: "outline", type: "button", onClick: handlePortal, disabled: opening, children: opening ? l10n.billing.opening : l10n.billing.managePortal }) : null
2600
+ ] })
2601
+ );
2602
+ }
2603
+
2604
+ // src/ui/profile/BillingSection.tsx
2605
+ import { Fragment as Fragment9, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2606
+ function intervalsOf(plans) {
2607
+ const seen = [];
2608
+ for (const plan of plans) {
2609
+ for (const price of plan.prices) {
2610
+ if (!seen.includes(price.interval)) seen.push(price.interval);
2611
+ }
2612
+ }
2613
+ return seen;
2614
+ }
2615
+ function BillingSection({
2616
+ productSlug,
2617
+ pricingProductIdOrSlug,
2618
+ portalReturnUrl,
2619
+ checkoutSuccessUrl,
2620
+ checkoutCancelUrl,
2621
+ showPlanSwitcher = true,
2622
+ showUsage = true,
2623
+ usageFeatureKeys,
2624
+ usageLabels
2625
+ }) {
2626
+ const { subscription } = useSubscription(productSlug);
2627
+ const { plans, isLoading: plansLoading } = usePlans(pricingProductIdOrSlug);
2628
+ const { openCheckout } = useCheckout();
2629
+ const l10n = useLocalization();
2630
+ const showError = useErrorResolver();
2631
+ const [busy, setBusy] = useState16(null);
2632
+ const [error, setError] = useState16(null);
2633
+ const intervals = useMemo(() => intervalsOf(plans), [plans]);
2634
+ const [interval, setInterval] = useState16(null);
2635
+ const activeInterval = interval ?? subscription?.plan_price.interval ?? intervals[0] ?? null;
2636
+ const handleSwitch = async (planPriceId) => {
2637
+ setError(null);
2638
+ setBusy(planPriceId);
2639
+ try {
2640
+ await openCheckout(planPriceId, {
2641
+ successUrl: checkoutSuccessUrl,
2642
+ cancelUrl: checkoutCancelUrl
2643
+ });
2644
+ } catch (e) {
2645
+ setError(showError(e));
2646
+ setBusy(null);
2647
+ }
2648
+ };
2649
+ return /* @__PURE__ */ jsxs16(Fragment9, { children: [
2650
+ error ? /* @__PURE__ */ jsx17(Panel, { variant: "danger", children: error }) : null,
2651
+ /* @__PURE__ */ jsx17(Subsection, { title: l10n.billing.currentPlan, children: /* @__PURE__ */ jsx17(
2652
+ SubscriptionCard,
2653
+ {
2654
+ title: null,
2655
+ productSlug,
2656
+ portalReturnUrl
2657
+ }
2658
+ ) }),
2659
+ showUsage ? /* @__PURE__ */ jsx17(Subsection, { title: l10n.usage.title, description: l10n.usage.subtitle, children: /* @__PURE__ */ jsx17(UsageMeters, { title: null, featureKeys: usageFeatureKeys, labels: usageLabels }) }) : null,
2660
+ showPlanSwitcher ? /* @__PURE__ */ jsx17(Subsection, { title: l10n.billing.changePlan, children: plansLoading ? /* @__PURE__ */ jsx17(
2661
+ "div",
2662
+ {
2663
+ className: "kerne-skeleton",
2664
+ style: { height: 64 },
2665
+ "aria-label": l10n.billing.loadingPlans
2666
+ }
2667
+ ) : plans.length === 0 ? /* @__PURE__ */ jsx17("p", { className: "kerne-subsection-desc", children: l10n.billing.noPlans }) : /* @__PURE__ */ jsxs16(Fragment9, { children: [
2668
+ intervals.length > 1 ? /* @__PURE__ */ jsx17(
2669
+ "div",
2670
+ {
2671
+ className: "kerne-segmented",
2672
+ role: "group",
2673
+ "aria-label": l10n.billing.intervalGroupLabel,
2674
+ children: intervals.map((i) => /* @__PURE__ */ jsx17(
2675
+ "button",
2676
+ {
2677
+ type: "button",
2678
+ className: "kerne-segment",
2679
+ "aria-pressed": i === activeInterval,
2680
+ onClick: () => setInterval(i),
2681
+ children: formatInterval(i, l10n.billing)
2682
+ },
2683
+ i
2684
+ ))
2685
+ }
2686
+ ) : null,
2687
+ /* @__PURE__ */ jsx17("div", { className: "kerne-plan-list", children: plans.map((plan) => {
2688
+ const price = plan.prices.find((p) => p.interval === activeInterval) ?? plan.prices[0];
2689
+ const isCurrent = subscription?.plan.slug === plan.slug;
2690
+ return /* @__PURE__ */ jsxs16(
2691
+ "div",
2692
+ {
2693
+ className: "kerne-plan-option",
2694
+ "data-current": isCurrent ? "true" : void 0,
2695
+ children: [
2696
+ /* @__PURE__ */ jsxs16("div", { className: "kerne-plan-option-text", children: [
2697
+ /* @__PURE__ */ jsxs16("span", { className: "kerne-plan-option-name", children: [
2698
+ plan.name,
2699
+ isCurrent ? /* @__PURE__ */ jsx17("span", { className: "kerne-badge", children: l10n.billing.current }) : null
2700
+ ] }),
2701
+ /* @__PURE__ */ jsx17("span", { className: "kerne-plan-option-price", children: price ? `${formatMoney(price.amount, price.currency)} / ${formatInterval(price.interval, l10n.billing)}` : l10n.billing.notOnInterval })
2702
+ ] }),
2703
+ !isCurrent && price ? /* @__PURE__ */ jsx17(
2704
+ Button,
2705
+ {
2706
+ variant: "outline",
2707
+ type: "button",
2708
+ onClick: () => handleSwitch(price.id),
2709
+ disabled: busy !== null,
2710
+ children: busy === price.id ? l10n.billing.opening : l10n.billing.switchTo
2711
+ }
2712
+ ) : null
2713
+ ]
2714
+ },
2715
+ plan.id
2716
+ );
2717
+ }) })
2718
+ ] }) }) : null
2719
+ ] });
2720
+ }
2721
+
2722
+ // src/ui/UserProfile.tsx
2723
+ import { Fragment as Fragment10, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
2724
+ function UserProfile({
2725
+ tabs = ["profile", "security", "billing"],
2726
+ extraTabs = [],
2727
+ defaultTab,
2728
+ activeTab: controlledTab,
2729
+ onTabChange,
2730
+ showDeleteAccount = true,
2731
+ onSaved,
2732
+ onDeleted,
2733
+ productSlug,
2734
+ pricingProductIdOrSlug,
2735
+ portalReturnUrl,
2736
+ checkoutSuccessUrl,
2737
+ checkoutCancelUrl,
2738
+ showPlanSwitcher = true,
2739
+ showUsage = true,
2740
+ usageFeatureKeys,
2741
+ usageLabels,
2742
+ verificationCallbackUrl,
2743
+ appearance,
2744
+ className
2745
+ }) {
2746
+ const l10n = useLocalization();
2747
+ const builtins = {
2748
+ profile: {
2749
+ label: l10n.profile.tabProfile,
2750
+ title: l10n.profile.profileTitle,
2751
+ description: l10n.profile.profileDesc
2752
+ },
2753
+ security: {
2754
+ label: l10n.profile.tabSecurity,
2755
+ title: l10n.profile.securityTitle,
2756
+ description: l10n.profile.securityDesc
2757
+ },
2758
+ billing: {
2759
+ label: l10n.profile.tabBilling,
2760
+ title: l10n.profile.billingTitle,
2761
+ description: l10n.profile.billingDesc
2762
+ }
2763
+ };
2764
+ const allTabs = [
2765
+ ...tabs.map((id) => ({ id, ...builtins[id] })),
2766
+ ...extraTabs.map((t) => ({
2767
+ id: t.id,
2768
+ label: t.label,
2769
+ title: t.title ?? t.label,
2770
+ description: t.description
2771
+ }))
2772
+ ];
2773
+ const [internalTab, setInternalTab] = useState17(defaultTab ?? allTabs[0]?.id ?? "profile");
2774
+ const current = controlledTab ?? internalTab;
2775
+ const select = (id) => {
2776
+ if (controlledTab === void 0) setInternalTab(id);
2777
+ onTabChange?.(id);
2778
+ };
2779
+ const active = allTabs.find((t) => t.id === current) ?? allTabs[0];
2780
+ const renderPane = () => {
2781
+ switch (active?.id) {
2782
+ case "profile":
2783
+ return /* @__PURE__ */ jsx18(ProfileSection, { verificationCallbackUrl, onSaved });
2784
+ case "security":
2785
+ return /* @__PURE__ */ jsx18(SecuritySection, { showDeleteAccount, onDeleted });
2786
+ case "billing":
2787
+ return /* @__PURE__ */ jsx18(
2788
+ BillingSection,
2789
+ {
2790
+ productSlug,
2791
+ pricingProductIdOrSlug,
2792
+ portalReturnUrl,
2793
+ checkoutSuccessUrl,
2794
+ checkoutCancelUrl,
2795
+ showPlanSwitcher,
2796
+ showUsage,
2797
+ usageFeatureKeys,
2798
+ usageLabels
2799
+ }
2800
+ );
2801
+ default:
2802
+ return extraTabs.find((t) => t.id === active?.id)?.content ?? null;
2803
+ }
2804
+ };
2805
+ return /* @__PURE__ */ jsx18(Shell, { appearance, className, size: "wide", children: /* @__PURE__ */ jsxs17("div", { className: "kerne-profile", children: [
2806
+ /* @__PURE__ */ jsx18("nav", { className: "kerne-nav", "aria-label": l10n.profile.navLabel, children: allTabs.map((tab) => /* @__PURE__ */ jsx18(
2807
+ "button",
2808
+ {
2809
+ type: "button",
2810
+ className: "kerne-nav-item",
2811
+ "aria-current": tab.id === active?.id ? "page" : void 0,
2812
+ onClick: () => select(tab.id),
2813
+ children: tab.label
2814
+ },
2815
+ tab.id
2816
+ )) }),
2817
+ /* @__PURE__ */ jsxs17("div", { className: "kerne-pane", children: [
2818
+ active ? /* @__PURE__ */ jsxs17(Fragment10, { children: [
2819
+ /* @__PURE__ */ jsx18("h2", { className: "kerne-pane-title", children: active.title }),
2820
+ active.description ? /* @__PURE__ */ jsx18("p", { className: "kerne-pane-desc", children: active.description }) : null
2821
+ ] }) : null,
2822
+ renderPane()
2823
+ ] })
2824
+ ] }) });
2825
+ }
2826
+
2827
+ // src/ui/CheckoutResult.tsx
2828
+ import { useEffect as useEffect7, useRef as useRef4, useState as useState18 } from "react";
2829
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2830
+ var POLL_INTERVAL_MS = 1500;
2831
+ function CheckoutResult({
2832
+ outcome = "success",
2833
+ productSlug,
2834
+ continueUrl,
2835
+ onContinue,
2836
+ pricingUrl,
2837
+ onRetry,
2838
+ onConfirmed,
2839
+ confirmTimeoutMs = 15e3,
2840
+ appearance,
2841
+ logo,
2842
+ className
2843
+ }) {
2844
+ const client = useClient();
2845
+ const { config } = useAuthConfig();
2846
+ const l10n = useLocalization();
2847
+ const [state, setState] = useState18("checking");
2848
+ const confirmed = useRef4(false);
2849
+ useEffect7(() => {
2850
+ if (outcome !== "success") return;
2851
+ let stopped = false;
2852
+ const startedAt = Date.now();
2853
+ const poll = async () => {
2854
+ if (stopped || confirmed.current) return;
2855
+ try {
2856
+ const sub = await client.getSubscription(productSlug);
2857
+ if (sub && !stopped) {
2858
+ confirmed.current = true;
2859
+ setState("confirmed");
2860
+ onConfirmed?.(sub);
2861
+ return;
2862
+ }
2863
+ } catch {
2864
+ }
2865
+ if (stopped) return;
2866
+ if (Date.now() - startedAt >= confirmTimeoutMs) {
2867
+ setState("pending");
2868
+ return;
2869
+ }
2870
+ window.setTimeout(poll, POLL_INTERVAL_MS);
2871
+ };
2872
+ void poll();
2873
+ return () => {
2874
+ stopped = true;
2875
+ };
2876
+ }, [outcome, productSlug, client, confirmTimeoutMs, onConfirmed]);
2877
+ const go = (url, handler) => handler ?? (() => {
2878
+ if (url) window.location.href = url;
2879
+ });
2880
+ const continueAction = continueUrl || onContinue ? /* @__PURE__ */ jsx19(Button, { type: "button", onClick: go(continueUrl, onContinue), children: l10n.common.continue }) : void 0;
2881
+ const shell = (children) => /* @__PURE__ */ jsxs18(Shell, { appearance, className, children: [
2882
+ logo ? /* @__PURE__ */ jsx19("div", { className: "kerne-masthead", children: logo }) : null,
2883
+ children,
2884
+ /* @__PURE__ */ jsx19(KerneBranding, { show: config?.show_kerne_branding ?? false })
2885
+ ] });
2886
+ if (outcome === "cancel") {
2887
+ return shell(
2888
+ /* @__PURE__ */ jsx19(
2889
+ Status,
2890
+ {
2891
+ tone: "danger",
2892
+ title: l10n.checkout.cancelTitle,
2893
+ action: pricingUrl || onRetry ? /* @__PURE__ */ jsx19(Button, { variant: "outline", type: "button", onClick: go(pricingUrl, onRetry), children: l10n.checkout.backToPlans }) : void 0,
2894
+ children: l10n.checkout.cancelBody
2895
+ }
2896
+ )
2897
+ );
2898
+ }
2899
+ if (state === "checking") {
2900
+ return shell(
2901
+ /* @__PURE__ */ jsx19(Status, { tone: "loading", title: l10n.checkout.confirmingTitle, children: l10n.checkout.confirmingBody })
2902
+ );
2903
+ }
2904
+ if (state === "pending") {
2905
+ return shell(
2906
+ /* @__PURE__ */ jsx19(Status, { tone: "success", title: l10n.checkout.pendingTitle, action: continueAction, children: l10n.checkout.pendingBody })
2907
+ );
2908
+ }
2909
+ return shell(
2910
+ /* @__PURE__ */ jsx19(Status, { tone: "success", title: l10n.checkout.doneTitle, action: continueAction, children: l10n.checkout.doneBody })
2911
+ );
2912
+ }
2913
+
2914
+ // src/ui/UpgradePrompt.tsx
2915
+ import { Fragment as Fragment11, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2916
+ function UpgradePrompt({
2917
+ featureKey,
2918
+ requested,
2919
+ featureName,
2920
+ children,
2921
+ upgradeUrl,
2922
+ onUpgradeClick,
2923
+ upgradeLabel,
2924
+ title,
2925
+ description,
2926
+ hideWhileLoading = false,
2927
+ appearance,
2928
+ className
2929
+ }) {
2930
+ const { details, allowed, isLoading } = useAccess(featureKey, requested);
2931
+ const l10n = useLocalization();
2932
+ if (isLoading) {
2933
+ if (hideWhileLoading) return null;
2934
+ return /* @__PURE__ */ jsx20(Shell, { appearance, className, children: /* @__PURE__ */ jsx20("div", { className: "kerne-skeleton", style: { height: 80 }, "aria-label": l10n.upgrade.checking }) });
2935
+ }
2936
+ if (allowed) return /* @__PURE__ */ jsx20(Fragment11, { children });
2937
+ const name = featureName ?? humanizeFeatureKey(featureKey).toLowerCase();
2938
+ const quotaExhausted = details?.feature_type === "QUOTA" && typeof details.limit === "number" && typeof details.used === "number";
2939
+ const heading = title ?? fill(quotaExhausted ? l10n.upgrade.quotaTitle : l10n.upgrade.featureTitle, { feature: name });
2940
+ const body = description ?? (quotaExhausted ? fill(l10n.upgrade.quotaBody, {
2941
+ used: formatNumber(details.used),
2942
+ limit: formatNumber(details.limit)
2943
+ }) : fill(l10n.upgrade.featureBody, { feature: name }));
2944
+ return /* @__PURE__ */ jsx20(Shell, { appearance, className, children: /* @__PURE__ */ jsxs19("div", { className: "kerne-status", children: [
2945
+ /* @__PURE__ */ jsxs19("div", { className: "kerne-heading", children: [
2946
+ /* @__PURE__ */ jsx20("h2", { className: "kerne-section-title", style: { fontSize: 17 }, children: heading }),
2947
+ /* @__PURE__ */ jsx20("p", { className: "kerne-status-text", children: body })
2948
+ ] }),
2949
+ upgradeUrl || onUpgradeClick ? /* @__PURE__ */ jsx20(
2950
+ Button,
2951
+ {
2952
+ type: "button",
2953
+ onClick: onUpgradeClick ?? (() => {
2954
+ if (upgradeUrl) window.location.href = upgradeUrl;
2955
+ }),
2956
+ children: upgradeLabel ?? l10n.upgrade.action
2957
+ }
2958
+ ) : null
2959
+ ] }) });
2960
+ }
2961
+ export {
2962
+ ActivateAccountForm,
2963
+ AuthFlow,
2964
+ BillingSection,
2965
+ ChangePasswordForm,
2966
+ CheckoutResult,
2967
+ ForgotPasswordForm,
2968
+ LoginForm,
2969
+ MagicLinkCallback,
2970
+ ProfileSection,
2971
+ RegisterForm,
2972
+ ResetPasswordForm,
2973
+ SecuritySection,
2974
+ SignOutButton,
2975
+ SubscriptionCard,
2976
+ UpgradePrompt,
2977
+ UsageMeters,
2978
+ UserButton,
2979
+ UserProfile,
2980
+ VerifyEmailForm,
2981
+ WaitlistForm,
2982
+ defaultLocalization,
2983
+ useLocalization
2984
+ };