300 lines
7.8 KiB
Svelte
300 lines
7.8 KiB
Svelte
<script lang="ts">
|
|
import { Dialog } from "bits-ui";
|
|
import { ripple } from "$lib/actions/ripple";
|
|
import { ApiError } from "$lib/api/client";
|
|
import AddAccountDialog from "$lib/components/settings/AddAccountDialog.svelte";
|
|
import SettingsItem from "$lib/components/settings/SettingsItem.svelte";
|
|
import Avatar from "$lib/components/ui/Avatar.svelte";
|
|
import Button from "$lib/components/ui/Button.svelte";
|
|
import Icon from "$lib/components/ui/Icon.svelte";
|
|
import { accountName } from "$lib/format/peer";
|
|
import { accounts } from "$lib/stores/accounts.svelte";
|
|
import { toasts } from "$lib/stores/toasts.svelte";
|
|
|
|
const current = $derived(accounts.selected);
|
|
const others = $derived(
|
|
accounts.list.filter(
|
|
(account) => account.account_id !== accounts.selectedId
|
|
)
|
|
);
|
|
|
|
const DEVICE_LIMIT = 32;
|
|
const DEFAULT_DEVICE = "Beavergram";
|
|
|
|
let adding = $state(false);
|
|
let confirming = $state(false);
|
|
let renaming = $state(false);
|
|
let deviceName = $state("");
|
|
let busy = $state(false);
|
|
|
|
const deviceLabel = $derived(current?.device_model ?? DEFAULT_DEVICE);
|
|
|
|
function openRename() {
|
|
deviceName = deviceLabel;
|
|
renaming = true;
|
|
}
|
|
|
|
async function rename(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
const value = deviceName.trim();
|
|
if (!(current && value) || busy) {
|
|
return;
|
|
}
|
|
busy = true;
|
|
try {
|
|
await accounts.renameDevice(current.account_id, value);
|
|
renaming = false;
|
|
toasts.success("Имя устройства обновлено");
|
|
} catch (error) {
|
|
toasts.error(
|
|
error instanceof ApiError ? error.detail : "Не удалось переименовать"
|
|
);
|
|
} finally {
|
|
busy = false;
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
if (!current || busy) {
|
|
return;
|
|
}
|
|
busy = true;
|
|
try {
|
|
await accounts.logout(current.account_id);
|
|
confirming = false;
|
|
toasts.success("Сессия завершена");
|
|
} catch {
|
|
toasts.error("Не удалось выйти из аккаунта");
|
|
} finally {
|
|
busy = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="my-account">
|
|
{#if current}
|
|
<div class="profile">
|
|
<Avatar
|
|
colorKey={current.account_id}
|
|
name={accountName(current)}
|
|
size={5}
|
|
/>
|
|
<div class="name">{accountName(current)}</div>
|
|
{#if current.phone}
|
|
<div class="phone">+{current.phone}</div>
|
|
{/if}
|
|
{#if !current.is_active}
|
|
<div class="inactive">Сессия завершена, доступен только архив</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if others.length > 0}
|
|
<div class="switch">
|
|
{#each others as account (account.account_id)}
|
|
<button
|
|
class="switch-row"
|
|
onclick={() => accounts.select(account.account_id)}
|
|
type="button"
|
|
use:ripple
|
|
>
|
|
<Avatar
|
|
colorKey={account.account_id}
|
|
name={accountName(account)}
|
|
size={2.25}
|
|
/>
|
|
<span>{accountName(account)}</span>
|
|
{#if !account.is_active}
|
|
<span class="tag">архив</span>
|
|
{/if}
|
|
<Icon class="chevron" name="arrow-right" size="1rem" />
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="actions">
|
|
{#if current?.is_active}
|
|
<SettingsItem
|
|
icon="active-sessions"
|
|
label="Имя устройства"
|
|
onclick={openRename}
|
|
value={deviceLabel}
|
|
/>
|
|
{/if}
|
|
<SettingsItem
|
|
icon="add-user"
|
|
label="Добавить аккаунт"
|
|
onclick={() => {
|
|
adding = true;
|
|
}}
|
|
/>
|
|
{#if current?.is_active}
|
|
<SettingsItem
|
|
icon="logout"
|
|
label="Выйти из аккаунта"
|
|
onclick={() => {
|
|
confirming = true;
|
|
}}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<AddAccountDialog bind:open={adding} />
|
|
|
|
<Dialog.Root bind:open={renaming}>
|
|
<Dialog.Portal>
|
|
<Dialog.Overlay class="dialog-overlay" />
|
|
<Dialog.Content class="dialog-content">
|
|
<header class="dialog-head">
|
|
<Dialog.Title class="dialog-title">Имя устройства</Dialog.Title>
|
|
<Dialog.Close aria-label="Закрыть" class="dialog-close">
|
|
<Icon name="close" size="1.25rem" />
|
|
</Dialog.Close>
|
|
</header>
|
|
<form onsubmit={rename}>
|
|
<div class="dialog-body">
|
|
<p class="confirm-text">
|
|
Так сессия называется в списке устройств Telegram. Чтобы имя
|
|
применилось, соединение переподключится — сбор сообщений прервётся
|
|
на пару секунд.
|
|
</p>
|
|
<div class="input-group">
|
|
<input
|
|
class="form-control"
|
|
id="device-name"
|
|
maxlength={DEVICE_LIMIT}
|
|
placeholder={DEFAULT_DEVICE}
|
|
type="text"
|
|
bind:value={deviceName}
|
|
>
|
|
<label for="device-name">Название</label>
|
|
</div>
|
|
</div>
|
|
<div class="dialog-actions">
|
|
<Button
|
|
disabled={busy || deviceName.trim().length === 0}
|
|
loading={busy}
|
|
pill
|
|
type="submit"
|
|
>
|
|
Сохранить
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Portal>
|
|
</Dialog.Root>
|
|
|
|
<Dialog.Root bind:open={confirming}>
|
|
<Dialog.Portal>
|
|
<Dialog.Overlay class="dialog-overlay" />
|
|
<Dialog.Content class="dialog-content">
|
|
<header class="dialog-head">
|
|
<Dialog.Title class="dialog-title">Выйти из аккаунта?</Dialog.Title>
|
|
<Dialog.Close aria-label="Закрыть" class="dialog-close">
|
|
<Icon name="close" size="1.25rem" />
|
|
</Dialog.Close>
|
|
</header>
|
|
<div class="dialog-body">
|
|
<p class="confirm-text">
|
|
Сессия {current ? accountName(current) : ""} будет завершена в
|
|
Telegram, новые сообщения перестанут собираться. Уже собранный архив
|
|
останется доступным.
|
|
</p>
|
|
</div>
|
|
<div class="dialog-actions">
|
|
<Button
|
|
onclick={() => {
|
|
confirming = false;
|
|
}}
|
|
pill
|
|
variant="text"
|
|
>
|
|
Отмена
|
|
</Button>
|
|
<Button loading={busy} onclick={logout} pill variant="danger">
|
|
Выйти
|
|
</Button>
|
|
</div>
|
|
</Dialog.Content>
|
|
</Dialog.Portal>
|
|
</Dialog.Root>
|
|
|
|
<style lang="scss">
|
|
.profile {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
|
|
padding: 1.5rem 1rem;
|
|
}
|
|
|
|
.name {
|
|
font-size: 1.25rem;
|
|
font-weight: var(--font-weight-medium);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.phone {
|
|
font-size: 0.9375rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.inactive {
|
|
font-size: 0.8125rem;
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.switch-row {
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
|
|
width: 100%;
|
|
min-height: 3.5rem;
|
|
padding: 0 1.25rem;
|
|
border: 0;
|
|
|
|
font-size: 1rem;
|
|
text-align: start;
|
|
color: var(--color-text);
|
|
background-color: transparent;
|
|
transition: background-color 0.15s ease;
|
|
|
|
&:hover {
|
|
background-color: var(--color-chat-hover);
|
|
}
|
|
|
|
span {
|
|
flex: 1;
|
|
}
|
|
}
|
|
|
|
.tag {
|
|
flex: 0 0 auto !important;
|
|
font-size: 0.75rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.actions {
|
|
padding: 0.5rem 0;
|
|
border-top: 1px solid var(--color-borders);
|
|
}
|
|
|
|
.confirm-text {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
:global(.switch-row .chevron) {
|
|
flex-shrink: 0;
|
|
color: var(--color-icon-secondary);
|
|
}
|
|
</style>
|