@kerne/react 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1285 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;"use client";
2
-
3
- // src/ui/i18n.ts
4
- var _react = require('react');
5
- var defaultLocalization = {
6
- common: {
7
- email: "E-mail",
8
- emailPlaceholder: "you@company.com",
9
- password: "Password",
10
- passwordPlaceholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
11
- name: "Name",
12
- namePlaceholder: "Ada Lovelace",
13
- cancel: "Cancel",
14
- continue: "Continue",
15
- or: "or",
16
- thisApp: "This app",
17
- backToSignIn: "Back to sign in",
18
- sending: "Sending...",
19
- continueWith: "Continue with {provider}",
20
- securedByKerne: "Secured by Kerne",
21
- loading: "Loading",
22
- showPassword: "Show password",
23
- hidePassword: "Hide password",
24
- capsLockOn: "Caps Lock is on",
25
- passwordHint: "At least {count} characters",
26
- passwordRequireUppercase: "One uppercase letter",
27
- passwordRequireNumber: "One number",
28
- passwordRequireSymbol: "One special character",
29
- strengthLabel: "Password strength",
30
- strengthWeak: "Weak",
31
- strengthFair: "Fair",
32
- strengthGood: "Good",
33
- strengthStrong: "Strong"
34
- },
35
- signIn: {
36
- title: "Welcome back",
37
- signUpPrompt: "First time here?",
38
- signUpLink: "Sign up for free",
39
- signUpForApp: "Sign up for {app}",
40
- submit: "Sign in",
41
- submitting: "Signing in...",
42
- magicSubmit: "Send me a sign-in link",
43
- magicSubmitting: "Sending link...",
44
- magicSubtitle: "We will email you a link to sign in.",
45
- forgotPassword: "Forgot password?",
46
- useMagicLink: "Send me a sign-in link instead",
47
- usePassword: "Sign in with a password",
48
- linkSent: "Check your inbox - if an account exists for {email}, a sign-in link is on its way.",
49
- unavailable: "Sign-in is not available right now. Contact your administrator.",
50
- loadFailed: "Could not load sign-in options. Please refresh the page."
51
- },
52
- signUp: {
53
- title: "Create your account",
54
- titleWithApp: "Create your {app} account",
55
- signInPrompt: "Already have an account?",
56
- signInLink: "Sign in",
57
- submit: "Create account",
58
- submitting: "Creating account...",
59
- magicSubmit: "Send me a sign-up link",
60
- magicSubmitting: "Sending link...",
61
- invitationCode: "Invitation code",
62
- invitationNeeded: "You need an invitation to sign up.",
63
- closedTitle: "Sign-ups are closed",
64
- closedBody: "{app} is not accepting new accounts right now.",
65
- linkSent: "Check your inbox - we sent a link to {email}. Opening it confirms your address and signs you in.",
66
- useMagicLink: "Send me a sign-up link instead",
67
- usePassword: "Sign up with a password",
68
- unavailable: "Sign-up is not available right now. Contact your administrator.",
69
- loadFailed: "Could not load sign-up options. Please refresh the page."
70
- },
71
- resetPassword: {
72
- requestTitle: "Reset your password",
73
- requestSubtitle: "We will email you a link to choose a new password.",
74
- requestSubmit: "Send me a reset link",
75
- requestSubmitting: "Sending link...",
76
- requestSent: "Check your inbox - if an account exists for {email}, a reset link is on its way.",
77
- rememberedPrompt: "Remembered it?",
78
- title: "Choose a new password",
79
- subtitle: "Pick something you have not used before.",
80
- newPassword: "New password",
81
- confirmPassword: "Confirm new password",
82
- mismatch: "Both passwords must match.",
83
- submit: "Update password",
84
- submitting: "Updating...",
85
- doneTitle: "Password updated",
86
- doneBody: "You can now sign in with your new password.",
87
- incompleteTitle: "This link is incomplete",
88
- incompleteBody: "It is missing its reset token. Request a new reset email and try again."
89
- },
90
- magicLink: {
91
- pendingTitle: "Signing you in",
92
- pendingBody: "One moment while we verify your link.",
93
- doneTitle: "You're signed in",
94
- doneBody: "You can close this tab if it does not redirect on its own.",
95
- failedTitle: "This link didn't work",
96
- missingToken: "This link is missing its sign-in token."
97
- },
98
- verifyEmail: {
99
- title: "Verify your email",
100
- subtitle: "Enter the 6-digit code we emailed you.",
101
- code: "Verification code",
102
- submit: "Verify email",
103
- submitting: "Verifying...",
104
- resend: "Didn't get it? Send a new code",
105
- resent: "A new code is on its way.",
106
- verifyingTitle: "Verifying your email",
107
- verifyingBody: "One moment while we confirm your link.",
108
- doneTitle: "Email verified",
109
- doneBody: "Your address is confirmed. You can continue.",
110
- failedTitle: "This link didn't work",
111
- newCode: "Send a new code"
112
- },
113
- activate: {
114
- title: "Activate your account",
115
- credentialsSubtitle: "Set a password for {email} to finish.",
116
- magicSubtitle: "Confirm {email} to finish setting up your account.",
117
- confirmPassword: "Confirm password",
118
- mismatch: "Both passwords must match.",
119
- submit: "Activate my account",
120
- submitting: "Activating...",
121
- failedTitle: "This link didn't work",
122
- missingToken: "It is missing its activation token. Ask for a new invitation.",
123
- goToSignIn: "Go to sign in"
124
- },
125
- waitlist: {
126
- title: "Join the waitlist",
127
- subtitle: "We will email you when a spot opens up.",
128
- submit: "Join the waitlist",
129
- submitting: "Adding you...",
130
- joined: "You're on the list. We will be in touch."
131
- },
132
- profile: {
133
- navLabel: "Account sections",
134
- tabProfile: "Profile",
135
- tabSecurity: "Security",
136
- tabBilling: "Billing",
137
- profileTitle: "Profile",
138
- profileDesc: "Your name and the address you sign in with.",
139
- securityTitle: "Security",
140
- securityDesc: "Manage your password and your account.",
141
- billingTitle: "Billing",
142
- billingDesc: "Your plan, what you have used, and how to change it.",
143
- firstName: "First name",
144
- firstNamePlaceholder: "Ada",
145
- lastName: "Last name",
146
- lastNamePlaceholder: "Lovelace",
147
- save: "Save",
148
- saving: "Saving...",
149
- saved: "Your profile has been saved.",
150
- emailSectionTitle: "Email address",
151
- emailSectionDesc: "The address you sign in with. Contact support to change it.",
152
- verified: "Verified",
153
- unverified: "Unverified",
154
- verify: "Verify",
155
- verificationSent: "Verification email sent.",
156
- unnamed: "Unnamed"
157
- },
158
- security: {
159
- passwordTitle: "Password",
160
- passwordDesc: "Used together with your email to sign in.",
161
- currentPassword: "Current password",
162
- newPassword: "New password",
163
- confirmPassword: "Confirm new password",
164
- submit: "Change password",
165
- submitting: "Changing...",
166
- changed: "Your password has been changed.",
167
- mismatch: "Both passwords must match.",
168
- sameAsCurrent: "Your new password must differ from the current one.",
169
- deleteTitle: "Delete account",
170
- deleteDesc: "Deactivates your account and signs you out. Contact support if you need it back.",
171
- deleteAction: "Delete my account",
172
- deleteConfirm: "Yes, delete my account",
173
- deleting: "Deleting..."
174
- },
175
- billing: {
176
- currentPlan: "Current plan",
177
- plan: "Plan",
178
- price: "Price",
179
- status: "Status",
180
- renews: "Renews",
181
- ends: "Ends",
182
- trialEnds: "Trial ends",
183
- managePortal: "Manage billing",
184
- opening: "Opening...",
185
- freePlan: "You are on the free plan.",
186
- seePlans: "See plans",
187
- cancelNotice: "Your plan is set to end - you keep access until the date above.",
188
- cancelSubscription: "Cancel subscription",
189
- canceling: "Canceling...",
190
- cancelConfirmTitle: "Cancel your subscription?",
191
- cancelConfirmBody: "You'll keep access until the end of the current billing period.",
192
- cancelConfirmButton: "Yes, cancel",
193
- cancelConfirmDismiss: "Never mind",
194
- changePlan: "Change plan",
195
- current: "Current",
196
- switchTo: "Switch",
197
- switching: "Switching...",
198
- switchConfirmTitle: "Switch to {plan}?",
199
- switchConfirmBody: "This applies right away, and your next invoice reflects the price difference for the rest of this period.",
200
- switchConfirmButton: "Yes, switch",
201
- switchConfirmDismiss: "Never mind",
202
- noPlans: "No other plans are available right now.",
203
- notOnInterval: "Not available on this interval",
204
- loadFailed: "Could not load your plan right now.",
205
- loadingPlan: "Loading subscription",
206
- loadingPlans: "Loading plans",
207
- intervalGroupLabel: "Billing interval",
208
- // Rendered after a price, as "12 € / month". A language that needs another
209
- // shape overrides the whole label rather than these fragments.
210
- intervalMonth: "month",
211
- intervalYear: "year",
212
- intervalWeek: "week",
213
- intervalDay: "day",
214
- statusActive: "Active",
215
- statusTrialing: "Trial",
216
- statusPastDue: "Payment due",
217
- statusCanceled: "Canceled",
218
- statusUnpaid: "Unpaid",
219
- statusPaused: "Paused"
220
- },
221
- usage: {
222
- title: "Usage",
223
- subtitle: "Counted against your plan for the current period.",
224
- used: "{count} used",
225
- nothingMetered: "Nothing metered on your plan - you have no usage limits to watch.",
226
- overage: "{count} over your included amount - at no extra charge.",
227
- overageBilled: "{count} over your included amount - the extra is billed on top of your plan.",
228
- limitReached: "You have reached your limit.",
229
- capReached: "You've reached your billing cap for this period - everything past this is free.",
230
- billedAmount: "{amount} billed this period.",
231
- loading: "Loading usage",
232
- loadFailed: "Could not load your usage right now."
233
- },
234
- pricing: {
235
- getStarted: "Get started",
236
- billedAt: "Billed {amount} / {interval}",
237
- save: "Save {percent}%",
238
- unlimited: "Unlimited",
239
- moreFeatures: "+{count} more",
240
- loading: "Loading plans",
241
- loadFailed: "Could not load plans right now."
242
- },
243
- checkout: {
244
- confirmingTitle: "Confirming your payment",
245
- confirmingBody: "This takes a few seconds - please keep this tab open.",
246
- pendingTitle: "Payment received",
247
- pendingBody: "Your plan is still being activated. It usually lands within a minute - refresh if it has not appeared by then.",
248
- doneTitle: "You're all set",
249
- doneBody: "Your subscription is active. Thank you.",
250
- cancelTitle: "Checkout cancelled",
251
- cancelBody: "Nothing was charged. You can pick a plan whenever you are ready.",
252
- backToPlans: "Back to plans"
253
- },
254
- upgrade: {
255
- quotaTitle: "You've used all your {feature}",
256
- quotaBody: "You have used {used} of {limit} this period. Upgrade for a higher limit.",
257
- featureTitle: "{feature} is not on your plan",
258
- featureBody: "Upgrade your plan to unlock {feature}.",
259
- action: "See plans",
260
- checking: "Checking access"
261
- },
262
- account: {
263
- menuLabel: "Account menu",
264
- signOut: "Sign out"
265
- },
266
- errors: {
267
- generic: "Something went wrong. Please try again.",
268
- // Auth (K1xxx)
269
- K1001: "Registration is not open right now.",
270
- K1002: "This app has reached its user limit.",
271
- K1003: "This invitation link is no longer valid.",
272
- K1004: "That invitation code is not valid.",
273
- K1005: "Invalid email or password.",
274
- K1006: "This link has expired. Request a new one.",
275
- K1007: "This link is missing its token.",
276
- K1008: "An account already exists for this email.",
277
- K1009: "That email address does not look valid.",
278
- K1010: "Please choose a stronger password.",
279
- K1011: "Please verify your email address first.",
280
- K1012: "You need to sign in to continue.",
281
- // Validation (K2xxx)
282
- K2001: "Some of the details you entered are not valid.",
283
- K2002: "Please fill in every required field.",
284
- K2003: "Some of the details you entered are not valid.",
285
- K2005: "That value is already taken.",
286
- // Permissions (K3xxx)
287
- K3002: "You do not have permission to do that.",
288
- K3004: "That action is not available on your account.",
289
- // Resources (K4xxx)
290
- K4001: "We could not find what you were looking for.",
291
- K4003: "We could not find that account.",
292
- // Billing (K5xxx)
293
- K5001: "You've reached the limit included in your plan.",
294
- K5002: "Your plan does not include this. Upgrade to continue.",
295
- K5003: "Your payment could not be processed.",
296
- // Rate limiting (K6xxx)
297
- K6001: "Too many attempts. Please wait a moment and try again."
298
- }
299
- };
300
- var LocalizationContext = _react.createContext.call(void 0, defaultLocalization);
301
- function mergeLocalization(override) {
302
- if (!override) return defaultLocalization;
303
- const merged = { ...defaultLocalization };
304
- for (const groupKey of Object.keys(defaultLocalization)) {
305
- const group = override[groupKey];
306
- if (group) {
307
- merged[groupKey] = {
308
- ...defaultLocalization[groupKey],
309
- ...group
310
- };
311
- }
312
- }
313
- return merged;
314
- }
315
- function useLocalization() {
316
- return _react.useContext.call(void 0, LocalizationContext);
317
- }
318
- function fill(template, values) {
319
- return template.replace(
320
- /\{(\w+)\}/g,
321
- (match, key) => key in values ? String(values[key]) : match
322
- );
323
- }
324
- function fillNode(template, values) {
325
- return template.split(/(\{\w+\})/g).map((part, index) => {
326
- const placeholder = /^\{(\w+)\}$/.exec(part);
327
- if (placeholder && placeholder[1] in values) {
328
- return _react.createElement.call(void 0, _react.Fragment, { key: index }, values[placeholder[1]]);
329
- }
330
- return part;
331
- });
332
- }
333
- function resolveErrorMessage(error, l10n) {
334
- const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
335
- if (code && l10n.errors[code]) return l10n.errors[code];
336
- return l10n.errors.generic;
337
- }
338
- function useErrorResolver() {
339
- const l10n = useLocalization();
340
- return _react.useCallback.call(void 0, (error) => resolveErrorMessage(error, l10n), [l10n]);
341
- }
342
-
343
- // src/provider.tsx
344
-
345
- var _server = require('@kerne/server');
346
- var _jsxruntime = require('react/jsx-runtime');
347
- var DEFAULT_STORAGE_KEY = "kerne_auth";
348
- var DEFAULT_REFRESH_BUFFER = 60;
349
- var HANDOFF_QUERY_PARAM = "kerne_handoff";
350
- var KerneClient = (_class = class {
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
- __init() {this.refreshTimer = null}
359
- __init2() {this.listeners = /* @__PURE__ */ new Set()}
360
- __init3() {this._user = null}
361
- __init4() {this._token = null}
362
- __init5() {this._refreshToken = null}
363
- __init6() {this._expiresAt = null}
364
- __init7() {this._isLoading = true}
365
- __init8() {this._authConfigPromise = null}
366
-
367
-
368
- constructor(config) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);
369
- this.baseUrl = _nullishCoalesce(config.baseUrl, () => ( "https://api.kerne.io"));
370
- this.appId = config.appId;
371
- this.storage = _nullishCoalesce(config.storage, () => ( (typeof window !== "undefined" ? localStorage : null)));
372
- this.storageKey = _nullishCoalesce(config.storageKey, () => ( DEFAULT_STORAGE_KEY));
373
- this.onAuthChange = config.onAuthChange;
374
- this.onHandoffError = config.onHandoffError;
375
- this.autoRefresh = _nullishCoalesce(config.autoRefresh, () => ( true));
376
- this.refreshBuffer = _nullishCoalesce(config.refreshBuffer, () => ( DEFAULT_REFRESH_BUFFER));
377
- this.kerne = new (0, _server.Kerne)({
378
- baseUrl: this.baseUrl,
379
- appId: this.appId,
380
- timeout: config.timeout,
381
- onUnauthorized: () => {
382
- if (process.env.NODE_ENV === "development") {
383
- console.warn("[Kerne] 401 received. Auto-logout disabled in dev.");
384
- return;
385
- }
386
- this.clearSession();
387
- }
388
- });
389
- this.loadSession();
390
- }
391
- subscribe(listener) {
392
- this.listeners.add(listener);
393
- return () => this.listeners.delete(listener);
394
- }
395
- notify() {
396
- this.listeners.forEach((l) => l());
397
- }
398
- get user() {
399
- return this._user;
400
- }
401
- get token() {
402
- return this._token;
403
- }
404
- get isAuthenticated() {
405
- return this._token !== null;
406
- }
407
- get isLoading() {
408
- return this._isLoading;
409
- }
410
- loadSession() {
411
- const handoffCode = this.getHandoffCodeFromUrl();
412
- if (handoffCode) {
413
- void this.exchangeHandoffOnLoad(handoffCode);
414
- return;
415
- }
416
- if (!this.storage) {
417
- this._isLoading = false;
418
- this.notify();
419
- return;
420
- }
421
- try {
422
- const data = this.storage.getItem(this.storageKey);
423
- if (data) {
424
- const { user, token, refresh_token, expires_at } = JSON.parse(data);
425
- const expiresAt = expires_at ? new Date(expires_at) : null;
426
- if (!expiresAt || expiresAt > /* @__PURE__ */ new Date()) {
427
- this._user = user;
428
- this._token = token;
429
- this._refreshToken = _nullishCoalesce(refresh_token, () => ( null));
430
- this._expiresAt = expiresAt;
431
- this.kerne = this.kerne.withToken(token);
432
- this.scheduleRefresh();
433
- } else if (refresh_token && this.autoRefresh) {
434
- this._refreshToken = refresh_token;
435
- void this.refreshOnLoad();
436
- return;
437
- } else {
438
- this.storage.removeItem(this.storageKey);
439
- }
440
- }
441
- } catch (e) {
442
- console.error("[Kerne] Failed to load session:", e);
443
- _optionalChain([this, 'access', _ => _.storage, 'optionalAccess', _2 => _2.removeItem, 'call', _3 => _3(this.storageKey)]);
444
- }
445
- this._isLoading = false;
446
- this.notify();
447
- }
448
- getHandoffCodeFromUrl() {
449
- if (typeof window === "undefined") return null;
450
- return new URLSearchParams(window.location.search).get(HANDOFF_QUERY_PARAM);
451
- }
452
- /**
453
- * Strips only `kerne_handoff` from the URL, leaving the rest of the
454
- * path/query (e.g. a `return_to` the host app added) untouched - and
455
- * without adding a history entry, so back-navigation doesn't resurrect it.
456
- */
457
- stripHandoffParamFromUrl() {
458
- if (typeof window === "undefined") return;
459
- const url = new URL(window.location.href);
460
- url.searchParams.delete(HANDOFF_QUERY_PARAM);
461
- window.history.replaceState(window.history.state, "", url.toString());
462
- }
463
- /**
464
- * Same shape as `refreshOnLoad`: a bootstrap-only path invoked once from
465
- * `loadSession()` when a `kerne_handoff` code is present in the URL,
466
- * entirely invisible to the host app - no code on their end required.
467
- *
468
- * Works no matter which page the browser lands on: `redirectUrl` is a hard
469
- * navigation (`window.location.href`), so the destination reloads the SDK
470
- * fresh and `loadSession()` reads the code straight off that page's own
471
- * URL - there is no dependency on a specific "callback route", as long as
472
- * `KerneProvider` wraps whatever the host renders there (normally true when
473
- * it is mounted once at the app root).
474
- *
475
- * One retry on a transient failure (network blip, 5xx) - worth it because
476
- * the code was never reached by the server in that case, so it is still
477
- * unspent. A 4xx (invalid/expired/already-consumed) is not retried: the
478
- * code is gone, retrying only delays the "logged out" outcome.
479
- */
480
- async exchangeHandoffOnLoad(code) {
481
- try {
482
- for (let attempt = 0; ; attempt++) {
483
- try {
484
- const response = await this.kerne.auth.exchangeHandoff(code);
485
- await this.saveSession(response);
486
- return;
487
- } catch (e) {
488
- const statusCode = e.statusCode;
489
- const transient = statusCode === 0 || typeof statusCode === "number" && statusCode >= 500;
490
- if (transient && attempt === 0) {
491
- await new Promise((resolve) => setTimeout(resolve, 400));
492
- continue;
493
- }
494
- console.warn("[Kerne] Handoff exchange failed:", e);
495
- _optionalChain([this, 'access', _4 => _4.onHandoffError, 'optionalCall', _5 => _5(e)]);
496
- return;
497
- }
498
- }
499
- } finally {
500
- this.stripHandoffParamFromUrl();
501
- this._isLoading = false;
502
- this.notify();
503
- }
504
- }
505
- async refreshOnLoad() {
506
- try {
507
- const response = await this.kerne.auth.refreshToken({
508
- refresh_token: this._refreshToken
509
- });
510
- await this.saveSession(response);
511
- } catch (e) {
512
- console.warn("[Kerne] Startup refresh failed, clearing session:", e);
513
- this._refreshToken = null;
514
- _optionalChain([this, 'access', _6 => _6.storage, 'optionalAccess', _7 => _7.removeItem, 'call', _8 => _8(this.storageKey)]);
515
- } finally {
516
- this._isLoading = false;
517
- this.notify();
518
- }
519
- }
520
- /**
521
- * `AuthResponse.user` is the minimal `{id, email, role}` claim set the
522
- * token endpoint returns, not the full profile (`User`) - so this always
523
- * follows up with `users.me()` rather than assigning it directly (that
524
- * used to leave `_user` looking like a `User` while actually missing
525
- * `email_verified`/`first_name`/`avatar`/etc).
526
- */
527
- async saveSession(response) {
528
- this._token = response.token;
529
- this._refreshToken = response.refresh_token;
530
- this._expiresAt = response.expires_at ? new Date(response.expires_at) : null;
531
- this.kerne = this.kerne.withToken(response.token);
532
- try {
533
- this._user = await this.kerne.users.me();
534
- } catch (e) {
535
- const sameUser = _optionalChain([this, 'access', _9 => _9._user, 'optionalAccess', _10 => _10.id]) === _optionalChain([response, 'access', _11 => _11.user, 'optionalAccess', _12 => _12.id]);
536
- console.error("[Kerne] Failed to load the user profile:", e);
537
- this._user = sameUser ? this._user : null;
538
- }
539
- if (this.storage) {
540
- this.storage.setItem(
541
- this.storageKey,
542
- JSON.stringify({
543
- user: this._user,
544
- token: response.token,
545
- refresh_token: response.refresh_token,
546
- expires_at: response.expires_at
547
- })
548
- );
549
- }
550
- this.scheduleRefresh();
551
- _optionalChain([this, 'access', _13 => _13.onAuthChange, 'optionalCall', _14 => _14(this._user)]);
552
- this.notify();
553
- }
554
- clearSession() {
555
- this._user = null;
556
- this._token = null;
557
- this._refreshToken = null;
558
- this._expiresAt = null;
559
- this.cancelRefresh();
560
- if (this.storage) {
561
- this.storage.removeItem(this.storageKey);
562
- }
563
- _optionalChain([this, 'access', _15 => _15.onAuthChange, 'optionalCall', _16 => _16(null)]);
564
- this.notify();
565
- }
566
- scheduleRefresh() {
567
- if (!this.autoRefresh || !this._expiresAt) return;
568
- this.cancelRefresh();
569
- const now = Date.now();
570
- const expiry = this._expiresAt.getTime();
571
- const refreshAt = expiry - this.refreshBuffer * 1e3;
572
- const delay = Math.max(0, refreshAt - now);
573
- if (delay > 0) {
574
- this.refreshTimer = setTimeout(() => this.silentRefresh(), delay);
575
- }
576
- }
577
- cancelRefresh() {
578
- if (this.refreshTimer) {
579
- clearTimeout(this.refreshTimer);
580
- this.refreshTimer = null;
581
- }
582
- }
583
- async silentRefresh() {
584
- if (!this._refreshToken) return;
585
- try {
586
- const response = await this.kerne.auth.refreshToken({ refresh_token: this._refreshToken });
587
- await this.saveSession(response);
588
- console.debug("[Kerne] Session refreshed silently");
589
- } catch (e) {
590
- console.warn("[Kerne] Silent refresh failed:", e);
591
- }
592
- }
593
- async register(params) {
594
- const [firstName, ...lastNameParts] = (params.name || "").split(" ");
595
- const response = await this.kerne.auth.signup({
596
- email: params.email,
597
- password: params.password,
598
- first_name: firstName || void 0,
599
- last_name: lastNameParts.join(" ") || void 0,
600
- invitationToken: params.invitationToken,
601
- invitationCode: params.invitationCode
602
- });
603
- await this.saveSession(response);
604
- return response;
605
- }
606
- async login(params) {
607
- const response = await this.kerne.auth.login(params);
608
- await this.saveSession(response);
609
- return response;
610
- }
611
- /** Manually trigger a token refresh - normally handled automatically by `autoRefresh`. */
612
- async refreshToken() {
613
- if (!this._refreshToken) return null;
614
- const response = await this.kerne.auth.refreshToken({ refresh_token: this._refreshToken });
615
- await this.saveSession(response);
616
- return response;
617
- }
618
- logout() {
619
- this.clearSession();
620
- }
621
- async refreshUser() {
622
- if (!this._token) return null;
623
- try {
624
- const user = await this.kerne.users.me();
625
- this._user = user;
626
- _optionalChain([this, 'access', _17 => _17.onAuthChange, 'optionalCall', _18 => _18(user)]);
627
- this.notify();
628
- return user;
629
- } catch (e2) {
630
- this.clearSession();
631
- return null;
632
- }
633
- }
634
- // No `avatar` here - UpdateUserDto doesn't accept it server-side
635
- // (`forbidNonWhitelisted` rejects the field with a 400).
636
- async updateProfile(params) {
637
- if (!this._user) throw new Error("User not authenticated");
638
- const updated = await this.kerne.users.update(this._user.id, params);
639
- this._user = updated;
640
- if (this.storage && this._token) {
641
- try {
642
- const data = this.storage.getItem(this.storageKey);
643
- if (data) {
644
- const session = JSON.parse(data);
645
- session.user = updated;
646
- this.storage.setItem(this.storageKey, JSON.stringify(session));
647
- }
648
- } catch (e) {
649
- }
650
- }
651
- _optionalChain([this, 'access', _19 => _19.onAuthChange, 'optionalCall', _20 => _20(updated)]);
652
- this.notify();
653
- return updated;
654
- }
655
- async updatePassword(params) {
656
- await this.kerne.auth.changePassword(params);
657
- }
658
- /** Soft-deletes (deactivates) the caller's own account, then clears the local session. */
659
- async requestAccountDeletion() {
660
- await this.kerne.auth.requestAccountDeletion();
661
- this.clearSession();
662
- }
663
- /** Forgot-password flow (logged out) - sends the reset email. */
664
- async requestPasswordReset(email, callbackUrl) {
665
- await this.kerne.auth.requestPasswordReset(email, callbackUrl);
666
- }
667
- /** Confirms a password reset with the token from the email. */
668
- async confirmPasswordReset(token, password) {
669
- await this.kerne.auth.confirmPasswordReset(token, password);
670
- }
671
- /**
672
- * Sends a magic-link email (logged out). Anti-enumeration: always resolves,
673
- * regardless of whether the email is registered.
674
- */
675
- async startPasswordless(email, callbackUrl, invitation) {
676
- await this.kerne.auth.startPasswordless(email, callbackUrl, invitation);
677
- }
678
- /** Completes a magic-link login using the `token` query param from the emailed link. */
679
- async loginWithMagicLink(token) {
680
- const response = await this.kerne.auth.loginWithMagicLink(token);
681
- await this.saveSession(response);
682
- return response;
683
- }
684
- // Account Activation
685
- // Same token/endpoint for an admin-created user and a self-service
686
- // magic-link signup confirmation - see AuthResource.getActivationContext.
687
- /** Read-only: which UI to render for an activation token, without consuming it. */
688
- async getActivationContext(token) {
689
- return this.kerne.auth.getActivationContext(token);
690
- }
691
- /** Completes activation (sets a password or confirms magic-link mode), then logs the user in. */
692
- async completeActivation(token, password) {
693
- const response = await this.kerne.auth.completeActivation(token, password);
694
- await this.saveSession(response);
695
- return response;
696
- }
697
- // Cross-Origin Session Handoff
698
- // For apps/portal (auth hosted on its own origin): call this at the end of
699
- // a successful auth flow (login/magic-link/register/activation) instead of
700
- // a plain `window.location.href = url` whenever `url` isn't same-origin -
701
- // the exchange on the other end is picked up automatically by
702
- // `exchangeHandoffOnLoad()`, no code required on the tenant app's side.
703
- /**
704
- * Mints a handoff code for the current session and redirects the browser
705
- * to `url` with it attached (`?kerne_handoff=...`) - never the real
706
- * token/refresh_token, which would otherwise leak into server logs,
707
- * browser history, and third-party `Referer` headers on the landing page.
708
- */
709
- async redirectWithSession(url) {
710
- const { code } = await this.kerne.auth.handoff();
711
- const separator = url.includes("?") ? "&" : "?";
712
- const target = `${url}${separator}${HANDOFF_QUERY_PARAM}=${encodeURIComponent(code)}`;
713
- if (typeof window !== "undefined") window.location.href = target;
714
- }
715
- /**
716
- * The tenant's public auth config - which sign-in methods to render, whether
717
- * registration is open, whether to show Kerne branding.
718
- *
719
- * Deduped on the in-flight promise rather than the resolved value: the
720
- * prebuilt forms all read this on mount, and two of them mounted together
721
- * (a login screen with a register link prefetching) would otherwise fire two
722
- * identical requests before either resolved. Cached for the client's
723
- * lifetime - this config changes on the order of "the owner edited their
724
- * settings", not per render.
725
- */
726
- async getAuthConfig() {
727
- if (!this._authConfigPromise) {
728
- this._authConfigPromise = this.kerne.auth.config().catch((e) => {
729
- this._authConfigPromise = null;
730
- throw e;
731
- });
732
- }
733
- return this._authConfigPromise;
734
- }
735
- /**
736
- * Errors are not swallowed here (unlike the old implementation, which
737
- * caught everything and returned `false`) - a 401/500 must not be
738
- * indistinguishable from a real denial, that's exactly what let the
739
- * `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
740
- * a fail-closed boolean regardless of the reason (e.g. `<Access>`)
741
- * catch around this themselves.
742
- */
743
- async allows(featureKey, requested) {
744
- const check = await this.kerne.check(featureKey, { requested });
745
- return check.allowed;
746
- }
747
- /** Full entitlement detail (limit/used/remaining/overage) - use `allows()` for a plain boolean. */
748
- async check(featureKey, requested) {
749
- return this.kerne.check(featureKey, { requested });
750
- }
751
- async createCheckout(planPriceId, options) {
752
- const { url } = await this.kerne.billing.checkout(planPriceId, options);
753
- return url;
754
- }
755
- async openCheckout(planPriceId, options) {
756
- const url = await this.createCheckout(planPriceId, options);
757
- if (typeof window !== "undefined") window.location.href = url;
758
- }
759
- async createPortal(returnUrl) {
760
- const { url } = await this.kerne.billing.portal({ returnUrl });
761
- return url;
762
- }
763
- async openPortal(returnUrl) {
764
- const url = await this.createPortal(returnUrl);
765
- if (typeof window !== "undefined") window.location.href = url;
766
- }
767
- async getSubscription(productSlug) {
768
- const subs = await this.kerne.billing.subscriptions.list({
769
- productSlug,
770
- subjectType: "USER"
771
- });
772
- const active = subs.find(
773
- (s) => s.status === "ACTIVE" || s.status === "TRIALING" || s.status === "PAST_DUE"
774
- );
775
- return _nullishCoalesce(active, () => ( null));
776
- }
777
- /** `immediately: true` ends it now; otherwise cancels at period end (API default). */
778
- async cancelSubscription(subscriptionId, immediately) {
779
- return this.kerne.billing.subscriptions.cancel(subscriptionId, immediately);
780
- }
781
- /** Swaps the plan/price, effective immediately - deferring a downgrade isn't supported yet. */
782
- async changeSubscriptionPlan(subscriptionId, planPriceId, options) {
783
- return this.kerne.billing.subscriptions.changePlanPrice(subscriptionId, planPriceId, options);
784
- }
785
- /** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
786
- async getPlans(productIdOrSlug) {
787
- return this.kerne.billing.plans(productIdOrSlug);
788
- }
789
- /** Every entitlement for the current user in one call - for a "your plan" screen. */
790
- async getEntitlements() {
791
- return this.kerne.entitlements();
792
- }
793
- /** Usage for the current user - all QUOTA features, or one via `featureKey`. */
794
- async getUsage(featureKey) {
795
- return this.kerne.usage({ featureKey });
796
- }
797
- async joinWaitlist(params) {
798
- return this.kerne.waitlist.join(params);
799
- }
800
- /** Validate an invitation token before showing the registration form. */
801
- async validateInvitationToken(token) {
802
- return this.kerne.invitations.validateToken(token);
803
- }
804
- /** Validate an invitation code before showing the registration form. */
805
- async validateInvitationCode(code) {
806
- return this.kerne.invitations.validateCode(code);
807
- }
808
- async sendVerificationEmail(verificationType = "code", callbackUrl) {
809
- await this.kerne.auth.sendVerificationEmail(verificationType, callbackUrl);
810
- }
811
- async verifyEmailWithCode(code) {
812
- return await this.kerne.auth.verifyEmailWithCode(code);
813
- }
814
- async verifyEmailWithLink(token) {
815
- return await this.kerne.auth.verifyEmailWithLink(token);
816
- }
817
- async getVerificationStatus() {
818
- return await this.kerne.auth.getVerificationStatus();
819
- }
820
- }, _class);
821
- var KerneContext = _react.createContext.call(void 0, null);
822
- function KerneProvider({ children, localization, ...config }) {
823
- const clientRef = _react.useRef.call(void 0, null);
824
- const client = _react.useMemo.call(void 0, () => {
825
- if (!clientRef.current) {
826
- clientRef.current = new KerneClient(config);
827
- }
828
- return clientRef.current;
829
- }, [config.appId, config.baseUrl]);
830
- _react.useEffect.call(void 0, () => {
831
- return () => {
832
- _optionalChain([client, 'access', _21 => _21.cancelRefresh, 'optionalCall', _22 => _22()]);
833
- };
834
- }, [client]);
835
- const l10n = _react.useMemo.call(void 0, () => mergeLocalization(localization), [localization]);
836
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneContext.Provider, { value: client, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, LocalizationContext.Provider, { value: l10n, children }) });
837
- }
838
-
839
- // src/hooks.ts
840
-
841
- function useClient() {
842
- const context = _react.useContext.call(void 0, KerneContext);
843
- if (!context) {
844
- throw new Error("useClient must be used within a KerneProvider");
845
- }
846
- return context;
847
- }
848
- function useAuth() {
849
- const client = useClient();
850
- const user = _react.useSyncExternalStore.call(void 0,
851
- (callback) => client.subscribe(callback),
852
- () => client.user,
853
- () => client.user
854
- // Server snapshot
855
- );
856
- const token = _react.useSyncExternalStore.call(void 0,
857
- (callback) => client.subscribe(callback),
858
- () => client.token,
859
- () => null
860
- // Server always null
861
- );
862
- const isAuthenticated = _react.useSyncExternalStore.call(void 0,
863
- (callback) => client.subscribe(callback),
864
- () => client.isAuthenticated,
865
- () => false
866
- // Server always unauthenticated
867
- );
868
- const isLoading = _react.useSyncExternalStore.call(void 0,
869
- (callback) => client.subscribe(callback),
870
- () => client.isLoading,
871
- () => true
872
- // Server always loading
873
- );
874
- return {
875
- user,
876
- token,
877
- isAuthenticated,
878
- isLoading,
879
- login: _react.useCallback.call(void 0,
880
- (params) => client.login(params),
881
- [client]
882
- ),
883
- register: _react.useCallback.call(void 0,
884
- (params) => client.register(params),
885
- [client]
886
- ),
887
- logout: _react.useCallback.call(void 0, () => client.logout(), [client]),
888
- refreshUser: _react.useCallback.call(void 0, () => client.refreshUser(), [client]),
889
- refreshToken: _react.useCallback.call(void 0, () => client.refreshToken(), [client]),
890
- updateProfile: _react.useCallback.call(void 0,
891
- (params) => client.updateProfile(params),
892
- [client]
893
- ),
894
- updatePassword: _react.useCallback.call(void 0,
895
- (params) => client.updatePassword(params),
896
- [client]
897
- ),
898
- requestAccountDeletion: _react.useCallback.call(void 0, () => client.requestAccountDeletion(), [client]),
899
- requestPasswordReset: _react.useCallback.call(void 0,
900
- (email, callbackUrl) => client.requestPasswordReset(email, callbackUrl),
901
- [client]
902
- ),
903
- confirmPasswordReset: _react.useCallback.call(void 0,
904
- (token2, password) => client.confirmPasswordReset(token2, password),
905
- [client]
906
- ),
907
- // Magic link
908
- startPasswordless: _react.useCallback.call(void 0,
909
- (email, callbackUrl, invitation) => client.startPasswordless(email, callbackUrl, invitation),
910
- [client]
911
- ),
912
- loginWithMagicLink: _react.useCallback.call(void 0, (token2) => client.loginWithMagicLink(token2), [client]),
913
- // Email verification
914
- sendVerificationEmail: _react.useCallback.call(void 0,
915
- (verificationType = "code", callbackUrl) => client.sendVerificationEmail(verificationType, callbackUrl),
916
- [client]
917
- ),
918
- verifyEmailWithCode: _react.useCallback.call(void 0, (code) => client.verifyEmailWithCode(code), [client]),
919
- verifyEmailWithLink: _react.useCallback.call(void 0,
920
- (token2) => client.verifyEmailWithLink(token2),
921
- [client]
922
- ),
923
- getVerificationStatus: _react.useCallback.call(void 0, () => client.getVerificationStatus(), [client]),
924
- // Account activation (admin-created user, or self-service magic-link signup confirmation)
925
- getActivationContext: _react.useCallback.call(void 0,
926
- (token2) => client.getActivationContext(token2),
927
- [client]
928
- ),
929
- completeActivation: _react.useCallback.call(void 0,
930
- (token2, password) => client.completeActivation(token2, password),
931
- [client]
932
- ),
933
- // Cross-origin handoff (apps/portal -> tenant app_url) - see redirectWithSession on KerneClient.
934
- redirectWithSession: _react.useCallback.call(void 0, (url) => client.redirectWithSession(url), [client])
935
- };
936
- }
937
- function useCheckout() {
938
- const client = useClient();
939
- return {
940
- createCheckout: _react.useCallback.call(void 0,
941
- (planPriceId, options) => client.createCheckout(planPriceId, options),
942
- [client]
943
- ),
944
- openCheckout: _react.useCallback.call(void 0,
945
- (planPriceId, options) => client.openCheckout(planPriceId, options),
946
- [client]
947
- )
948
- };
949
- }
950
- function usePortal() {
951
- const client = useClient();
952
- return {
953
- createPortal: _react.useCallback.call(void 0, (returnUrl) => client.createPortal(returnUrl), [client]),
954
- openPortal: _react.useCallback.call(void 0, (returnUrl) => client.openPortal(returnUrl), [client])
955
- };
956
- }
957
- function useCancelSubscription() {
958
- const client = useClient();
959
- const [isCanceling, setIsCanceling] = _react.useState.call(void 0, false);
960
- const cancelSubscription = _react.useCallback.call(void 0,
961
- async (subscriptionId, immediately) => {
962
- setIsCanceling(true);
963
- try {
964
- return await client.cancelSubscription(subscriptionId, immediately);
965
- } finally {
966
- setIsCanceling(false);
967
- }
968
- },
969
- [client]
970
- );
971
- return { cancelSubscription, isCanceling };
972
- }
973
- function useChangePlan() {
974
- const client = useClient();
975
- const [isChanging, setIsChanging] = _react.useState.call(void 0, false);
976
- const changePlan = _react.useCallback.call(void 0,
977
- async (subscriptionId, planPriceId, options) => {
978
- setIsChanging(true);
979
- try {
980
- return await client.changeSubscriptionPlan(subscriptionId, planPriceId, options);
981
- } finally {
982
- setIsChanging(false);
983
- }
984
- },
985
- [client]
986
- );
987
- return { changePlan, isChanging };
988
- }
989
- function usePlanSwitch(subscription, refetchSubscription, options) {
990
- const { openCheckout } = useCheckout();
991
- const { changePlan } = useChangePlan();
992
- const [pending, setPending] = _react.useState.call(void 0, null);
993
- const [switching, setSwitching] = _react.useState.call(void 0, false);
994
- const [switchError, setSwitchError] = _react.useState.call(void 0, null);
995
- const [checkoutPriceId, setCheckoutPriceId] = _react.useState.call(void 0, null);
996
- const [checkoutError, setCheckoutError] = _react.useState.call(void 0, null);
997
- const successUrl = _optionalChain([options, 'optionalAccess', _23 => _23.successUrl]);
998
- const cancelUrl = _optionalChain([options, 'optionalAccess', _24 => _24.cancelUrl]);
999
- const selectPlan = _react.useCallback.call(void 0,
1000
- async (plan, price) => {
1001
- if (subscription) {
1002
- setSwitchError(null);
1003
- setPending({ plan, price });
1004
- return;
1005
- }
1006
- setCheckoutError(null);
1007
- setCheckoutPriceId(price.id);
1008
- try {
1009
- await openCheckout(price.id, { successUrl, cancelUrl });
1010
- } catch (e) {
1011
- setCheckoutError(e);
1012
- setCheckoutPriceId(null);
1013
- }
1014
- },
1015
- [subscription, openCheckout, successUrl, cancelUrl]
1016
- );
1017
- const confirmSwitch = _react.useCallback.call(void 0, async () => {
1018
- if (!pending || !subscription) return;
1019
- setSwitching(true);
1020
- setSwitchError(null);
1021
- try {
1022
- await changePlan(subscription.id, pending.price.id);
1023
- setPending(null);
1024
- refetchSubscription();
1025
- } catch (e) {
1026
- setSwitchError(e);
1027
- } finally {
1028
- setSwitching(false);
1029
- }
1030
- }, [pending, subscription, changePlan, refetchSubscription]);
1031
- const dismissSwitch = _react.useCallback.call(void 0, () => {
1032
- setPending(null);
1033
- setSwitchError(null);
1034
- }, []);
1035
- return {
1036
- pending,
1037
- switching,
1038
- switchError,
1039
- checkoutPriceId,
1040
- checkoutError,
1041
- selectPlan,
1042
- confirmSwitch,
1043
- dismissSwitch
1044
- };
1045
- }
1046
- function useAccess(featureKey, requested) {
1047
- const client = useClient();
1048
- const [state, setState] = _react.useState.call(void 0, {
1049
- details: null,
1050
- // Fails closed: an unresolved or failed check must never read as a yes,
1051
- // since this is what gates paid capability.
1052
- allowed: false,
1053
- isLoading: true,
1054
- error: null
1055
- });
1056
- _react.useEffect.call(void 0, () => {
1057
- let mounted = true;
1058
- client.check(featureKey, requested).then((details) => {
1059
- if (mounted) {
1060
- setState({ details, allowed: details.allowed, isLoading: false, error: null });
1061
- }
1062
- }).catch((error) => {
1063
- if (mounted) {
1064
- setState({ details: null, allowed: false, isLoading: false, error });
1065
- }
1066
- });
1067
- return () => {
1068
- mounted = false;
1069
- };
1070
- }, [client, featureKey, requested]);
1071
- return state;
1072
- }
1073
- function useSubscription(productSlug) {
1074
- const client = useClient();
1075
- const [state, setState] = _react.useState.call(void 0, {
1076
- subscription: null,
1077
- isLoading: true,
1078
- error: null
1079
- });
1080
- const fetchOnce = _react.useCallback.call(void 0,
1081
- (mountedRef) => {
1082
- client.getSubscription(productSlug).then((subscription) => {
1083
- if (mountedRef.current) {
1084
- setState({ subscription, isLoading: false, error: null });
1085
- }
1086
- }).catch((error) => {
1087
- if (mountedRef.current) {
1088
- setState({ subscription: null, isLoading: false, error });
1089
- }
1090
- });
1091
- },
1092
- [client, productSlug]
1093
- );
1094
- _react.useEffect.call(void 0, () => {
1095
- const mountedRef = { current: true };
1096
- fetchOnce(mountedRef);
1097
- return () => {
1098
- mountedRef.current = false;
1099
- };
1100
- }, [fetchOnce]);
1101
- const refetch = _react.useCallback.call(void 0, () => {
1102
- setState((prev) => ({ ...prev, isLoading: true }));
1103
- fetchOnce({ current: true });
1104
- }, [fetchOnce]);
1105
- const isActive = _optionalChain([state, 'access', _25 => _25.subscription, 'optionalAccess', _26 => _26.status]) === "ACTIVE" || _optionalChain([state, 'access', _27 => _27.subscription, 'optionalAccess', _28 => _28.status]) === "TRIALING";
1106
- const planSlug = _optionalChain([state, 'access', _29 => _29.subscription, 'optionalAccess', _30 => _30.plan, 'optionalAccess', _31 => _31.slug]);
1107
- return {
1108
- ...state,
1109
- isActive,
1110
- planSlug,
1111
- refetch
1112
- };
1113
- }
1114
- function usePlans(productIdOrSlug) {
1115
- const client = useClient();
1116
- const [state, setState] = _react.useState.call(void 0, {
1117
- plans: [],
1118
- isLoading: true,
1119
- error: null
1120
- });
1121
- _react.useEffect.call(void 0, () => {
1122
- let mounted = true;
1123
- client.getPlans(productIdOrSlug).then((plans) => {
1124
- if (mounted) {
1125
- setState({ plans, isLoading: false, error: null });
1126
- }
1127
- }).catch((error) => {
1128
- if (mounted) {
1129
- setState({ plans: [], isLoading: false, error });
1130
- }
1131
- });
1132
- return () => {
1133
- mounted = false;
1134
- };
1135
- }, [client, productIdOrSlug]);
1136
- return state;
1137
- }
1138
- function useAuthConfig() {
1139
- const client = useClient();
1140
- const [state, setState] = _react.useState.call(void 0, {
1141
- config: null,
1142
- isLoading: true,
1143
- error: null
1144
- });
1145
- _react.useEffect.call(void 0, () => {
1146
- let mounted = true;
1147
- client.getAuthConfig().then((config) => {
1148
- if (mounted) {
1149
- setState({ config, isLoading: false, error: null });
1150
- }
1151
- }).catch((error) => {
1152
- if (mounted) {
1153
- setState({ config: null, isLoading: false, error });
1154
- }
1155
- });
1156
- return () => {
1157
- mounted = false;
1158
- };
1159
- }, [client]);
1160
- return state;
1161
- }
1162
- function useEntitlements() {
1163
- const client = useClient();
1164
- const [state, setState] = _react.useState.call(void 0, {
1165
- entitlements: null,
1166
- isLoading: true,
1167
- error: null
1168
- });
1169
- _react.useEffect.call(void 0, () => {
1170
- let mounted = true;
1171
- client.getEntitlements().then((entitlements) => {
1172
- if (mounted) {
1173
- setState({ entitlements, isLoading: false, error: null });
1174
- }
1175
- }).catch((error) => {
1176
- if (mounted) {
1177
- setState({ entitlements: null, isLoading: false, error });
1178
- }
1179
- });
1180
- return () => {
1181
- mounted = false;
1182
- };
1183
- }, [client]);
1184
- return state;
1185
- }
1186
- function useUsage(featureKey) {
1187
- const client = useClient();
1188
- const [state, setState] = _react.useState.call(void 0, {
1189
- usage: [],
1190
- isLoading: true,
1191
- error: null
1192
- });
1193
- _react.useEffect.call(void 0, () => {
1194
- let mounted = true;
1195
- client.getUsage(featureKey).then((usage) => {
1196
- if (mounted) {
1197
- setState({ usage, isLoading: false, error: null });
1198
- }
1199
- }).catch((error) => {
1200
- if (mounted) {
1201
- setState({ usage: [], isLoading: false, error });
1202
- }
1203
- });
1204
- return () => {
1205
- mounted = false;
1206
- };
1207
- }, [client, featureKey]);
1208
- return state;
1209
- }
1210
- function useWaitlist() {
1211
- const client = useClient();
1212
- return {
1213
- join: _react.useCallback.call(void 0, (params) => client.joinWaitlist(params), [client])
1214
- };
1215
- }
1216
- function useInvitation(params) {
1217
- const client = useClient();
1218
- const { token, code } = params;
1219
- const [state, setState] = _react.useState.call(void 0, {
1220
- result: null,
1221
- isLoading: !!(token || code),
1222
- error: null
1223
- });
1224
- _react.useEffect.call(void 0, () => {
1225
- if (!token && !code) {
1226
- setState({ result: null, isLoading: false, error: null });
1227
- return;
1228
- }
1229
- let mounted = true;
1230
- const validate = token ? client.validateInvitationToken(token) : client.validateInvitationCode(code);
1231
- validate.then((result) => {
1232
- if (mounted) {
1233
- setState({ result, isLoading: false, error: null });
1234
- }
1235
- }).catch((error) => {
1236
- if (mounted) {
1237
- setState({ result: null, isLoading: false, error });
1238
- }
1239
- });
1240
- return () => {
1241
- mounted = false;
1242
- };
1243
- }, [client, token, code]);
1244
- return state;
1245
- }
1246
- function useUser() {
1247
- const { user, isLoading, updateProfile } = useAuth();
1248
- return {
1249
- user,
1250
- isLoading,
1251
- update: updateProfile,
1252
- // Computed properties
1253
- fullName: user ? `${user.first_name || ""} ${user.last_name || ""}`.trim() : null,
1254
- initials: user ? `${_optionalChain([user, 'access', _32 => _32.first_name, 'optionalAccess', _33 => _33[0]]) || ""}${_optionalChain([user, 'access', _34 => _34.last_name, 'optionalAccess', _35 => _35[0]]) || ""}`.toUpperCase() : null,
1255
- email: _optionalChain([user, 'optionalAccess', _36 => _36.email]) || null,
1256
- emailVerified: _optionalChain([user, 'optionalAccess', _37 => _37.email_verified]) || false
1257
- };
1258
- }
1259
-
1260
-
1261
-
1262
-
1263
-
1264
-
1265
-
1266
-
1267
-
1268
-
1269
-
1270
-
1271
-
1272
-
1273
-
1274
-
1275
-
1276
-
1277
-
1278
-
1279
-
1280
-
1281
-
1282
-
1283
-
1284
-
1285
- exports.defaultLocalization = defaultLocalization; exports.useLocalization = useLocalization; exports.fill = fill; exports.fillNode = fillNode; exports.useErrorResolver = useErrorResolver; exports.KerneClient = KerneClient; exports.KerneContext = KerneContext; exports.KerneProvider = KerneProvider; exports.useClient = useClient; exports.useAuth = useAuth; exports.useCheckout = useCheckout; exports.usePortal = usePortal; exports.useCancelSubscription = useCancelSubscription; exports.useChangePlan = useChangePlan; exports.usePlanSwitch = usePlanSwitch; exports.useAccess = useAccess; exports.useSubscription = useSubscription; exports.usePlans = usePlans; exports.useAuthConfig = useAuthConfig; exports.useEntitlements = useEntitlements; exports.useUsage = useUsage; exports.useWaitlist = useWaitlist; exports.useInvitation = useInvitation; exports.useUser = useUser;