@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/README.md
CHANGED
|
@@ -44,20 +44,32 @@ function LoginForm() {
|
|
|
44
44
|
|
|
45
45
|
## Gating a feature
|
|
46
46
|
|
|
47
|
-
`
|
|
47
|
+
`Access` (and the underlying `useAccess` hook) check the logged-in user automatically - no scope to pass, the SDK already knows who's authenticated.
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
|
-
import {
|
|
50
|
+
import { Access } from '@kerne/react';
|
|
51
51
|
|
|
52
52
|
function ExportButton() {
|
|
53
53
|
return (
|
|
54
|
-
<
|
|
54
|
+
<Access featureKey="export_pdf" fallback={<UpgradePrompt />}>
|
|
55
55
|
<button onClick={exportPdf}>Export as PDF</button>
|
|
56
|
-
</
|
|
56
|
+
</Access>
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
Pass `requested` to check a quantity before it's spent, not after - useful right before a batch action:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<Access
|
|
65
|
+
featureKey="ai_credits"
|
|
66
|
+
requested={5}
|
|
67
|
+
fallback={<UpgradePrompt reason="Not enough credits for batch processing" />}
|
|
68
|
+
>
|
|
69
|
+
<BatchProcessButton count={5} />
|
|
70
|
+
</Access>
|
|
71
|
+
```
|
|
72
|
+
|
|
61
73
|
For the full detail behind a check (limit/used/remaining, not just a boolean), read `details` from the same hook - `const { allowed, details } = useAccess('api_calls')`.
|
|
62
74
|
|
|
63
75
|
## Checkout & billing portal
|
|
@@ -75,8 +87,9 @@ const { openPortal } = usePortal();
|
|
|
75
87
|
- `useUser`: the current authenticated user's profile.
|
|
76
88
|
- `useAccess`: feature access - `allowed` for the go/no-go, `details` for limit/used/remaining.
|
|
77
89
|
- `useUsage`, `useEntitlements`: usage and full entitlement lists for the current user.
|
|
78
|
-
- `useSubscription`, `usePlans`: the current subscription and the tenant's public plan catalog.
|
|
90
|
+
- `useSubscription`, `usePlans`: the current subscription (with `refetch`) and the tenant's public plan catalog.
|
|
79
91
|
- `useCheckout`, `usePortal`: hosted checkout and billing-portal sessions.
|
|
92
|
+
- `useCancelSubscription`, `useChangePlan`: cancel or switch plan. Both act on the payment provider first, then on Kerne, so a cancellation really stops the next invoice.
|
|
80
93
|
|
|
81
94
|
## Documentation
|
|
82
95
|
|
|
@@ -185,9 +185,20 @@ var defaultLocalization = {
|
|
|
185
185
|
freePlan: "You are on the free plan.",
|
|
186
186
|
seePlans: "See plans",
|
|
187
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",
|
|
188
194
|
changePlan: "Change plan",
|
|
189
195
|
current: "Current",
|
|
190
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",
|
|
191
202
|
noPlans: "No other plans are available right now.",
|
|
192
203
|
notOnInterval: "Not available on this interval",
|
|
193
204
|
loadFailed: "Could not load your plan right now.",
|
|
@@ -212,11 +223,23 @@ var defaultLocalization = {
|
|
|
212
223
|
subtitle: "Counted against your plan for the current period.",
|
|
213
224
|
used: "{count} used",
|
|
214
225
|
nothingMetered: "Nothing metered on your plan - you have no usage limits to watch.",
|
|
215
|
-
overage: "{count} over your included amount -
|
|
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.",
|
|
216
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.",
|
|
217
231
|
loading: "Loading usage",
|
|
218
232
|
loadFailed: "Could not load your usage right now."
|
|
219
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
|
+
},
|
|
220
243
|
checkout: {
|
|
221
244
|
confirmingTitle: "Confirming your payment",
|
|
222
245
|
confirmingBody: "This takes a few seconds - please keep this tab open.",
|
|
@@ -714,7 +737,7 @@ var KerneClient = class {
|
|
|
714
737
|
* caught everything and returned `false`) - a 401/500 must not be
|
|
715
738
|
* indistinguishable from a real denial, that's exactly what let the
|
|
716
739
|
* `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
|
|
717
|
-
* a fail-closed boolean regardless of the reason (e.g. `<
|
|
740
|
+
* a fail-closed boolean regardless of the reason (e.g. `<Access>`)
|
|
718
741
|
* catch around this themselves.
|
|
719
742
|
*/
|
|
720
743
|
async allows(featureKey, requested) {
|
|
@@ -744,13 +767,21 @@ var KerneClient = class {
|
|
|
744
767
|
async getSubscription(productSlug) {
|
|
745
768
|
const subs = await this.kerne.billing.subscriptions.list({
|
|
746
769
|
productSlug,
|
|
747
|
-
|
|
770
|
+
subjectType: "USER"
|
|
748
771
|
});
|
|
749
772
|
const active = subs.find(
|
|
750
773
|
(s) => s.status === "ACTIVE" || s.status === "TRIALING" || s.status === "PAST_DUE"
|
|
751
774
|
);
|
|
752
775
|
return active ?? null;
|
|
753
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
|
+
}
|
|
754
785
|
/** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
|
|
755
786
|
async getPlans(productIdOrSlug) {
|
|
756
787
|
return this.kerne.billing.plans(productIdOrSlug);
|
|
@@ -923,6 +954,95 @@ function usePortal() {
|
|
|
923
954
|
openPortal: useCallback2((returnUrl) => client.openPortal(returnUrl), [client])
|
|
924
955
|
};
|
|
925
956
|
}
|
|
957
|
+
function useCancelSubscription() {
|
|
958
|
+
const client = useClient();
|
|
959
|
+
const [isCanceling, setIsCanceling] = useState(false);
|
|
960
|
+
const cancelSubscription = useCallback2(
|
|
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] = useState(false);
|
|
976
|
+
const changePlan = useCallback2(
|
|
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] = useState(null);
|
|
993
|
+
const [switching, setSwitching] = useState(false);
|
|
994
|
+
const [switchError, setSwitchError] = useState(null);
|
|
995
|
+
const [checkoutPriceId, setCheckoutPriceId] = useState(null);
|
|
996
|
+
const [checkoutError, setCheckoutError] = useState(null);
|
|
997
|
+
const successUrl = options?.successUrl;
|
|
998
|
+
const cancelUrl = options?.cancelUrl;
|
|
999
|
+
const selectPlan = useCallback2(
|
|
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 = useCallback2(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 = useCallback2(() => {
|
|
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
|
+
}
|
|
926
1046
|
function useAccess(featureKey, requested) {
|
|
927
1047
|
const client = useClient();
|
|
928
1048
|
const [state, setState] = useState({
|
|
@@ -957,27 +1077,38 @@ function useSubscription(productSlug) {
|
|
|
957
1077
|
isLoading: true,
|
|
958
1078
|
error: null
|
|
959
1079
|
});
|
|
1080
|
+
const fetchOnce = useCallback2(
|
|
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
|
+
);
|
|
960
1094
|
useEffect2(() => {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
if (mounted) {
|
|
964
|
-
setState({ subscription, isLoading: false, error: null });
|
|
965
|
-
}
|
|
966
|
-
}).catch((error) => {
|
|
967
|
-
if (mounted) {
|
|
968
|
-
setState({ subscription: null, isLoading: false, error });
|
|
969
|
-
}
|
|
970
|
-
});
|
|
1095
|
+
const mountedRef = { current: true };
|
|
1096
|
+
fetchOnce(mountedRef);
|
|
971
1097
|
return () => {
|
|
972
|
-
|
|
1098
|
+
mountedRef.current = false;
|
|
973
1099
|
};
|
|
974
|
-
}, [
|
|
1100
|
+
}, [fetchOnce]);
|
|
1101
|
+
const refetch = useCallback2(() => {
|
|
1102
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
1103
|
+
fetchOnce({ current: true });
|
|
1104
|
+
}, [fetchOnce]);
|
|
975
1105
|
const isActive = state.subscription?.status === "ACTIVE" || state.subscription?.status === "TRIALING";
|
|
976
1106
|
const planSlug = state.subscription?.plan?.slug;
|
|
977
1107
|
return {
|
|
978
1108
|
...state,
|
|
979
1109
|
isActive,
|
|
980
|
-
planSlug
|
|
1110
|
+
planSlug,
|
|
1111
|
+
refetch
|
|
981
1112
|
};
|
|
982
1113
|
}
|
|
983
1114
|
function usePlans(productIdOrSlug) {
|
|
@@ -1139,6 +1270,9 @@ export {
|
|
|
1139
1270
|
useAuth,
|
|
1140
1271
|
useCheckout,
|
|
1141
1272
|
usePortal,
|
|
1273
|
+
useCancelSubscription,
|
|
1274
|
+
useChangePlan,
|
|
1275
|
+
usePlanSwitch,
|
|
1142
1276
|
useAccess,
|
|
1143
1277
|
useSubscription,
|
|
1144
1278
|
usePlans,
|
|
@@ -185,9 +185,20 @@ var defaultLocalization = {
|
|
|
185
185
|
freePlan: "You are on the free plan.",
|
|
186
186
|
seePlans: "See plans",
|
|
187
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",
|
|
188
194
|
changePlan: "Change plan",
|
|
189
195
|
current: "Current",
|
|
190
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",
|
|
191
202
|
noPlans: "No other plans are available right now.",
|
|
192
203
|
notOnInterval: "Not available on this interval",
|
|
193
204
|
loadFailed: "Could not load your plan right now.",
|
|
@@ -212,11 +223,23 @@ var defaultLocalization = {
|
|
|
212
223
|
subtitle: "Counted against your plan for the current period.",
|
|
213
224
|
used: "{count} used",
|
|
214
225
|
nothingMetered: "Nothing metered on your plan - you have no usage limits to watch.",
|
|
215
|
-
overage: "{count} over your included amount -
|
|
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.",
|
|
216
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.",
|
|
217
231
|
loading: "Loading usage",
|
|
218
232
|
loadFailed: "Could not load your usage right now."
|
|
219
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
|
+
},
|
|
220
243
|
checkout: {
|
|
221
244
|
confirmingTitle: "Confirming your payment",
|
|
222
245
|
confirmingBody: "This takes a few seconds - please keep this tab open.",
|
|
@@ -714,7 +737,7 @@ var KerneClient = (_class = class {
|
|
|
714
737
|
* caught everything and returned `false`) - a 401/500 must not be
|
|
715
738
|
* indistinguishable from a real denial, that's exactly what let the
|
|
716
739
|
* `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
|
|
717
|
-
* a fail-closed boolean regardless of the reason (e.g. `<
|
|
740
|
+
* a fail-closed boolean regardless of the reason (e.g. `<Access>`)
|
|
718
741
|
* catch around this themselves.
|
|
719
742
|
*/
|
|
720
743
|
async allows(featureKey, requested) {
|
|
@@ -744,13 +767,21 @@ var KerneClient = (_class = class {
|
|
|
744
767
|
async getSubscription(productSlug) {
|
|
745
768
|
const subs = await this.kerne.billing.subscriptions.list({
|
|
746
769
|
productSlug,
|
|
747
|
-
|
|
770
|
+
subjectType: "USER"
|
|
748
771
|
});
|
|
749
772
|
const active = subs.find(
|
|
750
773
|
(s) => s.status === "ACTIVE" || s.status === "TRIALING" || s.status === "PAST_DUE"
|
|
751
774
|
);
|
|
752
775
|
return _nullishCoalesce(active, () => ( null));
|
|
753
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
|
+
}
|
|
754
785
|
/** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
|
|
755
786
|
async getPlans(productIdOrSlug) {
|
|
756
787
|
return this.kerne.billing.plans(productIdOrSlug);
|
|
@@ -923,6 +954,95 @@ function usePortal() {
|
|
|
923
954
|
openPortal: _react.useCallback.call(void 0, (returnUrl) => client.openPortal(returnUrl), [client])
|
|
924
955
|
};
|
|
925
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
|
+
}
|
|
926
1046
|
function useAccess(featureKey, requested) {
|
|
927
1047
|
const client = useClient();
|
|
928
1048
|
const [state, setState] = _react.useState.call(void 0, {
|
|
@@ -957,27 +1077,38 @@ function useSubscription(productSlug) {
|
|
|
957
1077
|
isLoading: true,
|
|
958
1078
|
error: null
|
|
959
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
|
+
);
|
|
960
1094
|
_react.useEffect.call(void 0, () => {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
if (mounted) {
|
|
964
|
-
setState({ subscription, isLoading: false, error: null });
|
|
965
|
-
}
|
|
966
|
-
}).catch((error) => {
|
|
967
|
-
if (mounted) {
|
|
968
|
-
setState({ subscription: null, isLoading: false, error });
|
|
969
|
-
}
|
|
970
|
-
});
|
|
1095
|
+
const mountedRef = { current: true };
|
|
1096
|
+
fetchOnce(mountedRef);
|
|
971
1097
|
return () => {
|
|
972
|
-
|
|
1098
|
+
mountedRef.current = false;
|
|
973
1099
|
};
|
|
974
|
-
}, [
|
|
975
|
-
const
|
|
976
|
-
|
|
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]);
|
|
977
1107
|
return {
|
|
978
1108
|
...state,
|
|
979
1109
|
isActive,
|
|
980
|
-
planSlug
|
|
1110
|
+
planSlug,
|
|
1111
|
+
refetch
|
|
981
1112
|
};
|
|
982
1113
|
}
|
|
983
1114
|
function usePlans(productIdOrSlug) {
|
|
@@ -1120,9 +1251,9 @@ function useUser() {
|
|
|
1120
1251
|
update: updateProfile,
|
|
1121
1252
|
// Computed properties
|
|
1122
1253
|
fullName: user ? `${user.first_name || ""} ${user.last_name || ""}`.trim() : null,
|
|
1123
|
-
initials: user ? `${_optionalChain([user, 'access',
|
|
1124
|
-
email: _optionalChain([user, 'optionalAccess',
|
|
1125
|
-
emailVerified: _optionalChain([user, 'optionalAccess',
|
|
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
|
|
1126
1257
|
};
|
|
1127
1258
|
}
|
|
1128
1259
|
|
|
@@ -1148,4 +1279,7 @@ function useUser() {
|
|
|
1148
1279
|
|
|
1149
1280
|
|
|
1150
1281
|
|
|
1151
|
-
|
|
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;
|
|
@@ -186,9 +186,20 @@ interface KerneLocalization {
|
|
|
186
186
|
freePlan: string;
|
|
187
187
|
seePlans: string;
|
|
188
188
|
cancelNotice: string;
|
|
189
|
+
cancelSubscription: string;
|
|
190
|
+
canceling: string;
|
|
191
|
+
cancelConfirmTitle: string;
|
|
192
|
+
cancelConfirmBody: string;
|
|
193
|
+
cancelConfirmButton: string;
|
|
194
|
+
cancelConfirmDismiss: string;
|
|
189
195
|
changePlan: string;
|
|
190
196
|
current: string;
|
|
191
197
|
switchTo: string;
|
|
198
|
+
switching: string;
|
|
199
|
+
switchConfirmTitle: string;
|
|
200
|
+
switchConfirmBody: string;
|
|
201
|
+
switchConfirmButton: string;
|
|
202
|
+
switchConfirmDismiss: string;
|
|
192
203
|
noPlans: string;
|
|
193
204
|
notOnInterval: string;
|
|
194
205
|
loadFailed: string;
|
|
@@ -211,8 +222,30 @@ interface KerneLocalization {
|
|
|
211
222
|
subtitle: string;
|
|
212
223
|
used: string;
|
|
213
224
|
nothingMetered: string;
|
|
225
|
+
/** Overage ALLOW: past the included amount, tolerated and free. */
|
|
214
226
|
overage: string;
|
|
227
|
+
/** Overage BILL: past the included amount, charged on top of the plan. */
|
|
228
|
+
overageBilled: string;
|
|
229
|
+
/** Overage BLOCK only - ALLOW and BILL never "reach" a limit, they keep going. */
|
|
215
230
|
limitReached: string;
|
|
231
|
+
/** Overage BILL whose billing cap was hit: charging stopped, access didn't - the opposite of limitReached, not a variant of overageBilled. */
|
|
232
|
+
capReached: string;
|
|
233
|
+
/** Shown under a BILL entitlement once something has actually been billed this period. */
|
|
234
|
+
billedAmount: string;
|
|
235
|
+
loading: string;
|
|
236
|
+
loadFailed: string;
|
|
237
|
+
};
|
|
238
|
+
/** `<PricingTable>` - the marketing surface. Distinct from `billing.*`, the settings-page plan switcher. */
|
|
239
|
+
pricing: {
|
|
240
|
+
getStarted: string;
|
|
241
|
+
/** "{amount} / {interval}" - the caption under a price shown at a different cadence than it's billed. */
|
|
242
|
+
billedAt: string;
|
|
243
|
+
/** "Save {percent}%" - the label-switch hint. */
|
|
244
|
+
save: string;
|
|
245
|
+
/** Prefixes a QUOTA entitlement with no ceiling, e.g. "Unlimited projects". */
|
|
246
|
+
unlimited: string;
|
|
247
|
+
/** "+{count} more" - shown when `maxFeatures` truncates a plan's entitlement list. */
|
|
248
|
+
moreFeatures: string;
|
|
216
249
|
loading: string;
|
|
217
250
|
loadFailed: string;
|
|
218
251
|
};
|
|
@@ -186,9 +186,20 @@ interface KerneLocalization {
|
|
|
186
186
|
freePlan: string;
|
|
187
187
|
seePlans: string;
|
|
188
188
|
cancelNotice: string;
|
|
189
|
+
cancelSubscription: string;
|
|
190
|
+
canceling: string;
|
|
191
|
+
cancelConfirmTitle: string;
|
|
192
|
+
cancelConfirmBody: string;
|
|
193
|
+
cancelConfirmButton: string;
|
|
194
|
+
cancelConfirmDismiss: string;
|
|
189
195
|
changePlan: string;
|
|
190
196
|
current: string;
|
|
191
197
|
switchTo: string;
|
|
198
|
+
switching: string;
|
|
199
|
+
switchConfirmTitle: string;
|
|
200
|
+
switchConfirmBody: string;
|
|
201
|
+
switchConfirmButton: string;
|
|
202
|
+
switchConfirmDismiss: string;
|
|
192
203
|
noPlans: string;
|
|
193
204
|
notOnInterval: string;
|
|
194
205
|
loadFailed: string;
|
|
@@ -211,8 +222,30 @@ interface KerneLocalization {
|
|
|
211
222
|
subtitle: string;
|
|
212
223
|
used: string;
|
|
213
224
|
nothingMetered: string;
|
|
225
|
+
/** Overage ALLOW: past the included amount, tolerated and free. */
|
|
214
226
|
overage: string;
|
|
227
|
+
/** Overage BILL: past the included amount, charged on top of the plan. */
|
|
228
|
+
overageBilled: string;
|
|
229
|
+
/** Overage BLOCK only - ALLOW and BILL never "reach" a limit, they keep going. */
|
|
215
230
|
limitReached: string;
|
|
231
|
+
/** Overage BILL whose billing cap was hit: charging stopped, access didn't - the opposite of limitReached, not a variant of overageBilled. */
|
|
232
|
+
capReached: string;
|
|
233
|
+
/** Shown under a BILL entitlement once something has actually been billed this period. */
|
|
234
|
+
billedAmount: string;
|
|
235
|
+
loading: string;
|
|
236
|
+
loadFailed: string;
|
|
237
|
+
};
|
|
238
|
+
/** `<PricingTable>` - the marketing surface. Distinct from `billing.*`, the settings-page plan switcher. */
|
|
239
|
+
pricing: {
|
|
240
|
+
getStarted: string;
|
|
241
|
+
/** "{amount} / {interval}" - the caption under a price shown at a different cadence than it's billed. */
|
|
242
|
+
billedAt: string;
|
|
243
|
+
/** "Save {percent}%" - the label-switch hint. */
|
|
244
|
+
save: string;
|
|
245
|
+
/** Prefixes a QUOTA entitlement with no ceiling, e.g. "Unlimited projects". */
|
|
246
|
+
unlimited: string;
|
|
247
|
+
/** "+{count} more" - shown when `maxFeatures` truncates a plan's entitlement list. */
|
|
248
|
+
moreFeatures: string;
|
|
216
249
|
loading: string;
|
|
217
250
|
loadFailed: string;
|
|
218
251
|
};
|