feat: lock traffic topup when under 80% of traffic used

This commit is contained in:
3252a8
2026-05-12 21:27:37 +03:00
parent af66103111
commit 6afbb72a34
4 changed files with 55 additions and 19 deletions
+27 -7
View File
@@ -35,6 +35,9 @@
import { normalizedEmail, telegramName } from "./lib/webapp/formatters.js"; import { normalizedEmail, telegramName } from "./lib/webapp/formatters.js";
import { activeTariffName, buildTariffCatalog } from "./lib/webapp/tariffs.js"; import { activeTariffName, buildTariffCatalog } from "./lib/webapp/tariffs.js";
import { premiumTrafficPercent, trafficPercent } from "./lib/webapp/traffic.js"; import { premiumTrafficPercent, trafficPercent } from "./lib/webapp/traffic.js";
/** Used-traffic percent from which top-up modals and CTAs unlock in the web app home screen */
const TRAFFIC_TOPUP_UNLOCK_PERCENT = 80;
import { buildGravatarUrl } from "./lib/webapp/gravatar.js"; import { buildGravatarUrl } from "./lib/webapp/gravatar.js";
import { createBillingActions } from "./lib/webapp/billingActions.js"; import { createBillingActions } from "./lib/webapp/billingActions.js";
import { invalidateWebappTariffOptionCaches } from "./lib/webapp/billingOptionCache.js"; import { invalidateWebappTariffOptionCaches } from "./lib/webapp/billingOptionCache.js";
@@ -215,10 +218,26 @@
(subscription?.can_topup_premium_traffic ?? subscription?.can_topup_traffic) && (subscription?.can_topup_premium_traffic ?? subscription?.can_topup_traffic) &&
Number(subscription?.premium_limit_bytes || 0) > 0, Number(subscription?.premium_limit_bytes || 0) > 0,
); );
$: canOpenTopupModal = Boolean(canOpenRegularTopupModal || canOpenPremiumTopupModal); $: activeTariffCatalogEntry =
$: canShowTopupButton = Boolean( tariffCatalog.find((entry) => entry.key === String(subscription?.tariff_key || "").trim()) || null;
canOpenTopupModal && $: subscriptionIsTrafficTariff = Boolean(
(trafficPercent(subscription) >= 85 || premiumTrafficPercent(subscription) >= 85), String(subscription?.billing_model || activeTariffCatalogEntry?.billing_model || "")
.toLowerCase() === "traffic",
);
$: regularTrafficTopupUnlocked = Boolean(
canOpenRegularTopupModal && trafficPercent(subscription) >= TRAFFIC_TOPUP_UNLOCK_PERCENT,
);
$: premiumTrafficTopupUnlocked = Boolean(
canOpenPremiumTopupModal && premiumTrafficPercent(subscription) >= TRAFFIC_TOPUP_UNLOCK_PERCENT,
);
/** Progress-bar card opens top-up immediately on traffic-only tariffs; period tariffs still need 80% usage */
$: regularTrafficTopupBarClickable = Boolean(
canOpenRegularTopupModal &&
(subscriptionIsTrafficTariff || trafficPercent(subscription) >= TRAFFIC_TOPUP_UNLOCK_PERCENT),
);
$: premiumTrafficTopupBarClickable = Boolean(
canOpenPremiumTopupModal &&
(subscriptionIsTrafficTariff || premiumTrafficPercent(subscription) >= TRAFFIC_TOPUP_UNLOCK_PERCENT),
); );
$: user = data?.user || {}; $: user = data?.user || {};
$: isAdmin = Boolean(user?.is_admin); $: isAdmin = Boolean(user?.is_admin);
@@ -877,12 +896,13 @@
{brandEmoji} {brandEmoji}
{brandTitle} {brandTitle}
{canChangeTariff} {canChangeTariff}
{canOpenPremiumTopupModal}
{canOpenRegularTopupModal}
{canShowTopupButton}
{currentTariffName} {currentTariffName}
{hasActiveTariffSubscription} {hasActiveTariffSubscription}
{hasMultipleTariffs} {hasMultipleTariffs}
{premiumTrafficTopupBarClickable}
{premiumTrafficTopupUnlocked}
{regularTrafficTopupBarClickable}
{regularTrafficTopupUnlocked}
{subscription} {subscription}
{termUnitLabel} {termUnitLabel}
{trafficMode} {trafficMode}
@@ -23,9 +23,10 @@
export let brandEmoji = ""; export let brandEmoji = "";
export let brandTitle = ""; export let brandTitle = "";
export let canChangeTariff = false; export let canChangeTariff = false;
export let canOpenPremiumTopupModal = false; export let premiumTrafficTopupBarClickable = false;
export let canOpenRegularTopupModal = false; export let premiumTrafficTopupUnlocked = false;
export let canShowTopupButton = false; export let regularTrafficTopupBarClickable = false;
export let regularTrafficTopupUnlocked = false;
export let currentTariffName = ""; export let currentTariffName = "";
export let hasActiveTariffSubscription = false; export let hasActiveTariffSubscription = false;
export let hasMultipleTariffs = false; export let hasMultipleTariffs = false;
@@ -86,9 +87,9 @@
</Card> </Card>
{#if subscription.active} {#if subscription.active}
<Card class={canOpenRegularTopupModal ? "traffic-card-clickable" : ""}> <Card class={regularTrafficTopupBarClickable ? "traffic-card-clickable" : ""}>
{#if canOpenRegularTopupModal} {#if regularTrafficTopupBarClickable}
<button class="card-click-target" type="button" onclick={openRegularTopupModal} aria-label={t("wa_topup_traffic")}></button> <button class="card-click-target" type="button" onclick={openRegularTopupModal} aria-label={t("wa_add_traffic")}></button>
{/if} {/if}
<div class="traffic-top"> <div class="traffic-top">
<span>{t("wa_home_traffic_used")}</span> <span>{t("wa_home_traffic_used")}</span>
@@ -128,9 +129,14 @@
</div> </div>
</Card> </Card>
{:else if Number(subscription?.premium_limit_bytes || 0) > 0} {:else if Number(subscription?.premium_limit_bytes || 0) > 0}
<Card class={`${canOpenPremiumTopupModal ? "traffic-card-clickable " : ""}premium-traffic-card${subscription?.premium_is_limited ? " premium-traffic-card-limited" : ""}`}> <Card class={`${premiumTrafficTopupBarClickable ? "traffic-card-clickable " : ""}premium-traffic-card${subscription?.premium_is_limited ? " premium-traffic-card-limited" : ""}`}>
{#if canOpenPremiumTopupModal} {#if premiumTrafficTopupBarClickable}
<button class="card-click-target" type="button" onclick={openPremiumTopupModal} aria-label={premiumTitle(subscription)}></button> <button
class="card-click-target"
type="button"
onclick={openPremiumTopupModal}
aria-label={t("wa_add_traffic_premium", { target: premiumTitle(subscription) })}
></button>
{/if} {/if}
<div class="traffic-top"> <div class="traffic-top">
<span>{premiumTitle(subscription)}</span> <span>{premiumTitle(subscription)}</span>
@@ -199,10 +205,16 @@
{t("wa_change_tariff")} {t("wa_change_tariff")}
</Button> </Button>
{/if} {/if}
{#if canShowTopupButton} {#if regularTrafficTopupUnlocked}
<Button class="wide" variant="secondary" onclick={canOpenRegularTopupModal ? openRegularTopupModal : openPremiumTopupModal}> <Button class="wide" variant="secondary" onclick={openRegularTopupModal}>
<Database size={18} /> <Database size={18} />
{t("wa_topup_traffic")} {t("wa_add_traffic")}
</Button>
{/if}
{#if premiumTrafficTopupUnlocked}
<Button class="wide" variant="secondary" onclick={openPremiumTopupModal}>
<Database size={18} />
{t("wa_add_traffic_premium", { target: premiumTitle(subscription) })}
</Button> </Button>
{/if} {/if}
</div> </div>
+2
View File
@@ -701,6 +701,8 @@
"wa_tariff_no_description": "Tariff description is not configured", "wa_tariff_no_description": "Tariff description is not configured",
"wa_change_tariff": "Change tariff", "wa_change_tariff": "Change tariff",
"wa_topup_traffic": "Buy traffic", "wa_topup_traffic": "Buy traffic",
"wa_add_traffic": "Add traffic",
"wa_add_traffic_premium": "Add traffic on {target}",
"wa_current_tariff": "Current tariff: {tariff}", "wa_current_tariff": "Current tariff: {tariff}",
"wa_tariff_options_loading": "Loading options", "wa_tariff_options_loading": "Loading options",
"wa_tariff_options_failed": "Failed to load tariff options", "wa_tariff_options_failed": "Failed to load tariff options",
+2
View File
@@ -701,6 +701,8 @@
"wa_tariff_no_description": "Описание тарифа не задано", "wa_tariff_no_description": "Описание тарифа не задано",
"wa_change_tariff": "Сменить тариф", "wa_change_tariff": "Сменить тариф",
"wa_topup_traffic": "Докупить трафик", "wa_topup_traffic": "Докупить трафик",
"wa_add_traffic": "Добавить трафик",
"wa_add_traffic_premium": "Добавить трафик на {target}",
"wa_current_tariff": "Текущий тариф: {tariff}", "wa_current_tariff": "Текущий тариф: {tariff}",
"wa_tariff_options_loading": "Загружаем варианты", "wa_tariff_options_loading": "Загружаем варианты",
"wa_tariff_options_failed": "Не удалось загрузить варианты тарифов", "wa_tariff_options_failed": "Не удалось загрузить варианты тарифов",