@kerne/react 0.1.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/{chunk-F6NV76OR.js → chunk-IZMRK3WK.js} +150 -16
- package/dist/{chunk-7U3QBMA7.cjs → chunk-TYNZ2WU7.cjs} +156 -22
- package/dist/{i18n-BWTT1DWD.d.cts → i18n-QilNgwqe.d.cts} +33 -0
- package/dist/{i18n-BWTT1DWD.d.ts → i18n-QilNgwqe.d.ts} +33 -0
- package/dist/index.cjs +18 -20
- package/dist/index.d.cts +97 -13
- package/dist/index.d.ts +97 -13
- package/dist/index.js +13 -15
- package/dist/ui/index.cjs +669 -131
- package/dist/ui/index.d.cts +139 -4
- package/dist/ui/index.d.ts +139 -4
- package/dist/ui/index.js +602 -64
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -17,25 +17,28 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
var _chunkTYNZ2WU7cjs = require('./chunk-TYNZ2WU7.cjs');
|
|
21
24
|
|
|
22
25
|
// src/components.tsx
|
|
23
26
|
var _react = require('react'); var _react2 = _interopRequireDefault(_react);
|
|
24
27
|
var _jsxruntime = require('react/jsx-runtime');
|
|
25
28
|
function Authenticated({ children, fallback = null }) {
|
|
26
|
-
const { isAuthenticated } =
|
|
29
|
+
const { isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
|
|
27
30
|
return isAuthenticated ? children : fallback;
|
|
28
31
|
}
|
|
29
32
|
function Unauthenticated({ children, fallback = null }) {
|
|
30
|
-
const { isAuthenticated } =
|
|
33
|
+
const { isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
|
|
31
34
|
return !isAuthenticated ? children : fallback;
|
|
32
35
|
}
|
|
33
36
|
function AuthLoading({ children }) {
|
|
34
|
-
const client =
|
|
37
|
+
const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
|
|
35
38
|
return client.isLoading ? children : null;
|
|
36
39
|
}
|
|
37
40
|
function HasSubscription({ children, fallback = null }) {
|
|
38
|
-
const client =
|
|
41
|
+
const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
|
|
39
42
|
const [hasSubscription, setHasSubscription] = _react2.default.useState(null);
|
|
40
43
|
_react2.default.useEffect(() => {
|
|
41
44
|
client.getSubscription().then((sub) => {
|
|
@@ -47,22 +50,14 @@ function HasSubscription({ children, fallback = null }) {
|
|
|
47
50
|
if (hasSubscription === null) return null;
|
|
48
51
|
return hasSubscription ? children : fallback;
|
|
49
52
|
}
|
|
50
|
-
function
|
|
53
|
+
function Access({
|
|
51
54
|
children,
|
|
52
55
|
featureKey,
|
|
53
|
-
|
|
56
|
+
requested,
|
|
54
57
|
fallback = null
|
|
55
58
|
}) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
_react2.default.useEffect(() => {
|
|
59
|
-
client.allows(featureKey, minimum).then((result) => {
|
|
60
|
-
setAllowed(result);
|
|
61
|
-
}).catch(() => {
|
|
62
|
-
setAllowed(false);
|
|
63
|
-
});
|
|
64
|
-
}, [client, featureKey, minimum]);
|
|
65
|
-
if (allowed === null) return null;
|
|
59
|
+
const { allowed, isLoading } = _chunkTYNZ2WU7cjs.useAccess.call(void 0, featureKey, requested);
|
|
60
|
+
if (isLoading) return null;
|
|
66
61
|
return allowed ? children : fallback;
|
|
67
62
|
}
|
|
68
63
|
function Protected({
|
|
@@ -72,12 +67,12 @@ function Protected({
|
|
|
72
67
|
authFallback = null,
|
|
73
68
|
billingFallback = null
|
|
74
69
|
}) {
|
|
75
|
-
const { isAuthenticated } =
|
|
70
|
+
const { isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
|
|
76
71
|
if (auth && !isAuthenticated) {
|
|
77
72
|
return authFallback;
|
|
78
73
|
}
|
|
79
74
|
if (featureKey) {
|
|
80
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
75
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Access, { featureKey, fallback: billingFallback, children });
|
|
81
76
|
}
|
|
82
77
|
return children;
|
|
83
78
|
}
|
|
@@ -183,4 +178,7 @@ function useKerneError() {
|
|
|
183
178
|
|
|
184
179
|
|
|
185
180
|
|
|
186
|
-
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
exports.Access = Access; exports.AuthLoading = AuthLoading; exports.Authenticated = Authenticated; exports.HasSubscription = HasSubscription; exports.KerneClient = _chunkTYNZ2WU7cjs.KerneClient; exports.KerneContext = _chunkTYNZ2WU7cjs.KerneContext; exports.KerneErrorBoundary = KerneErrorBoundary; exports.KerneProvider = _chunkTYNZ2WU7cjs.KerneProvider; exports.Protected = Protected; exports.Unauthenticated = Unauthenticated; exports.defaultLocalization = _chunkTYNZ2WU7cjs.defaultLocalization; exports.useAccess = _chunkTYNZ2WU7cjs.useAccess; exports.useAuth = _chunkTYNZ2WU7cjs.useAuth; exports.useAuthConfig = _chunkTYNZ2WU7cjs.useAuthConfig; exports.useCancelSubscription = _chunkTYNZ2WU7cjs.useCancelSubscription; exports.useChangePlan = _chunkTYNZ2WU7cjs.useChangePlan; exports.useCheckout = _chunkTYNZ2WU7cjs.useCheckout; exports.useClient = _chunkTYNZ2WU7cjs.useClient; exports.useEntitlements = _chunkTYNZ2WU7cjs.useEntitlements; exports.useInvitation = _chunkTYNZ2WU7cjs.useInvitation; exports.useKerneError = useKerneError; exports.usePlanSwitch = _chunkTYNZ2WU7cjs.usePlanSwitch; exports.usePlans = _chunkTYNZ2WU7cjs.usePlans; exports.usePortal = _chunkTYNZ2WU7cjs.usePortal; exports.useSubscription = _chunkTYNZ2WU7cjs.useSubscription; exports.useUsage = _chunkTYNZ2WU7cjs.useUsage; exports.useUser = _chunkTYNZ2WU7cjs.useUser; exports.useWaitlist = _chunkTYNZ2WU7cjs.useWaitlist;
|
package/dist/index.d.cts
CHANGED
|
@@ -3,10 +3,10 @@ import React, { ReactNode, Component, ErrorInfo } from 'react';
|
|
|
3
3
|
import { KerneConfig, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
|
|
4
4
|
export { JoinWaitlistParams, ValidateCodeResponse, ValidateTokenResponse, WaitlistEntry } from '@kerne/server';
|
|
5
5
|
import * as _kerne_types from '@kerne/types';
|
|
6
|
-
import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, PublicPlan, Entitlements, UsageRecord } from '@kerne/types';
|
|
6
|
+
import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, Subscription, PublicPlan, Entitlements, UsageRecord, PublicPlanPrice } from '@kerne/types';
|
|
7
7
|
export { ActivationContext, AuthConfig, AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
|
|
8
|
-
import { D as DeepPartial, K as KerneLocalization } from './i18n-
|
|
9
|
-
export { d as defaultLocalization } from './i18n-
|
|
8
|
+
import { D as DeepPartial, K as KerneLocalization } from './i18n-QilNgwqe.cjs';
|
|
9
|
+
export { d as defaultLocalization } from './i18n-QilNgwqe.cjs';
|
|
10
10
|
|
|
11
11
|
interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
|
|
12
12
|
storage?: Storage;
|
|
@@ -160,7 +160,7 @@ declare class KerneClient {
|
|
|
160
160
|
* caught everything and returned `false`) - a 401/500 must not be
|
|
161
161
|
* indistinguishable from a real denial, that's exactly what let the
|
|
162
162
|
* `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
|
|
163
|
-
* a fail-closed boolean regardless of the reason (e.g. `<
|
|
163
|
+
* a fail-closed boolean regardless of the reason (e.g. `<Access>`)
|
|
164
164
|
* catch around this themselves.
|
|
165
165
|
*/
|
|
166
166
|
allows(featureKey: string, requested?: number): Promise<boolean>;
|
|
@@ -177,6 +177,12 @@ declare class KerneClient {
|
|
|
177
177
|
createPortal(returnUrl?: string): Promise<string>;
|
|
178
178
|
openPortal(returnUrl?: string): Promise<void>;
|
|
179
179
|
getSubscription(productSlug?: string): Promise<SubscriptionWithPlan | null>;
|
|
180
|
+
/** `immediately: true` ends it now; otherwise cancels at period end (API default). */
|
|
181
|
+
cancelSubscription(subscriptionId: string, immediately?: boolean): Promise<Subscription>;
|
|
182
|
+
/** Swaps the plan/price, effective immediately - deferring a downgrade isn't supported yet. */
|
|
183
|
+
changeSubscriptionPlan(subscriptionId: string, planPriceId: string, options?: {
|
|
184
|
+
prorationBehavior?: 'none' | 'create_prorations';
|
|
185
|
+
}): Promise<SubscriptionWithPlan>;
|
|
180
186
|
/** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
|
|
181
187
|
getPlans(productIdOrSlug?: string): Promise<PublicPlan[]>;
|
|
182
188
|
/** Every entitlement for the current user in one call - for a "your plan" screen. */
|
|
@@ -317,6 +323,72 @@ declare function usePortal(): {
|
|
|
317
323
|
createPortal: (returnUrl?: string) => Promise<string>;
|
|
318
324
|
openPortal: (returnUrl?: string) => Promise<void>;
|
|
319
325
|
};
|
|
326
|
+
/**
|
|
327
|
+
* Cancels or changes the current scope's own subscription - JWT-scoped like every other hook
|
|
328
|
+
* here, so this can only ever act on the caller's own subscription (see `SubscriptionCard`).
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```tsx
|
|
332
|
+
* const { cancelSubscription, isCanceling } = useCancelSubscription();
|
|
333
|
+
* <button onClick={() => cancelSubscription(subscriptionId)}>Cancel</button>
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
declare function useCancelSubscription(): {
|
|
337
|
+
cancelSubscription: (subscriptionId: string, immediately?: boolean) => Promise<_kerne_types.Subscription>;
|
|
338
|
+
isCanceling: boolean;
|
|
339
|
+
};
|
|
340
|
+
/** Changes the current scope's own subscription to a different plan/price, effective immediately. */
|
|
341
|
+
declare function useChangePlan(): {
|
|
342
|
+
changePlan: (subscriptionId: string, planPriceId: string, options?: {
|
|
343
|
+
prorationBehavior?: "none" | "create_prorations";
|
|
344
|
+
}) => Promise<SubscriptionWithPlan>;
|
|
345
|
+
isChanging: boolean;
|
|
346
|
+
};
|
|
347
|
+
interface PlanSwitchTarget {
|
|
348
|
+
plan: PublicPlan;
|
|
349
|
+
price: PublicPlanPrice;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The decision every "pick a plan" surface needs, shared so it is only
|
|
353
|
+
* decided once: an existing subscriber switching plans has to swap in place
|
|
354
|
+
* (`changePlanPrice`), never start a fresh checkout - Stripe/Polar don't know
|
|
355
|
+
* about the subscription already on file, so a new checkout session creates
|
|
356
|
+
* a SECOND, independent one instead of replacing it. The old one stays live
|
|
357
|
+
* and billed at the provider while Kerne only ever tracks the new one. A
|
|
358
|
+
* first-time subscriber has nothing to swap, so that case goes straight to
|
|
359
|
+
* checkout, same as before.
|
|
360
|
+
*
|
|
361
|
+
* The switch is staged rather than applied on `selectPlan` alone - it bills
|
|
362
|
+
* immediately on confirmation (unlike checkout, which only starts billing
|
|
363
|
+
* once the provider's own page is completed), so the caller's UI gets a
|
|
364
|
+
* chance to say so before `confirmSwitch()` commits it.
|
|
365
|
+
*
|
|
366
|
+
* @param subscription the subject's current subscription, or null - pass
|
|
367
|
+
* what `useSubscription()` already returned rather than fetching it again
|
|
368
|
+
* here, so a page showing the current plan and this hook always agree.
|
|
369
|
+
* @param refetchSubscription called after a successful switch, typically
|
|
370
|
+
* that same `useSubscription()` call's own `refetch()`.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```tsx
|
|
374
|
+
* const { subscription, refetch } = useSubscription();
|
|
375
|
+
* const { pending, switching, selectPlan, confirmSwitch, dismissSwitch } =
|
|
376
|
+
* usePlanSwitch(subscription, refetch);
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
declare function usePlanSwitch(subscription: SubscriptionWithPlan | null, refetchSubscription: () => void, options?: {
|
|
380
|
+
successUrl?: string;
|
|
381
|
+
cancelUrl?: string;
|
|
382
|
+
}): {
|
|
383
|
+
pending: PlanSwitchTarget | null;
|
|
384
|
+
switching: boolean;
|
|
385
|
+
switchError: unknown;
|
|
386
|
+
checkoutPriceId: string | null;
|
|
387
|
+
checkoutError: unknown;
|
|
388
|
+
selectPlan: (plan: PublicPlan, price: PublicPlanPrice) => Promise<void>;
|
|
389
|
+
confirmSwitch: () => Promise<void>;
|
|
390
|
+
dismissSwitch: () => void;
|
|
391
|
+
};
|
|
320
392
|
/**
|
|
321
393
|
* Whether the current scope has access to one feature - the single entry
|
|
322
394
|
* point for a live, per-feature decision. Deliberately not named after what
|
|
@@ -359,6 +431,7 @@ declare function useAccess(featureKey: string, requested?: number): {
|
|
|
359
431
|
declare function useSubscription(productSlug?: string): {
|
|
360
432
|
isActive: boolean;
|
|
361
433
|
planSlug: string | undefined;
|
|
434
|
+
refetch: () => void;
|
|
362
435
|
subscription: SubscriptionWithPlan | null;
|
|
363
436
|
isLoading: boolean;
|
|
364
437
|
error: Error | null;
|
|
@@ -552,24 +625,35 @@ interface EntitlementGuardProps {
|
|
|
552
625
|
* `UpgradePrompt`, so the whole entitlement surface reads alike.
|
|
553
626
|
*/
|
|
554
627
|
featureKey: string;
|
|
555
|
-
/**
|
|
556
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Units the gated action is about to consume - checked before it runs, not
|
|
630
|
+
* after. A QUOTA feature with 3 remaining still passes a plain `<Access>`
|
|
631
|
+
* (>0 left), but fails `requested={5}` up front, so a batch action can be
|
|
632
|
+
* blocked before the user commits to it instead of after a partial
|
|
633
|
+
* `consume()` fails mid-way. Omit for a plain yes/no gate.
|
|
634
|
+
*/
|
|
635
|
+
requested?: number;
|
|
557
636
|
/** Fallback content when entitlement is not met */
|
|
558
637
|
fallback?: ReactNode;
|
|
559
638
|
}
|
|
560
639
|
/**
|
|
561
|
-
* Renders children only when the
|
|
562
|
-
*
|
|
563
|
-
*
|
|
640
|
+
* Renders children only when the subject is entitled to a capability - and,
|
|
641
|
+
* with `requested`, entitled to that many units of it right now. Checks the
|
|
642
|
+
* entitlement itself, never a plan name - a plan can be renamed or
|
|
643
|
+
* restructured without breaking every place that gated on its slug.
|
|
564
644
|
*
|
|
565
645
|
* @example
|
|
566
646
|
* ```tsx
|
|
567
|
-
* <
|
|
647
|
+
* <Access featureKey="advanced_analytics" fallback={<UpgradePrompt featureKey="advanced_analytics" />}>
|
|
568
648
|
* <AnalyticsDashboard />
|
|
569
|
-
* </
|
|
649
|
+
* </Access>
|
|
650
|
+
*
|
|
651
|
+
* <Access featureKey="ai_credits" requested={5} fallback={<UpgradePrompt reason="Not enough credits for batch processing" />}>
|
|
652
|
+
* <BatchProcessButton count={5} />
|
|
653
|
+
* </Access>
|
|
570
654
|
* ```
|
|
571
655
|
*/
|
|
572
|
-
declare function
|
|
656
|
+
declare function Access({ children, featureKey, requested, fallback, }: EntitlementGuardProps): ReactNode;
|
|
573
657
|
interface ProtectedProps {
|
|
574
658
|
children: ReactNode;
|
|
575
659
|
/** Require authentication */
|
|
@@ -652,4 +736,4 @@ declare function useKerneError(): {
|
|
|
652
736
|
clearError: () => void;
|
|
653
737
|
};
|
|
654
738
|
|
|
655
|
-
export {
|
|
739
|
+
export { Access, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, type PlanSwitchTarget, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCancelSubscription, useChangePlan, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlanSwitch, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ import React, { ReactNode, Component, ErrorInfo } from 'react';
|
|
|
3
3
|
import { KerneConfig, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
|
|
4
4
|
export { JoinWaitlistParams, ValidateCodeResponse, ValidateTokenResponse, WaitlistEntry } from '@kerne/server';
|
|
5
5
|
import * as _kerne_types from '@kerne/types';
|
|
6
|
-
import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, PublicPlan, Entitlements, UsageRecord } from '@kerne/types';
|
|
6
|
+
import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, Subscription, PublicPlan, Entitlements, UsageRecord, PublicPlanPrice } from '@kerne/types';
|
|
7
7
|
export { ActivationContext, AuthConfig, AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
|
|
8
|
-
import { D as DeepPartial, K as KerneLocalization } from './i18n-
|
|
9
|
-
export { d as defaultLocalization } from './i18n-
|
|
8
|
+
import { D as DeepPartial, K as KerneLocalization } from './i18n-QilNgwqe.js';
|
|
9
|
+
export { d as defaultLocalization } from './i18n-QilNgwqe.js';
|
|
10
10
|
|
|
11
11
|
interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
|
|
12
12
|
storage?: Storage;
|
|
@@ -160,7 +160,7 @@ declare class KerneClient {
|
|
|
160
160
|
* caught everything and returned `false`) - a 401/500 must not be
|
|
161
161
|
* indistinguishable from a real denial, that's exactly what let the
|
|
162
162
|
* `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
|
|
163
|
-
* a fail-closed boolean regardless of the reason (e.g. `<
|
|
163
|
+
* a fail-closed boolean regardless of the reason (e.g. `<Access>`)
|
|
164
164
|
* catch around this themselves.
|
|
165
165
|
*/
|
|
166
166
|
allows(featureKey: string, requested?: number): Promise<boolean>;
|
|
@@ -177,6 +177,12 @@ declare class KerneClient {
|
|
|
177
177
|
createPortal(returnUrl?: string): Promise<string>;
|
|
178
178
|
openPortal(returnUrl?: string): Promise<void>;
|
|
179
179
|
getSubscription(productSlug?: string): Promise<SubscriptionWithPlan | null>;
|
|
180
|
+
/** `immediately: true` ends it now; otherwise cancels at period end (API default). */
|
|
181
|
+
cancelSubscription(subscriptionId: string, immediately?: boolean): Promise<Subscription>;
|
|
182
|
+
/** Swaps the plan/price, effective immediately - deferring a downgrade isn't supported yet. */
|
|
183
|
+
changeSubscriptionPlan(subscriptionId: string, planPriceId: string, options?: {
|
|
184
|
+
prorationBehavior?: 'none' | 'create_prorations';
|
|
185
|
+
}): Promise<SubscriptionWithPlan>;
|
|
180
186
|
/** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
|
|
181
187
|
getPlans(productIdOrSlug?: string): Promise<PublicPlan[]>;
|
|
182
188
|
/** Every entitlement for the current user in one call - for a "your plan" screen. */
|
|
@@ -317,6 +323,72 @@ declare function usePortal(): {
|
|
|
317
323
|
createPortal: (returnUrl?: string) => Promise<string>;
|
|
318
324
|
openPortal: (returnUrl?: string) => Promise<void>;
|
|
319
325
|
};
|
|
326
|
+
/**
|
|
327
|
+
* Cancels or changes the current scope's own subscription - JWT-scoped like every other hook
|
|
328
|
+
* here, so this can only ever act on the caller's own subscription (see `SubscriptionCard`).
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```tsx
|
|
332
|
+
* const { cancelSubscription, isCanceling } = useCancelSubscription();
|
|
333
|
+
* <button onClick={() => cancelSubscription(subscriptionId)}>Cancel</button>
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
declare function useCancelSubscription(): {
|
|
337
|
+
cancelSubscription: (subscriptionId: string, immediately?: boolean) => Promise<_kerne_types.Subscription>;
|
|
338
|
+
isCanceling: boolean;
|
|
339
|
+
};
|
|
340
|
+
/** Changes the current scope's own subscription to a different plan/price, effective immediately. */
|
|
341
|
+
declare function useChangePlan(): {
|
|
342
|
+
changePlan: (subscriptionId: string, planPriceId: string, options?: {
|
|
343
|
+
prorationBehavior?: "none" | "create_prorations";
|
|
344
|
+
}) => Promise<SubscriptionWithPlan>;
|
|
345
|
+
isChanging: boolean;
|
|
346
|
+
};
|
|
347
|
+
interface PlanSwitchTarget {
|
|
348
|
+
plan: PublicPlan;
|
|
349
|
+
price: PublicPlanPrice;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The decision every "pick a plan" surface needs, shared so it is only
|
|
353
|
+
* decided once: an existing subscriber switching plans has to swap in place
|
|
354
|
+
* (`changePlanPrice`), never start a fresh checkout - Stripe/Polar don't know
|
|
355
|
+
* about the subscription already on file, so a new checkout session creates
|
|
356
|
+
* a SECOND, independent one instead of replacing it. The old one stays live
|
|
357
|
+
* and billed at the provider while Kerne only ever tracks the new one. A
|
|
358
|
+
* first-time subscriber has nothing to swap, so that case goes straight to
|
|
359
|
+
* checkout, same as before.
|
|
360
|
+
*
|
|
361
|
+
* The switch is staged rather than applied on `selectPlan` alone - it bills
|
|
362
|
+
* immediately on confirmation (unlike checkout, which only starts billing
|
|
363
|
+
* once the provider's own page is completed), so the caller's UI gets a
|
|
364
|
+
* chance to say so before `confirmSwitch()` commits it.
|
|
365
|
+
*
|
|
366
|
+
* @param subscription the subject's current subscription, or null - pass
|
|
367
|
+
* what `useSubscription()` already returned rather than fetching it again
|
|
368
|
+
* here, so a page showing the current plan and this hook always agree.
|
|
369
|
+
* @param refetchSubscription called after a successful switch, typically
|
|
370
|
+
* that same `useSubscription()` call's own `refetch()`.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```tsx
|
|
374
|
+
* const { subscription, refetch } = useSubscription();
|
|
375
|
+
* const { pending, switching, selectPlan, confirmSwitch, dismissSwitch } =
|
|
376
|
+
* usePlanSwitch(subscription, refetch);
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
declare function usePlanSwitch(subscription: SubscriptionWithPlan | null, refetchSubscription: () => void, options?: {
|
|
380
|
+
successUrl?: string;
|
|
381
|
+
cancelUrl?: string;
|
|
382
|
+
}): {
|
|
383
|
+
pending: PlanSwitchTarget | null;
|
|
384
|
+
switching: boolean;
|
|
385
|
+
switchError: unknown;
|
|
386
|
+
checkoutPriceId: string | null;
|
|
387
|
+
checkoutError: unknown;
|
|
388
|
+
selectPlan: (plan: PublicPlan, price: PublicPlanPrice) => Promise<void>;
|
|
389
|
+
confirmSwitch: () => Promise<void>;
|
|
390
|
+
dismissSwitch: () => void;
|
|
391
|
+
};
|
|
320
392
|
/**
|
|
321
393
|
* Whether the current scope has access to one feature - the single entry
|
|
322
394
|
* point for a live, per-feature decision. Deliberately not named after what
|
|
@@ -359,6 +431,7 @@ declare function useAccess(featureKey: string, requested?: number): {
|
|
|
359
431
|
declare function useSubscription(productSlug?: string): {
|
|
360
432
|
isActive: boolean;
|
|
361
433
|
planSlug: string | undefined;
|
|
434
|
+
refetch: () => void;
|
|
362
435
|
subscription: SubscriptionWithPlan | null;
|
|
363
436
|
isLoading: boolean;
|
|
364
437
|
error: Error | null;
|
|
@@ -552,24 +625,35 @@ interface EntitlementGuardProps {
|
|
|
552
625
|
* `UpgradePrompt`, so the whole entitlement surface reads alike.
|
|
553
626
|
*/
|
|
554
627
|
featureKey: string;
|
|
555
|
-
/**
|
|
556
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Units the gated action is about to consume - checked before it runs, not
|
|
630
|
+
* after. A QUOTA feature with 3 remaining still passes a plain `<Access>`
|
|
631
|
+
* (>0 left), but fails `requested={5}` up front, so a batch action can be
|
|
632
|
+
* blocked before the user commits to it instead of after a partial
|
|
633
|
+
* `consume()` fails mid-way. Omit for a plain yes/no gate.
|
|
634
|
+
*/
|
|
635
|
+
requested?: number;
|
|
557
636
|
/** Fallback content when entitlement is not met */
|
|
558
637
|
fallback?: ReactNode;
|
|
559
638
|
}
|
|
560
639
|
/**
|
|
561
|
-
* Renders children only when the
|
|
562
|
-
*
|
|
563
|
-
*
|
|
640
|
+
* Renders children only when the subject is entitled to a capability - and,
|
|
641
|
+
* with `requested`, entitled to that many units of it right now. Checks the
|
|
642
|
+
* entitlement itself, never a plan name - a plan can be renamed or
|
|
643
|
+
* restructured without breaking every place that gated on its slug.
|
|
564
644
|
*
|
|
565
645
|
* @example
|
|
566
646
|
* ```tsx
|
|
567
|
-
* <
|
|
647
|
+
* <Access featureKey="advanced_analytics" fallback={<UpgradePrompt featureKey="advanced_analytics" />}>
|
|
568
648
|
* <AnalyticsDashboard />
|
|
569
|
-
* </
|
|
649
|
+
* </Access>
|
|
650
|
+
*
|
|
651
|
+
* <Access featureKey="ai_credits" requested={5} fallback={<UpgradePrompt reason="Not enough credits for batch processing" />}>
|
|
652
|
+
* <BatchProcessButton count={5} />
|
|
653
|
+
* </Access>
|
|
570
654
|
* ```
|
|
571
655
|
*/
|
|
572
|
-
declare function
|
|
656
|
+
declare function Access({ children, featureKey, requested, fallback, }: EntitlementGuardProps): ReactNode;
|
|
573
657
|
interface ProtectedProps {
|
|
574
658
|
children: ReactNode;
|
|
575
659
|
/** Require authentication */
|
|
@@ -652,4 +736,4 @@ declare function useKerneError(): {
|
|
|
652
736
|
clearError: () => void;
|
|
653
737
|
};
|
|
654
738
|
|
|
655
|
-
export {
|
|
739
|
+
export { Access, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, type PlanSwitchTarget, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCancelSubscription, useChangePlan, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlanSwitch, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
|
package/dist/index.js
CHANGED
|
@@ -7,17 +7,20 @@ import {
|
|
|
7
7
|
useAccess,
|
|
8
8
|
useAuth,
|
|
9
9
|
useAuthConfig,
|
|
10
|
+
useCancelSubscription,
|
|
11
|
+
useChangePlan,
|
|
10
12
|
useCheckout,
|
|
11
13
|
useClient,
|
|
12
14
|
useEntitlements,
|
|
13
15
|
useInvitation,
|
|
16
|
+
usePlanSwitch,
|
|
14
17
|
usePlans,
|
|
15
18
|
usePortal,
|
|
16
19
|
useSubscription,
|
|
17
20
|
useUsage,
|
|
18
21
|
useUser,
|
|
19
22
|
useWaitlist
|
|
20
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-IZMRK3WK.js";
|
|
21
24
|
|
|
22
25
|
// src/components.tsx
|
|
23
26
|
import React from "react";
|
|
@@ -47,22 +50,14 @@ function HasSubscription({ children, fallback = null }) {
|
|
|
47
50
|
if (hasSubscription === null) return null;
|
|
48
51
|
return hasSubscription ? children : fallback;
|
|
49
52
|
}
|
|
50
|
-
function
|
|
53
|
+
function Access({
|
|
51
54
|
children,
|
|
52
55
|
featureKey,
|
|
53
|
-
|
|
56
|
+
requested,
|
|
54
57
|
fallback = null
|
|
55
58
|
}) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
React.useEffect(() => {
|
|
59
|
-
client.allows(featureKey, minimum).then((result) => {
|
|
60
|
-
setAllowed(result);
|
|
61
|
-
}).catch(() => {
|
|
62
|
-
setAllowed(false);
|
|
63
|
-
});
|
|
64
|
-
}, [client, featureKey, minimum]);
|
|
65
|
-
if (allowed === null) return null;
|
|
59
|
+
const { allowed, isLoading } = useAccess(featureKey, requested);
|
|
60
|
+
if (isLoading) return null;
|
|
66
61
|
return allowed ? children : fallback;
|
|
67
62
|
}
|
|
68
63
|
function Protected({
|
|
@@ -77,7 +72,7 @@ function Protected({
|
|
|
77
72
|
return authFallback;
|
|
78
73
|
}
|
|
79
74
|
if (featureKey) {
|
|
80
|
-
return /* @__PURE__ */ jsx(
|
|
75
|
+
return /* @__PURE__ */ jsx(Access, { featureKey, fallback: billingFallback, children });
|
|
81
76
|
}
|
|
82
77
|
return children;
|
|
83
78
|
}
|
|
@@ -158,7 +153,7 @@ function useKerneError() {
|
|
|
158
153
|
};
|
|
159
154
|
}
|
|
160
155
|
export {
|
|
161
|
-
|
|
156
|
+
Access,
|
|
162
157
|
AuthLoading,
|
|
163
158
|
Authenticated,
|
|
164
159
|
HasSubscription,
|
|
@@ -172,11 +167,14 @@ export {
|
|
|
172
167
|
useAccess,
|
|
173
168
|
useAuth,
|
|
174
169
|
useAuthConfig,
|
|
170
|
+
useCancelSubscription,
|
|
171
|
+
useChangePlan,
|
|
175
172
|
useCheckout,
|
|
176
173
|
useClient,
|
|
177
174
|
useEntitlements,
|
|
178
175
|
useInvitation,
|
|
179
176
|
useKerneError,
|
|
177
|
+
usePlanSwitch,
|
|
180
178
|
usePlans,
|
|
181
179
|
usePortal,
|
|
182
180
|
useSubscription,
|