@sudobility/building_blocks 0.0.53 → 0.0.60

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.
@@ -45,14 +45,6 @@ export interface PricingPageFormatters {
45
45
  /** Get features for a product by its identifier */
46
46
  getProductFeatures: (productId: string) => string[];
47
47
  }
48
- /** Package ID to entitlement mapping */
49
- export interface EntitlementMap {
50
- [packageId: string]: string;
51
- }
52
- /** Entitlement to level mapping for comparing plan tiers */
53
- export interface EntitlementLevels {
54
- [entitlement: string]: number;
55
- }
56
48
  export interface AppPricingPageProps {
57
49
  /** Available subscription products */
58
50
  products: PricingProduct[];
@@ -68,10 +60,6 @@ export interface AppPricingPageProps {
68
60
  labels: PricingPageLabels;
69
61
  /** Formatter functions */
70
62
  formatters: PricingPageFormatters;
71
- /** Package ID to entitlement mapping for calculating savings */
72
- entitlementMap: EntitlementMap;
73
- /** Entitlement to level mapping for comparing tiers (higher = better) */
74
- entitlementLevels: EntitlementLevels;
75
63
  /** Called when user clicks on a plan */
76
64
  onPlanClick: (planIdentifier: string) => void;
77
65
  /** Called when user clicks on free plan */
@@ -82,6 +70,11 @@ export interface AppPricingPageProps {
82
70
  className?: string;
83
71
  /** Optional analytics tracking callback */
84
72
  onTrack?: (params: AnalyticsTrackingParams) => void;
73
+ /**
74
+ * Offer ID for subscription_lib hooks (e.g., "api", "default").
75
+ * Uses useSubscribable hook to determine which packages are enabled.
76
+ */
77
+ offerId: string;
85
78
  }
86
79
  /**
87
80
  * Public pricing page for displaying subscription options.
@@ -89,4 +82,4 @@ export interface AppPricingPageProps {
89
82
  * - Authenticated on free: Free tile shows "Current Plan" badge (no CTA), paid tiles show "Upgrade"
90
83
  * - Authenticated with subscription: Current plan shows badge (no CTA), higher tiers show "Upgrade"
91
84
  */
92
- export declare function AppPricingPage({ products, isAuthenticated, hasActiveSubscription, currentProductIdentifier, labels, formatters, entitlementMap, entitlementLevels, onPlanClick, onFreePlanClick, faqItems, className, onTrack, }: AppPricingPageProps): import("react/jsx-runtime").JSX.Element;
85
+ export declare function AppPricingPage({ products, isAuthenticated, hasActiveSubscription, currentProductIdentifier, labels, formatters, onPlanClick, onFreePlanClick, faqItems, className, onTrack, offerId, }: AppPricingPageProps): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,6 @@
1
1
  /**
2
2
  * @fileoverview App Subscriptions Page
3
3
  * @description Page for managing app subscriptions and viewing rate limits.
4
- *
5
- * This component uses Section internally for proper page layout.
6
- * Do NOT wrap this component in a Section when consuming it.
7
4
  */
8
5
  import type { RateLimitsConfigData } from '@sudobility/types';
9
6
  import type { AnalyticsTrackingParams } from '../../types';
@@ -94,14 +91,6 @@ export interface SubscriptionPageFormatters {
94
91
  /** Get features for a product by its identifier (required - same as pricing page) */
95
92
  getProductFeatures: (productId: string) => string[];
96
93
  }
97
- /** Package ID to entitlement mapping (same as PricingPage) */
98
- export interface EntitlementMap {
99
- [packageId: string]: string;
100
- }
101
- /** Entitlement to level mapping for comparing plan tiers (same as PricingPage) */
102
- export interface EntitlementLevels {
103
- [entitlement: string]: number;
104
- }
105
94
  export interface AppSubscriptionsPageProps {
106
95
  /** Subscription context value */
107
96
  subscription: SubscriptionContextValue;
@@ -113,10 +102,6 @@ export interface AppSubscriptionsPageProps {
113
102
  labels: SubscriptionPageLabels;
114
103
  /** Formatter functions for dynamic strings */
115
104
  formatters: SubscriptionPageFormatters;
116
- /** Package ID to entitlement mapping (same as PricingPage) */
117
- entitlementMap: EntitlementMap;
118
- /** Entitlement to level mapping for comparing tiers (same as PricingPage) */
119
- entitlementLevels: EntitlementLevels;
120
105
  /** Called when purchase succeeds */
121
106
  onPurchaseSuccess?: () => void;
122
107
  /** Called when restore succeeds */
@@ -127,9 +112,14 @@ export interface AppSubscriptionsPageProps {
127
112
  onWarning?: (title: string, message: string) => void;
128
113
  /** Optional analytics tracking callback */
129
114
  onTrack?: (params: AnalyticsTrackingParams) => void;
115
+ /**
116
+ * Offer ID for subscription_lib hooks (e.g., "api", "default").
117
+ * Uses useSubscribable hook to determine which packages are enabled.
118
+ */
119
+ offerId: string;
130
120
  }
131
121
  /**
132
122
  * Page for managing app subscriptions.
133
- * Uses the same entitlement mapping and features display as AppPricingPage.
123
+ * Uses useSubscribable hook to determine which packages are enabled.
134
124
  */
135
- export declare function AppSubscriptionsPage({ subscription, rateLimitsConfig, subscriptionUserId, labels, formatters, entitlementMap, entitlementLevels: _entitlementLevels, onPurchaseSuccess, onRestoreSuccess, onError, onWarning, onTrack, }: AppSubscriptionsPageProps): import("react/jsx-runtime").JSX.Element;
125
+ export declare function AppSubscriptionsPage({ subscription, rateLimitsConfig, subscriptionUserId, labels, formatters, onPurchaseSuccess, onRestoreSuccess, onError, onWarning, onTrack, offerId, }: AppSubscriptionsPageProps): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
1
  export { AppSubscriptionsPage } from './AppSubscriptionsPage';
2
2
  export type { AppSubscriptionsPageProps, SubscriptionProduct, CurrentSubscription, SubscriptionContextValue, SubscriptionPageLabels, SubscriptionPageFormatters, } from './AppSubscriptionsPage';
3
3
  export { AppPricingPage } from './AppPricingPage';
4
- export type { AppPricingPageProps, PricingProduct, FAQItem, PricingPageLabels, PricingPageFormatters, EntitlementMap, EntitlementLevels, } from './AppPricingPage';
4
+ export type { AppPricingPageProps, PricingProduct, FAQItem, PricingPageLabels, PricingPageFormatters, } from './AppPricingPage';
package/dist/index.js CHANGED
@@ -1258,20 +1258,169 @@ const GlobalSettingsPage = ({
1258
1258
  }
1259
1259
  ) });
1260
1260
  };
1261
+ function useSubscriptions(offerId) {
1262
+ const [offer, setOffer] = useState(null);
1263
+ const [isLoading, setIsLoading] = useState(true);
1264
+ const [error, setError] = useState(null);
1265
+ const loadData = useCallback(async () => {
1266
+ {
1267
+ setError(new Error("Subscription not initialized"));
1268
+ setIsLoading(false);
1269
+ return;
1270
+ }
1271
+ }, [offerId]);
1272
+ useEffect(() => {
1273
+ loadData();
1274
+ }, [loadData]);
1275
+ const refetch = useCallback(async () => {
1276
+ return;
1277
+ }, [offerId]);
1278
+ return { offer, isLoading, error, refetch };
1279
+ }
1280
+ function useUserSubscription() {
1281
+ const [subscription, setSubscription] = useState(null);
1282
+ const [isLoading, setIsLoading] = useState(true);
1283
+ const [error, setError] = useState(null);
1284
+ const loadData = useCallback(async () => {
1285
+ {
1286
+ setError(new Error("Subscription not initialized"));
1287
+ setIsLoading(false);
1288
+ return;
1289
+ }
1290
+ }, []);
1291
+ useEffect(() => {
1292
+ loadData();
1293
+ }, [loadData]);
1294
+ const refetch = useCallback(async () => {
1295
+ return;
1296
+ }, []);
1297
+ return { subscription, isLoading, error, refetch };
1298
+ }
1299
+ function calculatePackageLevels(packages) {
1300
+ var _a;
1301
+ const levels = /* @__PURE__ */ new Map();
1302
+ const byPeriod = /* @__PURE__ */ new Map();
1303
+ for (const pkg of packages) {
1304
+ if (!pkg.product) {
1305
+ levels.set(pkg.packageId, 0);
1306
+ continue;
1307
+ }
1308
+ const period = pkg.product.period;
1309
+ const existing = byPeriod.get(period) ?? [];
1310
+ existing.push(pkg);
1311
+ byPeriod.set(period, existing);
1312
+ }
1313
+ for (const [_period, periodPackages] of byPeriod) {
1314
+ const sorted = [...periodPackages].sort((a, b) => {
1315
+ var _a2, _b;
1316
+ const priceA = ((_a2 = a.product) == null ? void 0 : _a2.price) ?? 0;
1317
+ const priceB = ((_b = b.product) == null ? void 0 : _b.price) ?? 0;
1318
+ return priceA - priceB;
1319
+ });
1320
+ let currentLevel = 0;
1321
+ let lastPrice = null;
1322
+ for (const pkg of sorted) {
1323
+ const price = ((_a = pkg.product) == null ? void 0 : _a.price) ?? 0;
1324
+ if (lastPrice === null || price > lastPrice) {
1325
+ currentLevel++;
1326
+ lastPrice = price;
1327
+ }
1328
+ levels.set(pkg.packageId, currentLevel);
1329
+ }
1330
+ }
1331
+ return levels;
1332
+ }
1333
+ function findUpgradeablePackages(current, packages) {
1334
+ let currentPackageId;
1335
+ let currentProductId;
1336
+ if (typeof current === "string") {
1337
+ currentPackageId = current;
1338
+ } else if (current) {
1339
+ currentPackageId = current.packageId;
1340
+ currentProductId = current.productId;
1341
+ }
1342
+ if (!currentPackageId && !currentProductId) {
1343
+ return packages.map((p) => p.packageId);
1344
+ }
1345
+ let currentPkg = currentPackageId ? packages.find((p) => p.packageId === currentPackageId) : void 0;
1346
+ if (!currentPkg && currentProductId) {
1347
+ currentPkg = packages.find((p) => {
1348
+ var _a;
1349
+ return ((_a = p.product) == null ? void 0 : _a.productId) === currentProductId;
1350
+ });
1351
+ }
1352
+ if (!currentPkg) {
1353
+ return packages.map((p) => p.packageId);
1354
+ }
1355
+ const matchedPackageId = currentPkg.packageId;
1356
+ if (!currentPkg.product) {
1357
+ return packages.filter((p) => p.product !== void 0).map((p) => p.packageId);
1358
+ }
1359
+ const levels = calculatePackageLevels(packages);
1360
+ const currentLevel = levels.get(matchedPackageId) ?? 0;
1361
+ const currentPeriod = currentPkg.product.period;
1362
+ const upgrades = [];
1363
+ for (const pkg of packages) {
1364
+ if (!pkg.product)
1365
+ continue;
1366
+ if (pkg.packageId === matchedPackageId) {
1367
+ continue;
1368
+ }
1369
+ const pkgLevel = levels.get(pkg.packageId) ?? 0;
1370
+ const pkgPeriod = pkg.product.period;
1371
+ const periodRanks = {
1372
+ weekly: 1,
1373
+ monthly: 2,
1374
+ quarterly: 3,
1375
+ yearly: 4,
1376
+ lifetime: 5
1377
+ };
1378
+ const isPeriodOk = periodRanks[pkgPeriod] >= periodRanks[currentPeriod];
1379
+ const isLevelOk = pkgLevel >= currentLevel;
1380
+ if (isPeriodOk && isLevelOk) {
1381
+ upgrades.push(pkg.packageId);
1382
+ }
1383
+ }
1384
+ return upgrades;
1385
+ }
1386
+ function useSubscribable(offerId) {
1387
+ const { offer, isLoading: loadingOffer, error: offerError } = useSubscriptions(offerId);
1388
+ const { subscription, isLoading: loadingSub, error: subError } = useUserSubscription();
1389
+ const isLoading = loadingOffer || loadingSub;
1390
+ const error = offerError || subError;
1391
+ const subscribablePackageIds = useMemo(() => {
1392
+ if (!offer) {
1393
+ return [];
1394
+ }
1395
+ let allPackages = [...offer.packages];
1396
+ if (!subscription || !subscription.isActive) {
1397
+ return allPackages.map((p) => p.packageId);
1398
+ }
1399
+ const current = {
1400
+ packageId: subscription.packageId,
1401
+ productId: subscription.productId
1402
+ };
1403
+ if (!current.packageId && !current.productId) {
1404
+ return allPackages.map((p) => p.packageId);
1405
+ }
1406
+ return findUpgradeablePackages(current, allPackages);
1407
+ }, [offer, subscription]);
1408
+ return { subscribablePackageIds, isLoading, error };
1409
+ }
1261
1410
  function AppSubscriptionsPage({
1262
1411
  subscription,
1263
1412
  rateLimitsConfig,
1264
1413
  subscriptionUserId,
1265
1414
  labels,
1266
1415
  formatters,
1267
- entitlementMap,
1268
- entitlementLevels: _entitlementLevels,
1269
1416
  onPurchaseSuccess,
1270
1417
  onRestoreSuccess,
1271
1418
  onError,
1272
1419
  onWarning,
1273
- onTrack
1420
+ onTrack,
1421
+ offerId
1274
1422
  }) {
1423
+ const { subscribablePackageIds } = useSubscribable(offerId);
1275
1424
  const {
1276
1425
  products,
1277
1426
  currentSubscription,
@@ -1285,6 +1434,36 @@ function AppSubscriptionsPage({
1285
1434
  const [selectedPlan, setSelectedPlan] = useState(null);
1286
1435
  const [isPurchasing, setIsPurchasing] = useState(false);
1287
1436
  const [isRestoring, setIsRestoring] = useState(false);
1437
+ const isCurrentPlanProduct = useCallback(
1438
+ (productId) => {
1439
+ return !!((currentSubscription == null ? void 0 : currentSubscription.isActive) && currentSubscription.productIdentifier === productId);
1440
+ },
1441
+ [currentSubscription]
1442
+ );
1443
+ const isPackageEnabled = useCallback(
1444
+ (productId) => {
1445
+ return subscribablePackageIds.includes(productId);
1446
+ },
1447
+ [subscribablePackageIds]
1448
+ );
1449
+ const canUpgradeTo = useCallback(
1450
+ (productId) => {
1451
+ return isPackageEnabled(productId) && !isCurrentPlanProduct(productId);
1452
+ },
1453
+ [isPackageEnabled, isCurrentPlanProduct]
1454
+ );
1455
+ useEffect(() => {
1456
+ if ((currentSubscription == null ? void 0 : currentSubscription.isActive) && currentSubscription.productIdentifier) {
1457
+ setSelectedPlan(currentSubscription.productIdentifier);
1458
+ const currentProduct = products.find(
1459
+ (p) => p.identifier === currentSubscription.productIdentifier
1460
+ );
1461
+ if (currentProduct == null ? void 0 : currentProduct.period) {
1462
+ const isYearly = currentProduct.period.includes("Y") || currentProduct.period.includes("year");
1463
+ setBillingPeriod(isYearly ? "yearly" : "monthly");
1464
+ }
1465
+ }
1466
+ }, [currentSubscription, products]);
1288
1467
  const track = useCallback(
1289
1468
  (label, params) => {
1290
1469
  onTrack == null ? void 0 : onTrack({
@@ -1403,6 +1582,15 @@ function AppSubscriptionsPage({
1403
1582
  day: "numeric"
1404
1583
  }).format(date);
1405
1584
  }, []);
1585
+ const formatProductIdentifier = useCallback(
1586
+ (identifier) => {
1587
+ if (!identifier) return labels.labelPremium;
1588
+ const product = products.find((p) => p.identifier === identifier);
1589
+ if (product == null ? void 0 : product.title) return product.title;
1590
+ return identifier.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1591
+ },
1592
+ [products, labels.labelPremium]
1593
+ );
1406
1594
  const getPeriodLabel = useCallback(
1407
1595
  (period) => {
1408
1596
  if (!period) return "";
@@ -1448,17 +1636,11 @@ function AppSubscriptionsPage({
1448
1636
  }, [labels.freeTierFeatures]);
1449
1637
  const getYearlySavingsPercent = useCallback(
1450
1638
  (yearlyPackageId) => {
1451
- var _a;
1452
- const yearlyEntitlement = entitlementMap[yearlyPackageId];
1453
- if (!yearlyEntitlement) return void 0;
1454
1639
  const yearlyProduct = products.find(
1455
1640
  (p) => p.identifier === yearlyPackageId
1456
1641
  );
1457
1642
  if (!yearlyProduct) return void 0;
1458
- const monthlyPackageId = (_a = Object.entries(entitlementMap).find(
1459
- ([pkgId, ent]) => ent === yearlyEntitlement && pkgId.includes("monthly")
1460
- )) == null ? void 0 : _a[0];
1461
- if (!monthlyPackageId) return void 0;
1643
+ const monthlyPackageId = yearlyPackageId.replace("_yearly", "_monthly");
1462
1644
  const monthlyProduct = products.find(
1463
1645
  (p) => p.identifier === monthlyPackageId
1464
1646
  );
@@ -1470,13 +1652,13 @@ function AppSubscriptionsPage({
1470
1652
  const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
1471
1653
  return Math.round(savings);
1472
1654
  },
1473
- [products, entitlementMap]
1655
+ [products]
1474
1656
  );
1475
1657
  const billingPeriodOptions = [
1476
1658
  { value: "monthly", label: labels.billingMonthly },
1477
1659
  { value: "yearly", label: labels.billingYearly }
1478
1660
  ];
1479
- return /* @__PURE__ */ jsx(Section, { spacing: "lg", maxWidth: "4xl", children: /* @__PURE__ */ jsx(
1661
+ return /* @__PURE__ */ jsx(
1480
1662
  SubscriptionLayout,
1481
1663
  {
1482
1664
  title: labels.title,
@@ -1489,7 +1671,9 @@ function AppSubscriptionsPage({
1489
1671
  fields: [
1490
1672
  {
1491
1673
  label: labels.labelPlan,
1492
- value: currentSubscription.productIdentifier || labels.labelPremium
1674
+ value: formatProductIdentifier(
1675
+ currentSubscription.productIdentifier
1676
+ )
1493
1677
  },
1494
1678
  {
1495
1679
  label: labels.labelExpires,
@@ -1539,7 +1723,7 @@ function AppSubscriptionsPage({
1539
1723
  loading: isRestoring
1540
1724
  },
1541
1725
  children: isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" }) }) : products.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-theme-text-secondary", children: labels.noProducts }) : filteredProducts.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-theme-text-secondary", children: labels.noProductsForPeriod }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1542
- /* @__PURE__ */ jsx(
1726
+ !(currentSubscription == null ? void 0 : currentSubscription.isActive) && /* @__PURE__ */ jsx(
1543
1727
  SubscriptionTile,
1544
1728
  {
1545
1729
  id: "free",
@@ -1547,12 +1731,14 @@ function AppSubscriptionsPage({
1547
1731
  price: labels.freeTierPrice,
1548
1732
  periodLabel: labels.periodMonth,
1549
1733
  features: getFreeTierFeatures(),
1550
- isSelected: !(currentSubscription == null ? void 0 : currentSubscription.isActive) && selectedPlan === null,
1734
+ isSelected: selectedPlan === null,
1735
+ isCurrentPlan: !(currentSubscription == null ? void 0 : currentSubscription.isActive),
1551
1736
  onSelect: () => handlePlanSelect(null),
1552
- topBadge: !(currentSubscription == null ? void 0 : currentSubscription.isActive) ? {
1737
+ enabled: subscribablePackageIds.includes("free"),
1738
+ topBadge: {
1553
1739
  text: labels.currentPlanBadge,
1554
1740
  color: "green"
1555
- } : void 0,
1741
+ },
1556
1742
  disabled: isPurchasing || isRestoring,
1557
1743
  hideSelectionIndicator: true
1558
1744
  },
@@ -1560,6 +1746,10 @@ function AppSubscriptionsPage({
1560
1746
  ),
1561
1747
  filteredProducts.map((product) => {
1562
1748
  var _a;
1749
+ const isCurrent = isCurrentPlanProduct(product.identifier);
1750
+ const isEnabled = isPackageEnabled(product.identifier);
1751
+ const canUpgrade = canUpgradeTo(product.identifier);
1752
+ const canSelect = canUpgrade || isCurrent;
1563
1753
  return /* @__PURE__ */ jsx(
1564
1754
  SubscriptionTile,
1565
1755
  {
@@ -1569,8 +1759,14 @@ function AppSubscriptionsPage({
1569
1759
  periodLabel: getPeriodLabel(product.period),
1570
1760
  features: getProductFeatures(product.identifier),
1571
1761
  isSelected: selectedPlan === product.identifier,
1572
- onSelect: () => handlePlanSelect(product.identifier),
1762
+ isCurrentPlan: isCurrent,
1763
+ onSelect: () => canSelect && handlePlanSelect(product.identifier),
1764
+ enabled: isEnabled,
1573
1765
  isBestValue: product.identifier.includes("pro"),
1766
+ topBadge: isCurrent ? {
1767
+ text: labels.currentPlanBadge,
1768
+ color: "green"
1769
+ } : void 0,
1574
1770
  discountBadge: ((_a = product.period) == null ? void 0 : _a.includes("Y")) ? (() => {
1575
1771
  const savings = getYearlySavingsPercent(
1576
1772
  product.identifier
@@ -1581,14 +1777,14 @@ function AppSubscriptionsPage({
1581
1777
  } : void 0;
1582
1778
  })() : void 0,
1583
1779
  introPriceNote: product.freeTrialPeriod ? getTrialLabel(product.freeTrialPeriod) : product.introPrice ? formatters.formatIntroNote(product.introPrice) : void 0,
1584
- disabled: isPurchasing || isRestoring
1780
+ disabled: isPurchasing || isRestoring || !canSelect
1585
1781
  },
1586
1782
  product.identifier
1587
1783
  );
1588
1784
  })
1589
1785
  ] })
1590
1786
  }
1591
- ) });
1787
+ );
1592
1788
  }
1593
1789
  function AppPricingPage({
1594
1790
  products,
@@ -1597,15 +1793,15 @@ function AppPricingPage({
1597
1793
  currentProductIdentifier,
1598
1794
  labels,
1599
1795
  formatters,
1600
- entitlementMap,
1601
- entitlementLevels,
1602
1796
  onPlanClick,
1603
1797
  onFreePlanClick,
1604
1798
  faqItems,
1605
1799
  className,
1606
- onTrack
1800
+ onTrack,
1801
+ offerId
1607
1802
  }) {
1608
1803
  const [billingPeriod, setBillingPeriod] = useState("monthly");
1804
+ const { subscribablePackageIds } = useSubscribable(offerId);
1609
1805
  const track = useCallback(
1610
1806
  (label, params) => {
1611
1807
  onTrack == null ? void 0 : onTrack({
@@ -1639,15 +1835,6 @@ function AppPricingPage({
1639
1835
  },
1640
1836
  [track, onPlanClick]
1641
1837
  );
1642
- const getProductLevel = useCallback(
1643
- (productId) => {
1644
- const entitlement = entitlementMap[productId];
1645
- if (!entitlement) return 0;
1646
- return entitlementLevels[entitlement] ?? 0;
1647
- },
1648
- [entitlementMap, entitlementLevels]
1649
- );
1650
- const currentLevel = currentProductIdentifier ? getProductLevel(currentProductIdentifier) : 0;
1651
1838
  const filteredProducts = products.filter((product) => {
1652
1839
  if (!product.period) return false;
1653
1840
  const isYearly = product.period.includes("Y") || product.period.includes("year");
@@ -1668,17 +1855,11 @@ function AppPricingPage({
1668
1855
  );
1669
1856
  const getYearlySavingsPercent = useCallback(
1670
1857
  (yearlyPackageId) => {
1671
- var _a;
1672
- const yearlyEntitlement = entitlementMap[yearlyPackageId];
1673
- if (!yearlyEntitlement) return void 0;
1674
1858
  const yearlyProduct = products.find(
1675
1859
  (p) => p.identifier === yearlyPackageId
1676
1860
  );
1677
1861
  if (!yearlyProduct) return void 0;
1678
- const monthlyPackageId = (_a = Object.entries(entitlementMap).find(
1679
- ([pkgId, ent]) => ent === yearlyEntitlement && pkgId.includes("monthly")
1680
- )) == null ? void 0 : _a[0];
1681
- if (!monthlyPackageId) return void 0;
1862
+ const monthlyPackageId = yearlyPackageId.replace("_yearly", "_monthly");
1682
1863
  const monthlyProduct = products.find(
1683
1864
  (p) => p.identifier === monthlyPackageId
1684
1865
  );
@@ -1690,7 +1871,7 @@ function AppPricingPage({
1690
1871
  const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
1691
1872
  return Math.round(savings);
1692
1873
  },
1693
- [products, entitlementMap]
1874
+ [products]
1694
1875
  );
1695
1876
  const billingPeriodOptions = [
1696
1877
  { value: "monthly", label: labels.billingMonthly },
@@ -1700,16 +1881,22 @@ function AppPricingPage({
1700
1881
  (productId) => {
1701
1882
  if (!isAuthenticated) return false;
1702
1883
  if (!hasActiveSubscription) return false;
1703
- return getProductLevel(productId) === currentLevel && currentLevel > 0;
1884
+ return productId === currentProductIdentifier;
1704
1885
  },
1705
- [isAuthenticated, hasActiveSubscription, getProductLevel, currentLevel]
1886
+ [isAuthenticated, hasActiveSubscription, currentProductIdentifier]
1706
1887
  );
1707
- const isUpgrade = useCallback(
1888
+ const isPackageEnabled = useCallback(
1708
1889
  (productId) => {
1709
- const productLevel = getProductLevel(productId);
1710
- return productLevel > currentLevel;
1890
+ if (!isAuthenticated) return true;
1891
+ return subscribablePackageIds.includes(productId);
1711
1892
  },
1712
- [getProductLevel, currentLevel]
1893
+ [isAuthenticated, subscribablePackageIds]
1894
+ );
1895
+ const canUpgradeTo = useCallback(
1896
+ (productId) => {
1897
+ return isPackageEnabled(productId) && !isCurrentPlan(productId);
1898
+ },
1899
+ [isPackageEnabled, isCurrentPlan]
1713
1900
  );
1714
1901
  return /* @__PURE__ */ jsxs("div", { className, children: [
1715
1902
  /* @__PURE__ */ jsx(Section, { spacing: "2xl", maxWidth: "4xl", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
@@ -1745,8 +1932,13 @@ function AppPricingPage({
1745
1932
  periodLabel: labels.periodMonth,
1746
1933
  features: labels.freeTierFeatures,
1747
1934
  isSelected: false,
1935
+ isCurrentPlan: isAuthenticated && !hasActiveSubscription,
1748
1936
  onSelect: () => {
1749
1937
  },
1938
+ enabled: (
1939
+ // Free tier enabled: not authenticated, or in subscribable list
1940
+ !isAuthenticated || subscribablePackageIds.includes("free")
1941
+ ),
1750
1942
  topBadge: isAuthenticated && !hasActiveSubscription ? {
1751
1943
  text: labels.currentPlanBadge,
1752
1944
  color: "green"
@@ -1766,7 +1958,8 @@ function AppPricingPage({
1766
1958
  filteredProducts.map((product) => {
1767
1959
  var _a;
1768
1960
  const isCurrent = isCurrentPlan(product.identifier);
1769
- const canUpgrade = isUpgrade(product.identifier);
1961
+ const isEnabled = isPackageEnabled(product.identifier);
1962
+ const canUpgrade = canUpgradeTo(product.identifier);
1770
1963
  let ctaButton;
1771
1964
  if (!isAuthenticated) {
1772
1965
  ctaButton = {
@@ -1802,8 +1995,10 @@ function AppPricingPage({
1802
1995
  periodLabel: getPeriodLabel(product.period),
1803
1996
  features: formatters.getProductFeatures(product.identifier),
1804
1997
  isSelected: false,
1998
+ isCurrentPlan: isCurrent,
1805
1999
  onSelect: () => {
1806
2000
  },
2001
+ enabled: isEnabled,
1807
2002
  isBestValue: product.identifier.includes("pro"),
1808
2003
  topBadge,
1809
2004
  discountBadge: ((_a = product.period) == null ? void 0 : _a.includes("Y")) ? (() => {