@kerne/react 1.0.0 → 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.
@@ -194,6 +194,11 @@ var defaultLocalization = {
194
194
  changePlan: "Change plan",
195
195
  current: "Current",
196
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",
197
202
  noPlans: "No other plans are available right now.",
198
203
  notOnInterval: "Not available on this interval",
199
204
  loadFailed: "Could not load your plan right now.",
@@ -981,6 +986,63 @@ function useChangePlan() {
981
986
  );
982
987
  return { changePlan, isChanging };
983
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
+ }
984
1046
  function useAccess(featureKey, requested) {
985
1047
  const client = useClient();
986
1048
  const [state, setState] = useState({
@@ -1210,6 +1272,7 @@ export {
1210
1272
  usePortal,
1211
1273
  useCancelSubscription,
1212
1274
  useChangePlan,
1275
+ usePlanSwitch,
1213
1276
  useAccess,
1214
1277
  useSubscription,
1215
1278
  usePlans,
@@ -194,6 +194,11 @@ var defaultLocalization = {
194
194
  changePlan: "Change plan",
195
195
  current: "Current",
196
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",
197
202
  noPlans: "No other plans are available right now.",
198
203
  notOnInterval: "Not available on this interval",
199
204
  loadFailed: "Could not load your plan right now.",
@@ -981,6 +986,63 @@ function useChangePlan() {
981
986
  );
982
987
  return { changePlan, isChanging };
983
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
+ }
984
1046
  function useAccess(featureKey, requested) {
985
1047
  const client = useClient();
986
1048
  const [state, setState] = _react.useState.call(void 0, {
@@ -1040,8 +1102,8 @@ function useSubscription(productSlug) {
1040
1102
  setState((prev) => ({ ...prev, isLoading: true }));
1041
1103
  fetchOnce({ current: true });
1042
1104
  }, [fetchOnce]);
1043
- const isActive = _optionalChain([state, 'access', _23 => _23.subscription, 'optionalAccess', _24 => _24.status]) === "ACTIVE" || _optionalChain([state, 'access', _25 => _25.subscription, 'optionalAccess', _26 => _26.status]) === "TRIALING";
1044
- const planSlug = _optionalChain([state, 'access', _27 => _27.subscription, 'optionalAccess', _28 => _28.plan, 'optionalAccess', _29 => _29.slug]);
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]);
1045
1107
  return {
1046
1108
  ...state,
1047
1109
  isActive,
@@ -1189,9 +1251,9 @@ function useUser() {
1189
1251
  update: updateProfile,
1190
1252
  // Computed properties
1191
1253
  fullName: user ? `${user.first_name || ""} ${user.last_name || ""}`.trim() : null,
1192
- initials: user ? `${_optionalChain([user, 'access', _30 => _30.first_name, 'optionalAccess', _31 => _31[0]]) || ""}${_optionalChain([user, 'access', _32 => _32.last_name, 'optionalAccess', _33 => _33[0]]) || ""}`.toUpperCase() : null,
1193
- email: _optionalChain([user, 'optionalAccess', _34 => _34.email]) || null,
1194
- emailVerified: _optionalChain([user, 'optionalAccess', _35 => _35.email_verified]) || false
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
1195
1257
  };
1196
1258
  }
1197
1259
 
@@ -1219,4 +1281,5 @@ function useUser() {
1219
1281
 
1220
1282
 
1221
1283
 
1222
- 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.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;
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;
@@ -195,6 +195,11 @@ interface KerneLocalization {
195
195
  changePlan: string;
196
196
  current: string;
197
197
  switchTo: string;
198
+ switching: string;
199
+ switchConfirmTitle: string;
200
+ switchConfirmBody: string;
201
+ switchConfirmButton: string;
202
+ switchConfirmDismiss: string;
198
203
  noPlans: string;
199
204
  notOnInterval: string;
200
205
  loadFailed: string;
@@ -195,6 +195,11 @@ interface KerneLocalization {
195
195
  changePlan: string;
196
196
  current: string;
197
197
  switchTo: string;
198
+ switching: string;
199
+ switchConfirmTitle: string;
200
+ switchConfirmBody: string;
201
+ switchConfirmButton: string;
202
+ switchConfirmDismiss: string;
198
203
  noPlans: string;
199
204
  notOnInterval: string;
200
205
  loadFailed: string;
package/dist/index.cjs CHANGED
@@ -19,25 +19,26 @@
19
19
 
20
20
 
21
21
 
22
- var _chunkIMZFZ3P5cjs = require('./chunk-IMZFZ3P5.cjs');
22
+
23
+ var _chunkTYNZ2WU7cjs = require('./chunk-TYNZ2WU7.cjs');
23
24
 
24
25
  // src/components.tsx
25
26
  var _react = require('react'); var _react2 = _interopRequireDefault(_react);
26
27
  var _jsxruntime = require('react/jsx-runtime');
27
28
  function Authenticated({ children, fallback = null }) {
28
- const { isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
29
+ const { isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
29
30
  return isAuthenticated ? children : fallback;
30
31
  }
31
32
  function Unauthenticated({ children, fallback = null }) {
32
- const { isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
33
+ const { isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
33
34
  return !isAuthenticated ? children : fallback;
34
35
  }
35
36
  function AuthLoading({ children }) {
36
- const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
37
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
37
38
  return client.isLoading ? children : null;
38
39
  }
39
40
  function HasSubscription({ children, fallback = null }) {
40
- const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
41
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
41
42
  const [hasSubscription, setHasSubscription] = _react2.default.useState(null);
42
43
  _react2.default.useEffect(() => {
43
44
  client.getSubscription().then((sub) => {
@@ -55,7 +56,7 @@ function Access({
55
56
  requested,
56
57
  fallback = null
57
58
  }) {
58
- const { allowed, isLoading } = _chunkIMZFZ3P5cjs.useAccess.call(void 0, featureKey, requested);
59
+ const { allowed, isLoading } = _chunkTYNZ2WU7cjs.useAccess.call(void 0, featureKey, requested);
59
60
  if (isLoading) return null;
60
61
  return allowed ? children : fallback;
61
62
  }
@@ -66,7 +67,7 @@ function Protected({
66
67
  authFallback = null,
67
68
  billingFallback = null
68
69
  }) {
69
- const { isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
70
+ const { isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
70
71
  if (auth && !isAuthenticated) {
71
72
  return authFallback;
72
73
  }
@@ -179,4 +180,5 @@ function useKerneError() {
179
180
 
180
181
 
181
182
 
182
- exports.Access = Access; exports.AuthLoading = AuthLoading; exports.Authenticated = Authenticated; exports.HasSubscription = HasSubscription; exports.KerneClient = _chunkIMZFZ3P5cjs.KerneClient; exports.KerneContext = _chunkIMZFZ3P5cjs.KerneContext; exports.KerneErrorBoundary = KerneErrorBoundary; exports.KerneProvider = _chunkIMZFZ3P5cjs.KerneProvider; exports.Protected = Protected; exports.Unauthenticated = Unauthenticated; exports.defaultLocalization = _chunkIMZFZ3P5cjs.defaultLocalization; exports.useAccess = _chunkIMZFZ3P5cjs.useAccess; exports.useAuth = _chunkIMZFZ3P5cjs.useAuth; exports.useAuthConfig = _chunkIMZFZ3P5cjs.useAuthConfig; exports.useCancelSubscription = _chunkIMZFZ3P5cjs.useCancelSubscription; exports.useChangePlan = _chunkIMZFZ3P5cjs.useChangePlan; exports.useCheckout = _chunkIMZFZ3P5cjs.useCheckout; exports.useClient = _chunkIMZFZ3P5cjs.useClient; exports.useEntitlements = _chunkIMZFZ3P5cjs.useEntitlements; exports.useInvitation = _chunkIMZFZ3P5cjs.useInvitation; exports.useKerneError = useKerneError; exports.usePlans = _chunkIMZFZ3P5cjs.usePlans; exports.usePortal = _chunkIMZFZ3P5cjs.usePortal; exports.useSubscription = _chunkIMZFZ3P5cjs.useSubscription; exports.useUsage = _chunkIMZFZ3P5cjs.useUsage; exports.useUser = _chunkIMZFZ3P5cjs.useUser; exports.useWaitlist = _chunkIMZFZ3P5cjs.useWaitlist;
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, Subscription, 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-Di5qlnL1.cjs';
9
- export { d as defaultLocalization } from './i18n-Di5qlnL1.cjs';
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;
@@ -344,6 +344,51 @@ declare function useChangePlan(): {
344
344
  }) => Promise<SubscriptionWithPlan>;
345
345
  isChanging: boolean;
346
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
+ };
347
392
  /**
348
393
  * Whether the current scope has access to one feature - the single entry
349
394
  * point for a live, per-feature decision. Deliberately not named after what
@@ -691,4 +736,4 @@ declare function useKerneError(): {
691
736
  clearError: () => void;
692
737
  };
693
738
 
694
- export { Access, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCancelSubscription, useChangePlan, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
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, Subscription, 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-Di5qlnL1.js';
9
- export { d as defaultLocalization } from './i18n-Di5qlnL1.js';
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;
@@ -344,6 +344,51 @@ declare function useChangePlan(): {
344
344
  }) => Promise<SubscriptionWithPlan>;
345
345
  isChanging: boolean;
346
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
+ };
347
392
  /**
348
393
  * Whether the current scope has access to one feature - the single entry
349
394
  * point for a live, per-feature decision. Deliberately not named after what
@@ -691,4 +736,4 @@ declare function useKerneError(): {
691
736
  clearError: () => void;
692
737
  };
693
738
 
694
- export { Access, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCancelSubscription, useChangePlan, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
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
@@ -13,13 +13,14 @@ import {
13
13
  useClient,
14
14
  useEntitlements,
15
15
  useInvitation,
16
+ usePlanSwitch,
16
17
  usePlans,
17
18
  usePortal,
18
19
  useSubscription,
19
20
  useUsage,
20
21
  useUser,
21
22
  useWaitlist
22
- } from "./chunk-NFJ2S5VJ.js";
23
+ } from "./chunk-IZMRK3WK.js";
23
24
 
24
25
  // src/components.tsx
25
26
  import React from "react";
@@ -173,6 +174,7 @@ export {
173
174
  useEntitlements,
174
175
  useInvitation,
175
176
  useKerneError,
177
+ usePlanSwitch,
176
178
  usePlans,
177
179
  usePortal,
178
180
  useSubscription,