refactor: project architecture refactor, container splitting
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<script>
|
||||
import { CircleX, Plus, RefreshCw, Smartphone } from "$components/ui/icons.js";
|
||||
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import { EmptyCard, LinearProgress, StatusMessage } from "$components/patterns/webapp/index.js";
|
||||
import {
|
||||
devicesCountLabel,
|
||||
devicesLimitLabel,
|
||||
devicesPercent,
|
||||
} from "../../lib/webapp/devicesLabels.js";
|
||||
|
||||
export let devicesBusy = false;
|
||||
export let devicesData = {};
|
||||
export let devicesIsError = false;
|
||||
export let devicesLoaded = false;
|
||||
export let devicesStatus = "";
|
||||
export let subscription = {};
|
||||
|
||||
export let loadDevices = () => {};
|
||||
export let openDeviceDisconnectDialog = () => {};
|
||||
export let openDeviceTopupModal = () => {};
|
||||
export let t = (key) => key;
|
||||
</script>
|
||||
|
||||
<main class="content with-nav">
|
||||
<Card class="devices-summary-card">
|
||||
<div class="devices-summary-head">
|
||||
<Smartphone size={28} />
|
||||
<span>
|
||||
<strong>{t("wa_devices_title")}</strong>
|
||||
<small>{devicesCountLabel(devicesData, t)}</small>
|
||||
</span>
|
||||
<Button
|
||||
variant="icon"
|
||||
size="icon"
|
||||
onclick={() => loadDevices(true)}
|
||||
disabled={devicesBusy}
|
||||
aria-label={t("wa_devices_refresh")}
|
||||
>
|
||||
<RefreshCw size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
<LinearProgress
|
||||
class="devices-progress"
|
||||
value={devicesPercent(devicesData)}
|
||||
label={t("wa_devices_title")}
|
||||
/>
|
||||
{#if subscription?.active && subscription?.max_devices !== 0}
|
||||
<Button variant="secondary" class="wide" onclick={openDeviceTopupModal}>
|
||||
<Plus size={17} />
|
||||
{t("wa_buy_hwid_devices")}
|
||||
</Button>
|
||||
{/if}
|
||||
</Card>
|
||||
|
||||
{#if devicesBusy && !devicesLoaded}
|
||||
<EmptyCard>{t("wa_devices_loading")}</EmptyCard>
|
||||
{:else if devicesStatus}
|
||||
<EmptyCard>
|
||||
<StatusMessage error={devicesIsError}>{devicesStatus}</StatusMessage>
|
||||
</EmptyCard>
|
||||
{:else if !devicesData?.devices?.length}
|
||||
<EmptyCard class="devices-empty-card">
|
||||
<Smartphone size={28} />
|
||||
<span>{t("wa_devices_empty")}</span>
|
||||
<small>{t("wa_devices_empty_hint", { max: devicesLimitLabel(devicesData, t) })}</small>
|
||||
</EmptyCard>
|
||||
{:else}
|
||||
<div class="devices-list">
|
||||
{#each devicesData.devices as device (device.token || device.index)}
|
||||
<Card class="device-card">
|
||||
<div class="device-card-head">
|
||||
<div class="device-icon"><Smartphone size={20} /></div>
|
||||
<span>
|
||||
<strong
|
||||
>{device.display_name ||
|
||||
t("wa_device_fallback_name", { index: device.index })}</strong
|
||||
>
|
||||
<small>{device.platform_label || t("wa_devices_platform_unknown")}</small>
|
||||
</span>
|
||||
</div>
|
||||
<div class="device-meta">
|
||||
{#if device.created_at_text}
|
||||
<div>
|
||||
<span>{t("wa_devices_connected_at")}</span>
|
||||
<strong>{device.created_at_text}</strong>
|
||||
</div>
|
||||
{/if}
|
||||
{#if device.hwid_short}
|
||||
<div>
|
||||
<span>HWID</span>
|
||||
<code>{device.hwid_short}</code>
|
||||
</div>
|
||||
{/if}
|
||||
{#if device.user_agent}
|
||||
<div class="device-user-agent">
|
||||
<span>User Agent</span>
|
||||
<small>{device.user_agent}</small>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if device.can_disconnect}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="wide device-disconnect-button"
|
||||
onclick={() => openDeviceDisconnectDialog(device)}
|
||||
>
|
||||
<CircleX size={17} />
|
||||
{t("wa_devices_disconnect")}
|
||||
</Button>
|
||||
{/if}
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
@@ -0,0 +1,285 @@
|
||||
<script>
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronsUpDown,
|
||||
CircleX,
|
||||
Database,
|
||||
Download,
|
||||
Gift,
|
||||
RefreshCw,
|
||||
} from "$components/ui/icons.js";
|
||||
|
||||
import BrandMark from "$lib/webapp/BrandMark.svelte";
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import { LinearProgress } from "$components/patterns/webapp/index.js";
|
||||
import { formatTrafficGb } from "../../lib/webapp/formatters.js";
|
||||
import {
|
||||
trafficPercent as trafficPercentFn,
|
||||
trafficLabel as trafficLabelFn,
|
||||
trafficResetLabel as trafficResetLabelFn,
|
||||
premiumTrafficPercent as premiumTrafficPercentFn,
|
||||
premiumTrafficLabel as premiumTrafficLabelFn,
|
||||
premiumTitle as premiumTitleFn,
|
||||
premiumServerLabels as premiumServerLabelsFn,
|
||||
activeSubscriptionTermLabel as activeSubscriptionTermLabelFn,
|
||||
} from "../../lib/webapp/traffic.js";
|
||||
|
||||
export let appSettings = {};
|
||||
export let brand = {};
|
||||
export let brandTitle = "";
|
||||
export let canChangeTariff = false;
|
||||
export let premiumTrafficTopupBarClickable = false;
|
||||
export let premiumTrafficTopupUnlocked = false;
|
||||
export let regularTrafficTopupBarClickable = false;
|
||||
export let regularTrafficTopupUnlocked = false;
|
||||
export let currentTariffName = "";
|
||||
export let hasActiveTariffSubscription = false;
|
||||
export let hasMultipleTariffs = false;
|
||||
export let subscription = {};
|
||||
export let trafficMode = false;
|
||||
export let trialBusy = false;
|
||||
export let termUnitLabel = () => ""; // We need this passed from App or context. Actually, App.svelte doesn't pass it yet. We'll pass it.
|
||||
|
||||
function trafficPercent(sub) {
|
||||
return trafficPercentFn(sub);
|
||||
}
|
||||
function trafficLabel(sub) {
|
||||
return trafficLabelFn(sub, t);
|
||||
}
|
||||
function trafficResetLabel(sub) {
|
||||
return trafficResetLabelFn(sub, t);
|
||||
}
|
||||
function premiumTrafficPercent(sub) {
|
||||
return premiumTrafficPercentFn(sub);
|
||||
}
|
||||
function premiumTrafficLabel(sub) {
|
||||
return premiumTrafficLabelFn(sub, t);
|
||||
}
|
||||
function premiumTitle(sub = subscription) {
|
||||
return premiumTitleFn(sub, t);
|
||||
}
|
||||
function premiumServerLabels(sub) {
|
||||
return premiumServerLabelsFn(sub);
|
||||
}
|
||||
function activeSubscriptionTermLabel(sub) {
|
||||
return activeSubscriptionTermLabelFn(sub, { t, termUnitLabel });
|
||||
}
|
||||
function trialTrafficLabel() {
|
||||
const limit = Number(appSettings?.trial_traffic_limit_gb || 0);
|
||||
return limit > 0 ? formatTrafficGb(limit) : t("wa_unlimited_traffic");
|
||||
}
|
||||
|
||||
export let activateTrial = () => {};
|
||||
export let openConnectLink = () => {};
|
||||
export let openPaymentModal = () => {};
|
||||
export let openRegularTopupModal = () => {};
|
||||
export let openPremiumTopupModal = () => {};
|
||||
export let openTariffChangeModal = () => {};
|
||||
export let primaryPayActionLabel = () => "";
|
||||
export let t = (key) => key;
|
||||
</script>
|
||||
|
||||
<main class="home-layout">
|
||||
<div class="login-brand home-brand">
|
||||
<BrandMark {brand} size="xl" />
|
||||
<h1>{brandTitle}</h1>
|
||||
</div>
|
||||
|
||||
<div class="home-bottom">
|
||||
<Card class={`status-card${subscription.active ? "" : " status-card-inactive"}`}>
|
||||
{#if subscription.active}
|
||||
<div class="sub-status">
|
||||
<CheckCircle2 size={23} />
|
||||
<div>
|
||||
<h2>
|
||||
{trafficMode ? t("wa_home_access_active") : t("wa_home_subscription_active")} | {activeSubscriptionTermLabel(
|
||||
subscription
|
||||
)}
|
||||
</h2>
|
||||
{#if hasActiveTariffSubscription && hasMultipleTariffs && currentTariffName}
|
||||
<p class="current-tariff-line">
|
||||
{t("wa_current_tariff", { tariff: currentTariffName })}
|
||||
</p>
|
||||
{/if}
|
||||
<p>
|
||||
{subscription.end_date_text
|
||||
? t("wa_until_date", { date: subscription.end_date_text })
|
||||
: subscription.remaining_text}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="sub-status sub-status-inactive">
|
||||
<CircleX size={23} />
|
||||
<h2>{t("wa_home_subscription_inactive")}</h2>
|
||||
</div>
|
||||
{/if}
|
||||
</Card>
|
||||
|
||||
{#if subscription.active}
|
||||
<Card class={regularTrafficTopupBarClickable ? "traffic-card-clickable" : ""}>
|
||||
{#if regularTrafficTopupBarClickable}
|
||||
<button
|
||||
class="card-click-target"
|
||||
type="button"
|
||||
onclick={openRegularTopupModal}
|
||||
aria-label={t("wa_add_traffic")}
|
||||
></button>
|
||||
{/if}
|
||||
<div class="traffic-top">
|
||||
<span>{t("wa_home_traffic_used")}</span>
|
||||
<strong>{trafficLabel(subscription)}</strong>
|
||||
</div>
|
||||
<LinearProgress value={trafficPercent(subscription)} label={t("wa_home_traffic_used")} />
|
||||
<div class="traffic-meta">
|
||||
<span>{trafficResetLabel(subscription)}</span>
|
||||
<span class="traffic-percent">{trafficPercent(subscription)}%</span>
|
||||
</div>
|
||||
</Card>
|
||||
{#if subscription?.premium_unlimited_override}
|
||||
<Card class="premium-traffic-card">
|
||||
<div class="traffic-top">
|
||||
<span>{premiumTitle(subscription)}</span>
|
||||
<strong>∞</strong>
|
||||
</div>
|
||||
<div class="traffic-meta premium-traffic-meta">
|
||||
{#if premiumServerLabels(subscription).length}
|
||||
<details class="premium-server-dropdown">
|
||||
<summary>
|
||||
<span>{t("wa_premium_unlimited", {}, "Безлимит на premium-сервера")}</span>
|
||||
<ChevronsUpDown size={13} />
|
||||
</summary>
|
||||
<div class="premium-server-list premium-server-list-dropdown">
|
||||
<small
|
||||
>{t("wa_premium_servers_limited", {}, "Отдельный лимит действует на")}</small
|
||||
>
|
||||
<div>
|
||||
{#each premiumServerLabels(subscription).slice(0, 8) as label}
|
||||
<span>{label}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{:else}
|
||||
<span>{t("wa_premium_unlimited", {}, "Безлимит на premium-сервера")}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
{:else if Number(subscription?.premium_limit_bytes || 0) > 0}
|
||||
<Card
|
||||
class={`${premiumTrafficTopupBarClickable ? "traffic-card-clickable " : ""}premium-traffic-card${subscription?.premium_is_limited ? " premium-traffic-card-limited" : ""}`}
|
||||
>
|
||||
{#if premiumTrafficTopupBarClickable}
|
||||
<button
|
||||
class="card-click-target"
|
||||
type="button"
|
||||
onclick={openPremiumTopupModal}
|
||||
aria-label={t("wa_add_traffic_premium", { target: premiumTitle(subscription) })}
|
||||
></button>
|
||||
{/if}
|
||||
<div class="traffic-top">
|
||||
<span>{premiumTitle(subscription)}</span>
|
||||
<strong>{premiumTrafficLabel(subscription)}</strong>
|
||||
</div>
|
||||
<LinearProgress
|
||||
class="premium-progress"
|
||||
value={premiumTrafficPercent(subscription)}
|
||||
label={premiumTitle(subscription)}
|
||||
/>
|
||||
<div class="traffic-meta premium-traffic-meta">
|
||||
{#if premiumServerLabels(subscription).length}
|
||||
<details class="premium-server-dropdown">
|
||||
<summary>
|
||||
<span
|
||||
>{subscription?.premium_is_limited
|
||||
? t("wa_premium_access_limited", {}, "Доступ к premium временно ограничен")
|
||||
: t("wa_premium_reset_monthly", {}, "Отдельный лимит на месяц")}</span
|
||||
>
|
||||
<ChevronsUpDown size={13} />
|
||||
</summary>
|
||||
<div class="premium-server-list premium-server-list-dropdown">
|
||||
<small
|
||||
>{t("wa_premium_servers_limited", {}, "Отдельный лимит действует на")}</small
|
||||
>
|
||||
<div>
|
||||
{#each premiumServerLabels(subscription).slice(0, 8) as label}
|
||||
<span>{label}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{:else}
|
||||
<span
|
||||
>{subscription?.premium_is_limited
|
||||
? t("wa_premium_access_limited", {}, "Доступ к premium временно ограничен")
|
||||
: t("wa_premium_reset_monthly", {}, "Отдельный лимит на месяц")}</span
|
||||
>
|
||||
{/if}
|
||||
<span class="traffic-percent">{premiumTrafficPercent(subscription)}%</span>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
{:else if appSettings?.trial_enabled && appSettings?.trial_available}
|
||||
<Card class="trial-card">
|
||||
<div class="trial-card-head">
|
||||
<Gift size={22} />
|
||||
<span>
|
||||
<strong>{t("wa_trial_title")}</strong>
|
||||
<small
|
||||
>{t("wa_trial_details", {
|
||||
days: Number(appSettings?.trial_duration_days || 0),
|
||||
traffic: trialTrafficLabel(),
|
||||
})}</small
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
<div class="action-stack">
|
||||
{#if subscription.active}
|
||||
<Button class="wide" onclick={openConnectLink}>
|
||||
<Download size={18} />
|
||||
{t("wa_install_and_configure")}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
class="wide"
|
||||
variant={subscription.active ? "secondary" : "default"}
|
||||
onclick={openPaymentModal}
|
||||
>
|
||||
{#if subscription.active}
|
||||
<RefreshCw size={18} />
|
||||
{:else if trafficMode}
|
||||
<Database size={18} />
|
||||
{/if}
|
||||
{primaryPayActionLabel()}
|
||||
</Button>
|
||||
{#if !subscription.active && appSettings?.trial_enabled && appSettings?.trial_available}
|
||||
<Button class="wide" variant="secondary" onclick={activateTrial} disabled={trialBusy}>
|
||||
<Gift size={18} />
|
||||
{t("wa_activate_trial")}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if canChangeTariff}
|
||||
<Button class="wide" variant="secondary" onclick={openTariffChangeModal}>
|
||||
<RefreshCw size={18} />
|
||||
{t("wa_change_tariff")}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if regularTrafficTopupUnlocked}
|
||||
<Button class="wide" variant="secondary" onclick={openRegularTopupModal}>
|
||||
<Database size={18} />
|
||||
{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>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -0,0 +1,111 @@
|
||||
<script>
|
||||
import { Copy, Gift, Ticket, TriangleAlert } from "$components/ui/icons.js";
|
||||
import { Tooltip } from "$components/ui/primitives.js";
|
||||
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import Input from "$components/ui/input.svelte";
|
||||
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
||||
|
||||
export let referral = {};
|
||||
export let referralBonusDetails = [];
|
||||
export let referralOneBonusPerReferee = false;
|
||||
export let referralWelcomeBonusDays = 0;
|
||||
export let promoBusy = false;
|
||||
export let promoCode = "";
|
||||
export let promoFieldError = "";
|
||||
export let promoIsError = false;
|
||||
export let promoStatus = "";
|
||||
|
||||
export let applyPromo = () => {};
|
||||
export let clearPromoFieldError = () => {};
|
||||
export let copyText = () => {};
|
||||
export let t = (key) => key;
|
||||
</script>
|
||||
|
||||
<main class="content with-nav">
|
||||
<Card class="bonus-card">
|
||||
<div class="bonus-card-head">
|
||||
<Gift size={42} />
|
||||
<div>
|
||||
<strong>{t("wa_referral_bonus_overview_title")}</strong>
|
||||
{#if referralOneBonusPerReferee}
|
||||
<p>{t("wa_referral_bonus_once_note")}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="card-heading">{t("wa_referral_link_title")}</h3>
|
||||
<div class="copy-row referral-copy-row">
|
||||
<code>{referral.webapp_link || referral.bot_link || t("wa_link_unavailable")}</code>
|
||||
<Button
|
||||
class="referral-copy-button"
|
||||
onclick={() => copyText(referral.webapp_link || referral.bot_link, t("wa_link_copied"))}
|
||||
>
|
||||
{t("wa_copy")}
|
||||
<Copy size={17} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if referralBonusDetails.length || referralWelcomeBonusDays > 0}
|
||||
<div class="referral-bonus-list">
|
||||
{#if referralWelcomeBonusDays > 0}
|
||||
<div class="referral-bonus-row">
|
||||
<strong>{t("wa_referral_bonus_registration_title")}</strong>
|
||||
<small>{t("wa_referral_bonus_friend_days", { days: referralWelcomeBonusDays })}</small>
|
||||
</div>
|
||||
{/if}
|
||||
{#if referralBonusDetails.length}
|
||||
<p class="referral-bonus-intro">{t("wa_referral_bonus_paid_intro")}</p>
|
||||
{/if}
|
||||
{#each referralBonusDetails as bonus, index (bonus.months || index)}
|
||||
<div class="referral-bonus-row">
|
||||
<strong>{bonus.title || `${bonus.months || "?"}`}</strong>
|
||||
<small
|
||||
>{t("wa_referral_bonus_you_days", { days: Number(bonus.inviter_days || 0) })}</small
|
||||
>
|
||||
<small
|
||||
>{t("wa_referral_bonus_friend_days", { days: Number(bonus.friend_days || 0) })}</small
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<StatusMessage>{t("wa_referral_bonus_not_configured")}</StatusMessage>
|
||||
{/if}
|
||||
</Card>
|
||||
<Card>
|
||||
<h3 class="card-heading card-heading-accent promo-heading">
|
||||
<Ticket size={18} />
|
||||
<span>{t("wa_activate_promo_title")}</span>
|
||||
</h3>
|
||||
<div class="copy-row">
|
||||
<div class="field-error-wrap">
|
||||
<Tooltip.Root open={Boolean(promoFieldError)}>
|
||||
<Input
|
||||
bind:value={promoCode}
|
||||
placeholder="PROMO2026"
|
||||
class={promoFieldError ? "input-error" : ""}
|
||||
on:input={clearPromoFieldError}
|
||||
/>
|
||||
{#if promoFieldError}
|
||||
<Tooltip.Trigger class="field-error-trigger" aria-label={promoFieldError}>
|
||||
<span class="field-error-icon" aria-hidden="true"><TriangleAlert size={18} /></span>
|
||||
</Tooltip.Trigger>
|
||||
{/if}
|
||||
{#if promoFieldError}
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content class="field-error-tooltip">{promoFieldError}</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
{/if}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<Button variant="outline" onclick={applyPromo} disabled={promoBusy}>
|
||||
{t("wa_activate")}
|
||||
</Button>
|
||||
</div>
|
||||
{#if promoStatus && !(promoIsError && promoFieldError)}
|
||||
<StatusMessage error={promoIsError}>{promoStatus}</StatusMessage>
|
||||
{/if}
|
||||
</Card>
|
||||
</main>
|
||||
@@ -0,0 +1,182 @@
|
||||
<script>
|
||||
import {
|
||||
ArrowRight,
|
||||
CheckCircle2,
|
||||
FileText,
|
||||
Mail,
|
||||
Send,
|
||||
Shield,
|
||||
UserRound,
|
||||
} from "$components/ui/icons.js";
|
||||
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import { LanguageSelect } from "$components/patterns/webapp/index.js";
|
||||
|
||||
export let currentLang = "ru";
|
||||
export let currentLanguageOption = null;
|
||||
export let emailLinkStatus = "";
|
||||
export let isAdmin = false;
|
||||
export let languageBusy = false;
|
||||
export let languageClickGuard = false;
|
||||
export let languageClickGuardArmed = false;
|
||||
export let languageMenuOpen = false;
|
||||
export let languageOptions = [];
|
||||
export let linkEmailBusy = false;
|
||||
export let linkTelegramBusy = false;
|
||||
export let privacyPolicyUrl = "";
|
||||
export let profileAvatarUrl = "";
|
||||
export let profileEmail = "";
|
||||
export let profileTelegramId = "";
|
||||
export let supportUrl = "";
|
||||
export let telegramProfileName = "";
|
||||
export let user = {};
|
||||
export let userAgreementUrl = "";
|
||||
export let userLanguage = "";
|
||||
|
||||
export let linkTelegramAccount = () => {};
|
||||
export let logout = () => {};
|
||||
export let openAdminPanel = () => {};
|
||||
export let openExternalLink = () => {};
|
||||
export let openLinkEmailDialog = () => {};
|
||||
export let setLanguageMenuOpen = () => {};
|
||||
export let t = (key) => key;
|
||||
export let updateAccountLanguage = () => {};
|
||||
</script>
|
||||
|
||||
<main class="content with-nav">
|
||||
<Card class="settings-profile">
|
||||
<div class="settings-avatar">
|
||||
{#if profileAvatarUrl}
|
||||
<img
|
||||
src={profileAvatarUrl}
|
||||
alt={t("wa_settings_avatar_alt")}
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
{:else}
|
||||
<UserRound size={30} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="settings-profile-meta">
|
||||
<strong>{telegramProfileName}</strong>
|
||||
<small>{profileEmail}</small>
|
||||
<small>{profileTelegramId}</small>
|
||||
</div>
|
||||
</Card>
|
||||
{#if isAdmin}
|
||||
<div class="settings-admin-block">
|
||||
<div class="settings-divider" aria-hidden="true"></div>
|
||||
<button class="settings-row settings-row-admin" type="button" onclick={openAdminPanel}>
|
||||
<Shield size={21} />
|
||||
<span>
|
||||
<strong>{t("wa_settings_admin_panel", {}, "Админ-панель")}</strong>
|
||||
<small>{t("wa_settings_admin_panel_hint", {}, "Управление приложением")}</small>
|
||||
</span>
|
||||
<ArrowRight size={17} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="settings-links-block">
|
||||
<div class="settings-divider" aria-hidden="true"></div>
|
||||
{#if user?.telegram_linked}
|
||||
<div class="settings-row settings-row-linked">
|
||||
<CheckCircle2 size={21} />
|
||||
<span>
|
||||
<strong>{t("wa_settings_telegram_linked_title")}</strong>
|
||||
<small>{profileTelegramId}</small>
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<Button
|
||||
variant="telegram"
|
||||
class="wide settings-telegram-link-btn attention-wrap"
|
||||
onclick={linkTelegramAccount}
|
||||
disabled={linkTelegramBusy}
|
||||
>
|
||||
<span class="attention-dot" aria-hidden="true"></span>
|
||||
<Send size={18} />
|
||||
{t("wa_settings_link_telegram_action")}
|
||||
</Button>
|
||||
{/if}
|
||||
{#if user?.email}
|
||||
<div class="settings-row settings-row-linked">
|
||||
<CheckCircle2 size={21} />
|
||||
<span>
|
||||
<strong>{t("wa_settings_email_linked_title")}</strong>
|
||||
<small>{user?.email}</small>
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="settings-row attention-wrap"
|
||||
type="button"
|
||||
onclick={openLinkEmailDialog}
|
||||
disabled={linkEmailBusy}
|
||||
>
|
||||
<span class="attention-dot" aria-hidden="true"></span>
|
||||
<Mail size={21} />
|
||||
<span>
|
||||
<strong>{t("wa_settings_link_email_action")}</strong>
|
||||
<small>{emailLinkStatus}</small>
|
||||
</span>
|
||||
<ArrowRight size={17} />
|
||||
</button>
|
||||
{/if}
|
||||
<div class="settings-divider" aria-hidden="true"></div>
|
||||
</div>
|
||||
<div class="settings-list" class:settings-list--language-open={languageMenuOpen}>
|
||||
<LanguageSelect
|
||||
bind:open={languageMenuOpen}
|
||||
value={currentLang}
|
||||
currentOption={currentLanguageOption}
|
||||
{userLanguage}
|
||||
options={languageOptions}
|
||||
disabled={languageBusy}
|
||||
clickGuard={languageClickGuard}
|
||||
clickGuardArmed={languageClickGuardArmed}
|
||||
closeLabel={t("wa_close")}
|
||||
label={t("wa_settings_language")}
|
||||
onOpenChange={setLanguageMenuOpen}
|
||||
onValueChange={updateAccountLanguage}
|
||||
/>
|
||||
{#if supportUrl}
|
||||
<button
|
||||
class="settings-row settings-row-support"
|
||||
type="button"
|
||||
onclick={() => openExternalLink(supportUrl)}
|
||||
>
|
||||
<Send size={21} />
|
||||
<span><strong>{t("menu_support_button")}</strong></span>
|
||||
<ArrowRight size={17} />
|
||||
</button>
|
||||
{/if}
|
||||
{#if userAgreementUrl}
|
||||
<button
|
||||
class="settings-row settings-row-policy"
|
||||
type="button"
|
||||
onclick={() => openExternalLink(userAgreementUrl)}
|
||||
>
|
||||
<FileText size={21} />
|
||||
<span><strong>{t("wa_settings_user_agreement")}</strong></span>
|
||||
<ArrowRight size={17} />
|
||||
</button>
|
||||
{/if}
|
||||
{#if privacyPolicyUrl}
|
||||
<button
|
||||
class="settings-row settings-row-policy"
|
||||
type="button"
|
||||
onclick={() => openExternalLink(privacyPolicyUrl)}
|
||||
>
|
||||
<Shield size={21} />
|
||||
<span><strong>{t("wa_settings_privacy_policy")}</strong></span>
|
||||
<ArrowRight size={17} />
|
||||
</button>
|
||||
{/if}
|
||||
<button class="settings-row settings-row-logout" type="button" onclick={logout}>
|
||||
<UserRound size={21} />
|
||||
<span><strong>{t("wa_logout")}</strong><small>{t("wa_end_session")}</small></span>
|
||||
<ArrowRight size={17} />
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
Reference in New Issue
Block a user