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
@@ -1,5 +1,5 @@
<script>
import { Input } from "$components/ui/index.js";
import { Input, Textarea } from "$components/ui/index.js";
import {
ChevronRight,
RefreshCw,
@@ -31,12 +31,30 @@
"TRIAL_DURATION_DAYS",
"TRIAL_TRAFFIC_LIMIT_GB",
"TRIAL_TRAFFIC_STRATEGY",
"TRIAL_WITHOUT_TELEGRAM_ENABLED",
"TRIAL_SQUAD_UUIDS",
];
const TRIAL_SWITCH_KEYS = ["TRIAL_ENABLED"];
const TRIAL_SWITCH_KEYS = ["TRIAL_ENABLED", "TRIAL_WITHOUT_TELEGRAM_ENABLED"];
const TRIAL_GENERAL_KEYS = ["TRIAL_DURATION_DAYS", "TRIAL_TRAFFIC_LIMIT_GB"];
const TRIAL_RESET_KEYS = ["TRIAL_TRAFFIC_STRATEGY"];
const TRIAL_SQUAD_KEYS = ["TRIAL_SQUAD_UUIDS"];
const REFERRAL_SETTING_KEYS = [
"REFERRAL_WELCOME_BONUS_DAYS",
"REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED",
"REFERRAL_ONE_BONUS_PER_REFEREE",
"LEGACY_REFS",
"DISPOSABLE_EMAIL_DOMAINS",
];
const REFERRAL_WELCOME_KEYS = [
"REFERRAL_WELCOME_BONUS_DAYS",
"REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED",
];
const REFERRAL_RULE_KEYS = [
"REFERRAL_ONE_BONUS_PER_REFEREE",
"LEGACY_REFS",
"DISPOSABLE_EMAIL_DOMAINS",
];
const DISPOSABLE_EMAIL_DOMAINS_PLACEHOLDER = "mailinator.com\ntemp-mail.org\nyopmail.com";
const LEGACY_PERIODS = [
[
"1",
@@ -102,6 +120,7 @@
.map((field) => [field.key, field])
);
$: trialDirtyCount = TRIAL_SETTING_KEYS.filter((key) => Boolean(settingsDirty[key])).length;
$: referralDirtyCount = REFERRAL_SETTING_KEYS.filter((key) => Boolean(settingsDirty[key])).length;
$: legacyDirtyCount = LEGACY_TARIFF_SETTING_KEYS.filter((key) =>
Boolean(settingsDirty[key])
).length;
@@ -410,6 +429,53 @@
{/if}
</div>
</div>
<div
class="admin-setting admin-trial-setting-row"
class:is-dirty={isSettingDirty("TRIAL_WITHOUT_TELEGRAM_ENABLED", settingsDirty)}
>
<div class="admin-setting-meta">
<strong>
{at("tariffs_trial_without_telegram_enabled", {}, "Триал без Telegram")}
{#if isSettingDirty("TRIAL_WITHOUT_TELEGRAM_ENABLED", settingsDirty)}
<AdminBadge variant="warning"
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
>
{/if}
</strong>
<code>TRIAL_WITHOUT_TELEGRAM_ENABLED</code>
</div>
<div class="admin-setting-control">
<div class="admin-setting-switch">
<Switch.Root
checked={boolValue(
"TRIAL_WITHOUT_TELEGRAM_ENABLED",
settingsDirty,
settingsFieldMap
)}
onCheckedChange={(checked) =>
setSetting("TRIAL_WITHOUT_TELEGRAM_ENABLED", checked)}
class="admin-switch-root"
>
<Switch.Thumb class="admin-switch-thumb" />
</Switch.Root>
<span
>{boolValue("TRIAL_WITHOUT_TELEGRAM_ENABLED", settingsDirty, settingsFieldMap)
? at("enabled", {}, "Включено")
: at("disabled", {}, "Выключено")}</span
>
</div>
{#if isSettingDirty("TRIAL_WITHOUT_TELEGRAM_ENABLED", settingsDirty)}
<AdminButton
size="sm"
variant="ghost"
onclick={() => resetSetting("TRIAL_WITHOUT_TELEGRAM_ENABLED")}
>
<X size={12} />
{at("reset", {}, "Сбросить")}
</AdminButton>
{/if}
</div>
</div>
</div>
</section>
@@ -677,6 +743,352 @@
</div>
</article>
<article class="admin-card admin-tariff-settings-card">
<header class="admin-card-head">
<div>
<h3>{at("tariffs_referral_title", {}, "Реферальная программа")}</h3>
<small>
{at(
"tariffs_referral_subtitle",
{},
"Настройки приветственного бонуса, правил начисления и защиты от одноразовых email."
)}
</small>
</div>
<div class="admin-editor-section-actions">
<AdminBadge
variant={Number(
valueForKey("REFERRAL_WELCOME_BONUS_DAYS", settingsDirty, settingsFieldMap) || 0
) > 0
? "success"
: "muted"}
>
{Number(
valueForKey("REFERRAL_WELCOME_BONUS_DAYS", settingsDirty, settingsFieldMap) || 0
) > 0
? at("enabled", {}, "Включено")
: at("disabled", {}, "Выключено")}
</AdminBadge>
{#if referralDirtyCount}
<AdminBadge variant="warning">
{at(
"settings_dirty_count",
{ count: referralDirtyCount },
`Изменений: ${referralDirtyCount}`
)}
</AdminBadge>
<AdminButton
size="sm"
variant="primary"
onclick={saveTariffSettings}
disabled={settingsSaving}
>
<Save size={13} />
{settingsSaving
? at("btn_saving", {}, "Сохранение...")
: at("btn_save", {}, "Сохранить")}
</AdminButton>
{/if}
</div>
</header>
<div class="admin-card-body admin-trial-settings-body">
<div class="admin-settings-field-groups admin-trial-settings-groups">
<section
class="admin-settings-field-group"
class:is-dirty={dirtyCount(REFERRAL_WELCOME_KEYS, settingsDirty)}
>
<header class="admin-settings-field-group-head">
<div class="admin-settings-field-group-head-copy">
<strong>{at("tariffs_referral_group_welcome", {}, "Приветственный бонус")}</strong>
<small>
{at(
"tariffs_referral_group_welcome_hint",
{},
"Дни, которые получает приглашённый пользователь после регистрации по ссылке."
)}
</small>
</div>
{#if dirtyCount(REFERRAL_WELCOME_KEYS, settingsDirty)}
<AdminBadge variant="warning">
{at(
"settings_dirty_count",
{ count: dirtyCount(REFERRAL_WELCOME_KEYS, settingsDirty) },
`Изменений: ${dirtyCount(REFERRAL_WELCOME_KEYS, settingsDirty)}`
)}
</AdminBadge>
{/if}
</header>
<div class="admin-settings-field-group-body">
<div
class="admin-setting admin-trial-setting-row"
class:is-dirty={isSettingDirty("REFERRAL_WELCOME_BONUS_DAYS", settingsDirty)}
>
<div class="admin-setting-meta">
<strong>
{at("tariffs_referral_welcome_bonus_days", {}, "Приветственный бонус, дней")}
{#if isSettingDirty("REFERRAL_WELCOME_BONUS_DAYS", settingsDirty)}
<AdminBadge variant="warning"
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
>
{/if}
</strong>
<code>REFERRAL_WELCOME_BONUS_DAYS</code>
</div>
<div class="admin-setting-control">
<Input
class="input"
type="number"
min="0"
step="1"
value={valueForKey(
"REFERRAL_WELCOME_BONUS_DAYS",
settingsDirty,
settingsFieldMap
)}
oninput={(event) =>
setSetting("REFERRAL_WELCOME_BONUS_DAYS", event.currentTarget.value)}
/>
{#if isSettingDirty("REFERRAL_WELCOME_BONUS_DAYS", settingsDirty)}
<AdminButton
size="sm"
variant="ghost"
onclick={() => resetSetting("REFERRAL_WELCOME_BONUS_DAYS")}
>
<X size={12} />
{at("reset", {}, "Сбросить")}
</AdminButton>
{/if}
</div>
</div>
<div
class="admin-setting admin-trial-setting-row"
class:is-dirty={isSettingDirty(
"REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED",
settingsDirty
)}
>
<div class="admin-setting-meta">
<strong>
{at(
"tariffs_referral_without_telegram",
{},
"Начислять welcome bonus без Telegram"
)}
{#if isSettingDirty("REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED", settingsDirty)}
<AdminBadge variant="warning"
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
>
{/if}
</strong>
<code>REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED</code>
</div>
<div class="admin-setting-control">
<div class="admin-setting-switch">
<Switch.Root
checked={boolValue(
"REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED",
settingsDirty,
settingsFieldMap
)}
onCheckedChange={(checked) =>
setSetting("REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED", checked)}
class="admin-switch-root"
>
<Switch.Thumb class="admin-switch-thumb" />
</Switch.Root>
<span
>{boolValue(
"REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED",
settingsDirty,
settingsFieldMap
)
? at("enabled", {}, "Включено")
: at("disabled", {}, "Выключено")}</span
>
</div>
{#if isSettingDirty("REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED", settingsDirty)}
<AdminButton
size="sm"
variant="ghost"
onclick={() => resetSetting("REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED")}
>
<X size={12} />
{at("reset", {}, "Сбросить")}
</AdminButton>
{/if}
</div>
</div>
</div>
</section>
<section
class="admin-settings-field-group"
class:is-dirty={dirtyCount(REFERRAL_RULE_KEYS, settingsDirty)}
>
<header class="admin-settings-field-group-head">
<div class="admin-settings-field-group-head-copy">
<strong>{at("tariffs_referral_group_rules", {}, "Правила и антиабьюз")}</strong>
<small>
{at(
"tariffs_referral_group_rules_hint",
{},
"Ограничения повторных бонусов и домены одноразовой почты для no-Telegram аккаунтов."
)}
</small>
</div>
{#if dirtyCount(REFERRAL_RULE_KEYS, settingsDirty)}
<AdminBadge variant="warning">
{at(
"settings_dirty_count",
{ count: dirtyCount(REFERRAL_RULE_KEYS, settingsDirty) },
`Изменений: ${dirtyCount(REFERRAL_RULE_KEYS, settingsDirty)}`
)}
</AdminBadge>
{/if}
</header>
<div class="admin-settings-field-group-body">
<div
class="admin-setting admin-trial-setting-row"
class:is-dirty={isSettingDirty("REFERRAL_ONE_BONUS_PER_REFEREE", settingsDirty)}
>
<div class="admin-setting-meta">
<strong>
{at("tariffs_referral_one_bonus_per_referee", {}, "Один бонус на приглашённого")}
{#if isSettingDirty("REFERRAL_ONE_BONUS_PER_REFEREE", settingsDirty)}
<AdminBadge variant="warning"
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
>
{/if}
</strong>
<code>REFERRAL_ONE_BONUS_PER_REFEREE</code>
</div>
<div class="admin-setting-control">
<div class="admin-setting-switch">
<Switch.Root
checked={boolValue(
"REFERRAL_ONE_BONUS_PER_REFEREE",
settingsDirty,
settingsFieldMap
)}
onCheckedChange={(checked) =>
setSetting("REFERRAL_ONE_BONUS_PER_REFEREE", checked)}
class="admin-switch-root"
>
<Switch.Thumb class="admin-switch-thumb" />
</Switch.Root>
<span
>{boolValue("REFERRAL_ONE_BONUS_PER_REFEREE", settingsDirty, settingsFieldMap)
? at("enabled", {}, "Включено")
: at("disabled", {}, "Выключено")}</span
>
</div>
{#if isSettingDirty("REFERRAL_ONE_BONUS_PER_REFEREE", settingsDirty)}
<AdminButton
size="sm"
variant="ghost"
onclick={() => resetSetting("REFERRAL_ONE_BONUS_PER_REFEREE")}
>
<X size={12} />
{at("reset", {}, "Сбросить")}
</AdminButton>
{/if}
</div>
</div>
<div
class="admin-setting admin-trial-setting-row"
class:is-dirty={isSettingDirty("LEGACY_REFS", settingsDirty)}
>
<div class="admin-setting-meta">
<strong>
{at("tariffs_referral_legacy_refs", {}, "Старые ref-ссылки")}
{#if isSettingDirty("LEGACY_REFS", settingsDirty)}
<AdminBadge variant="warning"
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
>
{/if}
</strong>
<code>LEGACY_REFS</code>
</div>
<div class="admin-setting-control">
<div class="admin-setting-switch">
<Switch.Root
checked={boolValue("LEGACY_REFS", settingsDirty, settingsFieldMap)}
onCheckedChange={(checked) => setSetting("LEGACY_REFS", checked)}
class="admin-switch-root"
>
<Switch.Thumb class="admin-switch-thumb" />
</Switch.Root>
<span
>{boolValue("LEGACY_REFS", settingsDirty, settingsFieldMap)
? at("enabled", {}, "Включено")
: at("disabled", {}, "Выключено")}</span
>
</div>
{#if isSettingDirty("LEGACY_REFS", settingsDirty)}
<AdminButton
size="sm"
variant="ghost"
onclick={() => resetSetting("LEGACY_REFS")}
>
<X size={12} />
{at("reset", {}, "Сбросить")}
</AdminButton>
{/if}
</div>
</div>
<div
class="admin-setting admin-trial-setting-row"
class:is-dirty={isSettingDirty("DISPOSABLE_EMAIL_DOMAINS", settingsDirty)}
>
<div class="admin-setting-meta">
<strong>
{at("tariffs_referral_disposable_domains", {}, "Disposable email домены")}
{#if isSettingDirty("DISPOSABLE_EMAIL_DOMAINS", settingsDirty)}
<AdminBadge variant="warning"
>{at("settings_badge_dirty", {}, "Изменено")}</AdminBadge
>
{/if}
</strong>
<code>DISPOSABLE_EMAIL_DOMAINS</code>
<small>
{at(
"tariffs_referral_disposable_domains_hint",
{},
"По одному домену на строку или через запятую. Поддомены тоже считаются совпадением."
)}
</small>
</div>
<div class="admin-setting-control">
<Textarea
class="admin-setting-textarea"
rows="8"
placeholder={DISPOSABLE_EMAIL_DOMAINS_PLACEHOLDER}
value={valueForKey("DISPOSABLE_EMAIL_DOMAINS", settingsDirty, settingsFieldMap)}
oninput={(event) =>
setSetting("DISPOSABLE_EMAIL_DOMAINS", event.currentTarget.value)}
/>
{#if isSettingDirty("DISPOSABLE_EMAIL_DOMAINS", settingsDirty)}
<AdminButton
size="sm"
variant="ghost"
onclick={() => resetSetting("DISPOSABLE_EMAIL_DOMAINS")}
>
<X size={12} />
{at("reset", {}, "Сбросить")}
</AdminButton>
{/if}
</div>
</div>
</div>
</section>
</div>
</div>
</article>
<div class="admin-tariff-management">
<div class="admin-tariff-overview-grid">
<article class="admin-card admin-tariff-currency-card">