feat: add telegram guardrails for trials and referrals

This commit is contained in:
3252a8
2026-06-04 10:51:58 +03:00
parent 33707b7257
commit 2ad6b14513
26 changed files with 1944 additions and 125 deletions
+4 -4
View File
@@ -125,9 +125,9 @@
function showHwidRenewalUnavailableNote() {
return Boolean(
subscription?.active &&
Number(subscription?.extra_hwid_devices || 0) > 0 &&
isSubscriptionPlan(selectedPlan) &&
!showHwidRenewalBlock()
Number(subscription?.extra_hwid_devices || 0) > 0 &&
isSubscriptionPlan(selectedPlan) &&
!showHwidRenewalBlock()
);
}
function hwidRenewalCount(plan = selectedPlan) {
@@ -146,7 +146,7 @@
function showHwidDesyncNotice() {
return Boolean(
subscription?.device_topup_renewal_available &&
subscription?.extra_hwid_devices_valid_until_text
subscription?.extra_hwid_devices_valid_until_text
);
}
function planKey(plan) {
+121 -31
View File
@@ -9,9 +9,11 @@
Download,
Gift,
Repeat2,
Send,
} from "$components/ui/icons.js";
import BrandMark from "$lib/webapp/BrandMark.svelte";
import { AttentionDot } from "$components/ui/index.js";
import Button from "$components/ui/button.svelte";
import Card from "$components/ui/card.svelte";
import TelegramNotificationsBanner from "../TelegramNotificationsBanner.svelte";
@@ -39,10 +41,12 @@
export let premiumTrafficTopupUnlocked = false;
export let regularTrafficTopupBarClickable = false;
export let regularTrafficTopupUnlocked = false;
export let referral = {};
export let currentTariffName = "";
export let hasActiveTariffSubscription = false;
export let hasMultipleTariffs = false;
export let subscription = {};
export let linkTelegramBusy = false;
export let telegramNotificationsNeedPrompt = false;
export let telegramNotificationsStartLink = "";
export let telegramNotificationsStatus = "unknown";
@@ -142,6 +146,14 @@
$: trialOfferAvailable = Boolean(
!subscription?.active && appSettings?.trial_enabled && appSettings?.trial_available
);
$: trialRequiresTelegram = Boolean(
!subscription?.active && appSettings?.trial_enabled && appSettings?.trial_requires_telegram
);
$: referralWelcomeRequiresTelegram = Boolean(
!subscription?.active &&
referral?.welcome_bonus_requires_telegram &&
Number(referral?.welcome_bonus_days || 0) > 0
);
$: subscriptionEndMs = subscription?.active ? parseSubscriptionEndMs(subscription) : null;
$: subscriptionRemainingMs = Math.max(0, Number(subscriptionEndMs || 0) - nowMs);
$: subscriptionExpiryWarning = Boolean(
@@ -186,6 +198,8 @@
});
export let activateTrial = () => {};
export let linkTelegramAndActivateTrial = () => {};
export let linkTelegramAndClaimReferralWelcome = () => {};
export let openConnectLink = () => {};
export let openPaymentModal = () => {};
export let openTelegramNotificationsBot = () => {};
@@ -374,37 +388,113 @@
/>
</Card>
{/if}
{:else if trialOfferAvailable}
<Card class="trial-card trial-offer-card">
<div class="trial-card-head">
<Gift size={22} />
<span>
<strong>{t("wa_trial_offer_title", {}, "Можно начать с льготного периода")}</strong>
<small>{t("wa_trial_title")}</small>
</span>
</div>
<p class="trial-card-description">
{t(
"wa_trial_offer_description",
{ duration: trialDurationLabel(), traffic: trialTrafficLabel() },
"Активируйте триал: {duration} доступа и {traffic} для скачивания без оплаты."
)}
</p>
<div class="trial-card-facts">
<span>
<small>{t("wa_trial_duration_label", {}, "Срок")}</small>
<strong>{trialDurationLabel()}</strong>
</span>
<span>
<small>{t("wa_trial_download_traffic_label", {}, "Доступно для скачивания")}</small>
<strong>{trialTrafficLabel()}</strong>
</span>
</div>
<Button class="wide trial-card-action" onclick={activateTrial} disabled={trialBusy}>
<Gift size={18} />
{t("wa_trial_try_free", {}, "Попробовать бесплатно")}
</Button>
</Card>
{:else}
{#if referralWelcomeRequiresTelegram}
<Card class="trial-card trial-offer-card">
<div class="trial-card-head">
<Gift size={22} />
<span>
<strong>
{t(
"wa_referral_welcome_telegram_required_title",
{},
"Бонус ждёт привязки Telegram"
)}
</strong>
<small>{t("wa_referral_program_title", {}, "Реферальная программа")}</small>
</span>
</div>
<p class="trial-card-description">
{t(
"wa_referral_welcome_telegram_required_description",
{ days: Number(referral?.welcome_bonus_days || 0) },
"Привяжите Telegram, чтобы получить {days} бонусных дней за регистрацию по приглашению."
)}
</p>
<Button
class="wide trial-card-action settings-telegram-link-btn attention-wrap"
variant="telegram"
onclick={linkTelegramAndClaimReferralWelcome}
disabled={linkTelegramBusy}
>
<AttentionDot />
<Send size={18} />
{t("wa_referral_link_telegram_and_claim", {}, "Привязать и получить бонус")}
</Button>
</Card>
{/if}
{#if trialOfferAvailable}
<Card class="trial-card trial-offer-card">
<div class="trial-card-head">
<Gift size={22} />
<span>
<strong>{t("wa_trial_offer_title", {}, "Можно начать с льготного периода")}</strong>
<small>{t("wa_trial_title")}</small>
</span>
</div>
<p class="trial-card-description">
{t(
"wa_trial_offer_description",
{ duration: trialDurationLabel(), traffic: trialTrafficLabel() },
"Активируйте триал: {duration} доступа и {traffic} для скачивания без оплаты."
)}
</p>
<div class="trial-card-facts">
<span>
<small>{t("wa_trial_duration_label", {}, "Срок")}</small>
<strong>{trialDurationLabel()}</strong>
</span>
<span>
<small>{t("wa_trial_download_traffic_label", {}, "Доступно для скачивания")}</small>
<strong>{trialTrafficLabel()}</strong>
</span>
</div>
<Button class="wide trial-card-action" onclick={activateTrial} disabled={trialBusy}>
<Gift size={18} />
{t("wa_trial_try_free", {}, "Попробовать бесплатно")}
</Button>
</Card>
{:else if trialRequiresTelegram}
<Card class="trial-card trial-offer-card">
<div class="trial-card-head">
<Gift size={22} />
<span>
<strong>
{t("wa_trial_telegram_required_title", {}, "Привяжите Telegram для триала")}
</strong>
<small>{t("wa_trial_title")}</small>
</span>
</div>
<p class="trial-card-description">
{t(
"wa_trial_telegram_required_description",
{ duration: trialDurationLabel(), traffic: trialTrafficLabel() },
"Чтобы активировать триал на {duration} с лимитом {traffic}, сначала привяжите Telegram."
)}
</p>
<div class="trial-card-facts">
<span>
<small>{t("wa_trial_duration_label", {}, "Срок")}</small>
<strong>{trialDurationLabel()}</strong>
</span>
<span>
<small>{t("wa_trial_download_traffic_label", {}, "Доступно для скачивания")}</small>
<strong>{trialTrafficLabel()}</strong>
</span>
</div>
<Button
class="wide trial-card-action settings-telegram-link-btn attention-wrap"
variant="telegram"
onclick={linkTelegramAndActivateTrial}
disabled={linkTelegramBusy || trialBusy}
>
<AttentionDot />
<Send size={18} />
{t("wa_trial_link_telegram_and_activate", {}, "Привязать и активировать")}
</Button>
</Card>
{/if}
{/if}
<div class="action-stack">
@@ -7,9 +7,11 @@
Download,
Gift,
RefreshCw,
Send,
} from "$components/ui/icons.js";
import BrandMark from "$lib/webapp/BrandMark.svelte";
import { AttentionDot } from "$components/ui/index.js";
import Button from "$components/ui/button.svelte";
import Card from "$components/ui/card.svelte";
import { formatTrafficGb } from "../../lib/webapp/formatters.js";
@@ -19,9 +21,11 @@
export let brandTitle = "";
export let subscription = {};
export let trialBusy = false;
export let linkTelegramBusy = false;
export let trialResult = null;
export let trialError = "";
export let activateTrial = () => {};
export let linkTelegramAndActivateTrial = () => {};
export let openInstallOrConnect = () => {};
export let goHome = () => {};
export let t = (key, _params = {}, fallback = "") => fallback || key;
@@ -30,6 +34,9 @@
$: trialEnabled = Boolean(appSettings?.trial_enabled);
$: trialAvailable = Boolean(appSettings?.trial_available);
$: trialRequiresTelegram = Boolean(
trialEnabled && appSettings?.trial_requires_telegram && !subscription?.active
);
$: canRequestTrial = Boolean(trialEnabled && trialAvailable && !subscription?.active);
$: isTrialStatus =
Boolean(trialResult?.activated) ||
@@ -78,6 +85,8 @@
<RefreshCw size={27} />
{:else if hasActiveAccess}
<CheckCircle2 size={30} />
{:else if trialRequiresTelegram}
<Gift size={30} />
{:else if trialError || !canRequestTrial}
<CircleX size={30} />
{:else}
@@ -116,6 +125,31 @@
<dd>{trafficLabel}</dd>
</div>
</dl>
{:else if trialRequiresTelegram}
<h2>{t("wa_trial_telegram_required_title", {}, "Привяжите Telegram для триала")}</h2>
<p>
{t(
"wa_trial_telegram_required_description",
{
duration:
daysLeft > 0 ? t("wa_trial_days_left", { days: daysLeft }, "{days} days") : "",
traffic: trafficLabel,
},
"Чтобы активировать пробный период, сначала привяжите Telegram."
)}
</p>
<dl class="trial-activation-facts">
{#if daysLeft > 0}
<div>
<dt>{t("wa_trial_duration_label", {}, "Срок")}</dt>
<dd>{t("wa_trial_days_left", { days: daysLeft }, "{days} days")}</dd>
</div>
{/if}
<div>
<dt>{t("wa_trial_traffic_label", {}, "Traffic")}</dt>
<dd>{trafficLabel}</dd>
</div>
</dl>
{:else if trialError}
<h2>{t("wa_trial_activation_failed")}</h2>
<p>{trialError}</p>
@@ -138,6 +172,17 @@
<Download size={18} />
{t("wa_install_and_configure")}
</Button>
{:else if trialRequiresTelegram}
<Button
class="wide settings-telegram-link-btn attention-wrap"
variant="telegram"
onclick={linkTelegramAndActivateTrial}
disabled={linkTelegramBusy || trialBusy}
>
<AttentionDot />
<Send size={18} />
{t("wa_trial_link_telegram_and_activate", {}, "Привязать и активировать")}
</Button>
{:else if trialError && canRequestTrial}
<Button class="wide" onclick={activateTrial} disabled={trialBusy}>
<RefreshCw size={18} />