@kerne/react 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/dist/chunk-7U3QBMA7.cjs +1151 -0
- package/dist/chunk-F6NV76OR.js +1151 -0
- package/dist/i18n-BWTT1DWD.d.cts +257 -0
- package/dist/i18n-BWTT1DWD.d.ts +257 -0
- package/dist/index.cjs +80 -817
- package/dist/index.d.cts +165 -77
- package/dist/index.d.ts +165 -77
- package/dist/index.js +43 -719
- package/dist/ui/index.cjs +3268 -0
- package/dist/ui/index.d.cts +391 -0
- package/dist/ui/index.d.ts +391 -0
- package/dist/ui/index.js +3268 -0
- package/package.json +16 -6
package/dist/index.js
CHANGED
|
@@ -1,655 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Public config properties
|
|
22
|
-
baseUrl;
|
|
23
|
-
appId;
|
|
24
|
-
constructor(config) {
|
|
25
|
-
this.baseUrl = config.baseUrl ?? "https://api.kerne.io";
|
|
26
|
-
this.appId = config.appId;
|
|
27
|
-
this.storage = config.storage ?? (typeof window !== "undefined" ? localStorage : null);
|
|
28
|
-
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
29
|
-
this.onAuthChange = config.onAuthChange;
|
|
30
|
-
this.autoRefresh = config.autoRefresh ?? true;
|
|
31
|
-
this.refreshBuffer = config.refreshBuffer ?? DEFAULT_REFRESH_BUFFER;
|
|
32
|
-
this.kerne = new Kerne({
|
|
33
|
-
baseUrl: this.baseUrl,
|
|
34
|
-
appId: this.appId,
|
|
35
|
-
timeout: config.timeout,
|
|
36
|
-
onUnauthorized: () => {
|
|
37
|
-
if (process.env.NODE_ENV === "development") {
|
|
38
|
-
console.warn("[Kerne] 401 received. Auto-logout disabled in dev.");
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
this.clearSession();
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
this.loadSession();
|
|
45
|
-
}
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// Reactive State (useSyncExternalStore compatible)
|
|
48
|
-
// ============================================================================
|
|
49
|
-
subscribe(listener) {
|
|
50
|
-
this.listeners.add(listener);
|
|
51
|
-
return () => this.listeners.delete(listener);
|
|
52
|
-
}
|
|
53
|
-
notify() {
|
|
54
|
-
this.listeners.forEach((l) => l());
|
|
55
|
-
}
|
|
56
|
-
get user() {
|
|
57
|
-
return this._user;
|
|
58
|
-
}
|
|
59
|
-
get token() {
|
|
60
|
-
return this._token;
|
|
61
|
-
}
|
|
62
|
-
get isAuthenticated() {
|
|
63
|
-
return this._token !== null;
|
|
64
|
-
}
|
|
65
|
-
get isLoading() {
|
|
66
|
-
return this._isLoading;
|
|
67
|
-
}
|
|
68
|
-
// ============================================================================
|
|
69
|
-
// Session Management
|
|
70
|
-
// ============================================================================
|
|
71
|
-
loadSession() {
|
|
72
|
-
if (!this.storage) {
|
|
73
|
-
this._isLoading = false;
|
|
74
|
-
this.notify();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const data = this.storage.getItem(this.storageKey);
|
|
79
|
-
if (data) {
|
|
80
|
-
const { user, token, refresh_token, expires_at } = JSON.parse(data);
|
|
81
|
-
const expiresAt = expires_at ? new Date(expires_at) : null;
|
|
82
|
-
if (!expiresAt || expiresAt > /* @__PURE__ */ new Date()) {
|
|
83
|
-
this._user = user;
|
|
84
|
-
this._token = token;
|
|
85
|
-
this._refreshToken = refresh_token ?? null;
|
|
86
|
-
this._expiresAt = expiresAt;
|
|
87
|
-
this.kerne = this.kerne.withToken(token);
|
|
88
|
-
this.scheduleRefresh();
|
|
89
|
-
} else {
|
|
90
|
-
this.storage.removeItem(this.storageKey);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch (e) {
|
|
94
|
-
console.error("[Kerne] Failed to load session:", e);
|
|
95
|
-
this.storage?.removeItem(this.storageKey);
|
|
96
|
-
}
|
|
97
|
-
this._isLoading = false;
|
|
98
|
-
this.notify();
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* `AuthResponse.user` is the minimal `{id, email, role}` claim set the
|
|
102
|
-
* token endpoint returns, not the full profile (`User`) - so this always
|
|
103
|
-
* follows up with `users.me()` rather than assigning it directly (that
|
|
104
|
-
* used to leave `_user` looking like a `User` while actually missing
|
|
105
|
-
* `email_verified`/`first_name`/`avatar`/etc).
|
|
106
|
-
*/
|
|
107
|
-
async saveSession(response) {
|
|
108
|
-
this._token = response.token;
|
|
109
|
-
this._refreshToken = response.refresh_token;
|
|
110
|
-
this._expiresAt = response.expires_at ? new Date(response.expires_at) : null;
|
|
111
|
-
this.kerne = this.kerne.withToken(response.token);
|
|
112
|
-
this._user = await this.kerne.users.me();
|
|
113
|
-
if (this.storage) {
|
|
114
|
-
this.storage.setItem(
|
|
115
|
-
this.storageKey,
|
|
116
|
-
JSON.stringify({
|
|
117
|
-
user: this._user,
|
|
118
|
-
token: response.token,
|
|
119
|
-
refresh_token: response.refresh_token,
|
|
120
|
-
expires_at: response.expires_at
|
|
121
|
-
})
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
this.scheduleRefresh();
|
|
125
|
-
this.onAuthChange?.(this._user);
|
|
126
|
-
this.notify();
|
|
127
|
-
}
|
|
128
|
-
clearSession() {
|
|
129
|
-
this._user = null;
|
|
130
|
-
this._token = null;
|
|
131
|
-
this._refreshToken = null;
|
|
132
|
-
this._expiresAt = null;
|
|
133
|
-
this.cancelRefresh();
|
|
134
|
-
if (this.storage) {
|
|
135
|
-
this.storage.removeItem(this.storageKey);
|
|
136
|
-
}
|
|
137
|
-
this.onAuthChange?.(null);
|
|
138
|
-
this.notify();
|
|
139
|
-
}
|
|
140
|
-
// ============================================================================
|
|
141
|
-
// Auto-Refresh
|
|
142
|
-
// ============================================================================
|
|
143
|
-
scheduleRefresh() {
|
|
144
|
-
if (!this.autoRefresh || !this._expiresAt) return;
|
|
145
|
-
this.cancelRefresh();
|
|
146
|
-
const now = Date.now();
|
|
147
|
-
const expiry = this._expiresAt.getTime();
|
|
148
|
-
const refreshAt = expiry - this.refreshBuffer * 1e3;
|
|
149
|
-
const delay = Math.max(0, refreshAt - now);
|
|
150
|
-
if (delay > 0) {
|
|
151
|
-
this.refreshTimer = setTimeout(() => this.silentRefresh(), delay);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
cancelRefresh() {
|
|
155
|
-
if (this.refreshTimer) {
|
|
156
|
-
clearTimeout(this.refreshTimer);
|
|
157
|
-
this.refreshTimer = null;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async silentRefresh() {
|
|
161
|
-
if (!this._refreshToken) return;
|
|
162
|
-
try {
|
|
163
|
-
const response = await this.kerne.auth.refreshToken({ refresh_token: this._refreshToken });
|
|
164
|
-
await this.saveSession(response);
|
|
165
|
-
console.debug("[Kerne] Session refreshed silently");
|
|
166
|
-
} catch (e) {
|
|
167
|
-
console.warn("[Kerne] Silent refresh failed:", e);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// ============================================================================
|
|
171
|
-
// Auth Methods
|
|
172
|
-
// ============================================================================
|
|
173
|
-
async register(params) {
|
|
174
|
-
const [firstName, ...lastNameParts] = (params.name || "").split(" ");
|
|
175
|
-
const response = await this.kerne.auth.signup({
|
|
176
|
-
email: params.email,
|
|
177
|
-
password: params.password,
|
|
178
|
-
first_name: firstName || void 0,
|
|
179
|
-
last_name: lastNameParts.join(" ") || void 0,
|
|
180
|
-
invitationToken: params.invitationToken,
|
|
181
|
-
invitationCode: params.invitationCode
|
|
182
|
-
});
|
|
183
|
-
await this.saveSession(response);
|
|
184
|
-
return response;
|
|
185
|
-
}
|
|
186
|
-
async login(params) {
|
|
187
|
-
const response = await this.kerne.auth.login(params);
|
|
188
|
-
await this.saveSession(response);
|
|
189
|
-
return response;
|
|
190
|
-
}
|
|
191
|
-
/** Manually trigger a token refresh - normally handled automatically by `autoRefresh`. */
|
|
192
|
-
async refreshToken() {
|
|
193
|
-
if (!this._refreshToken) return null;
|
|
194
|
-
const response = await this.kerne.auth.refreshToken({ refresh_token: this._refreshToken });
|
|
195
|
-
await this.saveSession(response);
|
|
196
|
-
return response;
|
|
197
|
-
}
|
|
198
|
-
logout() {
|
|
199
|
-
this.clearSession();
|
|
200
|
-
}
|
|
201
|
-
async refreshUser() {
|
|
202
|
-
if (!this._token) return null;
|
|
203
|
-
try {
|
|
204
|
-
const user = await this.kerne.users.me();
|
|
205
|
-
this._user = user;
|
|
206
|
-
this.onAuthChange?.(user);
|
|
207
|
-
this.notify();
|
|
208
|
-
return user;
|
|
209
|
-
} catch {
|
|
210
|
-
this.clearSession();
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// No `avatar` here - UpdateUserDto doesn't accept it server-side
|
|
215
|
-
// (`forbidNonWhitelisted` rejects the field with a 400).
|
|
216
|
-
async updateProfile(params) {
|
|
217
|
-
if (!this._user) throw new Error("User not authenticated");
|
|
218
|
-
const updated = await this.kerne.users.update(this._user.id, params);
|
|
219
|
-
this._user = updated;
|
|
220
|
-
if (this.storage && this._token) {
|
|
221
|
-
try {
|
|
222
|
-
const data = this.storage.getItem(this.storageKey);
|
|
223
|
-
if (data) {
|
|
224
|
-
const session = JSON.parse(data);
|
|
225
|
-
session.user = updated;
|
|
226
|
-
this.storage.setItem(this.storageKey, JSON.stringify(session));
|
|
227
|
-
}
|
|
228
|
-
} catch (e) {
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
this.onAuthChange?.(updated);
|
|
232
|
-
this.notify();
|
|
233
|
-
return updated;
|
|
234
|
-
}
|
|
235
|
-
async updatePassword(params) {
|
|
236
|
-
await this.kerne.auth.changePassword(params);
|
|
237
|
-
}
|
|
238
|
-
/** Forgot-password flow (logged out) - sends the reset email. */
|
|
239
|
-
async requestPasswordReset(email) {
|
|
240
|
-
await this.kerne.auth.requestPasswordReset(email);
|
|
241
|
-
}
|
|
242
|
-
/** Confirms a password reset with the token from the email. */
|
|
243
|
-
async confirmPasswordReset(token, password) {
|
|
244
|
-
await this.kerne.auth.confirmPasswordReset(token, password);
|
|
245
|
-
}
|
|
246
|
-
// ============================================================================
|
|
247
|
-
// Billing Methods
|
|
248
|
-
// ============================================================================
|
|
249
|
-
/**
|
|
250
|
-
* Errors are not swallowed here (unlike the old implementation, which
|
|
251
|
-
* caught everything and returned `false`) - a 401/500 must not be
|
|
252
|
-
* indistinguishable from a real denial, that's exactly what let the
|
|
253
|
-
* `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
|
|
254
|
-
* a fail-closed boolean regardless of the reason (e.g. `<Allows>`)
|
|
255
|
-
* catch around this themselves.
|
|
256
|
-
*/
|
|
257
|
-
async allows(featureKey, requested) {
|
|
258
|
-
const check = await this.kerne.check(featureKey, { requested });
|
|
259
|
-
return check.allowed;
|
|
260
|
-
}
|
|
261
|
-
/** Full entitlement detail (limit/used/remaining/overage) - use `allows()` for a plain boolean. */
|
|
262
|
-
async check(featureKey, requested) {
|
|
263
|
-
return this.kerne.check(featureKey, { requested });
|
|
264
|
-
}
|
|
265
|
-
async createCheckout(planPriceId, options) {
|
|
266
|
-
const { url } = await this.kerne.billing.checkout(planPriceId, options);
|
|
267
|
-
return url;
|
|
268
|
-
}
|
|
269
|
-
async openCheckout(planPriceId, options) {
|
|
270
|
-
const url = await this.createCheckout(planPriceId, options);
|
|
271
|
-
if (typeof window !== "undefined") window.location.href = url;
|
|
272
|
-
}
|
|
273
|
-
async createPortal(returnUrl) {
|
|
274
|
-
const { url } = await this.kerne.billing.portal({ returnUrl });
|
|
275
|
-
return url;
|
|
276
|
-
}
|
|
277
|
-
async openPortal(returnUrl) {
|
|
278
|
-
const url = await this.createPortal(returnUrl);
|
|
279
|
-
if (typeof window !== "undefined") window.location.href = url;
|
|
280
|
-
}
|
|
281
|
-
async getSubscription(productSlug) {
|
|
282
|
-
const subs = await this.kerne.billing.subscriptions.list({
|
|
283
|
-
productSlug,
|
|
284
|
-
scopeType: "USER"
|
|
285
|
-
});
|
|
286
|
-
const active = subs.find(
|
|
287
|
-
(s) => s.status === "ACTIVE" || s.status === "TRIALING" || s.status === "PAST_DUE"
|
|
288
|
-
);
|
|
289
|
-
return active ?? null;
|
|
290
|
-
}
|
|
291
|
-
/** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
|
|
292
|
-
async getPlans(productIdOrSlug) {
|
|
293
|
-
return this.kerne.billing.plans(productIdOrSlug);
|
|
294
|
-
}
|
|
295
|
-
/** Every entitlement for the current user in one call - for a "your plan" screen. */
|
|
296
|
-
async getEntitlements() {
|
|
297
|
-
return this.kerne.entitlements();
|
|
298
|
-
}
|
|
299
|
-
/** Usage for the current user - all QUOTA features, or one via `featureKey`. */
|
|
300
|
-
async getUsage(featureKey) {
|
|
301
|
-
return this.kerne.usage({ featureKey });
|
|
302
|
-
}
|
|
303
|
-
// ============================================================================
|
|
304
|
-
// Growth Methods (waitlist, invitations)
|
|
305
|
-
// ============================================================================
|
|
306
|
-
async joinWaitlist(params) {
|
|
307
|
-
return this.kerne.waitlist.join(params);
|
|
308
|
-
}
|
|
309
|
-
/** Validate an invitation token before showing the registration form. */
|
|
310
|
-
async validateInvitationToken(token) {
|
|
311
|
-
return this.kerne.invitations.validateToken(token);
|
|
312
|
-
}
|
|
313
|
-
/** Validate an invitation code before showing the registration form. */
|
|
314
|
-
async validateInvitationCode(code) {
|
|
315
|
-
return this.kerne.invitations.validateCode(code);
|
|
316
|
-
}
|
|
317
|
-
// ============================================================================
|
|
318
|
-
// Email Verification Methods
|
|
319
|
-
// ============================================================================
|
|
320
|
-
async sendVerificationEmail(verificationType = "code") {
|
|
321
|
-
await this.kerne.auth.sendVerificationEmail(verificationType);
|
|
322
|
-
}
|
|
323
|
-
async verifyEmailWithCode(code) {
|
|
324
|
-
return await this.kerne.auth.verifyEmailWithCode(code);
|
|
325
|
-
}
|
|
326
|
-
async verifyEmailWithLink(token) {
|
|
327
|
-
return await this.kerne.auth.verifyEmailWithLink(token);
|
|
328
|
-
}
|
|
329
|
-
async getVerificationStatus() {
|
|
330
|
-
return await this.kerne.auth.getVerificationStatus();
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
var KerneContext = createContext(null);
|
|
334
|
-
function KerneProvider({ children, ...config }) {
|
|
335
|
-
const clientRef = useRef(null);
|
|
336
|
-
const client = useMemo(() => {
|
|
337
|
-
if (!clientRef.current) {
|
|
338
|
-
clientRef.current = new KerneClient(config);
|
|
339
|
-
}
|
|
340
|
-
return clientRef.current;
|
|
341
|
-
}, [config.appId, config.baseUrl]);
|
|
342
|
-
useEffect(() => {
|
|
343
|
-
return () => {
|
|
344
|
-
client.cancelRefresh?.();
|
|
345
|
-
};
|
|
346
|
-
}, [client]);
|
|
347
|
-
return /* @__PURE__ */ jsx(KerneContext.Provider, { value: client, children });
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// src/hooks.ts
|
|
351
|
-
import { useContext, useSyncExternalStore, useCallback, useEffect as useEffect2, useState } from "react";
|
|
352
|
-
function useClient() {
|
|
353
|
-
const context = useContext(KerneContext);
|
|
354
|
-
if (!context) {
|
|
355
|
-
throw new Error("useClient must be used within a KerneProvider");
|
|
356
|
-
}
|
|
357
|
-
return context;
|
|
358
|
-
}
|
|
359
|
-
function useAuth() {
|
|
360
|
-
const client = useClient();
|
|
361
|
-
const user = useSyncExternalStore(
|
|
362
|
-
(callback) => client.subscribe(callback),
|
|
363
|
-
() => client.user,
|
|
364
|
-
() => client.user
|
|
365
|
-
// Server snapshot
|
|
366
|
-
);
|
|
367
|
-
const token = useSyncExternalStore(
|
|
368
|
-
(callback) => client.subscribe(callback),
|
|
369
|
-
() => client.token,
|
|
370
|
-
() => null
|
|
371
|
-
// Server always null
|
|
372
|
-
);
|
|
373
|
-
const isAuthenticated = useSyncExternalStore(
|
|
374
|
-
(callback) => client.subscribe(callback),
|
|
375
|
-
() => client.isAuthenticated,
|
|
376
|
-
() => false
|
|
377
|
-
// Server always unauthenticated
|
|
378
|
-
);
|
|
379
|
-
const isLoading = useSyncExternalStore(
|
|
380
|
-
(callback) => client.subscribe(callback),
|
|
381
|
-
() => client.isLoading,
|
|
382
|
-
() => true
|
|
383
|
-
// Server always loading
|
|
384
|
-
);
|
|
385
|
-
return {
|
|
386
|
-
user,
|
|
387
|
-
token,
|
|
388
|
-
isAuthenticated,
|
|
389
|
-
isLoading,
|
|
390
|
-
login: useCallback(
|
|
391
|
-
(params) => client.login(params),
|
|
392
|
-
[client]
|
|
393
|
-
),
|
|
394
|
-
register: useCallback(
|
|
395
|
-
(params) => client.register(params),
|
|
396
|
-
[client]
|
|
397
|
-
),
|
|
398
|
-
logout: useCallback(() => client.logout(), [client]),
|
|
399
|
-
refreshUser: useCallback(() => client.refreshUser(), [client]),
|
|
400
|
-
refreshToken: useCallback(() => client.refreshToken(), [client]),
|
|
401
|
-
updateProfile: useCallback(
|
|
402
|
-
(params) => client.updateProfile(params),
|
|
403
|
-
[client]
|
|
404
|
-
),
|
|
405
|
-
updatePassword: useCallback(
|
|
406
|
-
(params) => client.updatePassword(params),
|
|
407
|
-
[client]
|
|
408
|
-
),
|
|
409
|
-
requestPasswordReset: useCallback(
|
|
410
|
-
(email) => client.requestPasswordReset(email),
|
|
411
|
-
[client]
|
|
412
|
-
),
|
|
413
|
-
confirmPasswordReset: useCallback(
|
|
414
|
-
(token2, password) => client.confirmPasswordReset(token2, password),
|
|
415
|
-
[client]
|
|
416
|
-
),
|
|
417
|
-
// Email verification
|
|
418
|
-
sendVerificationEmail: useCallback(
|
|
419
|
-
(verificationType = "code") => client.sendVerificationEmail(verificationType),
|
|
420
|
-
[client]
|
|
421
|
-
),
|
|
422
|
-
verifyEmailWithCode: useCallback((code) => client.verifyEmailWithCode(code), [client]),
|
|
423
|
-
verifyEmailWithLink: useCallback(
|
|
424
|
-
(token2) => client.verifyEmailWithLink(token2),
|
|
425
|
-
[client]
|
|
426
|
-
),
|
|
427
|
-
getVerificationStatus: useCallback(() => client.getVerificationStatus(), [client])
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
function useCheckout() {
|
|
431
|
-
const client = useClient();
|
|
432
|
-
return {
|
|
433
|
-
createCheckout: useCallback(
|
|
434
|
-
(planPriceId, options) => client.createCheckout(planPriceId, options),
|
|
435
|
-
[client]
|
|
436
|
-
),
|
|
437
|
-
openCheckout: useCallback(
|
|
438
|
-
(planPriceId, options) => client.openCheckout(planPriceId, options),
|
|
439
|
-
[client]
|
|
440
|
-
)
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
function usePortal() {
|
|
444
|
-
const client = useClient();
|
|
445
|
-
return {
|
|
446
|
-
createPortal: useCallback((returnUrl) => client.createPortal(returnUrl), [client]),
|
|
447
|
-
openPortal: useCallback((returnUrl) => client.openPortal(returnUrl), [client])
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
function useAllows(featureKey, requested) {
|
|
451
|
-
const client = useClient();
|
|
452
|
-
const [state, setState] = useState({
|
|
453
|
-
allowed: false,
|
|
454
|
-
isLoading: true,
|
|
455
|
-
error: null
|
|
456
|
-
});
|
|
457
|
-
useEffect2(() => {
|
|
458
|
-
let mounted = true;
|
|
459
|
-
client.allows(featureKey, requested).then((allowed) => {
|
|
460
|
-
if (mounted) {
|
|
461
|
-
setState({ allowed, isLoading: false, error: null });
|
|
462
|
-
}
|
|
463
|
-
}).catch((error) => {
|
|
464
|
-
if (mounted) {
|
|
465
|
-
setState({ allowed: false, isLoading: false, error });
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
return () => {
|
|
469
|
-
mounted = false;
|
|
470
|
-
};
|
|
471
|
-
}, [client, featureKey, requested]);
|
|
472
|
-
return state;
|
|
473
|
-
}
|
|
474
|
-
function useCheck(featureKey, requested) {
|
|
475
|
-
const client = useClient();
|
|
476
|
-
const [state, setState] = useState({
|
|
477
|
-
check: null,
|
|
478
|
-
isLoading: true,
|
|
479
|
-
error: null
|
|
480
|
-
});
|
|
481
|
-
useEffect2(() => {
|
|
482
|
-
let mounted = true;
|
|
483
|
-
client.check(featureKey, requested).then((check) => {
|
|
484
|
-
if (mounted) {
|
|
485
|
-
setState({ check, isLoading: false, error: null });
|
|
486
|
-
}
|
|
487
|
-
}).catch((error) => {
|
|
488
|
-
if (mounted) {
|
|
489
|
-
setState({ check: null, isLoading: false, error });
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
return () => {
|
|
493
|
-
mounted = false;
|
|
494
|
-
};
|
|
495
|
-
}, [client, featureKey, requested]);
|
|
496
|
-
return state;
|
|
497
|
-
}
|
|
498
|
-
function useSubscription(productSlug) {
|
|
499
|
-
const client = useClient();
|
|
500
|
-
const [state, setState] = useState({
|
|
501
|
-
subscription: null,
|
|
502
|
-
isLoading: true,
|
|
503
|
-
error: null
|
|
504
|
-
});
|
|
505
|
-
useEffect2(() => {
|
|
506
|
-
let mounted = true;
|
|
507
|
-
client.getSubscription(productSlug).then((subscription) => {
|
|
508
|
-
if (mounted) {
|
|
509
|
-
setState({ subscription, isLoading: false, error: null });
|
|
510
|
-
}
|
|
511
|
-
}).catch((error) => {
|
|
512
|
-
if (mounted) {
|
|
513
|
-
setState({ subscription: null, isLoading: false, error });
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
return () => {
|
|
517
|
-
mounted = false;
|
|
518
|
-
};
|
|
519
|
-
}, [client, productSlug]);
|
|
520
|
-
const isActive = state.subscription?.status === "ACTIVE" || state.subscription?.status === "TRIALING";
|
|
521
|
-
const planSlug = state.subscription?.plan?.slug;
|
|
522
|
-
return {
|
|
523
|
-
...state,
|
|
524
|
-
isActive,
|
|
525
|
-
planSlug
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
function usePlans(productIdOrSlug) {
|
|
529
|
-
const client = useClient();
|
|
530
|
-
const [state, setState] = useState({
|
|
531
|
-
plans: [],
|
|
532
|
-
isLoading: true,
|
|
533
|
-
error: null
|
|
534
|
-
});
|
|
535
|
-
useEffect2(() => {
|
|
536
|
-
let mounted = true;
|
|
537
|
-
client.getPlans(productIdOrSlug).then((plans) => {
|
|
538
|
-
if (mounted) {
|
|
539
|
-
setState({ plans, isLoading: false, error: null });
|
|
540
|
-
}
|
|
541
|
-
}).catch((error) => {
|
|
542
|
-
if (mounted) {
|
|
543
|
-
setState({ plans: [], isLoading: false, error });
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
return () => {
|
|
547
|
-
mounted = false;
|
|
548
|
-
};
|
|
549
|
-
}, [client, productIdOrSlug]);
|
|
550
|
-
return state;
|
|
551
|
-
}
|
|
552
|
-
function useEntitlements() {
|
|
553
|
-
const client = useClient();
|
|
554
|
-
const [state, setState] = useState({
|
|
555
|
-
entitlements: null,
|
|
556
|
-
isLoading: true,
|
|
557
|
-
error: null
|
|
558
|
-
});
|
|
559
|
-
useEffect2(() => {
|
|
560
|
-
let mounted = true;
|
|
561
|
-
client.getEntitlements().then((entitlements) => {
|
|
562
|
-
if (mounted) {
|
|
563
|
-
setState({ entitlements, isLoading: false, error: null });
|
|
564
|
-
}
|
|
565
|
-
}).catch((error) => {
|
|
566
|
-
if (mounted) {
|
|
567
|
-
setState({ entitlements: null, isLoading: false, error });
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
return () => {
|
|
571
|
-
mounted = false;
|
|
572
|
-
};
|
|
573
|
-
}, [client]);
|
|
574
|
-
return state;
|
|
575
|
-
}
|
|
576
|
-
function useUsage(featureKey) {
|
|
577
|
-
const client = useClient();
|
|
578
|
-
const [state, setState] = useState({
|
|
579
|
-
usage: [],
|
|
580
|
-
isLoading: true,
|
|
581
|
-
error: null
|
|
582
|
-
});
|
|
583
|
-
useEffect2(() => {
|
|
584
|
-
let mounted = true;
|
|
585
|
-
client.getUsage(featureKey).then((usage) => {
|
|
586
|
-
if (mounted) {
|
|
587
|
-
setState({ usage, isLoading: false, error: null });
|
|
588
|
-
}
|
|
589
|
-
}).catch((error) => {
|
|
590
|
-
if (mounted) {
|
|
591
|
-
setState({ usage: [], isLoading: false, error });
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
return () => {
|
|
595
|
-
mounted = false;
|
|
596
|
-
};
|
|
597
|
-
}, [client, featureKey]);
|
|
598
|
-
return state;
|
|
599
|
-
}
|
|
600
|
-
function useWaitlist() {
|
|
601
|
-
const client = useClient();
|
|
602
|
-
return {
|
|
603
|
-
join: useCallback((params) => client.joinWaitlist(params), [client])
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
function useInvitation(params) {
|
|
607
|
-
const client = useClient();
|
|
608
|
-
const { token, code } = params;
|
|
609
|
-
const [state, setState] = useState({
|
|
610
|
-
result: null,
|
|
611
|
-
isLoading: !!(token || code),
|
|
612
|
-
error: null
|
|
613
|
-
});
|
|
614
|
-
useEffect2(() => {
|
|
615
|
-
if (!token && !code) {
|
|
616
|
-
setState({ result: null, isLoading: false, error: null });
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
let mounted = true;
|
|
620
|
-
const validate = token ? client.validateInvitationToken(token) : client.validateInvitationCode(code);
|
|
621
|
-
validate.then((result) => {
|
|
622
|
-
if (mounted) {
|
|
623
|
-
setState({ result, isLoading: false, error: null });
|
|
624
|
-
}
|
|
625
|
-
}).catch((error) => {
|
|
626
|
-
if (mounted) {
|
|
627
|
-
setState({ result: null, isLoading: false, error });
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
return () => {
|
|
631
|
-
mounted = false;
|
|
632
|
-
};
|
|
633
|
-
}, [client, token, code]);
|
|
634
|
-
return state;
|
|
635
|
-
}
|
|
636
|
-
function useUser() {
|
|
637
|
-
const { user, isLoading, updateProfile } = useAuth();
|
|
638
|
-
return {
|
|
639
|
-
user,
|
|
640
|
-
isLoading,
|
|
641
|
-
update: updateProfile,
|
|
642
|
-
// Computed properties
|
|
643
|
-
fullName: user ? `${user.first_name || ""} ${user.last_name || ""}`.trim() : null,
|
|
644
|
-
initials: user ? `${user.first_name?.[0] || ""}${user.last_name?.[0] || ""}`.toUpperCase() : null,
|
|
645
|
-
email: user?.email || null,
|
|
646
|
-
emailVerified: user?.email_verified || false
|
|
647
|
-
};
|
|
648
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
KerneClient,
|
|
4
|
+
KerneContext,
|
|
5
|
+
KerneProvider,
|
|
6
|
+
defaultLocalization,
|
|
7
|
+
useAccess,
|
|
8
|
+
useAuth,
|
|
9
|
+
useAuthConfig,
|
|
10
|
+
useCheckout,
|
|
11
|
+
useClient,
|
|
12
|
+
useEntitlements,
|
|
13
|
+
useInvitation,
|
|
14
|
+
usePlans,
|
|
15
|
+
usePortal,
|
|
16
|
+
useSubscription,
|
|
17
|
+
useUsage,
|
|
18
|
+
useUser,
|
|
19
|
+
useWaitlist
|
|
20
|
+
} from "./chunk-F6NV76OR.js";
|
|
649
21
|
|
|
650
22
|
// src/components.tsx
|
|
651
|
-
import
|
|
652
|
-
import { jsx
|
|
23
|
+
import React from "react";
|
|
24
|
+
import { jsx } from "react/jsx-runtime";
|
|
653
25
|
function Authenticated({ children, fallback = null }) {
|
|
654
26
|
const { isAuthenticated } = useAuth();
|
|
655
27
|
return isAuthenticated ? children : fallback;
|
|
@@ -664,8 +36,8 @@ function AuthLoading({ children }) {
|
|
|
664
36
|
}
|
|
665
37
|
function HasSubscription({ children, fallback = null }) {
|
|
666
38
|
const client = useClient();
|
|
667
|
-
const [hasSubscription, setHasSubscription] =
|
|
668
|
-
|
|
39
|
+
const [hasSubscription, setHasSubscription] = React.useState(null);
|
|
40
|
+
React.useEffect(() => {
|
|
669
41
|
client.getSubscription().then((sub) => {
|
|
670
42
|
setHasSubscription(sub !== null && ["ACTIVE", "TRIALING"].includes(sub.status));
|
|
671
43
|
}).catch(() => {
|
|
@@ -677,26 +49,26 @@ function HasSubscription({ children, fallback = null }) {
|
|
|
677
49
|
}
|
|
678
50
|
function Allows({
|
|
679
51
|
children,
|
|
680
|
-
|
|
52
|
+
featureKey,
|
|
681
53
|
minimum,
|
|
682
54
|
fallback = null
|
|
683
55
|
}) {
|
|
684
56
|
const client = useClient();
|
|
685
|
-
const [allowed, setAllowed] =
|
|
686
|
-
|
|
687
|
-
client.allows(
|
|
57
|
+
const [allowed, setAllowed] = React.useState(null);
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
client.allows(featureKey, minimum).then((result) => {
|
|
688
60
|
setAllowed(result);
|
|
689
61
|
}).catch(() => {
|
|
690
62
|
setAllowed(false);
|
|
691
63
|
});
|
|
692
|
-
}, [client,
|
|
64
|
+
}, [client, featureKey, minimum]);
|
|
693
65
|
if (allowed === null) return null;
|
|
694
66
|
return allowed ? children : fallback;
|
|
695
67
|
}
|
|
696
68
|
function Protected({
|
|
697
69
|
children,
|
|
698
70
|
auth = true,
|
|
699
|
-
|
|
71
|
+
featureKey,
|
|
700
72
|
authFallback = null,
|
|
701
73
|
billingFallback = null
|
|
702
74
|
}) {
|
|
@@ -704,15 +76,15 @@ function Protected({
|
|
|
704
76
|
if (auth && !isAuthenticated) {
|
|
705
77
|
return authFallback;
|
|
706
78
|
}
|
|
707
|
-
if (
|
|
708
|
-
return /* @__PURE__ */
|
|
79
|
+
if (featureKey) {
|
|
80
|
+
return /* @__PURE__ */ jsx(Allows, { featureKey, fallback: billingFallback, children });
|
|
709
81
|
}
|
|
710
82
|
return children;
|
|
711
83
|
}
|
|
712
84
|
|
|
713
85
|
// src/error-boundary.tsx
|
|
714
|
-
import
|
|
715
|
-
import { jsx as
|
|
86
|
+
import React2, { Component } from "react";
|
|
87
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
716
88
|
var KerneErrorBoundary = class extends Component {
|
|
717
89
|
constructor(props) {
|
|
718
90
|
super(props);
|
|
@@ -744,13 +116,13 @@ var KerneErrorBoundary = class extends Component {
|
|
|
744
116
|
return fallback;
|
|
745
117
|
}
|
|
746
118
|
return /* @__PURE__ */ jsxs("div", { style: { padding: "20px", textAlign: "center" }, children: [
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
119
|
+
/* @__PURE__ */ jsx2("h2", { style: { color: "#dc2626", marginBottom: "10px" }, children: "Something went wrong" }),
|
|
120
|
+
/* @__PURE__ */ jsx2("p", { style: { color: "#6b7280", marginBottom: "20px" }, children: error.message || "An unexpected error occurred" }),
|
|
749
121
|
error.code && /* @__PURE__ */ jsxs("p", { style: { color: "#9ca3af", fontSize: "12px", marginBottom: "20px" }, children: [
|
|
750
122
|
"Error code: ",
|
|
751
123
|
error.code
|
|
752
124
|
] }),
|
|
753
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ jsx2(
|
|
754
126
|
"button",
|
|
755
127
|
{
|
|
756
128
|
onClick: this.reset,
|
|
@@ -771,11 +143,11 @@ var KerneErrorBoundary = class extends Component {
|
|
|
771
143
|
}
|
|
772
144
|
};
|
|
773
145
|
function useKerneError() {
|
|
774
|
-
const [error, setError] =
|
|
775
|
-
const handleError =
|
|
146
|
+
const [error, setError] = React2.useState(null);
|
|
147
|
+
const handleError = React2.useCallback((err) => {
|
|
776
148
|
setError(err);
|
|
777
149
|
}, []);
|
|
778
|
-
const clearError =
|
|
150
|
+
const clearError = React2.useCallback(() => {
|
|
779
151
|
setError(null);
|
|
780
152
|
}, []);
|
|
781
153
|
return {
|
|
@@ -785,54 +157,6 @@ function useKerneError() {
|
|
|
785
157
|
clearError
|
|
786
158
|
};
|
|
787
159
|
}
|
|
788
|
-
|
|
789
|
-
// src/store.ts
|
|
790
|
-
var KerneStore = class {
|
|
791
|
-
state;
|
|
792
|
-
listeners = /* @__PURE__ */ new Set();
|
|
793
|
-
constructor(initialState) {
|
|
794
|
-
this.state = {
|
|
795
|
-
user: null,
|
|
796
|
-
isAuthenticated: false,
|
|
797
|
-
isLoading: true,
|
|
798
|
-
subscription: null,
|
|
799
|
-
error: null,
|
|
800
|
-
...initialState
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
getState() {
|
|
804
|
-
return this.state;
|
|
805
|
-
}
|
|
806
|
-
getSnapshot() {
|
|
807
|
-
return this.state;
|
|
808
|
-
}
|
|
809
|
-
getServerSnapshot() {
|
|
810
|
-
return this.state;
|
|
811
|
-
}
|
|
812
|
-
setState(partial) {
|
|
813
|
-
const prevState = this.state;
|
|
814
|
-
this.state = { ...this.state, ...partial };
|
|
815
|
-
if (this.hasChanged(prevState, this.state)) {
|
|
816
|
-
this.notify();
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
hasChanged(prev, next) {
|
|
820
|
-
return prev.user !== next.user || prev.isAuthenticated !== next.isAuthenticated || prev.isLoading !== next.isLoading || prev.subscription !== next.subscription || prev.error !== next.error;
|
|
821
|
-
}
|
|
822
|
-
subscribe(listener) {
|
|
823
|
-
this.listeners.add(listener);
|
|
824
|
-
return () => {
|
|
825
|
-
this.listeners.delete(listener);
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
notify() {
|
|
829
|
-
this.listeners.forEach((listener) => listener());
|
|
830
|
-
}
|
|
831
|
-
// Selector-based subscription for optimized re-renders
|
|
832
|
-
select(selector) {
|
|
833
|
-
return selector(this.state);
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
160
|
export {
|
|
837
161
|
Allows,
|
|
838
162
|
AuthLoading,
|
|
@@ -842,12 +166,12 @@ export {
|
|
|
842
166
|
KerneContext,
|
|
843
167
|
KerneErrorBoundary,
|
|
844
168
|
KerneProvider,
|
|
845
|
-
KerneStore,
|
|
846
169
|
Protected,
|
|
847
170
|
Unauthenticated,
|
|
848
|
-
|
|
171
|
+
defaultLocalization,
|
|
172
|
+
useAccess,
|
|
849
173
|
useAuth,
|
|
850
|
-
|
|
174
|
+
useAuthConfig,
|
|
851
175
|
useCheckout,
|
|
852
176
|
useClient,
|
|
853
177
|
useEntitlements,
|