chore: standardize web controls
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import { Trash2 } from "$components/ui/icons.js";
|
import { Trash2 } from "$components/ui/icons.js";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import Dialog from "$components/ui/dialog.svelte";
|
import Dialog from "$components/ui/dialog.svelte";
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
<div class="admin-form" data-dialog-content>
|
<div class="admin-form" data-dialog-content>
|
||||||
<div class="admin-dialog-form-section">
|
<div class="admin-dialog-form-section">
|
||||||
<AdminField label={at("ad_label_source", {}, "Источник")}>
|
<AdminField label={at("ad_label_source", {}, "Источник")}>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="telegram_ads"
|
placeholder="telegram_ads"
|
||||||
@@ -118,7 +119,7 @@
|
|||||||
label={at("ad_label_param", {}, "start-параметр")}
|
label={at("ad_label_param", {}, "start-параметр")}
|
||||||
hint={at("ad_hint_param", {}, "Передаётся в /start, должен быть уникален")}
|
hint={at("ad_hint_param", {}, "Передаётся в /start, должен быть уникален")}
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="ads_summer25"
|
placeholder="ads_summer25"
|
||||||
@@ -129,7 +130,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="admin-dialog-form-section">
|
<div class="admin-dialog-form-section">
|
||||||
<AdminField label={at("ad_label_cost", {}, "Стоимость, RUB")}>
|
<AdminField label={at("ad_label_cost", {}, "Стоимость, RUB")}>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
AdminEmptyState,
|
AdminEmptyState,
|
||||||
AdminSelect,
|
AdminSelect,
|
||||||
} from "$components/patterns/admin/index.js";
|
} from "$components/patterns/admin/index.js";
|
||||||
import { Checkbox } from "$components/ui/index.js";
|
import { Checkbox, ColorInput, FileInput, Input, RangeInput } from "$components/ui/index.js";
|
||||||
import { Switch } from "$components/ui/primitives.js";
|
import { Switch } from "$components/ui/primitives.js";
|
||||||
import { getContext, onDestroy, onMount } from "svelte";
|
import { getContext, onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
@@ -444,10 +444,9 @@
|
|||||||
|
|
||||||
<div class="appearance-controls">
|
<div class="appearance-controls">
|
||||||
<section class="appearance-control-card">
|
<section class="appearance-control-card">
|
||||||
<input
|
<FileInput
|
||||||
bind:this={logoFileInput}
|
bind:element={logoFileInput}
|
||||||
class="appearance-file-input"
|
class="appearance-file-input"
|
||||||
type="file"
|
|
||||||
accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon"
|
accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon"
|
||||||
onchange={handleLogoFileChange}
|
onchange={handleLogoFileChange}
|
||||||
/>
|
/>
|
||||||
@@ -461,7 +460,7 @@
|
|||||||
{at("appearance_logo_upload_file", {}, "Загрузить файл")}
|
{at("appearance_logo_upload_file", {}, "Загрузить файл")}
|
||||||
</AdminButton>
|
</AdminButton>
|
||||||
<div class="appearance-url-row">
|
<div class="appearance-url-row">
|
||||||
<input
|
<Input
|
||||||
class="input appearance-control"
|
class="input appearance-control"
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="https://example.com/logo.png"
|
placeholder="https://example.com/logo.png"
|
||||||
@@ -490,7 +489,7 @@
|
|||||||
<span>{at("appearance_use_emoji_logo", {}, "Использовать emoji-логотип")}</span>
|
<span>{at("appearance_use_emoji_logo", {}, "Использовать emoji-логотип")}</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="appearance-emoji-grid">
|
<div class="appearance-emoji-grid">
|
||||||
<input
|
<Input
|
||||||
class="input appearance-control"
|
class="input appearance-control"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="8"
|
maxlength="8"
|
||||||
@@ -547,10 +546,9 @@
|
|||||||
>{at("appearance_use_custom_favicon", {}, "Использовать отдельную favicon")}</span
|
>{at("appearance_use_custom_favicon", {}, "Использовать отдельную favicon")}</span
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<FileInput
|
||||||
bind:this={faviconFileInput}
|
bind:element={faviconFileInput}
|
||||||
class="appearance-file-input"
|
class="appearance-file-input"
|
||||||
type="file"
|
|
||||||
accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon,.ico"
|
accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon,.ico"
|
||||||
onchange={handleFaviconFileChange}
|
onchange={handleFaviconFileChange}
|
||||||
/>
|
/>
|
||||||
@@ -564,7 +562,7 @@
|
|||||||
{at("appearance_favicon_upload_file", {}, "Загрузить favicon")}
|
{at("appearance_favicon_upload_file", {}, "Загрузить favicon")}
|
||||||
</AdminButton>
|
</AdminButton>
|
||||||
<div class="appearance-url-row">
|
<div class="appearance-url-row">
|
||||||
<input
|
<Input
|
||||||
class="input appearance-control"
|
class="input appearance-control"
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="https://example.com/icon.png"
|
placeholder="https://example.com/icon.png"
|
||||||
@@ -657,18 +655,17 @@
|
|||||||
</span>
|
</span>
|
||||||
<label class="admin-theme-card-option appearance-color-row">
|
<label class="admin-theme-card-option appearance-color-row">
|
||||||
<span>{at("appearance_theme_accent", {}, "Accent")}</span>
|
<span>{at("appearance_theme_accent", {}, "Accent")}</span>
|
||||||
<input
|
<ColorInput
|
||||||
class="admin-color"
|
class={`admin-color${!isThemeAccentSet(theme) ? " is-empty" : ""}`}
|
||||||
class:is-empty={!isThemeAccentSet(theme)}
|
|
||||||
type="color"
|
|
||||||
value={pickerHex(theme.tokens?.accent)}
|
value={pickerHex(theme.tokens?.accent)}
|
||||||
|
ariaLabel={at("appearance_theme_accent", {}, "Accent")}
|
||||||
title={isThemeAccentSet(theme)
|
title={isThemeAccentSet(theme)
|
||||||
? theme.tokens?.accent
|
? theme.tokens?.accent
|
||||||
: at("appearance_theme_accent_empty", {}, "Не задан")}
|
: at("appearance_theme_accent_empty", {}, "Не задан")}
|
||||||
onclick={() => openThemeAccentPicker(theme)}
|
onclick={() => openThemeAccentPicker(theme)}
|
||||||
oninput={(event) => setThemeAccent(theme, event.currentTarget.value)}
|
oninput={(event) => setThemeAccent(theme, event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input appearance-color-text"
|
class="input appearance-color-text"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("appearance_theme_accent_placeholder", {}, "Не задан")}
|
placeholder={at("appearance_theme_accent_placeholder", {}, "Не задан")}
|
||||||
@@ -693,17 +690,17 @@
|
|||||||
"Логотип на главной и входе"
|
"Логотип на главной и входе"
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<input
|
<RangeInput
|
||||||
class="appearance-logo-scale-range"
|
class="appearance-logo-scale-range"
|
||||||
type="range"
|
|
||||||
min="50"
|
min="50"
|
||||||
max="300"
|
max="300"
|
||||||
step="5"
|
step="5"
|
||||||
|
ariaLabel={at("appearance_theme_home_logo_scale", {}, "Home logo scale")}
|
||||||
value={homeLogoScale(theme)}
|
value={homeLogoScale(theme)}
|
||||||
oninput={(event) => setThemeHomeLogoScale(theme, event.currentTarget.value)}
|
onValueChange={(value) => setThemeHomeLogoScale(theme, value)}
|
||||||
/>
|
/>
|
||||||
<span class="appearance-logo-scale-value">
|
<span class="appearance-logo-scale-value">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="50"
|
min="50"
|
||||||
@@ -814,7 +811,7 @@
|
|||||||
background: color-mix(in srgb, var(--admin-surface-2) 40%, transparent);
|
background: color-mix(in srgb, var(--admin-surface-2) 40%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.appearance-file-input {
|
:global(.appearance-file-input) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,7 +946,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appearance-color-text {
|
:global(.appearance-color-text) {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -959,7 +956,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appearance-logo-scale-range {
|
:global(.appearance-logo-scale-range) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
accent-color: var(--accent);
|
accent-color: var(--accent);
|
||||||
}
|
}
|
||||||
@@ -971,14 +968,14 @@
|
|||||||
color: var(--admin-text);
|
color: var(--admin-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.appearance-logo-scale-value .input {
|
:global(.appearance-logo-scale-value .input) {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-color.is-empty {
|
:global(.admin-color.is-empty) {
|
||||||
opacity: 0.42;
|
opacity: 0.42;
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
AdminTable,
|
AdminTable,
|
||||||
AdminTableSkeleton,
|
AdminTableSkeleton,
|
||||||
} from "$components/patterns/admin/index.js";
|
} from "$components/patterns/admin/index.js";
|
||||||
import { Checkbox, RadioGroup, RadioGroupItem } from "$components/ui/index.js";
|
import { Checkbox, FileInput, RadioGroup, RadioGroupItem } from "$components/ui/index.js";
|
||||||
import {
|
import {
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Database,
|
Database,
|
||||||
@@ -178,10 +178,9 @@
|
|||||||
? at("backups_uploading", {}, "Загрузка...")
|
? at("backups_uploading", {}, "Загрузка...")
|
||||||
: at("backups_upload", {}, "Загрузить архив")}
|
: at("backups_upload", {}, "Загрузить архив")}
|
||||||
</AdminButton>
|
</AdminButton>
|
||||||
<input
|
<FileInput
|
||||||
bind:this={fileInput}
|
bind:element={fileInput}
|
||||||
class="backups-file-input"
|
class="backups-file-input"
|
||||||
type="file"
|
|
||||||
accept=".zip,application/zip"
|
accept=".zip,application/zip"
|
||||||
on:change={uploadSelectedFile}
|
on:change={uploadSelectedFile}
|
||||||
/>
|
/>
|
||||||
@@ -362,7 +361,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backups-file-input {
|
:global(.backups-file-input) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Textarea } from "$components/ui/index.js";
|
||||||
import { Send } from "$components/ui/icons.js";
|
import { Send } from "$components/ui/icons.js";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { Label } from "$components/ui/primitives.js";
|
import { Label } from "$components/ui/primitives.js";
|
||||||
@@ -31,12 +32,12 @@
|
|||||||
<Label.Root class="admin-field-label">
|
<Label.Root class="admin-field-label">
|
||||||
<span>{at("broadcast_label_text", {}, "Текст сообщения")}</span>
|
<span>{at("broadcast_label_text", {}, "Текст сообщения")}</span>
|
||||||
<small>{at("broadcast_hint_text", {}, "Поддерживается HTML-разметка Telegram")}</small>
|
<small>{at("broadcast_hint_text", {}, "Поддерживается HTML-разметка Telegram")}</small>
|
||||||
<textarea
|
<Textarea
|
||||||
class="admin-textarea"
|
class="admin-textarea"
|
||||||
rows="6"
|
rows="6"
|
||||||
value={broadcastText}
|
value={broadcastText}
|
||||||
on:input={(e) => broadcastStore.updateField({ broadcastText: e.target.value })}
|
on:input={(e) => broadcastStore.updateField({ broadcastText: e.target.value })}
|
||||||
></textarea>
|
/>
|
||||||
</Label.Root>
|
</Label.Root>
|
||||||
<div style="display:flex; gap:8px; align-items:center;">
|
<div style="display:flex; gap:8px; align-items:center;">
|
||||||
<AdminButton
|
<AdminButton
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import {
|
import {
|
||||||
AdminButton,
|
AdminButton,
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
|
|
||||||
<div class="admin-toolbar admin-toolbar-card">
|
<div class="admin-toolbar admin-toolbar-card">
|
||||||
<div class="admin-toolbar-search admin-toolbar-search-actions">
|
<div class="admin-toolbar-search admin-toolbar-search-actions">
|
||||||
<input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
class="input"
|
class="input"
|
||||||
placeholder={at("logs_user_filter_placeholder", {}, "Фильтр по ID пользователя")}
|
placeholder={at("logs_user_filter_placeholder", {}, "Фильтр по ID пользователя")}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import { Trash2 } from "$components/ui/icons.js";
|
import { Trash2 } from "$components/ui/icons.js";
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import Dialog from "$components/ui/dialog.svelte";
|
import Dialog from "$components/ui/dialog.svelte";
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
<div class="admin-form" data-dialog-content>
|
<div class="admin-form" data-dialog-content>
|
||||||
<div class="admin-dialog-form-section">
|
<div class="admin-dialog-form-section">
|
||||||
<AdminField label={at("promo_label_code", {}, "Код")}>
|
<AdminField label={at("promo_label_code", {}, "Код")}>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
class="input"
|
class="input"
|
||||||
value={promoDraft.code}
|
value={promoDraft.code}
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
<div class="admin-dialog-form-section">
|
<div class="admin-dialog-form-section">
|
||||||
<div class="admin-form-row-2">
|
<div class="admin-form-row-2">
|
||||||
<AdminField label={at("promo_label_bonus_days", {}, "Бонус (дней)")}>
|
<AdminField label={at("promo_label_bonus_days", {}, "Бонус (дней)")}>
|
||||||
<input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
class="input"
|
class="input"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -131,7 +132,7 @@
|
|||||||
/>
|
/>
|
||||||
</AdminField>
|
</AdminField>
|
||||||
<AdminField label={at("promo_label_max_activations", {}, "Макс. активаций")}>
|
<AdminField label={at("promo_label_max_activations", {}, "Макс. активаций")}>
|
||||||
<input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
class="input"
|
class="input"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -141,7 +142,7 @@
|
|||||||
</AdminField>
|
</AdminField>
|
||||||
</div>
|
</div>
|
||||||
<AdminField label={at("promo_label_valid_days", {}, "Срок действия (дней от текущего)")}>
|
<AdminField label={at("promo_label_valid_days", {}, "Срок действия (дней от текущего)")}>
|
||||||
<input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
class="input"
|
class="input"
|
||||||
min="1"
|
min="1"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { ColorInput, FileInput, Input, Textarea } from "$components/ui/index.js";
|
||||||
import {
|
import {
|
||||||
Check,
|
Check,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
@@ -515,13 +516,13 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{:else if field.type === "color"}
|
{:else if field.type === "color"}
|
||||||
<input
|
<ColorInput
|
||||||
class="admin-color"
|
class="admin-color"
|
||||||
type="color"
|
|
||||||
value={valueFor(field) || "#00fe7a"}
|
value={valueFor(field) || "#00fe7a"}
|
||||||
|
ariaLabel={fieldLabelText(field)}
|
||||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
value={valueFor(field) || ""}
|
value={valueFor(field) || ""}
|
||||||
@@ -560,7 +561,7 @@
|
|||||||
onValueChange={(value) => settingsStore.markDirty(field.key, value)}
|
onValueChange={(value) => settingsStore.markDirty(field.key, value)}
|
||||||
/>
|
/>
|
||||||
{:else if field.type === "int" || field.type === "float"}
|
{:else if field.type === "int" || field.type === "float"}
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
step={field.type === "float" ? "0.1" : "1"}
|
step={field.type === "float" ? "0.1" : "1"}
|
||||||
@@ -571,19 +572,18 @@
|
|||||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
{:else if field.type === "text"}
|
{:else if field.type === "text"}
|
||||||
<textarea
|
<Textarea
|
||||||
class="admin-setting-textarea"
|
class="admin-setting-textarea"
|
||||||
rows="4"
|
rows="4"
|
||||||
placeholder={fieldPlaceholderText(field)}
|
placeholder={fieldPlaceholderText(field)}
|
||||||
value={valueFor(field) ?? ""}
|
value={valueFor(field) ?? ""}
|
||||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||||
></textarea>
|
/>
|
||||||
{:else if field.type === "json"}
|
{:else if field.type === "json"}
|
||||||
<div class="admin-json-toolbar">
|
<div class="admin-json-toolbar">
|
||||||
<input
|
<FileInput
|
||||||
id={"json-file-" + field.key}
|
id={"json-file-" + field.key}
|
||||||
class="admin-json-file-input"
|
class="admin-json-file-input"
|
||||||
type="file"
|
|
||||||
accept="application/json,.json"
|
accept="application/json,.json"
|
||||||
onchange={(event) => handleJsonFile(field, event)}
|
onchange={(event) => handleJsonFile(field, event)}
|
||||||
/>
|
/>
|
||||||
@@ -605,16 +605,16 @@
|
|||||||
</AdminButton>
|
</AdminButton>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<Textarea
|
||||||
class="admin-setting-textarea admin-setting-json-textarea"
|
class="admin-setting-textarea admin-setting-json-textarea"
|
||||||
rows="10"
|
rows="10"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
placeholder={fieldPlaceholderText(field)}
|
placeholder={fieldPlaceholderText(field)}
|
||||||
value={valueFor(field) ?? ""}
|
value={valueFor(field) ?? ""}
|
||||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||||
></textarea>
|
/>
|
||||||
{:else if field.secret}
|
{:else if field.secret}
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type={revealed ? "text" : "password"}
|
type={revealed ? "text" : "password"}
|
||||||
placeholder={secretPlaceholder(field)}
|
placeholder={secretPlaceholder(field)}
|
||||||
@@ -631,7 +631,7 @@
|
|||||||
{#if revealed}<EyeOff size={13} />{:else}<Eye size={13} />{/if}
|
{#if revealed}<EyeOff size={13} />{:else}<Eye size={13} />{/if}
|
||||||
</AdminButton>
|
</AdminButton>
|
||||||
{:else}
|
{:else}
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={fieldPlaceholderText(field)}
|
placeholder={fieldPlaceholderText(field)}
|
||||||
@@ -816,7 +816,7 @@
|
|||||||
<div class="admin-icon-picker-toolbar">
|
<div class="admin-icon-picker-toolbar">
|
||||||
<label class="admin-icon-picker-search">
|
<label class="admin-icon-picker-search">
|
||||||
<Search size={15} />
|
<Search size={15} />
|
||||||
<input
|
<Input
|
||||||
bind:value={iconPickerSearch}
|
bind:value={iconPickerSearch}
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import { TicketMessageBubble } from "$components/patterns/webapp/index.js";
|
import { TicketMessageBubble } from "$components/patterns/webapp/index.js";
|
||||||
import Dialog from "$components/ui/dialog.svelte";
|
import Dialog from "$components/ui/dialog.svelte";
|
||||||
import { Search } from "$components/ui/icons.js";
|
import { Search } from "$components/ui/icons.js";
|
||||||
import { ScrollArea, Skeleton } from "$components/ui/index.js";
|
import { Input, ScrollArea, Skeleton } from "$components/ui/index.js";
|
||||||
|
|
||||||
export let at = (key) => key;
|
export let at = (key) => key;
|
||||||
export let initialTicketId = null;
|
export let initialTicketId = null;
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
<div class="support-admin-toolbar admin-toolbar-card">
|
<div class="support-admin-toolbar admin-toolbar-card">
|
||||||
<label class="support-admin-search">
|
<label class="support-admin-search">
|
||||||
<Search size={16} />
|
<Search size={16} />
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={at("support_search", {}, "Поиск")}
|
placeholder={at("support_search", {}, "Поиск")}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import { Tabs, Switch, Label } from "$components/ui/primitives.js";
|
import { Tabs, Switch, Label } from "$components/ui/primitives.js";
|
||||||
import Dialog from "$components/ui/dialog.svelte";
|
import Dialog from "$components/ui/dialog.svelte";
|
||||||
import { Plus, Save, Trash2, X } from "$components/ui/icons.js";
|
import { Plus, Save, Trash2, X } from "$components/ui/icons.js";
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
"Латиницей, без пробелов. Используется в платежах и подписках, менять после публикации не рекомендуется"
|
"Латиницей, без пробелов. Используется в платежах и подписках, менять после публикации не рекомендуется"
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="standard"
|
placeholder="standard"
|
||||||
@@ -127,7 +128,7 @@
|
|||||||
<div class="admin-form-row admin-form-row-2">
|
<div class="admin-form-row admin-form-row-2">
|
||||||
<Label.Root class="admin-field-label">
|
<Label.Root class="admin-field-label">
|
||||||
<span>{at("tariff_label_name_ru", {}, "Название · RU")}</span>
|
<span>{at("tariff_label_name_ru", {}, "Название · RU")}</span>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("tariff_placeholder_name_ru", {}, "Стандарт")}
|
placeholder={at("tariff_placeholder_name_ru", {}, "Стандарт")}
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
</Label.Root>
|
</Label.Root>
|
||||||
<Label.Root class="admin-field-label">
|
<Label.Root class="admin-field-label">
|
||||||
<span>{at("tariff_label_name_en", {}, "Название · EN")}</span>
|
<span>{at("tariff_label_name_en", {}, "Название · EN")}</span>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("tariff_placeholder_name_en", {}, "Standard")}
|
placeholder={at("tariff_placeholder_name_en", {}, "Standard")}
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
<div class="admin-form-row admin-form-row-2">
|
<div class="admin-form-row admin-form-row-2">
|
||||||
<Label.Root class="admin-field-label">
|
<Label.Root class="admin-field-label">
|
||||||
<span>{at("tariff_label_desc_ru", {}, "Описание · RU")}</span>
|
<span>{at("tariff_label_desc_ru", {}, "Описание · RU")}</span>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("tariff_placeholder_desc_ru", {}, "Базовый набор серверов")}
|
placeholder={at("tariff_placeholder_desc_ru", {}, "Базовый набор серверов")}
|
||||||
@@ -157,7 +158,7 @@
|
|||||||
</Label.Root>
|
</Label.Root>
|
||||||
<Label.Root class="admin-field-label">
|
<Label.Root class="admin-field-label">
|
||||||
<span>{at("tariff_label_desc_en", {}, "Описание · EN")}</span>
|
<span>{at("tariff_label_desc_en", {}, "Описание · EN")}</span>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("tariff_placeholder_desc_en", {}, "Base server pool")}
|
placeholder={at("tariff_placeholder_desc_en", {}, "Base server pool")}
|
||||||
@@ -211,7 +212,7 @@
|
|||||||
"Сколько устройств может одновременно использовать подписку. Пусто — взять значение из .env, 0 — без ограничений"
|
"Сколько устройств может одновременно использовать подписку. Пусто — взять значение из .env, 0 — без ограничений"
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -229,7 +230,7 @@
|
|||||||
"Сколько GB включено в тариф на каждый месяц. 0 — безлимитный трафика. Сверху можно докупать пакеты на вкладке «Докупки»"
|
"Сколько GB включено в тариф на каждый месяц. 0 — безлимитный трафика. Сверху можно докупать пакеты на вкладке «Докупки»"
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -248,7 +249,7 @@
|
|||||||
"По этому курсу остаток подписки пересчитывается в гигабайты при переходе пользователя с тарифа «Период» на «Трафик»"
|
"По этому курсу остаток подписки пересчитывается в гигабайты при переходе пользователя с тарифа «Период» на «Трафик»"
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -287,7 +288,7 @@
|
|||||||
"Эта строка заменит «Premium-серверы» в кабинете, докупках и карточках лимитов."
|
"Эта строка заменит «Premium-серверы» в кабинете, докупках и карточках лимитов."
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("tariff_placeholder_premium_name_ru", {}, "Premium-серверы")}
|
placeholder={at("tariff_placeholder_premium_name_ru", {}, "Premium-серверы")}
|
||||||
@@ -303,7 +304,7 @@
|
|||||||
"Опционально для английского интерфейса."
|
"Опционально для английского интерфейса."
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={at("tariff_placeholder_premium_name_en", {}, "Premium servers")}
|
placeholder={at("tariff_placeholder_premium_name_en", {}, "Premium servers")}
|
||||||
@@ -359,7 +360,7 @@
|
|||||||
"Сколько GB через premium-сквады включено в тариф каждый месяц. 0 или пусто — отдельного premium-лимита нет (premium-нодами можно пользоваться без ограничения)"
|
"Сколько GB через premium-сквады включено в тариф каждый месяц. 0 или пусто — отдельного premium-лимита нет (premium-нодами можно пользоваться без ограничения)"
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -409,7 +410,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.premiumTopupRubRows as row, index}
|
{#each tariffDraft.premiumTopupRubRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
@@ -418,7 +419,7 @@
|
|||||||
bind:value={row.gb}
|
bind:value={row.gb}
|
||||||
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
|
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -449,7 +450,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.premiumTopupStarsRows as row, index}
|
{#each tariffDraft.premiumTopupStarsRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
@@ -458,7 +459,7 @@
|
|||||||
bind:value={row.gb}
|
bind:value={row.gb}
|
||||||
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
|
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -533,7 +534,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{#each tariffDraft.periodRows as row, index}
|
{#each tariffDraft.periodRows as row, index}
|
||||||
<div class="admin-row-editor-line admin-row-editor-6">
|
<div class="admin-row-editor-line admin-row-editor-6">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -541,7 +542,7 @@
|
|||||||
bind:value={row.months}
|
bind:value={row.months}
|
||||||
aria-label={at("tariff_col_period_months", {}, "Срок (месяцы)")}
|
aria-label={at("tariff_col_period_months", {}, "Срок (месяцы)")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -550,7 +551,7 @@
|
|||||||
bind:value={row.rub}
|
bind:value={row.rub}
|
||||||
aria-label={at("tariff_label_price_rub", {}, "Цена в рублях")}
|
aria-label={at("tariff_label_price_rub", {}, "Цена в рублях")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -559,7 +560,7 @@
|
|||||||
bind:value={row.stars}
|
bind:value={row.stars}
|
||||||
aria-label={at("tariff_label_price_stars", {}, "Цена в Telegram Stars")}
|
aria-label={at("tariff_label_price_stars", {}, "Цена в Telegram Stars")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -568,7 +569,7 @@
|
|||||||
bind:value={row.referral_inviter}
|
bind:value={row.referral_inviter}
|
||||||
aria-label={at("tariff_label_ref_inviter", {}, "Бонус приглашающему")}
|
aria-label={at("tariff_label_ref_inviter", {}, "Бонус приглашающему")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -629,7 +630,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.trafficRubRows as row, index}
|
{#each tariffDraft.trafficRubRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
@@ -638,7 +639,7 @@
|
|||||||
bind:value={row.gb}
|
bind:value={row.gb}
|
||||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -669,7 +670,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.trafficStarsRows as row, index}
|
{#each tariffDraft.trafficStarsRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
@@ -678,7 +679,7 @@
|
|||||||
bind:value={row.gb}
|
bind:value={row.gb}
|
||||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -743,7 +744,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.topupRubRows as row, index}
|
{#each tariffDraft.topupRubRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
@@ -752,7 +753,7 @@
|
|||||||
bind:value={row.gb}
|
bind:value={row.gb}
|
||||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -783,7 +784,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.topupStarsRows as row, index}
|
{#each tariffDraft.topupStarsRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
@@ -792,7 +793,7 @@
|
|||||||
bind:value={row.gb}
|
bind:value={row.gb}
|
||||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -867,7 +868,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.hwidRubRows as row, index}
|
{#each tariffDraft.hwidRubRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -880,7 +881,7 @@
|
|||||||
"Сколько устройств добавляет пакет"
|
"Сколько устройств добавляет пакет"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -911,7 +912,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each tariffDraft.hwidStarsRows as row, index}
|
{#each tariffDraft.hwidStarsRows as row, index}
|
||||||
<div class="admin-row-editor-line">
|
<div class="admin-row-editor-line">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -924,7 +925,7 @@
|
|||||||
"Сколько устройств добавляет пакет"
|
"Сколько устройств добавляет пакет"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
@@ -403,7 +404,7 @@
|
|||||||
<code>TRIAL_DURATION_DAYS</code>
|
<code>TRIAL_DURATION_DAYS</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-setting-control">
|
<div class="admin-setting-control">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -439,7 +440,7 @@
|
|||||||
<code>TRIAL_TRAFFIC_LIMIT_GB</code>
|
<code>TRIAL_TRAFFIC_LIMIT_GB</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-setting-control">
|
<div class="admin-setting-control">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -591,7 +592,7 @@
|
|||||||
onValueChange={handleTrialSquadSelect}
|
onValueChange={handleTrialSquadSelect}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={valueForKey("USER_SQUAD_UUIDS", settingsDirty, settingsFieldMap) ||
|
placeholder={valueForKey("USER_SQUAD_UUIDS", settingsDirty, settingsFieldMap) ||
|
||||||
@@ -837,7 +838,7 @@
|
|||||||
<Switch.Thumb class="admin-switch-thumb" />
|
<Switch.Thumb class="admin-switch-thumb" />
|
||||||
</Switch.Root>
|
</Switch.Root>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -845,7 +846,7 @@
|
|||||||
value={valueForKey(rubKey, settingsDirty, settingsFieldMap)}
|
value={valueForKey(rubKey, settingsDirty, settingsFieldMap)}
|
||||||
oninput={(event) => setSetting(rubKey, event.currentTarget.value)}
|
oninput={(event) => setSetting(rubKey, event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -853,7 +854,7 @@
|
|||||||
value={valueForKey(starsKey, settingsDirty, settingsFieldMap)}
|
value={valueForKey(starsKey, settingsDirty, settingsFieldMap)}
|
||||||
oninput={(event) => setSetting(starsKey, event.currentTarget.value)}
|
oninput={(event) => setSetting(starsKey, event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -861,7 +862,7 @@
|
|||||||
value={valueForKey(inviterKey, settingsDirty, settingsFieldMap)}
|
value={valueForKey(inviterKey, settingsDirty, settingsFieldMap)}
|
||||||
oninput={(event) => setSetting(inviterKey, event.currentTarget.value)}
|
oninput={(event) => setSetting(inviterKey, event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -877,7 +878,7 @@
|
|||||||
<label class="admin-field-label admin-field-label-compact">
|
<label class="admin-field-label admin-field-label-compact">
|
||||||
<span>{at("tariffs_legacy_traffic_packages", {}, "Traffic packages")}</span>
|
<span>{at("tariffs_legacy_traffic_packages", {}, "Traffic packages")}</span>
|
||||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
value={valueForKey("TRAFFIC_PACKAGES", settingsDirty, settingsFieldMap)}
|
value={valueForKey("TRAFFIC_PACKAGES", settingsDirty, settingsFieldMap)}
|
||||||
@@ -889,7 +890,7 @@
|
|||||||
>{at("tariffs_legacy_stars_traffic_packages", {}, "Traffic packages, Stars")}</span
|
>{at("tariffs_legacy_stars_traffic_packages", {}, "Traffic packages, Stars")}</span
|
||||||
>
|
>
|
||||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
value={valueForKey("STARS_TRAFFIC_PACKAGES", settingsDirty, settingsFieldMap)}
|
value={valueForKey("STARS_TRAFFIC_PACKAGES", settingsDirty, settingsFieldMap)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input, Textarea } from "$components/ui/index.js";
|
||||||
import { ChevronRight, Languages, Plus, Search, X } from "$components/ui/icons.js";
|
import { ChevronRight, Languages, Plus, Search, X } from "$components/ui/icons.js";
|
||||||
import { AdminBadge, AdminButton, AdminEmptyState } from "$components/patterns/admin/index.js";
|
import { AdminBadge, AdminButton, AdminEmptyState } from "$components/patterns/admin/index.js";
|
||||||
import { getContext, onDestroy, onMount } from "svelte";
|
import { getContext, onDestroy, onMount } from "svelte";
|
||||||
@@ -396,7 +397,7 @@
|
|||||||
|
|
||||||
{#if expanded}
|
{#if expanded}
|
||||||
<div class="admin-translation-locale-body" transition:slide={{ duration: 130 }}>
|
<div class="admin-translation-locale-body" transition:slide={{ duration: 130 }}>
|
||||||
<textarea
|
<Textarea
|
||||||
class="admin-setting-textarea admin-translation-textarea"
|
class="admin-setting-textarea admin-translation-textarea"
|
||||||
rows="3"
|
rows="3"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
@@ -404,7 +405,7 @@
|
|||||||
value={localeValue(item, lang, dirtyEntry)}
|
value={localeValue(item, lang, dirtyEntry)}
|
||||||
oninput={(event) =>
|
oninput={(event) =>
|
||||||
translationsStore.markDirty(lang, item.key, event.currentTarget.value)}
|
translationsStore.markDirty(lang, item.key, event.currentTarget.value)}
|
||||||
></textarea>
|
/>
|
||||||
<div class="admin-translation-base">
|
<div class="admin-translation-base">
|
||||||
<small>{baseKind(item, lang)}</small>
|
<small>{baseKind(item, lang)}</small>
|
||||||
<span title={baseValue(item, lang)}>{baseValue(item, lang) || "-"}</span>
|
<span title={baseValue(item, lang)}>{baseValue(item, lang) || "-"}</span>
|
||||||
@@ -457,7 +458,7 @@
|
|||||||
<div class="admin-translations-toolbar">
|
<div class="admin-translations-toolbar">
|
||||||
<label class="admin-translations-search">
|
<label class="admin-translations-search">
|
||||||
<Search size={15} />
|
<Search size={15} />
|
||||||
<input
|
<Input
|
||||||
bind:value={search}
|
bind:value={search}
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -502,7 +503,7 @@
|
|||||||
addLanguage();
|
addLanguage();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
bind:value={newLanguageCode}
|
bind:value={newLanguageCode}
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Label, Separator, Tabs } from "$components/ui/primitives.js";
|
import { Label, Separator, Tabs } from "$components/ui/primitives.js";
|
||||||
import { Checkbox } from "$components/ui/index.js";
|
import { Checkbox, Input, Textarea } from "$components/ui/index.js";
|
||||||
import Dialog from "$components/ui/dialog.svelte";
|
import Dialog from "$components/ui/dialog.svelte";
|
||||||
import {
|
import {
|
||||||
AdminBadge,
|
AdminBadge,
|
||||||
@@ -632,7 +632,7 @@
|
|||||||
<Label.Root class="admin-field-label admin-extend-field">
|
<Label.Root class="admin-field-label admin-extend-field">
|
||||||
<span>{at("user_label_extend", {}, "Продлить подписку")}</span>
|
<span>{at("user_label_extend", {}, "Продлить подписку")}</span>
|
||||||
<div class="admin-extend-control">
|
<div class="admin-extend-control">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -662,7 +662,7 @@
|
|||||||
<span>{at("user_premium_override_bonus", {}, "Доп. премиум-трафик, GB")}</span
|
<span>{at("user_premium_override_bonus", {}, "Доп. премиум-трафик, GB")}</span
|
||||||
>
|
>
|
||||||
<small>{at("user_premium_override_bonus_hint", {}, "")}</small>
|
<small>{at("user_premium_override_bonus_hint", {}, "")}</small>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -741,7 +741,7 @@
|
|||||||
>{at("user_regular_override_bonus", {}, "Доп. основной трафик, GB")}</span
|
>{at("user_regular_override_bonus", {}, "Доп. основной трафик, GB")}</span
|
||||||
>
|
>
|
||||||
<small>{at("user_regular_override_bonus_hint", {}, "")}</small>
|
<small>{at("user_regular_override_bonus_hint", {}, "")}</small>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -837,7 +837,7 @@
|
|||||||
<Label.Root class="admin-field-label admin-extend-field">
|
<Label.Root class="admin-field-label admin-extend-field">
|
||||||
<span>{at("user_traffic_grant_gb", {}, "ГБ к выдаче")}</span>
|
<span>{at("user_traffic_grant_gb", {}, "ГБ к выдаче")}</span>
|
||||||
<div class="admin-extend-control">
|
<div class="admin-extend-control">
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -869,12 +869,12 @@
|
|||||||
"Поддерживается HTML-разметка Telegram"
|
"Поддерживается HTML-разметка Telegram"
|
||||||
)}</small
|
)}</small
|
||||||
>
|
>
|
||||||
<textarea
|
<Textarea
|
||||||
class="admin-textarea"
|
class="admin-textarea"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder={at("user_placeholder_msg", {}, "Текст сообщения")}
|
placeholder={at("user_placeholder_msg", {}, "Текст сообщения")}
|
||||||
bind:value={$usersStore.userMessageDraft}
|
bind:value={$usersStore.userMessageDraft}
|
||||||
></textarea>
|
/>
|
||||||
</Label.Root>
|
</Label.Root>
|
||||||
<div class="admin-message-actions">
|
<div class="admin-message-actions">
|
||||||
<AdminButton
|
<AdminButton
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import { Label } from "$components/ui/primitives.js";
|
import { Label } from "$components/ui/primitives.js";
|
||||||
import {
|
import {
|
||||||
AdminBadge,
|
AdminBadge,
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
|
|
||||||
<div class="admin-toolbar admin-toolbar-users">
|
<div class="admin-toolbar admin-toolbar-users">
|
||||||
<div class="admin-toolbar-search">
|
<div class="admin-toolbar-search">
|
||||||
<input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
class="input"
|
class="input"
|
||||||
placeholder={at("users_search_placeholder", {}, "ID, @username или email")}
|
placeholder={at("users_search_placeholder", {}, "ID, @username или email")}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
export let value = "#000000";
|
||||||
|
export let disabled = false;
|
||||||
|
export let ariaLabel = "";
|
||||||
|
let className = "";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
bind:value
|
||||||
|
class={cn("ui-color-input", className)}
|
||||||
|
type="color"
|
||||||
|
{disabled}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
on:input
|
||||||
|
on:change
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
export let element = null;
|
||||||
|
export let accept = undefined;
|
||||||
|
export let disabled = false;
|
||||||
|
let className = "";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
bind:this={element}
|
||||||
|
class={cn("ui-file-input", className)}
|
||||||
|
type="file"
|
||||||
|
{accept}
|
||||||
|
{disabled}
|
||||||
|
on:change
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
||||||
@@ -2,11 +2,14 @@ export { default as AttentionDot } from "./attention-dot.svelte";
|
|||||||
export { default as Badge } from "./badge.svelte";
|
export { default as Badge } from "./badge.svelte";
|
||||||
export { default as Button } from "./button.svelte";
|
export { default as Button } from "./button.svelte";
|
||||||
export { default as Checkbox } from "./checkbox.svelte";
|
export { default as Checkbox } from "./checkbox.svelte";
|
||||||
|
export { default as ColorInput } from "./color-input.svelte";
|
||||||
export { default as Dialog } from "./dialog.svelte";
|
export { default as Dialog } from "./dialog.svelte";
|
||||||
|
export { default as FileInput } from "./file-input.svelte";
|
||||||
export { default as Input } from "./input.svelte";
|
export { default as Input } from "./input.svelte";
|
||||||
export { default as LegacyCard } from "./card.svelte";
|
export { default as LegacyCard } from "./card.svelte";
|
||||||
export { default as RadioGroup } from "./radio-group.svelte";
|
export { default as RadioGroup } from "./radio-group.svelte";
|
||||||
export { default as RadioGroupItem } from "./radio-group-item.svelte";
|
export { default as RadioGroupItem } from "./radio-group-item.svelte";
|
||||||
|
export { default as RangeInput } from "./range-input.svelte";
|
||||||
export { default as Skeleton } from "./skeleton.svelte";
|
export { default as Skeleton } from "./skeleton.svelte";
|
||||||
export { default as Spinner } from "./spinner.svelte";
|
export { default as Spinner } from "./spinner.svelte";
|
||||||
export { default as ScrollArea } from "./scroll-area.svelte";
|
export { default as ScrollArea } from "./scroll-area.svelte";
|
||||||
|
|||||||
@@ -1 +1,11 @@
|
|||||||
export { Accordion, Label, Select, Separator, Switch, Tabs, Tooltip } from "bits-ui";
|
export {
|
||||||
|
Accordion,
|
||||||
|
Label,
|
||||||
|
ScrollArea,
|
||||||
|
Select,
|
||||||
|
Separator,
|
||||||
|
Slider,
|
||||||
|
Switch,
|
||||||
|
Tabs,
|
||||||
|
Tooltip,
|
||||||
|
} from "bits-ui";
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { Slider } from "./primitives.js";
|
||||||
|
|
||||||
|
export let value = 0;
|
||||||
|
export let min = undefined;
|
||||||
|
export let max = undefined;
|
||||||
|
export let step = undefined;
|
||||||
|
export let disabled = false;
|
||||||
|
export let ariaLabel = "";
|
||||||
|
export let onValueChange = () => {};
|
||||||
|
export let onValueCommit = () => {};
|
||||||
|
let className = "";
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
$: sliderValue = Number(value ?? min ?? 0);
|
||||||
|
$: sliderMin = min === undefined ? undefined : Number(min);
|
||||||
|
$: sliderMax = max === undefined ? undefined : Number(max);
|
||||||
|
$: sliderStep = step === undefined ? undefined : Number(step);
|
||||||
|
|
||||||
|
function handleValueChange(next) {
|
||||||
|
value = next;
|
||||||
|
onValueChange(next);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Slider.Root
|
||||||
|
class={cn("ui-range-input", className)}
|
||||||
|
type="single"
|
||||||
|
value={sliderValue}
|
||||||
|
min={sliderMin}
|
||||||
|
max={sliderMax}
|
||||||
|
step={sliderStep}
|
||||||
|
{disabled}
|
||||||
|
onValueChange={handleValueChange}
|
||||||
|
{onValueCommit}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<Slider.Range class="ui-range-input__range" />
|
||||||
|
<Slider.Thumb class="ui-range-input__thumb" aria-label={ariaLabel} />
|
||||||
|
</Slider.Root>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.ui-range-input) {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ui-range-input::before) {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, var(--admin-border-strong, var(--border)) 70%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ui-range-input__range) {
|
||||||
|
position: absolute;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ui-range-input__thumb) {
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid var(--accent);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--admin-surface, var(--panel));
|
||||||
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.24);
|
||||||
|
outline: none;
|
||||||
|
transition:
|
||||||
|
box-shadow 0.14s ease,
|
||||||
|
transform 0.14s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ui-range-input__thumb:hover) {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ui-range-input__thumb:focus-visible) {
|
||||||
|
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 24%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ui-range-input[data-disabled]) {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,15 +1,86 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { ScrollArea as ScrollAreaPrimitive } from "./primitives.js";
|
||||||
|
|
||||||
export let maxHeight = "100%";
|
export let maxHeight = "100%";
|
||||||
export let element = null;
|
export let element = null;
|
||||||
|
export let type = "auto";
|
||||||
let className = "";
|
let className = "";
|
||||||
export { className as class };
|
export { className as class };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<ScrollAreaPrimitive.Root
|
||||||
bind:this={element}
|
class={cn("scroll-area scroll-area--mono", className)}
|
||||||
class={`scroll-area ${className}`.trim()}
|
|
||||||
style={`max-height:${maxHeight};`}
|
style={`max-height:${maxHeight};`}
|
||||||
|
{type}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
>
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport bind:ref={element} class="scroll-area__viewport">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollAreaPrimitive.Scrollbar class="scroll-area__scrollbar" orientation="vertical">
|
||||||
|
<ScrollAreaPrimitive.Thumb class="scroll-area__thumb" />
|
||||||
|
</ScrollAreaPrimitive.Scrollbar>
|
||||||
|
<ScrollAreaPrimitive.Scrollbar
|
||||||
|
class="scroll-area__scrollbar scroll-area__scrollbar--horizontal"
|
||||||
|
orientation="horizontal"
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Thumb class="scroll-area__thumb" />
|
||||||
|
</ScrollAreaPrimitive.Scrollbar>
|
||||||
|
<ScrollAreaPrimitive.Corner class="scroll-area__corner" />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.scroll-area) {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__viewport) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: inherit;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__scrollbar) {
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
padding: 2px;
|
||||||
|
transition: background 0.14s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__scrollbar[data-orientation="vertical"]) {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__scrollbar[data-orientation="horizontal"]) {
|
||||||
|
flex-direction: column;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__thumb) {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--admin-muted, var(--muted)) 34%,
|
||||||
|
var(--admin-border, var(--border))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__thumb:hover) {
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--admin-muted, var(--muted)) 48%,
|
||||||
|
var(--admin-border, var(--border))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.scroll-area__corner) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ArrowLeft, RefreshCw } from "$components/ui/icons.js";
|
import { ArrowLeft, RefreshCw } from "$components/ui/icons.js";
|
||||||
|
|
||||||
import Button from "$components/ui/button.svelte";
|
import Button from "$components/ui/button.svelte";
|
||||||
|
import { Input } from "$components/ui/index.js";
|
||||||
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
||||||
|
|
||||||
export let code = "";
|
export let code = "";
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="otp-wrap">
|
<div class="otp-wrap">
|
||||||
<label class="otp-input-wrap">
|
<label class="otp-input-wrap">
|
||||||
<input
|
<Input
|
||||||
bind:value={code}
|
bind:value={code}
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
autocomplete="one-time-code"
|
autocomplete="one-time-code"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { Check, ChevronsUpDown, LifeBuoy, MessageSquarePlus } from "$components/ui/icons.js";
|
import { Check, ChevronsUpDown, LifeBuoy, MessageSquarePlus } from "$components/ui/icons.js";
|
||||||
import Button from "$components/ui/button.svelte";
|
import Button from "$components/ui/button.svelte";
|
||||||
import Card from "$components/ui/card.svelte";
|
import Card from "$components/ui/card.svelte";
|
||||||
import { Skeleton } from "$components/ui/index.js";
|
import { Input, Skeleton, Textarea } from "$components/ui/index.js";
|
||||||
import { TicketCard } from "$components/patterns/webapp/index.js";
|
import { TicketCard } from "$components/patterns/webapp/index.js";
|
||||||
import { Select, Tabs } from "$components/ui/primitives.js";
|
import { Select, Tabs } from "$components/ui/primitives.js";
|
||||||
import {
|
import {
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
<div class="support-create-panel-inner" in:fade={{ duration: 140 }}>
|
<div class="support-create-panel-inner" in:fade={{ duration: 140 }}>
|
||||||
<label class="support-field">
|
<label class="support-field">
|
||||||
<span>{t("wa_support_subject")}</span>
|
<span>{t("wa_support_subject")}</span>
|
||||||
<input
|
<Input
|
||||||
class="input"
|
class="input"
|
||||||
bind:value={subject}
|
bind:value={subject}
|
||||||
maxlength={maxSubjectLength}
|
maxlength={maxSubjectLength}
|
||||||
@@ -255,13 +255,13 @@
|
|||||||
|
|
||||||
<label class="support-field">
|
<label class="support-field">
|
||||||
<span>{t("wa_support_message")}</span>
|
<span>{t("wa_support_message")}</span>
|
||||||
<textarea
|
<Textarea
|
||||||
class="textarea support-message-input"
|
class="textarea support-message-input"
|
||||||
bind:value={body}
|
bind:value={body}
|
||||||
maxlength={maxBodyLength}
|
maxlength={maxBodyLength}
|
||||||
rows="5"
|
rows="5"
|
||||||
placeholder={t("wa_support_message_placeholder")}
|
placeholder={t("wa_support_message_placeholder")}
|
||||||
></textarea>
|
/>
|
||||||
<small>{body.length}/{maxBodyLength}</small>
|
<small>{body.length}/{maxBodyLength}</small>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user