@sudobility/building_blocks 0.0.54 → 0.0.62
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/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ChevronDownIcon, CalendarDaysIcon, PaintBrushIcon, LanguageIcon, Chevro
|
|
|
7
7
|
import { GRADIENT_CLASSES, textVariants } from "@sudobility/design";
|
|
8
8
|
import { cva } from "class-variance-authority";
|
|
9
9
|
import { SubscriptionLayout, SubscriptionTile, SegmentedControl } from "@sudobility/subscription-components";
|
|
10
|
+
import { useSubscriptionPeriods, useSubscriptionForPeriod, useSubscribable, useUserSubscription } from "@sudobility/subscription_lib";
|
|
10
11
|
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
|
|
11
12
|
function cn(...inputs) {
|
|
12
13
|
return twMerge(clsx(inputs));
|
|
@@ -1259,13 +1260,12 @@ const GlobalSettingsPage = ({
|
|
|
1259
1260
|
) });
|
|
1260
1261
|
};
|
|
1261
1262
|
function AppSubscriptionsPage({
|
|
1262
|
-
|
|
1263
|
+
offerId,
|
|
1263
1264
|
rateLimitsConfig,
|
|
1264
|
-
subscriptionUserId,
|
|
1265
1265
|
labels,
|
|
1266
1266
|
formatters,
|
|
1267
|
-
|
|
1268
|
-
|
|
1267
|
+
onPurchase,
|
|
1268
|
+
onRestore,
|
|
1269
1269
|
onPurchaseSuccess,
|
|
1270
1270
|
onRestoreSuccess,
|
|
1271
1271
|
onError,
|
|
@@ -1273,15 +1273,33 @@ function AppSubscriptionsPage({
|
|
|
1273
1273
|
onTrack
|
|
1274
1274
|
}) {
|
|
1275
1275
|
const {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1276
|
+
periods,
|
|
1277
|
+
isLoading: periodsLoading,
|
|
1278
|
+
error: periodsError
|
|
1279
|
+
} = useSubscriptionPeriods(offerId);
|
|
1280
|
+
const [selectedPeriod, setSelectedPeriod] = useState("monthly");
|
|
1281
|
+
useEffect(() => {
|
|
1282
|
+
if (periods.length > 0 && !periods.includes(selectedPeriod)) {
|
|
1283
|
+
setSelectedPeriod(periods[0]);
|
|
1284
|
+
}
|
|
1285
|
+
}, [periods, selectedPeriod]);
|
|
1286
|
+
const {
|
|
1287
|
+
packages,
|
|
1288
|
+
isLoading: packagesLoading,
|
|
1289
|
+
error: packagesError
|
|
1290
|
+
} = useSubscriptionForPeriod(offerId, selectedPeriod);
|
|
1291
|
+
const {
|
|
1292
|
+
subscribablePackageIds,
|
|
1293
|
+
isLoading: subscribableLoading,
|
|
1294
|
+
error: subscribableError
|
|
1295
|
+
} = useSubscribable(offerId);
|
|
1296
|
+
const {
|
|
1297
|
+
subscription: currentSubscription,
|
|
1298
|
+
isLoading: subscriptionLoading,
|
|
1299
|
+
error: subscriptionError
|
|
1300
|
+
} = useUserSubscription();
|
|
1301
|
+
const isLoading = periodsLoading || packagesLoading || subscribableLoading || subscriptionLoading;
|
|
1302
|
+
const error = periodsError || packagesError || subscribableError || subscriptionError;
|
|
1285
1303
|
const [selectedPlan, setSelectedPlan] = useState(null);
|
|
1286
1304
|
const [isPurchasing, setIsPurchasing] = useState(false);
|
|
1287
1305
|
const [isRestoring, setIsRestoring] = useState(false);
|
|
@@ -1296,32 +1314,99 @@ function AppSubscriptionsPage({
|
|
|
1296
1314
|
},
|
|
1297
1315
|
[onTrack]
|
|
1298
1316
|
);
|
|
1299
|
-
useEffect(() => {
|
|
1300
|
-
if (error) {
|
|
1301
|
-
onError == null ? void 0 : onError(labels.errorTitle, error);
|
|
1302
|
-
clearError();
|
|
1303
|
-
}
|
|
1304
|
-
}, [error, clearError, labels.errorTitle, onError]);
|
|
1305
|
-
const filteredProducts = products.filter((product) => {
|
|
1306
|
-
if (!product.period) return false;
|
|
1307
|
-
const isYearly = product.period.includes("Y") || product.period.includes("year");
|
|
1308
|
-
return billingPeriod === "yearly" ? isYearly : !isYearly;
|
|
1309
|
-
}).sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
|
|
1310
1317
|
const handlePeriodChange = useCallback(
|
|
1311
|
-
(
|
|
1312
|
-
|
|
1318
|
+
(value) => {
|
|
1319
|
+
const newPeriod = value;
|
|
1320
|
+
setSelectedPeriod(newPeriod);
|
|
1313
1321
|
setSelectedPlan(null);
|
|
1314
|
-
track("billing_period_changed", { billing_period:
|
|
1322
|
+
track("billing_period_changed", { billing_period: newPeriod });
|
|
1323
|
+
},
|
|
1324
|
+
[track]
|
|
1325
|
+
);
|
|
1326
|
+
const getPeriodLabel = useCallback(
|
|
1327
|
+
(period) => {
|
|
1328
|
+
switch (period) {
|
|
1329
|
+
case "yearly":
|
|
1330
|
+
return labels.periodYear;
|
|
1331
|
+
case "monthly":
|
|
1332
|
+
return labels.periodMonth;
|
|
1333
|
+
case "weekly":
|
|
1334
|
+
return labels.periodWeek;
|
|
1335
|
+
default:
|
|
1336
|
+
return period;
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
[labels]
|
|
1340
|
+
);
|
|
1341
|
+
const getYearlySavingsPercent = useCallback(
|
|
1342
|
+
(yearlyPackage) => {
|
|
1343
|
+
if (!yearlyPackage.product) return void 0;
|
|
1344
|
+
const monthlyPackageId = yearlyPackage.packageId.replace(
|
|
1345
|
+
"_yearly",
|
|
1346
|
+
"_monthly"
|
|
1347
|
+
);
|
|
1348
|
+
const monthlyPkg = packages.find((p) => p.packageId === monthlyPackageId);
|
|
1349
|
+
if (!(monthlyPkg == null ? void 0 : monthlyPkg.product)) return void 0;
|
|
1350
|
+
const yearlyPrice = yearlyPackage.product.price;
|
|
1351
|
+
const monthlyPrice = monthlyPkg.product.price;
|
|
1352
|
+
if (monthlyPrice <= 0 || yearlyPrice <= 0) return void 0;
|
|
1353
|
+
const annualizedMonthly = monthlyPrice * 12;
|
|
1354
|
+
const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
|
|
1355
|
+
return Math.round(savings);
|
|
1356
|
+
},
|
|
1357
|
+
[packages]
|
|
1358
|
+
);
|
|
1359
|
+
const billingPeriodOptions = useMemo(() => {
|
|
1360
|
+
return periods.map((period) => ({
|
|
1361
|
+
value: period,
|
|
1362
|
+
label: period === "monthly" ? labels.billingMonthly : labels.billingYearly
|
|
1363
|
+
}));
|
|
1364
|
+
}, [periods, labels]);
|
|
1365
|
+
const isCurrentPlan = useCallback(
|
|
1366
|
+
(packageId, productId) => {
|
|
1367
|
+
if (!(currentSubscription == null ? void 0 : currentSubscription.isActive)) return false;
|
|
1368
|
+
return productId === currentSubscription.productId || packageId === currentSubscription.packageId;
|
|
1369
|
+
},
|
|
1370
|
+
[currentSubscription]
|
|
1371
|
+
);
|
|
1372
|
+
const isPackageEnabled = useCallback(
|
|
1373
|
+
(packageId) => {
|
|
1374
|
+
if (subscribableLoading || subscribablePackageIds.length === 0)
|
|
1375
|
+
return true;
|
|
1376
|
+
return subscribablePackageIds.includes(packageId);
|
|
1377
|
+
},
|
|
1378
|
+
[subscribableLoading, subscribablePackageIds]
|
|
1379
|
+
);
|
|
1380
|
+
const canUpgradeTo = useCallback(
|
|
1381
|
+
(packageId, productId) => {
|
|
1382
|
+
return isPackageEnabled(packageId) && !isCurrentPlan(packageId, productId);
|
|
1383
|
+
},
|
|
1384
|
+
[isPackageEnabled, isCurrentPlan]
|
|
1385
|
+
);
|
|
1386
|
+
useEffect(() => {
|
|
1387
|
+
if ((currentSubscription == null ? void 0 : currentSubscription.isActive) && currentSubscription.packageId) {
|
|
1388
|
+
setSelectedPlan(currentSubscription.packageId);
|
|
1389
|
+
if (currentSubscription.period && periods.includes(currentSubscription.period)) {
|
|
1390
|
+
setSelectedPeriod(currentSubscription.period);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}, [currentSubscription, periods]);
|
|
1394
|
+
const handlePlanSelect = useCallback(
|
|
1395
|
+
(planIdentifier) => {
|
|
1396
|
+
setSelectedPlan(planIdentifier);
|
|
1397
|
+
track("plan_selected", {
|
|
1398
|
+
plan_identifier: planIdentifier ?? "free",
|
|
1399
|
+
is_free_tier: planIdentifier === null
|
|
1400
|
+
});
|
|
1315
1401
|
},
|
|
1316
1402
|
[track]
|
|
1317
1403
|
);
|
|
1318
1404
|
const handlePurchase = useCallback(async () => {
|
|
1319
1405
|
if (!selectedPlan) return;
|
|
1320
1406
|
setIsPurchasing(true);
|
|
1321
|
-
clearError();
|
|
1322
1407
|
track("purchase_initiated", { plan_identifier: selectedPlan });
|
|
1323
1408
|
try {
|
|
1324
|
-
const result = await
|
|
1409
|
+
const result = await onPurchase(selectedPlan);
|
|
1325
1410
|
if (result) {
|
|
1326
1411
|
track("purchase_completed", { plan_identifier: selectedPlan });
|
|
1327
1412
|
onPurchaseSuccess == null ? void 0 : onPurchaseSuccess();
|
|
@@ -1344,31 +1429,18 @@ function AppSubscriptionsPage({
|
|
|
1344
1429
|
}
|
|
1345
1430
|
}, [
|
|
1346
1431
|
selectedPlan,
|
|
1347
|
-
clearError,
|
|
1348
1432
|
track,
|
|
1349
|
-
|
|
1350
|
-
subscriptionUserId,
|
|
1433
|
+
onPurchase,
|
|
1351
1434
|
onPurchaseSuccess,
|
|
1352
1435
|
labels.errorTitle,
|
|
1353
1436
|
labels.purchaseError,
|
|
1354
1437
|
onError
|
|
1355
1438
|
]);
|
|
1356
|
-
const handlePlanSelect = useCallback(
|
|
1357
|
-
(planIdentifier) => {
|
|
1358
|
-
setSelectedPlan(planIdentifier);
|
|
1359
|
-
track("plan_selected", {
|
|
1360
|
-
plan_identifier: planIdentifier ?? "free",
|
|
1361
|
-
is_free_tier: planIdentifier === null
|
|
1362
|
-
});
|
|
1363
|
-
},
|
|
1364
|
-
[track]
|
|
1365
|
-
);
|
|
1366
1439
|
const handleRestore = useCallback(async () => {
|
|
1367
1440
|
setIsRestoring(true);
|
|
1368
|
-
clearError();
|
|
1369
1441
|
track("restore_initiated");
|
|
1370
1442
|
try {
|
|
1371
|
-
const result = await
|
|
1443
|
+
const result = await onRestore();
|
|
1372
1444
|
if (result) {
|
|
1373
1445
|
track("restore_completed");
|
|
1374
1446
|
onRestoreSuccess == null ? void 0 : onRestoreSuccess();
|
|
@@ -1384,10 +1456,8 @@ function AppSubscriptionsPage({
|
|
|
1384
1456
|
setIsRestoring(false);
|
|
1385
1457
|
}
|
|
1386
1458
|
}, [
|
|
1387
|
-
clearError,
|
|
1388
1459
|
track,
|
|
1389
|
-
|
|
1390
|
-
subscriptionUserId,
|
|
1460
|
+
onRestore,
|
|
1391
1461
|
onRestoreSuccess,
|
|
1392
1462
|
labels.errorTitle,
|
|
1393
1463
|
labels.restoreNoPurchases,
|
|
@@ -1403,32 +1473,20 @@ function AppSubscriptionsPage({
|
|
|
1403
1473
|
day: "numeric"
|
|
1404
1474
|
}).format(date);
|
|
1405
1475
|
}, []);
|
|
1406
|
-
const
|
|
1407
|
-
(
|
|
1408
|
-
if (!
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
return
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
);
|
|
1419
|
-
const getTrialLabel = useCallback(
|
|
1420
|
-
(trialPeriod) => {
|
|
1421
|
-
if (!trialPeriod) return void 0;
|
|
1422
|
-
const num = parseInt(trialPeriod.replace(/\D/g, "") || "1", 10);
|
|
1423
|
-
if (trialPeriod.includes("W")) {
|
|
1424
|
-
return formatters.formatTrialWeeks(num);
|
|
1425
|
-
}
|
|
1426
|
-
if (trialPeriod.includes("M")) {
|
|
1427
|
-
return formatters.formatTrialMonths(num);
|
|
1428
|
-
}
|
|
1429
|
-
return formatters.formatTrialDays(num);
|
|
1476
|
+
const formatProductName = useCallback(
|
|
1477
|
+
(packageId, productId) => {
|
|
1478
|
+
if (!packageId && !productId) return labels.labelPremium;
|
|
1479
|
+
const pkg = packages.find(
|
|
1480
|
+
(p) => {
|
|
1481
|
+
var _a;
|
|
1482
|
+
return p.packageId === packageId || ((_a = p.product) == null ? void 0 : _a.productId) === productId;
|
|
1483
|
+
}
|
|
1484
|
+
);
|
|
1485
|
+
if (pkg == null ? void 0 : pkg.name) return pkg.name;
|
|
1486
|
+
const identifier = packageId || productId || "";
|
|
1487
|
+
return identifier.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
1430
1488
|
},
|
|
1431
|
-
[
|
|
1489
|
+
[packages, labels.labelPremium]
|
|
1432
1490
|
);
|
|
1433
1491
|
const formatRateLimit = useCallback(
|
|
1434
1492
|
(limit) => {
|
|
@@ -1437,50 +1495,13 @@ function AppSubscriptionsPage({
|
|
|
1437
1495
|
},
|
|
1438
1496
|
[labels.unlimited]
|
|
1439
1497
|
);
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
return formatters.getProductFeatures(packageId);
|
|
1443
|
-
},
|
|
1444
|
-
[formatters]
|
|
1445
|
-
);
|
|
1446
|
-
const getFreeTierFeatures = useCallback(() => {
|
|
1447
|
-
return labels.freeTierFeatures;
|
|
1448
|
-
}, [labels.freeTierFeatures]);
|
|
1449
|
-
const getYearlySavingsPercent = useCallback(
|
|
1450
|
-
(yearlyPackageId) => {
|
|
1451
|
-
var _a;
|
|
1452
|
-
const yearlyEntitlement = entitlementMap[yearlyPackageId];
|
|
1453
|
-
if (!yearlyEntitlement) return void 0;
|
|
1454
|
-
const yearlyProduct = products.find(
|
|
1455
|
-
(p) => p.identifier === yearlyPackageId
|
|
1456
|
-
);
|
|
1457
|
-
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;
|
|
1462
|
-
const monthlyProduct = products.find(
|
|
1463
|
-
(p) => p.identifier === monthlyPackageId
|
|
1464
|
-
);
|
|
1465
|
-
if (!monthlyProduct) return void 0;
|
|
1466
|
-
const yearlyPrice = parseFloat(yearlyProduct.price);
|
|
1467
|
-
const monthlyPrice = parseFloat(monthlyProduct.price);
|
|
1468
|
-
if (monthlyPrice <= 0 || yearlyPrice <= 0) return void 0;
|
|
1469
|
-
const annualizedMonthly = monthlyPrice * 12;
|
|
1470
|
-
const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
|
|
1471
|
-
return Math.round(savings);
|
|
1472
|
-
},
|
|
1473
|
-
[products, entitlementMap]
|
|
1474
|
-
);
|
|
1475
|
-
const billingPeriodOptions = [
|
|
1476
|
-
{ value: "monthly", label: labels.billingMonthly },
|
|
1477
|
-
{ value: "yearly", label: labels.billingYearly }
|
|
1478
|
-
];
|
|
1498
|
+
const freeTierPackage = packages.find((p) => !p.product);
|
|
1499
|
+
const paidPackages = packages.filter((p) => p.product);
|
|
1479
1500
|
return /* @__PURE__ */ jsx(
|
|
1480
1501
|
SubscriptionLayout,
|
|
1481
1502
|
{
|
|
1482
1503
|
title: labels.title,
|
|
1483
|
-
error,
|
|
1504
|
+
error: error == null ? void 0 : error.message,
|
|
1484
1505
|
currentStatusLabel: labels.currentStatusLabel,
|
|
1485
1506
|
currentStatus: {
|
|
1486
1507
|
isActive: (currentSubscription == null ? void 0 : currentSubscription.isActive) ?? false,
|
|
@@ -1489,7 +1510,10 @@ function AppSubscriptionsPage({
|
|
|
1489
1510
|
fields: [
|
|
1490
1511
|
{
|
|
1491
1512
|
label: labels.labelPlan,
|
|
1492
|
-
value:
|
|
1513
|
+
value: formatProductName(
|
|
1514
|
+
currentSubscription.packageId,
|
|
1515
|
+
currentSubscription.productId
|
|
1516
|
+
)
|
|
1493
1517
|
},
|
|
1494
1518
|
{
|
|
1495
1519
|
label: labels.labelExpires,
|
|
@@ -1518,11 +1542,11 @@ function AppSubscriptionsPage({
|
|
|
1518
1542
|
message: labels.statusInactiveMessage
|
|
1519
1543
|
} : void 0
|
|
1520
1544
|
},
|
|
1521
|
-
aboveProducts: !isLoading &&
|
|
1545
|
+
aboveProducts: !isLoading && periods.length > 1 ? /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsx(
|
|
1522
1546
|
SegmentedControl,
|
|
1523
1547
|
{
|
|
1524
1548
|
options: billingPeriodOptions,
|
|
1525
|
-
value:
|
|
1549
|
+
value: selectedPeriod,
|
|
1526
1550
|
onChange: handlePeriodChange
|
|
1527
1551
|
}
|
|
1528
1552
|
) }) : null,
|
|
@@ -1538,52 +1562,81 @@ function AppSubscriptionsPage({
|
|
|
1538
1562
|
disabled: isPurchasing || isRestoring,
|
|
1539
1563
|
loading: isRestoring
|
|
1540
1564
|
},
|
|
1541
|
-
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" }) }) :
|
|
1542
|
-
/* @__PURE__ */ jsx(
|
|
1565
|
+
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" }) }) : error ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-red-500", children: error.message }) : packages.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-theme-text-secondary", children: labels.noProducts }) : paidPackages.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-theme-text-secondary", children: labels.noProductsForPeriod }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1566
|
+
freeTierPackage && !(currentSubscription == null ? void 0 : currentSubscription.isActive) && /* @__PURE__ */ jsx(
|
|
1543
1567
|
SubscriptionTile,
|
|
1544
1568
|
{
|
|
1545
1569
|
id: "free",
|
|
1546
1570
|
title: labels.freeTierTitle,
|
|
1547
1571
|
price: labels.freeTierPrice,
|
|
1548
1572
|
periodLabel: labels.periodMonth,
|
|
1549
|
-
features:
|
|
1550
|
-
isSelected:
|
|
1573
|
+
features: labels.freeTierFeatures,
|
|
1574
|
+
isSelected: selectedPlan === null,
|
|
1575
|
+
isCurrentPlan: !(currentSubscription == null ? void 0 : currentSubscription.isActive),
|
|
1551
1576
|
onSelect: () => handlePlanSelect(null),
|
|
1552
|
-
|
|
1577
|
+
enabled: isPackageEnabled("free"),
|
|
1578
|
+
topBadge: {
|
|
1553
1579
|
text: labels.currentPlanBadge,
|
|
1554
1580
|
color: "green"
|
|
1555
|
-
}
|
|
1581
|
+
},
|
|
1556
1582
|
disabled: isPurchasing || isRestoring,
|
|
1557
1583
|
hideSelectionIndicator: true
|
|
1558
1584
|
},
|
|
1559
1585
|
"free"
|
|
1560
1586
|
),
|
|
1561
|
-
|
|
1562
|
-
var _a;
|
|
1587
|
+
paidPackages.map((pkg) => {
|
|
1588
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1589
|
+
const isCurrent = isCurrentPlan(
|
|
1590
|
+
pkg.packageId,
|
|
1591
|
+
(_a = pkg.product) == null ? void 0 : _a.productId
|
|
1592
|
+
);
|
|
1593
|
+
const isEnabled = isPackageEnabled(pkg.packageId);
|
|
1594
|
+
const canUpgrade = canUpgradeTo(
|
|
1595
|
+
pkg.packageId,
|
|
1596
|
+
(_b = pkg.product) == null ? void 0 : _b.productId
|
|
1597
|
+
);
|
|
1598
|
+
const canSelect = canUpgrade || isCurrent;
|
|
1563
1599
|
return /* @__PURE__ */ jsx(
|
|
1564
1600
|
SubscriptionTile,
|
|
1565
1601
|
{
|
|
1566
|
-
id:
|
|
1567
|
-
title:
|
|
1568
|
-
price: product.priceString,
|
|
1569
|
-
periodLabel: getPeriodLabel(product.period),
|
|
1570
|
-
features: getProductFeatures(
|
|
1571
|
-
isSelected: selectedPlan ===
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1602
|
+
id: pkg.packageId,
|
|
1603
|
+
title: pkg.name,
|
|
1604
|
+
price: ((_c = pkg.product) == null ? void 0 : _c.priceString) ?? "$0",
|
|
1605
|
+
periodLabel: getPeriodLabel(((_d = pkg.product) == null ? void 0 : _d.period) ?? "monthly"),
|
|
1606
|
+
features: formatters.getProductFeatures(pkg.packageId),
|
|
1607
|
+
isSelected: selectedPlan === pkg.packageId,
|
|
1608
|
+
isCurrentPlan: isCurrent,
|
|
1609
|
+
onSelect: () => canSelect && handlePlanSelect(pkg.packageId),
|
|
1610
|
+
enabled: isEnabled,
|
|
1611
|
+
isBestValue: pkg.packageId.includes("pro"),
|
|
1612
|
+
topBadge: isCurrent ? {
|
|
1613
|
+
text: labels.currentPlanBadge,
|
|
1614
|
+
color: "green"
|
|
1615
|
+
} : void 0,
|
|
1616
|
+
discountBadge: selectedPeriod === "yearly" ? (() => {
|
|
1617
|
+
const savings = getYearlySavingsPercent(pkg);
|
|
1578
1618
|
return savings && savings > 0 ? {
|
|
1579
1619
|
text: formatters.formatSavePercent(savings),
|
|
1580
1620
|
isBestValue: true
|
|
1581
1621
|
} : void 0;
|
|
1582
1622
|
})() : void 0,
|
|
1583
|
-
introPriceNote:
|
|
1584
|
-
|
|
1623
|
+
introPriceNote: ((_e = pkg.product) == null ? void 0 : _e.trialPeriod) ? (() => {
|
|
1624
|
+
var _a2;
|
|
1625
|
+
const period = (_a2 = pkg.product) == null ? void 0 : _a2.trialPeriod;
|
|
1626
|
+
if (!period) return void 0;
|
|
1627
|
+
const num = parseInt(
|
|
1628
|
+
period.replace(/\D/g, "") || "1",
|
|
1629
|
+
10
|
|
1630
|
+
);
|
|
1631
|
+
if (period.includes("W"))
|
|
1632
|
+
return formatters.formatTrialWeeks(num);
|
|
1633
|
+
if (period.includes("M"))
|
|
1634
|
+
return formatters.formatTrialMonths(num);
|
|
1635
|
+
return formatters.formatTrialDays(num);
|
|
1636
|
+
})() : ((_f = pkg.product) == null ? void 0 : _f.introPrice) ? formatters.formatIntroNote(pkg.product.introPrice) : void 0,
|
|
1637
|
+
disabled: isPurchasing || isRestoring || !canSelect
|
|
1585
1638
|
},
|
|
1586
|
-
|
|
1639
|
+
pkg.packageId
|
|
1587
1640
|
);
|
|
1588
1641
|
})
|
|
1589
1642
|
] })
|
|
@@ -1591,21 +1644,46 @@ function AppSubscriptionsPage({
|
|
|
1591
1644
|
);
|
|
1592
1645
|
}
|
|
1593
1646
|
function AppPricingPage({
|
|
1594
|
-
products,
|
|
1595
1647
|
isAuthenticated,
|
|
1596
|
-
hasActiveSubscription,
|
|
1597
|
-
currentProductIdentifier,
|
|
1598
1648
|
labels,
|
|
1599
1649
|
formatters,
|
|
1600
|
-
entitlementMap,
|
|
1601
|
-
entitlementLevels,
|
|
1602
1650
|
onPlanClick,
|
|
1603
1651
|
onFreePlanClick,
|
|
1604
1652
|
faqItems,
|
|
1605
1653
|
className,
|
|
1606
|
-
onTrack
|
|
1654
|
+
onTrack,
|
|
1655
|
+
offerId
|
|
1607
1656
|
}) {
|
|
1608
|
-
const
|
|
1657
|
+
const {
|
|
1658
|
+
subscription: currentSubscription,
|
|
1659
|
+
isLoading: subscriptionLoading,
|
|
1660
|
+
error: subscriptionError
|
|
1661
|
+
} = useUserSubscription();
|
|
1662
|
+
const hasActiveSubscription = (currentSubscription == null ? void 0 : currentSubscription.isActive) ?? false;
|
|
1663
|
+
const currentProductIdentifier = currentSubscription == null ? void 0 : currentSubscription.productId;
|
|
1664
|
+
const {
|
|
1665
|
+
periods,
|
|
1666
|
+
isLoading: periodsLoading,
|
|
1667
|
+
error: periodsError
|
|
1668
|
+
} = useSubscriptionPeriods(offerId);
|
|
1669
|
+
const [selectedPeriod, setSelectedPeriod] = useState("monthly");
|
|
1670
|
+
useEffect(() => {
|
|
1671
|
+
if (periods.length > 0 && !periods.includes(selectedPeriod)) {
|
|
1672
|
+
setSelectedPeriod(periods[0]);
|
|
1673
|
+
}
|
|
1674
|
+
}, [periods, selectedPeriod]);
|
|
1675
|
+
const {
|
|
1676
|
+
packages,
|
|
1677
|
+
isLoading: packagesLoading,
|
|
1678
|
+
error: packagesError
|
|
1679
|
+
} = useSubscriptionForPeriod(offerId, selectedPeriod);
|
|
1680
|
+
const {
|
|
1681
|
+
subscribablePackageIds,
|
|
1682
|
+
isLoading: subscribableLoading,
|
|
1683
|
+
error: subscribableError
|
|
1684
|
+
} = useSubscribable(offerId);
|
|
1685
|
+
const isLoading = periodsLoading || packagesLoading || subscribableLoading || subscriptionLoading;
|
|
1686
|
+
const error = periodsError || packagesError || subscribableError || subscriptionError;
|
|
1609
1687
|
const track = useCallback(
|
|
1610
1688
|
(label, params) => {
|
|
1611
1689
|
onTrack == null ? void 0 : onTrack({
|
|
@@ -1620,7 +1698,7 @@ function AppPricingPage({
|
|
|
1620
1698
|
const handleBillingPeriodChange = useCallback(
|
|
1621
1699
|
(value) => {
|
|
1622
1700
|
const newPeriod = value;
|
|
1623
|
-
|
|
1701
|
+
setSelectedPeriod(newPeriod);
|
|
1624
1702
|
track("billing_period_changed", { billing_period: newPeriod });
|
|
1625
1703
|
},
|
|
1626
1704
|
[track]
|
|
@@ -1639,93 +1717,87 @@ function AppPricingPage({
|
|
|
1639
1717
|
},
|
|
1640
1718
|
[track, onPlanClick]
|
|
1641
1719
|
);
|
|
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
|
-
const filteredProducts = products.filter((product) => {
|
|
1652
|
-
if (!product.period) return false;
|
|
1653
|
-
const isYearly = product.period.includes("Y") || product.period.includes("year");
|
|
1654
|
-
return billingPeriod === "yearly" ? isYearly : !isYearly;
|
|
1655
|
-
}).sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
|
|
1656
1720
|
const getPeriodLabel = useCallback(
|
|
1657
1721
|
(period) => {
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1722
|
+
switch (period) {
|
|
1723
|
+
case "yearly":
|
|
1724
|
+
return labels.periodYear;
|
|
1725
|
+
case "monthly":
|
|
1726
|
+
return labels.periodMonth;
|
|
1727
|
+
case "weekly":
|
|
1728
|
+
return labels.periodWeek;
|
|
1729
|
+
default:
|
|
1730
|
+
return period;
|
|
1731
|
+
}
|
|
1666
1732
|
},
|
|
1667
1733
|
[labels]
|
|
1668
1734
|
);
|
|
1669
1735
|
const getYearlySavingsPercent = useCallback(
|
|
1670
|
-
(
|
|
1671
|
-
|
|
1672
|
-
const
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
(p) => p.identifier === yearlyPackageId
|
|
1736
|
+
(yearlyPackage) => {
|
|
1737
|
+
if (!yearlyPackage.product) return void 0;
|
|
1738
|
+
const monthlyPackageId = yearlyPackage.packageId.replace(
|
|
1739
|
+
"_yearly",
|
|
1740
|
+
"_monthly"
|
|
1676
1741
|
);
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
if (!monthlyPackageId) return void 0;
|
|
1682
|
-
const monthlyProduct = products.find(
|
|
1683
|
-
(p) => p.identifier === monthlyPackageId
|
|
1684
|
-
);
|
|
1685
|
-
if (!monthlyProduct) return void 0;
|
|
1686
|
-
const yearlyPrice = parseFloat(yearlyProduct.price);
|
|
1687
|
-
const monthlyPrice = parseFloat(monthlyProduct.price);
|
|
1742
|
+
const monthlyPkg = packages.find((p) => p.packageId === monthlyPackageId);
|
|
1743
|
+
if (!(monthlyPkg == null ? void 0 : monthlyPkg.product)) return void 0;
|
|
1744
|
+
const yearlyPrice = yearlyPackage.product.price;
|
|
1745
|
+
const monthlyPrice = monthlyPkg.product.price;
|
|
1688
1746
|
if (monthlyPrice <= 0 || yearlyPrice <= 0) return void 0;
|
|
1689
1747
|
const annualizedMonthly = monthlyPrice * 12;
|
|
1690
1748
|
const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
|
|
1691
1749
|
return Math.round(savings);
|
|
1692
1750
|
},
|
|
1693
|
-
[
|
|
1751
|
+
[packages]
|
|
1694
1752
|
);
|
|
1695
|
-
const billingPeriodOptions =
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1753
|
+
const billingPeriodOptions = useMemo(() => {
|
|
1754
|
+
return periods.map((period) => ({
|
|
1755
|
+
value: period,
|
|
1756
|
+
label: period === "monthly" ? labels.billingMonthly : labels.billingYearly
|
|
1757
|
+
}));
|
|
1758
|
+
}, [periods, labels]);
|
|
1699
1759
|
const isCurrentPlan = useCallback(
|
|
1700
|
-
(productId) => {
|
|
1760
|
+
(packageId, productId) => {
|
|
1701
1761
|
if (!isAuthenticated) return false;
|
|
1702
1762
|
if (!hasActiveSubscription) return false;
|
|
1703
|
-
return
|
|
1763
|
+
return productId === currentProductIdentifier || packageId === currentProductIdentifier;
|
|
1704
1764
|
},
|
|
1705
|
-
[isAuthenticated, hasActiveSubscription,
|
|
1765
|
+
[isAuthenticated, hasActiveSubscription, currentProductIdentifier]
|
|
1706
1766
|
);
|
|
1707
|
-
const
|
|
1708
|
-
(
|
|
1709
|
-
|
|
1710
|
-
|
|
1767
|
+
const isPackageEnabled = useCallback(
|
|
1768
|
+
(packageId) => {
|
|
1769
|
+
if (!isAuthenticated) return true;
|
|
1770
|
+
if (subscribableLoading || subscribablePackageIds.length === 0)
|
|
1771
|
+
return true;
|
|
1772
|
+
return subscribablePackageIds.includes(packageId);
|
|
1711
1773
|
},
|
|
1712
|
-
[
|
|
1774
|
+
[isAuthenticated, subscribableLoading, subscribablePackageIds]
|
|
1713
1775
|
);
|
|
1776
|
+
const canUpgradeTo = useCallback(
|
|
1777
|
+
(packageId, productId) => {
|
|
1778
|
+
return isPackageEnabled(packageId) && !isCurrentPlan(packageId, productId);
|
|
1779
|
+
},
|
|
1780
|
+
[isPackageEnabled, isCurrentPlan]
|
|
1781
|
+
);
|
|
1782
|
+
const freeTierPackage = packages.find((p) => !p.product);
|
|
1783
|
+
const paidPackages = packages.filter((p) => p.product);
|
|
1714
1784
|
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
1715
1785
|
/* @__PURE__ */ jsx(Section, { spacing: "2xl", maxWidth: "4xl", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
1716
1786
|
/* @__PURE__ */ jsx("h1", { className: "text-4xl sm:text-5xl font-bold text-theme-text-primary mb-4", children: labels.title }),
|
|
1717
1787
|
/* @__PURE__ */ jsx("p", { className: "text-lg text-theme-text-secondary", children: labels.subtitle })
|
|
1718
1788
|
] }) }),
|
|
1719
1789
|
/* @__PURE__ */ jsxs(Section, { spacing: "3xl", maxWidth: "6xl", children: [
|
|
1720
|
-
/* @__PURE__ */ jsx("div", { className: "flex justify-center mb-8", children: /* @__PURE__ */ jsx(
|
|
1790
|
+
periods.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-8", children: /* @__PURE__ */ jsx(
|
|
1721
1791
|
SegmentedControl,
|
|
1722
1792
|
{
|
|
1723
1793
|
options: billingPeriodOptions,
|
|
1724
|
-
value:
|
|
1794
|
+
value: selectedPeriod,
|
|
1725
1795
|
onChange: handleBillingPeriodChange
|
|
1726
1796
|
}
|
|
1727
1797
|
) }),
|
|
1728
|
-
/* @__PURE__ */
|
|
1798
|
+
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" }) }),
|
|
1799
|
+
error && !isLoading && /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-red-500", children: error.message }),
|
|
1800
|
+
!isLoading && !error && /* @__PURE__ */ jsxs(
|
|
1729
1801
|
"div",
|
|
1730
1802
|
{
|
|
1731
1803
|
style: {
|
|
@@ -1736,7 +1808,7 @@ function AppPricingPage({
|
|
|
1736
1808
|
overflow: "visible"
|
|
1737
1809
|
},
|
|
1738
1810
|
children: [
|
|
1739
|
-
/* @__PURE__ */ jsx(
|
|
1811
|
+
freeTierPackage && /* @__PURE__ */ jsx(
|
|
1740
1812
|
SubscriptionTile,
|
|
1741
1813
|
{
|
|
1742
1814
|
id: "free",
|
|
@@ -1745,8 +1817,10 @@ function AppPricingPage({
|
|
|
1745
1817
|
periodLabel: labels.periodMonth,
|
|
1746
1818
|
features: labels.freeTierFeatures,
|
|
1747
1819
|
isSelected: false,
|
|
1820
|
+
isCurrentPlan: isAuthenticated && !hasActiveSubscription,
|
|
1748
1821
|
onSelect: () => {
|
|
1749
1822
|
},
|
|
1823
|
+
enabled: isPackageEnabled("free"),
|
|
1750
1824
|
topBadge: isAuthenticated && !hasActiveSubscription ? {
|
|
1751
1825
|
text: labels.currentPlanBadge,
|
|
1752
1826
|
color: "green"
|
|
@@ -1763,22 +1837,29 @@ function AppPricingPage({
|
|
|
1763
1837
|
hideSelectionIndicator: isAuthenticated
|
|
1764
1838
|
}
|
|
1765
1839
|
),
|
|
1766
|
-
|
|
1767
|
-
var _a;
|
|
1768
|
-
const isCurrent = isCurrentPlan(
|
|
1769
|
-
|
|
1840
|
+
paidPackages.map((pkg) => {
|
|
1841
|
+
var _a, _b, _c, _d;
|
|
1842
|
+
const isCurrent = isCurrentPlan(
|
|
1843
|
+
pkg.packageId,
|
|
1844
|
+
(_a = pkg.product) == null ? void 0 : _a.productId
|
|
1845
|
+
);
|
|
1846
|
+
const isEnabled = isPackageEnabled(pkg.packageId);
|
|
1847
|
+
const canUpgrade = canUpgradeTo(
|
|
1848
|
+
pkg.packageId,
|
|
1849
|
+
(_b = pkg.product) == null ? void 0 : _b.productId
|
|
1850
|
+
);
|
|
1770
1851
|
let ctaButton;
|
|
1771
1852
|
if (!isAuthenticated) {
|
|
1772
1853
|
ctaButton = {
|
|
1773
1854
|
label: labels.ctaLogIn,
|
|
1774
|
-
onClick: () => handlePlanClick(
|
|
1855
|
+
onClick: () => handlePlanClick(pkg.packageId, "login")
|
|
1775
1856
|
};
|
|
1776
1857
|
} else if (isCurrent) {
|
|
1777
1858
|
ctaButton = void 0;
|
|
1778
1859
|
} else if (canUpgrade) {
|
|
1779
1860
|
ctaButton = {
|
|
1780
1861
|
label: labels.ctaUpgrade,
|
|
1781
|
-
onClick: () => handlePlanClick(
|
|
1862
|
+
onClick: () => handlePlanClick(pkg.packageId, "upgrade")
|
|
1782
1863
|
};
|
|
1783
1864
|
}
|
|
1784
1865
|
let topBadge;
|
|
@@ -1787,7 +1868,7 @@ function AppPricingPage({
|
|
|
1787
1868
|
text: labels.currentPlanBadge,
|
|
1788
1869
|
color: "green"
|
|
1789
1870
|
};
|
|
1790
|
-
} else if (
|
|
1871
|
+
} else if (pkg.packageId.includes("pro")) {
|
|
1791
1872
|
topBadge = {
|
|
1792
1873
|
text: labels.mostPopularBadge,
|
|
1793
1874
|
color: "yellow"
|
|
@@ -1796,20 +1877,20 @@ function AppPricingPage({
|
|
|
1796
1877
|
return /* @__PURE__ */ jsx(
|
|
1797
1878
|
SubscriptionTile,
|
|
1798
1879
|
{
|
|
1799
|
-
id:
|
|
1800
|
-
title:
|
|
1801
|
-
price: product.priceString,
|
|
1802
|
-
periodLabel: getPeriodLabel(product.period),
|
|
1803
|
-
features: formatters.getProductFeatures(
|
|
1880
|
+
id: pkg.packageId,
|
|
1881
|
+
title: pkg.name,
|
|
1882
|
+
price: ((_c = pkg.product) == null ? void 0 : _c.priceString) ?? "$0",
|
|
1883
|
+
periodLabel: getPeriodLabel(((_d = pkg.product) == null ? void 0 : _d.period) ?? "monthly"),
|
|
1884
|
+
features: formatters.getProductFeatures(pkg.packageId),
|
|
1804
1885
|
isSelected: false,
|
|
1886
|
+
isCurrentPlan: isCurrent,
|
|
1805
1887
|
onSelect: () => {
|
|
1806
1888
|
},
|
|
1807
|
-
|
|
1889
|
+
enabled: isEnabled,
|
|
1890
|
+
isBestValue: pkg.packageId.includes("pro"),
|
|
1808
1891
|
topBadge,
|
|
1809
|
-
discountBadge:
|
|
1810
|
-
const savings = getYearlySavingsPercent(
|
|
1811
|
-
product.identifier
|
|
1812
|
-
);
|
|
1892
|
+
discountBadge: selectedPeriod === "yearly" ? (() => {
|
|
1893
|
+
const savings = getYearlySavingsPercent(pkg);
|
|
1813
1894
|
return savings && savings > 0 ? {
|
|
1814
1895
|
text: formatters.formatSavePercent(savings),
|
|
1815
1896
|
isBestValue: true
|
|
@@ -1818,7 +1899,7 @@ function AppPricingPage({
|
|
|
1818
1899
|
ctaButton,
|
|
1819
1900
|
hideSelectionIndicator: !ctaButton
|
|
1820
1901
|
},
|
|
1821
|
-
|
|
1902
|
+
pkg.packageId
|
|
1822
1903
|
);
|
|
1823
1904
|
})
|
|
1824
1905
|
]
|