@sudobility/building_blocks 0.0.155 → 0.0.156

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
@@ -1,9 +1,4 @@
1
1
  import { b, a, S, u } from "./SafeSubscriptionContext-CNuEKeqJ.js";
2
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
- import React, { useState, useRef, useMemo, useCallback, useEffect } from "react";
4
- import { SubscriptionLayout, SubscriptionTile, SegmentedControl } from "@sudobility/subscription-components";
5
- import { useSubscriptionPeriods, useSubscriptionForPeriod, useSubscribable, useUserSubscription, refreshSubscription } from "@sudobility/subscription_lib";
6
- import { TopbarProvider, Topbar, TopbarLeft, TopbarNavigation, TopbarLogo, Logo, TopbarCenter, TopbarRight, TopbarActions, TopbarMobileContent, Footer, FooterCompact, FooterCompactLeft, FooterVersion, FooterCopyright, FooterCompactRight, FooterGrid, FooterLinkSection, FooterLink, FooterBottom, FooterBrand, FooterSocialLinks, LayoutProvider, AspectFitView, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MasterListItem, Section, MasterDetailLayout } from "@sudobility/components";
7
2
  import { clsx } from "clsx";
8
3
  import { twMerge } from "tailwind-merge";
9
4
  import i18n from "i18next";
@@ -11,6 +6,9 @@ import { default as default2 } from "i18next";
11
6
  import { initReactI18next } from "react-i18next";
12
7
  import Backend from "i18next-http-backend";
13
8
  import LanguageDetector from "i18next-browser-languagedetector";
9
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
10
+ import React, { useState, useRef, useMemo, useCallback, useEffect } from "react";
11
+ import { TopbarProvider, Topbar, TopbarLeft, TopbarNavigation, TopbarLogo, Logo, TopbarCenter, TopbarRight, TopbarActions, TopbarMobileContent, Footer, FooterCompact, FooterCompactLeft, FooterVersion, FooterCopyright, FooterCompactRight, FooterGrid, FooterLinkSection, FooterLink, FooterBottom, FooterBrand, FooterSocialLinks, LayoutProvider, AspectFitView, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MasterListItem, Section, MasterDetailLayout } from "@sudobility/components";
14
12
  import { ChevronDownIcon, CalendarDaysIcon, PaintBrushIcon, LanguageIcon, ChevronRightIcon, EnvelopeIcon, DocumentTextIcon, CogIcon, HomeIcon } from "@heroicons/react/24/outline";
15
13
  import { GRADIENT_CLASSES, textVariants } from "@sudobility/design";
16
14
  import { cva } from "class-variance-authority";
@@ -2023,728 +2021,6 @@ function LoginPage({
2023
2021
  }
2024
2022
  );
2025
2023
  }
2026
- function AppSubscriptionsPage({
2027
- offerId,
2028
- rateLimitsConfig,
2029
- labels,
2030
- formatters,
2031
- onPurchase,
2032
- onRestore,
2033
- onPurchaseSuccess,
2034
- onRestoreSuccess,
2035
- onError,
2036
- onWarning,
2037
- onTrack
2038
- }) {
2039
- const {
2040
- periods,
2041
- isLoading: periodsLoading,
2042
- error: periodsError
2043
- } = useSubscriptionPeriods(offerId);
2044
- const [selectedPeriod, setSelectedPeriod] = useState("monthly");
2045
- useEffect(() => {
2046
- if (periods.length > 0 && !periods.includes(selectedPeriod)) {
2047
- setSelectedPeriod(periods[0]);
2048
- }
2049
- }, [periods, selectedPeriod]);
2050
- const {
2051
- packages,
2052
- isLoading: packagesLoading,
2053
- error: packagesError
2054
- } = useSubscriptionForPeriod(offerId, selectedPeriod);
2055
- const {
2056
- subscribablePackageIds,
2057
- isLoading: subscribableLoading,
2058
- error: subscribableError
2059
- } = useSubscribable(offerId);
2060
- const {
2061
- subscription: currentSubscription,
2062
- isLoading: subscriptionLoading,
2063
- error: subscriptionError,
2064
- update: updateSubscription
2065
- } = useUserSubscription();
2066
- useEffect(() => {
2067
- updateSubscription();
2068
- }, [updateSubscription]);
2069
- const isLoading = periodsLoading || packagesLoading || subscribableLoading || subscriptionLoading;
2070
- const error = periodsError || packagesError || subscribableError || subscriptionError;
2071
- const [selectedPlan, setSelectedPlan] = useState(null);
2072
- const [isPurchasing, setIsPurchasing] = useState(false);
2073
- const [isRestoring, setIsRestoring] = useState(false);
2074
- const track = useCallback(
2075
- (label, params) => {
2076
- onTrack == null ? void 0 : onTrack({
2077
- eventType: "subscription_action",
2078
- componentName: "AppSubscriptionsPage",
2079
- label,
2080
- params
2081
- });
2082
- },
2083
- [onTrack]
2084
- );
2085
- const handlePeriodChange = useCallback(
2086
- (value) => {
2087
- const newPeriod = value;
2088
- setSelectedPeriod(newPeriod);
2089
- setSelectedPlan(null);
2090
- track("billing_period_changed", { billing_period: newPeriod });
2091
- },
2092
- [track]
2093
- );
2094
- const getPeriodLabel = useCallback(
2095
- (period) => {
2096
- switch (period) {
2097
- case "yearly":
2098
- return labels.periodYear;
2099
- case "monthly":
2100
- return labels.periodMonth;
2101
- case "weekly":
2102
- return labels.periodWeek;
2103
- default:
2104
- return period;
2105
- }
2106
- },
2107
- [labels]
2108
- );
2109
- const getYearlySavingsPercent = useCallback(
2110
- (yearlyPackage) => {
2111
- if (!yearlyPackage.product) return void 0;
2112
- const monthlyPackageId = yearlyPackage.packageId.replace(
2113
- "_yearly",
2114
- "_monthly"
2115
- );
2116
- const monthlyPkg = packages.find((p) => p.packageId === monthlyPackageId);
2117
- if (!(monthlyPkg == null ? void 0 : monthlyPkg.product)) return void 0;
2118
- const yearlyPrice = yearlyPackage.product.price;
2119
- const monthlyPrice = monthlyPkg.product.price;
2120
- if (monthlyPrice <= 0 || yearlyPrice <= 0) return void 0;
2121
- const annualizedMonthly = monthlyPrice * 12;
2122
- const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
2123
- return Math.round(savings);
2124
- },
2125
- [packages]
2126
- );
2127
- const billingPeriodOptions = useMemo(() => {
2128
- return periods.map((period) => ({
2129
- value: period,
2130
- label: period === "monthly" ? labels.billingMonthly : labels.billingYearly
2131
- }));
2132
- }, [periods, labels]);
2133
- const isCurrentPlan = useCallback(
2134
- (packageId, productId) => {
2135
- if (!(currentSubscription == null ? void 0 : currentSubscription.isActive)) return false;
2136
- return productId === currentSubscription.productId || packageId === currentSubscription.packageId;
2137
- },
2138
- [currentSubscription]
2139
- );
2140
- const isPackageEnabled = useCallback(
2141
- (packageId) => {
2142
- if (subscribableLoading || subscribablePackageIds.length === 0)
2143
- return true;
2144
- return subscribablePackageIds.includes(packageId);
2145
- },
2146
- [subscribableLoading, subscribablePackageIds]
2147
- );
2148
- const canUpgradeTo = useCallback(
2149
- (packageId, productId) => {
2150
- return isPackageEnabled(packageId) && !isCurrentPlan(packageId, productId);
2151
- },
2152
- [isPackageEnabled, isCurrentPlan]
2153
- );
2154
- useEffect(() => {
2155
- if ((currentSubscription == null ? void 0 : currentSubscription.isActive) && currentSubscription.packageId) {
2156
- setSelectedPlan(currentSubscription.packageId);
2157
- if (currentSubscription.period && periods.includes(currentSubscription.period)) {
2158
- setSelectedPeriod(currentSubscription.period);
2159
- }
2160
- }
2161
- }, [currentSubscription, periods]);
2162
- const handlePlanSelect = useCallback(
2163
- (planIdentifier) => {
2164
- setSelectedPlan(planIdentifier);
2165
- track("plan_selected", {
2166
- plan_identifier: planIdentifier ?? "free",
2167
- is_free_tier: planIdentifier === null
2168
- });
2169
- },
2170
- [track]
2171
- );
2172
- const handlePurchase = useCallback(async () => {
2173
- if (!selectedPlan) return;
2174
- setIsPurchasing(true);
2175
- track("purchase_initiated", { plan_identifier: selectedPlan });
2176
- try {
2177
- const result = await onPurchase(selectedPlan);
2178
- if (result) {
2179
- await refreshSubscription();
2180
- track("purchase_completed", { plan_identifier: selectedPlan });
2181
- onPurchaseSuccess == null ? void 0 : onPurchaseSuccess();
2182
- setSelectedPlan(null);
2183
- } else {
2184
- track("purchase_failed", {
2185
- plan_identifier: selectedPlan,
2186
- reason: "purchase_returned_false"
2187
- });
2188
- }
2189
- } catch (err) {
2190
- const errorMessage = err instanceof Error ? err.message : labels.purchaseError;
2191
- track("purchase_failed", {
2192
- plan_identifier: selectedPlan,
2193
- reason: errorMessage
2194
- });
2195
- onError == null ? void 0 : onError(labels.errorTitle, errorMessage);
2196
- } finally {
2197
- setIsPurchasing(false);
2198
- }
2199
- }, [
2200
- selectedPlan,
2201
- track,
2202
- onPurchase,
2203
- onPurchaseSuccess,
2204
- labels.errorTitle,
2205
- labels.purchaseError,
2206
- onError
2207
- ]);
2208
- const handleRestore = useCallback(async () => {
2209
- setIsRestoring(true);
2210
- track("restore_initiated");
2211
- try {
2212
- const result = await onRestore();
2213
- if (result) {
2214
- await refreshSubscription();
2215
- track("restore_completed");
2216
- onRestoreSuccess == null ? void 0 : onRestoreSuccess();
2217
- } else {
2218
- track("restore_failed", { reason: "no_purchases_found" });
2219
- onWarning == null ? void 0 : onWarning(labels.errorTitle, labels.restoreNoPurchases);
2220
- }
2221
- } catch (err) {
2222
- const errorMessage = err instanceof Error ? err.message : labels.restoreError;
2223
- track("restore_failed", { reason: errorMessage });
2224
- onError == null ? void 0 : onError(labels.errorTitle, errorMessage);
2225
- } finally {
2226
- setIsRestoring(false);
2227
- }
2228
- }, [
2229
- track,
2230
- onRestore,
2231
- onRestoreSuccess,
2232
- labels.errorTitle,
2233
- labels.restoreNoPurchases,
2234
- labels.restoreError,
2235
- onWarning,
2236
- onError
2237
- ]);
2238
- const isUpgrading = useMemo(() => {
2239
- if (!(currentSubscription == null ? void 0 : currentSubscription.isActive)) return false;
2240
- if (!selectedPlan) return false;
2241
- return selectedPlan !== currentSubscription.packageId && selectedPlan !== currentSubscription.productId;
2242
- }, [currentSubscription, selectedPlan]);
2243
- const handleUpgrade = useCallback(() => {
2244
- if (!(currentSubscription == null ? void 0 : currentSubscription.managementUrl)) {
2245
- onError == null ? void 0 : onError(
2246
- labels.errorTitle,
2247
- "Unable to open subscription management. Please try again."
2248
- );
2249
- return;
2250
- }
2251
- track("upgrade_initiated", {
2252
- current_plan: currentSubscription.packageId,
2253
- target_plan: selectedPlan
2254
- });
2255
- window.open(currentSubscription.managementUrl, "_blank");
2256
- }, [currentSubscription, selectedPlan, track, onError, labels.errorTitle]);
2257
- const formatExpirationDate = useCallback((date) => {
2258
- if (!date) return "";
2259
- return new Intl.DateTimeFormat(void 0, {
2260
- year: "numeric",
2261
- month: "long",
2262
- day: "numeric"
2263
- }).format(date);
2264
- }, []);
2265
- const formatProductName = useCallback(
2266
- (packageId, productId) => {
2267
- if (!packageId && !productId) return labels.labelPremium;
2268
- const pkg = packages.find(
2269
- (p) => {
2270
- var _a;
2271
- return p.packageId === packageId || ((_a = p.product) == null ? void 0 : _a.productId) === productId;
2272
- }
2273
- );
2274
- if (pkg == null ? void 0 : pkg.name) return pkg.name;
2275
- const identifier = packageId || productId || "";
2276
- return identifier.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
2277
- },
2278
- [packages, labels.labelPremium]
2279
- );
2280
- const formatRateLimit = useCallback(
2281
- (limit) => {
2282
- if (limit === null) return labels.unlimited;
2283
- return limit.toLocaleString();
2284
- },
2285
- [labels.unlimited]
2286
- );
2287
- const freeTierPackage = packages.find((p) => !p.product);
2288
- const paidPackages = packages.filter((p) => p.product);
2289
- return /* @__PURE__ */ jsx(
2290
- SubscriptionLayout,
2291
- {
2292
- title: labels.title,
2293
- error: error == null ? void 0 : error.message,
2294
- currentStatusLabel: labels.currentStatusLabel,
2295
- currentStatus: {
2296
- isActive: (currentSubscription == null ? void 0 : currentSubscription.isActive) ?? false,
2297
- activeContent: (currentSubscription == null ? void 0 : currentSubscription.isActive) ? {
2298
- title: labels.statusActive,
2299
- fields: [
2300
- {
2301
- label: labels.labelPlan,
2302
- value: formatProductName(
2303
- currentSubscription.packageId,
2304
- currentSubscription.productId
2305
- )
2306
- },
2307
- {
2308
- label: labels.labelExpires,
2309
- value: formatExpirationDate(
2310
- currentSubscription.expirationDate
2311
- )
2312
- },
2313
- {
2314
- label: labels.labelWillRenew,
2315
- value: currentSubscription.willRenew ? labels.yes : labels.no
2316
- },
2317
- ...rateLimitsConfig ? [
2318
- {
2319
- label: labels.labelMonthlyUsage,
2320
- value: `${rateLimitsConfig.currentUsage.monthly.toLocaleString()} / ${formatRateLimit(rateLimitsConfig.currentLimits.monthly)}`
2321
- },
2322
- {
2323
- label: labels.labelDailyUsage,
2324
- value: `${rateLimitsConfig.currentUsage.daily.toLocaleString()} / ${formatRateLimit(rateLimitsConfig.currentLimits.daily)}`
2325
- }
2326
- ] : []
2327
- ]
2328
- } : void 0,
2329
- inactiveContent: !(currentSubscription == null ? void 0 : currentSubscription.isActive) ? {
2330
- title: labels.statusInactive,
2331
- message: labels.statusInactiveMessage
2332
- } : void 0
2333
- },
2334
- aboveProducts: !isLoading && periods.length > 1 ? /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsx(
2335
- SegmentedControl,
2336
- {
2337
- options: billingPeriodOptions,
2338
- value: selectedPeriod,
2339
- onChange: handlePeriodChange
2340
- }
2341
- ) }) : null,
2342
- primaryAction: {
2343
- label: isPurchasing ? labels.buttonPurchasing : isUpgrading ? labels.buttonUpgrade : labels.buttonSubscribe,
2344
- onClick: isUpgrading ? handleUpgrade : handlePurchase,
2345
- disabled: !selectedPlan || isPurchasing || isRestoring,
2346
- loading: isPurchasing
2347
- },
2348
- secondaryAction: {
2349
- label: isRestoring ? labels.buttonRestoring : labels.buttonRestore,
2350
- onClick: handleRestore,
2351
- disabled: isPurchasing || isRestoring,
2352
- loading: isRestoring
2353
- },
2354
- 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: [
2355
- freeTierPackage && !(currentSubscription == null ? void 0 : currentSubscription.isActive) && /* @__PURE__ */ jsx(
2356
- SubscriptionTile,
2357
- {
2358
- id: "free",
2359
- title: labels.freeTierTitle,
2360
- price: labels.freeTierPrice,
2361
- periodLabel: labels.periodMonth,
2362
- features: labels.freeTierFeatures,
2363
- isSelected: selectedPlan === null,
2364
- isCurrentPlan: !(currentSubscription == null ? void 0 : currentSubscription.isActive),
2365
- onSelect: () => handlePlanSelect(null),
2366
- enabled: isPackageEnabled("free"),
2367
- topBadge: {
2368
- text: labels.currentPlanBadge,
2369
- color: "green"
2370
- },
2371
- disabled: isPurchasing || isRestoring,
2372
- hideSelectionIndicator: true
2373
- },
2374
- "free"
2375
- ),
2376
- paidPackages.map((pkg) => {
2377
- var _a, _b, _c, _d, _e, _f;
2378
- const isCurrent = isCurrentPlan(
2379
- pkg.packageId,
2380
- (_a = pkg.product) == null ? void 0 : _a.productId
2381
- );
2382
- const isEnabled = isPackageEnabled(pkg.packageId);
2383
- const canUpgrade = canUpgradeTo(
2384
- pkg.packageId,
2385
- (_b = pkg.product) == null ? void 0 : _b.productId
2386
- );
2387
- const canSelect = canUpgrade || isCurrent;
2388
- return /* @__PURE__ */ jsx(
2389
- SubscriptionTile,
2390
- {
2391
- id: pkg.packageId,
2392
- title: pkg.name,
2393
- price: ((_c = pkg.product) == null ? void 0 : _c.priceString) ?? "$0",
2394
- periodLabel: getPeriodLabel(((_d = pkg.product) == null ? void 0 : _d.period) ?? "monthly"),
2395
- features: formatters.getProductFeatures(pkg.packageId),
2396
- isSelected: selectedPlan === pkg.packageId,
2397
- isCurrentPlan: isCurrent,
2398
- onSelect: () => canSelect && handlePlanSelect(pkg.packageId),
2399
- enabled: isEnabled,
2400
- isBestValue: pkg.packageId.includes("pro"),
2401
- topBadge: isCurrent ? {
2402
- text: labels.currentPlanBadge,
2403
- color: "green"
2404
- } : void 0,
2405
- discountBadge: selectedPeriod === "yearly" ? (() => {
2406
- const savings = getYearlySavingsPercent(pkg);
2407
- return savings && savings > 0 ? {
2408
- text: formatters.formatSavePercent(savings),
2409
- isBestValue: true
2410
- } : void 0;
2411
- })() : void 0,
2412
- introPriceNote: ((_e = pkg.product) == null ? void 0 : _e.trialPeriod) ? (() => {
2413
- var _a2;
2414
- const period = (_a2 = pkg.product) == null ? void 0 : _a2.trialPeriod;
2415
- if (!period) return void 0;
2416
- const num = parseInt(
2417
- period.replace(/\D/g, "") || "1",
2418
- 10
2419
- );
2420
- if (period.includes("W"))
2421
- return formatters.formatTrialWeeks(num);
2422
- if (period.includes("M"))
2423
- return formatters.formatTrialMonths(num);
2424
- return formatters.formatTrialDays(num);
2425
- })() : ((_f = pkg.product) == null ? void 0 : _f.introPrice) ? formatters.formatIntroNote(pkg.product.introPrice) : void 0,
2426
- disabled: isPurchasing || isRestoring || !canSelect
2427
- },
2428
- pkg.packageId
2429
- );
2430
- })
2431
- ] })
2432
- }
2433
- );
2434
- }
2435
- function AppPricingPage({
2436
- isAuthenticated,
2437
- labels,
2438
- formatters,
2439
- onPlanClick,
2440
- onFreePlanClick,
2441
- onPurchase,
2442
- onPurchaseSuccess,
2443
- onPurchaseError,
2444
- faqItems,
2445
- className,
2446
- onTrack,
2447
- offerId
2448
- }) {
2449
- const [isPurchasing, setIsPurchasing] = useState(false);
2450
- const {
2451
- subscription: currentSubscription,
2452
- isLoading: subscriptionLoading,
2453
- error: subscriptionError,
2454
- update: updateSubscription
2455
- } = useUserSubscription();
2456
- useEffect(() => {
2457
- updateSubscription();
2458
- }, [updateSubscription]);
2459
- const hasActiveSubscription = (currentSubscription == null ? void 0 : currentSubscription.isActive) ?? false;
2460
- const currentProductIdentifier = currentSubscription == null ? void 0 : currentSubscription.productId;
2461
- const {
2462
- periods,
2463
- isLoading: periodsLoading,
2464
- error: periodsError
2465
- } = useSubscriptionPeriods(offerId);
2466
- const [selectedPeriod, setSelectedPeriod] = useState("monthly");
2467
- useEffect(() => {
2468
- if (periods.length > 0 && !periods.includes(selectedPeriod)) {
2469
- setSelectedPeriod(periods[0]);
2470
- }
2471
- }, [periods, selectedPeriod]);
2472
- const {
2473
- packages,
2474
- isLoading: packagesLoading,
2475
- error: packagesError
2476
- } = useSubscriptionForPeriod(offerId, selectedPeriod);
2477
- const {
2478
- subscribablePackageIds,
2479
- isLoading: subscribableLoading,
2480
- error: subscribableError
2481
- } = useSubscribable(offerId);
2482
- const isLoading = periodsLoading || packagesLoading || subscribableLoading || subscriptionLoading || isPurchasing;
2483
- const error = periodsError || packagesError || subscribableError || subscriptionError;
2484
- const track = useCallback(
2485
- (label, params) => {
2486
- onTrack == null ? void 0 : onTrack({
2487
- eventType: "subscription_action",
2488
- componentName: "AppPricingPage",
2489
- label,
2490
- params
2491
- });
2492
- },
2493
- [onTrack]
2494
- );
2495
- const handleBillingPeriodChange = useCallback(
2496
- (value) => {
2497
- const newPeriod = value;
2498
- setSelectedPeriod(newPeriod);
2499
- track("billing_period_changed", { billing_period: newPeriod });
2500
- },
2501
- [track]
2502
- );
2503
- const handleFreePlanClick = useCallback(() => {
2504
- track("free_plan_clicked", { plan: "free" });
2505
- onFreePlanClick();
2506
- }, [track, onFreePlanClick]);
2507
- const handlePlanClick = useCallback(
2508
- async (planIdentifier, actionType) => {
2509
- track("plan_clicked", {
2510
- plan_identifier: planIdentifier,
2511
- action_type: actionType
2512
- });
2513
- if (actionType === "login") {
2514
- onPlanClick(planIdentifier);
2515
- return;
2516
- }
2517
- if (onPurchase) {
2518
- setIsPurchasing(true);
2519
- try {
2520
- const result = await onPurchase(planIdentifier);
2521
- if (result) {
2522
- await refreshSubscription();
2523
- track("purchase_completed", { plan_identifier: planIdentifier });
2524
- onPurchaseSuccess == null ? void 0 : onPurchaseSuccess();
2525
- }
2526
- } catch (err) {
2527
- track("purchase_failed", {
2528
- plan_identifier: planIdentifier,
2529
- reason: err instanceof Error ? err.message : "Unknown error"
2530
- });
2531
- onPurchaseError == null ? void 0 : onPurchaseError(
2532
- err instanceof Error ? err : new Error("Purchase failed")
2533
- );
2534
- } finally {
2535
- setIsPurchasing(false);
2536
- }
2537
- } else {
2538
- onPlanClick(planIdentifier);
2539
- }
2540
- },
2541
- [track, onPlanClick, onPurchase, onPurchaseSuccess, onPurchaseError]
2542
- );
2543
- const getPeriodLabel = useCallback(
2544
- (period) => {
2545
- switch (period) {
2546
- case "yearly":
2547
- return labels.periodYear;
2548
- case "monthly":
2549
- return labels.periodMonth;
2550
- case "weekly":
2551
- return labels.periodWeek;
2552
- default:
2553
- return period;
2554
- }
2555
- },
2556
- [labels]
2557
- );
2558
- const getYearlySavingsPercent = useCallback(
2559
- (yearlyPackage) => {
2560
- if (!yearlyPackage.product) return void 0;
2561
- const monthlyPackageId = yearlyPackage.packageId.replace(
2562
- "_yearly",
2563
- "_monthly"
2564
- );
2565
- const monthlyPkg = packages.find((p) => p.packageId === monthlyPackageId);
2566
- if (!(monthlyPkg == null ? void 0 : monthlyPkg.product)) return void 0;
2567
- const yearlyPrice = yearlyPackage.product.price;
2568
- const monthlyPrice = monthlyPkg.product.price;
2569
- if (monthlyPrice <= 0 || yearlyPrice <= 0) return void 0;
2570
- const annualizedMonthly = monthlyPrice * 12;
2571
- const savings = (annualizedMonthly - yearlyPrice) / annualizedMonthly * 100;
2572
- return Math.round(savings);
2573
- },
2574
- [packages]
2575
- );
2576
- const billingPeriodOptions = useMemo(() => {
2577
- return periods.map((period) => ({
2578
- value: period,
2579
- label: period === "monthly" ? labels.billingMonthly : labels.billingYearly
2580
- }));
2581
- }, [periods, labels]);
2582
- const isCurrentPlan = useCallback(
2583
- (packageId, productId) => {
2584
- if (!isAuthenticated) return false;
2585
- if (!hasActiveSubscription) return false;
2586
- return productId === currentProductIdentifier || packageId === currentProductIdentifier;
2587
- },
2588
- [isAuthenticated, hasActiveSubscription, currentProductIdentifier]
2589
- );
2590
- const isPackageEnabled = useCallback(
2591
- (packageId) => {
2592
- if (!isAuthenticated) return true;
2593
- if (subscribableLoading || subscribablePackageIds.length === 0)
2594
- return true;
2595
- return subscribablePackageIds.includes(packageId);
2596
- },
2597
- [isAuthenticated, subscribableLoading, subscribablePackageIds]
2598
- );
2599
- const canUpgradeTo = useCallback(
2600
- (packageId, productId) => {
2601
- return isPackageEnabled(packageId) && !isCurrentPlan(packageId, productId);
2602
- },
2603
- [isPackageEnabled, isCurrentPlan]
2604
- );
2605
- const freeTierPackage = packages.find((p) => !p.product);
2606
- const paidPackages = packages.filter((p) => p.product);
2607
- return /* @__PURE__ */ jsxs("div", { className, children: [
2608
- /* @__PURE__ */ jsx(Section, { spacing: "2xl", maxWidth: "4xl", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2609
- /* @__PURE__ */ jsx("h1", { className: "text-4xl sm:text-5xl font-bold text-theme-text-primary mb-4", children: labels.title }),
2610
- /* @__PURE__ */ jsx("p", { className: "text-lg text-theme-text-secondary", children: labels.subtitle })
2611
- ] }) }),
2612
- /* @__PURE__ */ jsxs(Section, { spacing: "3xl", maxWidth: "6xl", children: [
2613
- periods.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-8", children: /* @__PURE__ */ jsx(
2614
- SegmentedControl,
2615
- {
2616
- options: billingPeriodOptions,
2617
- value: selectedPeriod,
2618
- onChange: handleBillingPeriodChange
2619
- }
2620
- ) }),
2621
- 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" }) }),
2622
- error && !isLoading && /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-red-500", children: error.message }),
2623
- !isLoading && !error && /* @__PURE__ */ jsxs(
2624
- "div",
2625
- {
2626
- style: {
2627
- display: "grid",
2628
- gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 280px), 1fr))",
2629
- gridAutoRows: "1fr",
2630
- gap: "1.5rem",
2631
- overflow: "visible"
2632
- },
2633
- children: [
2634
- freeTierPackage && /* @__PURE__ */ jsx(
2635
- SubscriptionTile,
2636
- {
2637
- id: "free",
2638
- title: labels.freeTierTitle,
2639
- price: labels.freeTierPrice,
2640
- periodLabel: labels.periodMonth,
2641
- features: labels.freeTierFeatures,
2642
- isSelected: false,
2643
- isCurrentPlan: isAuthenticated && !hasActiveSubscription,
2644
- onSelect: () => {
2645
- },
2646
- enabled: isPackageEnabled("free"),
2647
- topBadge: isAuthenticated && !hasActiveSubscription ? {
2648
- text: labels.currentPlanBadge,
2649
- color: "green"
2650
- } : void 0,
2651
- ctaButton: (
2652
- // Not logged in: show "Try it for Free"
2653
- // Logged in on free plan: no CTA (current plan)
2654
- // Logged in with subscription: no CTA (can't downgrade here)
2655
- !isAuthenticated ? {
2656
- label: labels.ctaTryFree,
2657
- onClick: handleFreePlanClick
2658
- } : void 0
2659
- ),
2660
- hideSelectionIndicator: isAuthenticated
2661
- }
2662
- ),
2663
- paidPackages.map((pkg) => {
2664
- var _a, _b, _c, _d;
2665
- const isCurrent = isCurrentPlan(
2666
- pkg.packageId,
2667
- (_a = pkg.product) == null ? void 0 : _a.productId
2668
- );
2669
- const isEnabled = isPackageEnabled(pkg.packageId);
2670
- const canUpgrade = canUpgradeTo(
2671
- pkg.packageId,
2672
- (_b = pkg.product) == null ? void 0 : _b.productId
2673
- );
2674
- let ctaButton;
2675
- if (!isAuthenticated) {
2676
- ctaButton = {
2677
- label: labels.ctaLogIn,
2678
- onClick: () => handlePlanClick(pkg.packageId, "login")
2679
- };
2680
- } else if (isCurrent) {
2681
- ctaButton = void 0;
2682
- } else if (canUpgrade) {
2683
- ctaButton = {
2684
- label: labels.ctaUpgrade,
2685
- onClick: () => handlePlanClick(pkg.packageId, "upgrade")
2686
- };
2687
- }
2688
- let topBadge;
2689
- if (isCurrent) {
2690
- topBadge = {
2691
- text: labels.currentPlanBadge,
2692
- color: "green"
2693
- };
2694
- } else if (pkg.packageId.includes("pro")) {
2695
- topBadge = {
2696
- text: labels.mostPopularBadge,
2697
- color: "yellow"
2698
- };
2699
- }
2700
- return /* @__PURE__ */ jsx(
2701
- SubscriptionTile,
2702
- {
2703
- id: pkg.packageId,
2704
- title: pkg.name,
2705
- price: ((_c = pkg.product) == null ? void 0 : _c.priceString) ?? "$0",
2706
- periodLabel: getPeriodLabel(((_d = pkg.product) == null ? void 0 : _d.period) ?? "monthly"),
2707
- features: formatters.getProductFeatures(pkg.packageId),
2708
- isSelected: false,
2709
- isCurrentPlan: isCurrent,
2710
- onSelect: () => {
2711
- },
2712
- enabled: isEnabled,
2713
- isBestValue: pkg.packageId.includes("pro"),
2714
- topBadge,
2715
- discountBadge: selectedPeriod === "yearly" ? (() => {
2716
- const savings = getYearlySavingsPercent(pkg);
2717
- return savings && savings > 0 ? {
2718
- text: formatters.formatSavePercent(savings),
2719
- isBestValue: true
2720
- } : void 0;
2721
- })() : void 0,
2722
- ctaButton,
2723
- hideSelectionIndicator: !ctaButton
2724
- },
2725
- pkg.packageId
2726
- );
2727
- })
2728
- ]
2729
- }
2730
- )
2731
- ] }),
2732
- faqItems && faqItems.length > 0 && /* @__PURE__ */ jsxs(Section, { spacing: "3xl", background: "surface", maxWidth: "3xl", children: [
2733
- /* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold text-theme-text-primary text-center mb-12", children: labels.faqTitle }),
2734
- /* @__PURE__ */ jsx("div", { className: "space-y-6", children: faqItems.map((item, index) => /* @__PURE__ */ jsxs(
2735
- "div",
2736
- {
2737
- className: "bg-theme-bg-primary p-6 rounded-xl border border-theme-border",
2738
- children: [
2739
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-theme-text-primary mb-2", children: item.question }),
2740
- /* @__PURE__ */ jsx("p", { className: "text-theme-text-secondary", children: item.answer })
2741
- ]
2742
- },
2743
- index
2744
- )) })
2745
- ] })
2746
- ] });
2747
- }
2748
2024
  const DEFAULT_SUPPORTED_LANGUAGES = ["en"];
2749
2025
  const DEFAULT_NAMESPACES = [
2750
2026
  "common",
@@ -2830,9 +2106,7 @@ export {
2830
2106
  AppFooter,
2831
2107
  AppFooterForHomePage,
2832
2108
  AppPageLayout,
2833
- AppPricingPage,
2834
2109
  AppSitemapPage,
2835
- AppSubscriptionsPage,
2836
2110
  AppTextPage,
2837
2111
  AppTopBar,
2838
2112
  AppTopBarWithFirebaseAuth,