chore: improve tariff admin layout
This commit is contained in:
@@ -211,9 +211,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: catalogCurrencyKey = normalizeCurrencyKey(tariffsCatalog.default_currency || "rub");
|
$: catalogCurrencyKey = normalizeCurrencyKey(tariffsCatalog.default_currency || "rub");
|
||||||
$: defaultCurrencyDraft = catalogCurrencyKey.toUpperCase();
|
$: catalogCurrencyCode = catalogCurrencyKey.toUpperCase();
|
||||||
|
$: defaultCurrencyDraft = catalogCurrencyCode;
|
||||||
$: defaultCurrencyDraftKey = normalizeCurrencyKey(defaultCurrencyDraft || "rub");
|
$: defaultCurrencyDraftKey = normalizeCurrencyKey(defaultCurrencyDraft || "rub");
|
||||||
$: defaultCurrencyDirty = defaultCurrencyDraftKey !== catalogCurrencyKey;
|
$: defaultCurrencyDirty = defaultCurrencyDraftKey !== catalogCurrencyKey;
|
||||||
|
$: providerSupportSummary = (providerCurrencySupport || []).reduce(
|
||||||
|
(summary, provider) => {
|
||||||
|
const enabled = Boolean(provider.enabled);
|
||||||
|
const configured = Boolean(provider.configured);
|
||||||
|
const supportsDefault = Boolean(provider.supports_default_currency);
|
||||||
|
summary.total += 1;
|
||||||
|
if (enabled) summary.enabled += 1;
|
||||||
|
if (enabled && configured) summary.configured += 1;
|
||||||
|
if (enabled && configured && supportsDefault) summary.available += 1;
|
||||||
|
if (enabled && configured && !supportsDefault) summary.blocked += 1;
|
||||||
|
return summary;
|
||||||
|
},
|
||||||
|
{ total: 0, enabled: 0, configured: 0, available: 0, blocked: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
async function saveDefaultCurrency() {
|
async function saveDefaultCurrency() {
|
||||||
await tariffsStore.setDefaultCurrency(defaultCurrencyDraft);
|
await tariffsStore.setDefaultCurrency(defaultCurrencyDraft);
|
||||||
@@ -222,7 +237,8 @@
|
|||||||
function providerCurrencyLabel(provider) {
|
function providerCurrencyLabel(provider) {
|
||||||
if (provider.accepts_any_currency) return at("tariff_provider_any_currency", {}, "Любая");
|
if (provider.accepts_any_currency) return at("tariff_provider_any_currency", {}, "Любая");
|
||||||
return (
|
return (
|
||||||
(provider.currencies || []).join(", ") || at("tariff_provider_not_declared", {}, "Не задано")
|
(provider.currencies || []).map((currency) => String(currency).toUpperCase()).join(", ") ||
|
||||||
|
at("tariff_provider_not_declared", {}, "Не задано")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +247,13 @@
|
|||||||
return provider.supports_default_currency ? "success" : "warning";
|
return provider.supports_default_currency ? "success" : "warning";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerCurrencyStatus(provider) {
|
||||||
|
if (!provider.enabled) return at("disabled", {}, "Отключен");
|
||||||
|
if (!provider.configured) return at("status_not_configured", {}, "Не настроен");
|
||||||
|
if (provider.supports_default_currency) return at("tariff_currency_supported", {}, "Доступен");
|
||||||
|
return at("tariff_currency_unsupported", {}, "Заблокирован");
|
||||||
|
}
|
||||||
|
|
||||||
function removeTrialSquad(uuid) {
|
function removeTrialSquad(uuid) {
|
||||||
setCsvList(
|
setCsvList(
|
||||||
"TRIAL_SQUAD_UUIDS",
|
"TRIAL_SQUAD_UUIDS",
|
||||||
@@ -654,185 +677,258 @@
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="admin-card">
|
<div class="admin-tariff-management">
|
||||||
<header class="admin-card-head">
|
<div class="admin-tariff-overview-grid">
|
||||||
<div>
|
<article class="admin-card admin-tariff-currency-card">
|
||||||
<h3>{at("tariffs_title", {}, "Каталог тарифов")}</h3>
|
<header class="admin-card-head admin-tariff-panel-head">
|
||||||
<small>{tariffsPath || "data/tariffs.json"}</small>
|
<div>
|
||||||
</div>
|
<h3>{at("tariffs_currency_title", {}, "Валюта каталога")}</h3>
|
||||||
<div class="admin-editor-section-actions">
|
<small>
|
||||||
<AdminButton
|
{at(
|
||||||
size="sm"
|
"tariffs_currency_subtitle",
|
||||||
onclick={tariffsStore.loadTariffs}
|
{},
|
||||||
disabled={tariffsLoading || tariffsSaving}
|
"Цены тарифов и платёжные провайдеры проверяются по этой валюте."
|
||||||
>
|
)}
|
||||||
<RefreshCw size={13} />
|
</small>
|
||||||
{at("btn_refresh", {}, "Обновить")}
|
</div>
|
||||||
</AdminButton>
|
<AdminBadge variant="muted">{catalogCurrencyCode}</AdminBadge>
|
||||||
<AdminButton
|
</header>
|
||||||
size="sm"
|
<div class="admin-card-body admin-tariff-currency-body">
|
||||||
variant="primary"
|
<div class="admin-tariff-currency-current">
|
||||||
onclick={tariffsStore.openCreateTariff}
|
<span>{at("tariffs_currency_current", {}, "Текущая валюта")}</span>
|
||||||
disabled={tariffsLoading || tariffsSaving}
|
<strong>{catalogCurrencyCode}</strong>
|
||||||
>
|
</div>
|
||||||
<Plus size={13} />
|
<div class="admin-tariff-catalog-bar">
|
||||||
{at("btn_create_tariff", {}, "Создать тариф")}
|
<label class="admin-field-label-compact admin-tariff-currency-field">
|
||||||
</AdminButton>
|
<span>{at("tariff_default_currency", {}, "Валюта оплаты")}</span>
|
||||||
</div>
|
<Input
|
||||||
</header>
|
class="input admin-currency-input"
|
||||||
<div class="admin-card-body">
|
type="text"
|
||||||
<div class="admin-tariff-catalog-bar">
|
maxlength="12"
|
||||||
<label class="admin-field-label-compact">
|
value={defaultCurrencyDraft}
|
||||||
<span>{at("tariff_default_currency", {}, "Валюта оплаты")}</span>
|
oninput={(event) =>
|
||||||
<Input
|
(defaultCurrencyDraft = event.currentTarget.value.toUpperCase())}
|
||||||
class="input admin-currency-input"
|
onkeydown={(event) => {
|
||||||
type="text"
|
if (event.key === "Enter" && defaultCurrencyDirty) saveDefaultCurrency();
|
||||||
maxlength="12"
|
}}
|
||||||
value={defaultCurrencyDraft}
|
/>
|
||||||
oninput={(event) => (defaultCurrencyDraft = event.currentTarget.value.toUpperCase())}
|
</label>
|
||||||
onkeydown={(event) => {
|
{#if defaultCurrencyDirty}
|
||||||
if (event.key === "Enter" && defaultCurrencyDirty) saveDefaultCurrency();
|
<AdminButton
|
||||||
}}
|
size="sm"
|
||||||
/>
|
variant="primary"
|
||||||
</label>
|
onclick={saveDefaultCurrency}
|
||||||
<AdminBadge variant="muted">{catalogCurrencyKey.toUpperCase()}</AdminBadge>
|
disabled={tariffsSaving}
|
||||||
{#if defaultCurrencyDirty}
|
>
|
||||||
|
<Save size={13} />
|
||||||
|
{tariffsSaving
|
||||||
|
? at("btn_saving", {}, "Сохранение...")
|
||||||
|
: at("btn_save", {}, "Сохранить")}
|
||||||
|
</AdminButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="admin-card admin-tariff-providers-card">
|
||||||
|
<header class="admin-card-head admin-tariff-panel-head">
|
||||||
|
<div>
|
||||||
|
<h3>{at("tariffs_provider_title", {}, "Платёжные провайдеры")}</h3>
|
||||||
|
<small>
|
||||||
|
{at(
|
||||||
|
"tariffs_provider_subtitle",
|
||||||
|
{},
|
||||||
|
"Здесь видно, какие провайдеры смогут принять текущую валюту каталога."
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="admin-provider-summary">
|
||||||
|
<AdminBadge variant="success">
|
||||||
|
{at(
|
||||||
|
"tariffs_provider_available_count",
|
||||||
|
{ count: providerSupportSummary.available },
|
||||||
|
"Доступно: {count}"
|
||||||
|
)}
|
||||||
|
</AdminBadge>
|
||||||
|
<AdminBadge variant="muted">
|
||||||
|
{at(
|
||||||
|
"tariffs_provider_enabled_count",
|
||||||
|
{ count: providerSupportSummary.enabled },
|
||||||
|
"Включено: {count}"
|
||||||
|
)}
|
||||||
|
</AdminBadge>
|
||||||
|
{#if providerSupportSummary.blocked}
|
||||||
|
<AdminBadge variant="warning">
|
||||||
|
{at(
|
||||||
|
"tariffs_provider_blocked_count",
|
||||||
|
{ count: providerSupportSummary.blocked },
|
||||||
|
"Не подходят: {count}"
|
||||||
|
)}
|
||||||
|
</AdminBadge>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="admin-card-body">
|
||||||
|
{#if providerCurrencySupport?.length}
|
||||||
|
<div class="admin-provider-currency-grid">
|
||||||
|
{#each providerCurrencySupport as provider}
|
||||||
|
<div
|
||||||
|
class="admin-provider-currency"
|
||||||
|
class:is-supported={provider.supports_default_currency &&
|
||||||
|
provider.enabled &&
|
||||||
|
provider.configured}
|
||||||
|
class:is-unavailable={!provider.supports_default_currency ||
|
||||||
|
!provider.enabled ||
|
||||||
|
!provider.configured}
|
||||||
|
>
|
||||||
|
<div class="admin-provider-currency-main">
|
||||||
|
<strong>{provider.label}</strong>
|
||||||
|
<small>{providerCurrencyLabel(provider)}</small>
|
||||||
|
</div>
|
||||||
|
<AdminBadge variant={providerCurrencyVariant(provider)}>
|
||||||
|
{providerCurrencyStatus(provider)}
|
||||||
|
</AdminBadge>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<AdminEmptyState>
|
||||||
|
{at("tariffs_provider_empty", {}, "Данные по провайдерам пока не загружены.")}
|
||||||
|
</AdminEmptyState>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="admin-card admin-tariff-list-card">
|
||||||
|
<header class="admin-card-head admin-tariff-list-head">
|
||||||
|
<div>
|
||||||
|
<h3>{at("tariffs_title", {}, "Каталог тарифов")}</h3>
|
||||||
|
<small>
|
||||||
|
{at("tariffs_catalog_subtitle", {}, "Периоды, цены, трафик и доступы пользователей.")}
|
||||||
|
</small>
|
||||||
|
<code class="admin-tariff-path">{tariffsPath || "data/tariffs.json"}</code>
|
||||||
|
</div>
|
||||||
|
<div class="admin-editor-section-actions">
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
onclick={tariffsStore.loadTariffs}
|
||||||
|
disabled={tariffsLoading || tariffsSaving}
|
||||||
|
>
|
||||||
|
<RefreshCw size={13} />
|
||||||
|
{at("btn_refresh", {}, "Обновить")}
|
||||||
|
</AdminButton>
|
||||||
<AdminButton
|
<AdminButton
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onclick={saveDefaultCurrency}
|
onclick={tariffsStore.openCreateTariff}
|
||||||
disabled={tariffsSaving}
|
disabled={tariffsLoading || tariffsSaving}
|
||||||
>
|
>
|
||||||
<Save size={13} />
|
<Plus size={13} />
|
||||||
{tariffsSaving
|
{at("btn_create_tariff", {}, "Создать тариф")}
|
||||||
? at("btn_saving", {}, "Сохранение...")
|
|
||||||
: at("btn_save", {}, "Сохранить")}
|
|
||||||
</AdminButton>
|
</AdminButton>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="admin-card-body">
|
||||||
|
{#if !tariffsCatalog.tariffs.length}
|
||||||
|
<AdminEmptyState>
|
||||||
|
{at(
|
||||||
|
"tariffs_catalog_empty",
|
||||||
|
{},
|
||||||
|
"Каталог пуст. Добавьте первый тариф, после сохранения будет создан JSON-файл каталога."
|
||||||
|
)}
|
||||||
|
</AdminEmptyState>
|
||||||
|
{:else}
|
||||||
|
<div class="admin-tariff-grid">
|
||||||
|
{#each tariffsCatalog.tariffs as tariff}
|
||||||
|
<article class="admin-tariff-card" class:is-disabled={tariff.enabled === false}>
|
||||||
|
<div class="admin-tariff-top">
|
||||||
|
<div>
|
||||||
|
<div class="admin-tariff-title">
|
||||||
|
<strong>{tariffName(tariff)}</strong>
|
||||||
|
{#if tariff.key === tariffsCatalog.default_tariff}
|
||||||
|
<AdminBadge variant="success"
|
||||||
|
>{at("status_default", {}, "Default")}</AdminBadge
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<code>{tariff.key}</code>
|
||||||
|
</div>
|
||||||
|
{#if tariff.enabled === false}
|
||||||
|
<AdminBadge variant="muted">{at("status_disabled", {}, "Выключен")}</AdminBadge>
|
||||||
|
{:else}
|
||||||
|
<AdminBadge variant="success">{at("status_active", {}, "Активен")}</AdminBadge>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{tariff.descriptions?.ru ||
|
||||||
|
tariff.descriptions?.en ||
|
||||||
|
at("no_description", {}, "Без описания")}
|
||||||
|
</p>
|
||||||
|
<div class="admin-tariff-facts">
|
||||||
|
<span
|
||||||
|
>{tariff.billing_model === "traffic"
|
||||||
|
? at("tariff_model_traffic", {}, "Трафик")
|
||||||
|
: at("tariff_model_periods", {}, "Периоды")}</span
|
||||||
|
>
|
||||||
|
<span>{tariffPriceSummary(tariff)}</span>
|
||||||
|
<span
|
||||||
|
>{at("tariff_squads", {}, "Squads")}: {(tariff.squad_uuids || []).length}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>{at("tariff_premium", {}, "Premium")}: {(tariff.premium_squad_uuids || [])
|
||||||
|
.length
|
||||||
|
? `${tariff.premium_monthly_gb || 0} GB`
|
||||||
|
: "—"}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>{at("tariff_devices", {}, "Устройства")}: {tariff.hwid_device_limit ??
|
||||||
|
"env"}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="admin-tariff-actions">
|
||||||
|
<AdminButton size="sm" onclick={() => tariffsStore.openEditTariff(tariff)}>
|
||||||
|
{at("btn_configure", {}, "Настроить")}
|
||||||
|
</AdminButton>
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
onclick={() => tariffsStore.toggleTariffEnabled(tariff)}
|
||||||
|
disabled={tariffsSaving}
|
||||||
|
>
|
||||||
|
{tariff.enabled === false
|
||||||
|
? at("btn_enable", {}, "Включить")
|
||||||
|
: at("btn_disable", {}, "Выключить")}
|
||||||
|
</AdminButton>
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
onclick={() => tariffsStore.setDefaultTariff(tariff.key)}
|
||||||
|
disabled={tariffsSaving ||
|
||||||
|
tariff.enabled === false ||
|
||||||
|
tariff.key === tariffsCatalog.default_tariff}
|
||||||
|
>
|
||||||
|
{at("btn_set_default", {}, "По умолчанию")}
|
||||||
|
</AdminButton>
|
||||||
|
<AdminButton
|
||||||
|
size="sm"
|
||||||
|
variant="danger"
|
||||||
|
onclick={() =>
|
||||||
|
tariffsStore.updateState({
|
||||||
|
tariffDeleteTarget: tariff,
|
||||||
|
tariffDeleteOpen: true,
|
||||||
|
})}
|
||||||
|
disabled={tariffsSaving}
|
||||||
|
aria-label={at("btn_delete_tariff", {}, "Удалить тариф")}
|
||||||
|
>
|
||||||
|
<Trash2 size={13} />
|
||||||
|
</AdminButton>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if providerCurrencySupport?.length}
|
</article>
|
||||||
<div class="admin-provider-currency-grid">
|
</div>
|
||||||
{#each providerCurrencySupport as provider}
|
|
||||||
<div
|
|
||||||
class="admin-provider-currency"
|
|
||||||
class:is-supported={provider.supports_default_currency && provider.enabled}
|
|
||||||
class:is-unavailable={!provider.supports_default_currency || !provider.enabled}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<strong>{provider.label}</strong>
|
|
||||||
<small>{providerCurrencyLabel(provider)}</small>
|
|
||||||
</div>
|
|
||||||
<AdminBadge variant={providerCurrencyVariant(provider)}>
|
|
||||||
{#if !provider.enabled}
|
|
||||||
{at("disabled", {}, "Отключен")}
|
|
||||||
{:else if !provider.configured}
|
|
||||||
{at("status_not_configured", {}, "Не настроен")}
|
|
||||||
{:else if provider.supports_default_currency}
|
|
||||||
{at("tariff_currency_supported", {}, "Доступен")}
|
|
||||||
{:else}
|
|
||||||
{at("tariff_currency_unsupported", {}, "Заблокирован")}
|
|
||||||
{/if}
|
|
||||||
</AdminBadge>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if !tariffsCatalog.tariffs.length}
|
|
||||||
<AdminEmptyState>
|
|
||||||
{at(
|
|
||||||
"tariffs_catalog_empty",
|
|
||||||
{},
|
|
||||||
"Каталог пуст. Добавьте первый тариф, после сохранения будет создан JSON-файл каталога."
|
|
||||||
)}
|
|
||||||
</AdminEmptyState>
|
|
||||||
{:else}
|
|
||||||
<div class="admin-tariff-grid">
|
|
||||||
{#each tariffsCatalog.tariffs as tariff}
|
|
||||||
<article class="admin-tariff-card" class:is-disabled={tariff.enabled === false}>
|
|
||||||
<div class="admin-tariff-top">
|
|
||||||
<div>
|
|
||||||
<div class="admin-tariff-title">
|
|
||||||
<strong>{tariffName(tariff)}</strong>
|
|
||||||
{#if tariff.key === tariffsCatalog.default_tariff}
|
|
||||||
<AdminBadge variant="success"
|
|
||||||
>{at("status_default", {}, "Default")}</AdminBadge
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<code>{tariff.key}</code>
|
|
||||||
</div>
|
|
||||||
{#if tariff.enabled === false}
|
|
||||||
<AdminBadge variant="muted">{at("status_disabled", {}, "Выключен")}</AdminBadge>
|
|
||||||
{:else}
|
|
||||||
<AdminBadge variant="success">{at("status_active", {}, "Активен")}</AdminBadge>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{tariff.descriptions?.ru ||
|
|
||||||
tariff.descriptions?.en ||
|
|
||||||
at("no_description", {}, "Без описания")}
|
|
||||||
</p>
|
|
||||||
<div class="admin-tariff-facts">
|
|
||||||
<span
|
|
||||||
>{tariff.billing_model === "traffic"
|
|
||||||
? at("tariff_model_traffic", {}, "Трафик")
|
|
||||||
: at("tariff_model_periods", {}, "Периоды")}</span
|
|
||||||
>
|
|
||||||
<span>{tariffPriceSummary(tariff)}</span>
|
|
||||||
<span>{at("tariff_squads", {}, "Squads")}: {(tariff.squad_uuids || []).length}</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
>{at("tariff_premium", {}, "Premium")}: {(tariff.premium_squad_uuids || []).length
|
|
||||||
? `${tariff.premium_monthly_gb || 0} GB`
|
|
||||||
: "—"}</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
>{at("tariff_devices", {}, "Устройства")}: {tariff.hwid_device_limit ??
|
|
||||||
"env"}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="admin-tariff-actions">
|
|
||||||
<AdminButton size="sm" onclick={() => tariffsStore.openEditTariff(tariff)}>
|
|
||||||
{at("btn_configure", {}, "Настроить")}
|
|
||||||
</AdminButton>
|
|
||||||
<AdminButton
|
|
||||||
size="sm"
|
|
||||||
onclick={() => tariffsStore.toggleTariffEnabled(tariff)}
|
|
||||||
disabled={tariffsSaving}
|
|
||||||
>
|
|
||||||
{tariff.enabled === false
|
|
||||||
? at("btn_enable", {}, "Включить")
|
|
||||||
: at("btn_disable", {}, "Выключить")}
|
|
||||||
</AdminButton>
|
|
||||||
<AdminButton
|
|
||||||
size="sm"
|
|
||||||
onclick={() => tariffsStore.setDefaultTariff(tariff.key)}
|
|
||||||
disabled={tariffsSaving ||
|
|
||||||
tariff.enabled === false ||
|
|
||||||
tariff.key === tariffsCatalog.default_tariff}
|
|
||||||
>
|
|
||||||
{at("btn_set_default", {}, "По умолчанию")}
|
|
||||||
</AdminButton>
|
|
||||||
<AdminButton
|
|
||||||
size="sm"
|
|
||||||
variant="danger"
|
|
||||||
onclick={() =>
|
|
||||||
tariffsStore.updateState({
|
|
||||||
tariffDeleteTarget: tariff,
|
|
||||||
tariffDeleteOpen: true,
|
|
||||||
})}
|
|
||||||
disabled={tariffsSaving}
|
|
||||||
aria-label={at("btn_delete_tariff", {}, "Удалить тариф")}
|
|
||||||
>
|
|
||||||
<Trash2 size={13} />
|
|
||||||
</AdminButton>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<Accordion.Root
|
<Accordion.Root
|
||||||
type="multiple"
|
type="multiple"
|
||||||
|
|||||||
+205
-69
@@ -3125,6 +3125,169 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-tariff-management {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-overview-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-overview-grid > .admin-card {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 980px) {
|
||||||
|
.admin-tariff-overview-grid {
|
||||||
|
grid-template-columns: minmax(260px, 0.82fr) minmax(0, 1.6fr);
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-panel-head > div,
|
||||||
|
.admin-tariff-list-head > div {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-panel-head small,
|
||||||
|
.admin-tariff-list-head small {
|
||||||
|
max-width: 72ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-path {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-top: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--admin-dim);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-currency-body {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-currency-current {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--admin-surface-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-currency-current span {
|
||||||
|
color: var(--admin-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-currency-current strong {
|
||||||
|
color: var(--admin-text);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-catalog-bar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-currency-field {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-currency-field > span {
|
||||||
|
color: var(--admin-text);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-currency-input {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-summary {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--admin-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--admin-surface-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency.is-supported {
|
||||||
|
border-color: color-mix(in srgb, var(--success-border) 70%, var(--admin-border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency.is-unavailable {
|
||||||
|
opacity: 0.74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency-main {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency-main strong,
|
||||||
|
.admin-provider-currency-main small {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency-main strong {
|
||||||
|
color: var(--admin-text);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency-main small {
|
||||||
|
color: var(--admin-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency > .admin-badge {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-tariff-grid {
|
.admin-tariff-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
@@ -3145,6 +3308,7 @@
|
|||||||
|
|
||||||
.admin-tariff-card {
|
.admin-tariff-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border: 1px solid var(--admin-border);
|
border: 1px solid var(--admin-border);
|
||||||
@@ -3168,6 +3332,11 @@
|
|||||||
|
|
||||||
.admin-tariff-top {
|
.admin-tariff-top {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-top > div {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-tariff-title {
|
.admin-tariff-title {
|
||||||
@@ -3204,79 +3373,13 @@
|
|||||||
|
|
||||||
.admin-tariff-facts span {
|
.admin-tariff-facts span {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow-wrap: anywhere;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-tariff-actions {
|
.admin-tariff-actions {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-tariff-catalog-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-currency-input {
|
|
||||||
width: 120px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 9px 10px;
|
|
||||||
border: 1px solid var(--admin-border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--admin-surface-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency.is-supported {
|
|
||||||
border-color: color-mix(in srgb, var(--success-border) 70%, var(--admin-border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency.is-unavailable {
|
|
||||||
opacity: 0.72;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency div {
|
|
||||||
display: grid;
|
|
||||||
gap: 2px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency strong,
|
|
||||||
.admin-provider-currency small {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency strong {
|
|
||||||
color: var(--admin-text);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-provider-currency small {
|
|
||||||
color: var(--admin-muted);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-tariff-settings-card {
|
.admin-tariff-settings-card {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
@@ -3614,6 +3717,33 @@
|
|||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-tariff-panel-head,
|
||||||
|
.admin-tariff-list-head {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-summary {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tariff-catalog-bar {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-currency-input {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-provider-currency-main strong,
|
||||||
|
.admin-provider-currency-main small {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-tariff-card {
|
.admin-tariff-card {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
@@ -3626,12 +3756,18 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-tariff-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.admin-tariff-actions .admin-btn {
|
.admin-tariff-actions .admin-btn {
|
||||||
flex: 1 1 calc(50% - 4px);
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-tariff-actions .admin-btn[aria-label] {
|
.admin-tariff-actions .admin-btn[aria-label] {
|
||||||
flex: 0 0 auto;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (legacy three-row user-row layout removed in favour of the compact
|
/* (legacy three-row user-row layout removed in favour of the compact
|
||||||
|
|||||||
@@ -1233,6 +1233,7 @@
|
|||||||
"admin_status_limited": "Limited",
|
"admin_status_limited": "Limited",
|
||||||
"admin_status_expired": "Expired",
|
"admin_status_expired": "Expired",
|
||||||
"admin_status_disabled": "Disabled",
|
"admin_status_disabled": "Disabled",
|
||||||
|
"admin_status_not_configured": "Not configured",
|
||||||
"admin_status_bot_only": "Bot Only",
|
"admin_status_bot_only": "Bot Only",
|
||||||
"admin_user_detail_title": "User #{id}",
|
"admin_user_detail_title": "User #{id}",
|
||||||
"admin_user_avatar_open": "Open avatar",
|
"admin_user_avatar_open": "Open avatar",
|
||||||
@@ -1980,6 +1981,9 @@
|
|||||||
"admin_tariff_btn_period": "Period",
|
"admin_tariff_btn_period": "Period",
|
||||||
"admin_tariff_create_subtitle": "The catalog will be saved to JSON after confirmation",
|
"admin_tariff_create_subtitle": "The catalog will be saved to JSON after confirmation",
|
||||||
"admin_tariff_create_title": "New tariff",
|
"admin_tariff_create_title": "New tariff",
|
||||||
|
"admin_tariff_currency_supported": "Available",
|
||||||
|
"admin_tariff_currency_unsupported": "Blocked",
|
||||||
|
"admin_tariff_default_currency": "Payment currency",
|
||||||
"admin_tariff_default_updated": "Default tariff updated",
|
"admin_tariff_default_updated": "Default tariff updated",
|
||||||
"admin_tariff_delete_subtitle": "Tariff {key} will disappear from the sales catalog.",
|
"admin_tariff_delete_subtitle": "Tariff {key} will disappear from the sales catalog.",
|
||||||
"admin_tariff_delete_title": "Delete tariff?",
|
"admin_tariff_delete_title": "Delete tariff?",
|
||||||
@@ -2026,6 +2030,8 @@
|
|||||||
"admin_tariff_premium_subhead": "Premium squads give the user access to faster/premium nodes; their traffic is counted separately from main traffic so it can be limited or sold separately",
|
"admin_tariff_premium_subhead": "Premium squads give the user access to faster/premium nodes; their traffic is counted separately from main traffic so it can be limited or sold separately",
|
||||||
"admin_tariff_premium_topup_subtitle": "Packages that extend the monthly premium limit when the user runs out",
|
"admin_tariff_premium_topup_subtitle": "Packages that extend the monthly premium limit when the user runs out",
|
||||||
"admin_tariff_premium_topup_title": "Premium traffic top-up",
|
"admin_tariff_premium_topup_title": "Premium traffic top-up",
|
||||||
|
"admin_tariff_provider_any_currency": "Any",
|
||||||
|
"admin_tariff_provider_not_declared": "Not declared",
|
||||||
"admin_tariff_pricing_empty": "Add at least one period so the tariff appears in the storefront.",
|
"admin_tariff_pricing_empty": "Add at least one period so the tariff appears in the storefront.",
|
||||||
"admin_tariff_pricing_period_subtitle": "Each row is a separate storefront option: how many months the user pays for and how much it costs",
|
"admin_tariff_pricing_period_subtitle": "Each row is a separate storefront option: how many months the user pays for and how much it costs",
|
||||||
"admin_tariff_pricing_period_title": "Subscription periods and prices",
|
"admin_tariff_pricing_period_title": "Subscription periods and prices",
|
||||||
@@ -2037,6 +2043,16 @@
|
|||||||
"admin_tariff_topup_title": "Traffic top-up over the monthly limit",
|
"admin_tariff_topup_title": "Traffic top-up over the monthly limit",
|
||||||
"admin_tariff_topup_traffic_hint": "For the traffic model, separate top-ups are not needed: packages configured on the Prices tab are the top-ups users can buy again as they run out.",
|
"admin_tariff_topup_traffic_hint": "For the traffic model, separate top-ups are not needed: packages configured on the Prices tab are the top-ups users can buy again as they run out.",
|
||||||
"admin_tariff_visible": "Tariff is visible in the storefront",
|
"admin_tariff_visible": "Tariff is visible in the storefront",
|
||||||
|
"admin_tariffs_catalog_subtitle": "Periods, prices, traffic, and user access.",
|
||||||
|
"admin_tariffs_currency_current": "Current currency",
|
||||||
|
"admin_tariffs_currency_subtitle": "Tariff prices and payment providers are checked against this currency.",
|
||||||
|
"admin_tariffs_currency_title": "Catalog currency",
|
||||||
|
"admin_tariffs_provider_available_count": "Available: {count}",
|
||||||
|
"admin_tariffs_provider_blocked_count": "Unsupported: {count}",
|
||||||
|
"admin_tariffs_provider_empty": "Provider data has not been loaded yet.",
|
||||||
|
"admin_tariffs_provider_enabled_count": "Enabled: {count}",
|
||||||
|
"admin_tariffs_provider_subtitle": "Shows which providers can accept the current catalog currency.",
|
||||||
|
"admin_tariffs_provider_title": "Payment providers",
|
||||||
"admin_tariffs_save_failed": "Failed to save tariffs",
|
"admin_tariffs_save_failed": "Failed to save tariffs",
|
||||||
"admin_tariffs_saved": "Tariffs saved",
|
"admin_tariffs_saved": "Tariffs saved",
|
||||||
"admin_tariffs_title": "Tariff catalog",
|
"admin_tariffs_title": "Tariff catalog",
|
||||||
|
|||||||
@@ -1233,6 +1233,7 @@
|
|||||||
"admin_status_limited": "Ограничен",
|
"admin_status_limited": "Ограничен",
|
||||||
"admin_status_expired": "Истёк",
|
"admin_status_expired": "Истёк",
|
||||||
"admin_status_disabled": "Выключен",
|
"admin_status_disabled": "Выключен",
|
||||||
|
"admin_status_not_configured": "Не настроен",
|
||||||
"admin_status_bot_only": "Только бот",
|
"admin_status_bot_only": "Только бот",
|
||||||
"admin_user_detail_title": "Пользователь #{id}",
|
"admin_user_detail_title": "Пользователь #{id}",
|
||||||
"admin_user_avatar_open": "Открыть аватар",
|
"admin_user_avatar_open": "Открыть аватар",
|
||||||
@@ -1980,6 +1981,9 @@
|
|||||||
"admin_tariff_btn_period": "Период",
|
"admin_tariff_btn_period": "Период",
|
||||||
"admin_tariff_create_subtitle": "Каталог будет сохранён в JSON после подтверждения",
|
"admin_tariff_create_subtitle": "Каталог будет сохранён в JSON после подтверждения",
|
||||||
"admin_tariff_create_title": "Новый тариф",
|
"admin_tariff_create_title": "Новый тариф",
|
||||||
|
"admin_tariff_currency_supported": "Доступен",
|
||||||
|
"admin_tariff_currency_unsupported": "Заблокирован",
|
||||||
|
"admin_tariff_default_currency": "Валюта оплаты",
|
||||||
"admin_tariff_default_updated": "Тариф по умолчанию обновлён",
|
"admin_tariff_default_updated": "Тариф по умолчанию обновлён",
|
||||||
"admin_tariff_delete_subtitle": "Тариф {key} исчезнет из каталога продаж.",
|
"admin_tariff_delete_subtitle": "Тариф {key} исчезнет из каталога продаж.",
|
||||||
"admin_tariff_delete_title": "Удалить тариф?",
|
"admin_tariff_delete_title": "Удалить тариф?",
|
||||||
@@ -2026,6 +2030,8 @@
|
|||||||
"admin_tariff_premium_subhead": "Premium-сквады дают пользователю доступ к более быстрым/премиальным нодам; их трафик считается отдельно от основного, чтобы можно было ограничить или продавать дополнительно",
|
"admin_tariff_premium_subhead": "Premium-сквады дают пользователю доступ к более быстрым/премиальным нодам; их трафик считается отдельно от основного, чтобы можно было ограничить или продавать дополнительно",
|
||||||
"admin_tariff_premium_topup_subtitle": "Пакеты для расширения месячного premium-лимита, когда пользователь его исчерпал",
|
"admin_tariff_premium_topup_subtitle": "Пакеты для расширения месячного premium-лимита, когда пользователь его исчерпал",
|
||||||
"admin_tariff_premium_topup_title": "Докупка premium-трафика",
|
"admin_tariff_premium_topup_title": "Докупка premium-трафика",
|
||||||
|
"admin_tariff_provider_any_currency": "Любая",
|
||||||
|
"admin_tariff_provider_not_declared": "Не задано",
|
||||||
"admin_tariff_pricing_empty": "Добавьте хотя бы один период — без него тариф не появится на витрине.",
|
"admin_tariff_pricing_empty": "Добавьте хотя бы один период — без него тариф не появится на витрине.",
|
||||||
"admin_tariff_pricing_period_subtitle": "Каждая строка — отдельный вариант на витрине: за сколько месяцев пользователь платит и сколько это стоит",
|
"admin_tariff_pricing_period_subtitle": "Каждая строка — отдельный вариант на витрине: за сколько месяцев пользователь платит и сколько это стоит",
|
||||||
"admin_tariff_pricing_period_title": "Периоды подписки и цены",
|
"admin_tariff_pricing_period_title": "Периоды подписки и цены",
|
||||||
@@ -2037,6 +2043,16 @@
|
|||||||
"admin_tariff_topup_title": "Докупка трафика поверх месячного лимита",
|
"admin_tariff_topup_title": "Докупка трафика поверх месячного лимита",
|
||||||
"admin_tariff_topup_traffic_hint": "Для трафиковой модели отдельные «докупки» не нужны — пакеты, которые вы настроили на вкладке «Цены», и являются докупками: пользователь покупает их повторно по мере исчерпания.",
|
"admin_tariff_topup_traffic_hint": "Для трафиковой модели отдельные «докупки» не нужны — пакеты, которые вы настроили на вкладке «Цены», и являются докупками: пользователь покупает их повторно по мере исчерпания.",
|
||||||
"admin_tariff_visible": "Тариф виден на витрине",
|
"admin_tariff_visible": "Тариф виден на витрине",
|
||||||
|
"admin_tariffs_catalog_subtitle": "Периоды, цены, трафик и доступы пользователей.",
|
||||||
|
"admin_tariffs_currency_current": "Текущая валюта",
|
||||||
|
"admin_tariffs_currency_subtitle": "Цены тарифов и платёжные провайдеры проверяются по этой валюте.",
|
||||||
|
"admin_tariffs_currency_title": "Валюта каталога",
|
||||||
|
"admin_tariffs_provider_available_count": "Доступно: {count}",
|
||||||
|
"admin_tariffs_provider_blocked_count": "Не подходят: {count}",
|
||||||
|
"admin_tariffs_provider_empty": "Данные по провайдерам пока не загружены.",
|
||||||
|
"admin_tariffs_provider_enabled_count": "Включено: {count}",
|
||||||
|
"admin_tariffs_provider_subtitle": "Здесь видно, какие провайдеры смогут принять текущую валюту каталога.",
|
||||||
|
"admin_tariffs_provider_title": "Платёжные провайдеры",
|
||||||
"admin_tariffs_save_failed": "Ошибка сохранения тарифов",
|
"admin_tariffs_save_failed": "Ошибка сохранения тарифов",
|
||||||
"admin_tariffs_saved": "Тарифы сохранены",
|
"admin_tariffs_saved": "Тарифы сохранены",
|
||||||
"admin_tariffs_title": "Каталог тарифов",
|
"admin_tariffs_title": "Каталог тарифов",
|
||||||
|
|||||||
Reference in New Issue
Block a user