@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,
|
|
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
|
|
123
|
+
* Uses useSubscribable hook to determine which packages are enabled.
|
|
134
124
|
*/
|
|
135
|
-
export declare function AppSubscriptionsPage({ subscription, rateLimitsConfig, subscriptionUserId, labels, formatters,
|
|
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,
|
|
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 = (
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
1734
|
+
isSelected: selectedPlan === null,
|
|
1735
|
+
isCurrentPlan: !(currentSubscription == null ? void 0 : currentSubscription.isActive),
|
|
1551
1736
|
onSelect: () => handlePlanSelect(null),
|
|
1552
|
-
|
|
1737
|
+
enabled: subscribablePackageIds.includes("free"),
|
|
1738
|
+
topBadge: {
|
|
1553
1739
|
text: labels.currentPlanBadge,
|
|
1554
1740
|
color: "green"
|
|
1555
|
-
}
|
|
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
|
-
|
|
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 = (
|
|
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
|
|
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
|
|
1884
|
+
return productId === currentProductIdentifier;
|
|
1704
1885
|
},
|
|
1705
|
-
[isAuthenticated, hasActiveSubscription,
|
|
1886
|
+
[isAuthenticated, hasActiveSubscription, currentProductIdentifier]
|
|
1706
1887
|
);
|
|
1707
|
-
const
|
|
1888
|
+
const isPackageEnabled = useCallback(
|
|
1708
1889
|
(productId) => {
|
|
1709
|
-
|
|
1710
|
-
return
|
|
1890
|
+
if (!isAuthenticated) return true;
|
|
1891
|
+
return subscribablePackageIds.includes(productId);
|
|
1711
1892
|
},
|
|
1712
|
-
[
|
|
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
|
|
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")) ? (() => {
|