Files

264 lines
14 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { Head, useForm, usePage } from '@inertiajs/vue3';
import { computed, ref } from 'vue';
import PortalLayout from '../../Layouts/PortalLayout.vue';
import { arrowSvg } from '../Landing/Components/symbols';
const props = defineProps({
site: { type: Object, required: true },
auth: { type: Object, required: true },
urls: { type: Object, required: true },
csrfToken: { type: String, required: true },
flash: { type: Object, default: () => ({}) },
account: { type: Object, required: true },
characters: { type: Array, default: () => [] },
client: { type: Object, required: true },
wallet: { type: Object, required: true },
});
const page = usePage();
const sharedErrors = computed(() => page.props.errors || {});
const activeSection = ref('overview');
const passwordForm = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const sections = [
{ id: 'overview', label: 'Обзор', number: '01' },
{ id: 'characters', label: 'Персонажи', number: '02' },
{ id: 'wallet', label: 'Слёзы Элуны', number: '03' },
{ id: 'security', label: 'Безопасность', number: '04' },
];
function goTo(id) {
activeSection.value = id;
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function updatePassword() {
passwordForm.post(props.urls.password, {
preserveScroll: true,
onSuccess: () => passwordForm.reset(),
onError: () => {
passwordForm.reset('password', 'password_confirmation');
goTo('security');
},
});
}
</script>
<template>
<Head>
<title>Личный кабинет — {{ site.name }}</title>
<meta name="description" content="Персонажи, баланс и настройки игрового аккаунта MoonWell." />
</Head>
<PortalLayout :site="site" :auth="auth" :urls="urls" :flash="flash">
<section class="portal-welcome wrap">
<div>
<span class="eyebrow">Личный кабинет</span>
<h1 class="h-display">С возвращением, <em>{{ account.username }}</em></h1>
<p class="lead">Твой аккаунт, герои и всё необходимое для следующего путешествия.</p>
</div>
<div class="portal-welcome__status">
<span :class="['portal-status-dot', { danger: account.locked }]"></span>
<span>
<small>Статус аккаунта</small>
<strong>{{ account.status_label }}</strong>
</span>
</div>
</section>
<div class="portal-dashboard wrap">
<aside class="portal-sidebar">
<span class="portal-sidebar__label">Навигация</span>
<button
v-for="section in sections"
:key="section.id"
:class="{ active: activeSection === section.id }"
type="button"
@click="goTo(section.id)"
>
<span>{{ section.number }}</span>
{{ section.label }}
</button>
<div class="portal-sidebar__balance">
<small>Баланс</small>
<strong>{{ wallet.formatted_amount }}</strong>
<span>Слёз Элуны</span>
</div>
</aside>
<div class="portal-content">
<section id="overview" class="portal-section">
<div class="portal-section__head">
<div>
<span class="eyebrow">Обзор</span>
<h2 class="h-serif">Данные аккаунта</h2>
</div>
<a v-if="auth.is_admin" class="btn btn-ghost portal-small-button" :href="urls.admin">Открыть админку</a>
</div>
<div class="portal-grid portal-grid--account">
<article class="portal-card portal-account-card corners">
<dl class="portal-stats">
<div><dt>Логин</dt><dd>{{ account.username }}</dd></div>
<div><dt>Email</dt><dd>{{ account.email }}</dd></div>
<div><dt>Уровень доступа</dt><dd>{{ account.access_label }}</dd></div>
<div><dt>Регистрация</dt><dd>{{ account.joined_at }}</dd></div>
<div><dt>Последний вход</dt><dd>{{ account.last_login_at }}</dd></div>
<div><dt>Персонажей</dt><dd>{{ characters.length }}</dd></div>
</dl>
</article>
<article class="portal-card portal-launcher-card corners">
<span class="eyebrow">Лаунчер</span>
<h3 class="h-serif">Врата в Азерот</h3>
<p>Лаунчер автоматически скачает клиент, настроит realmlist и установит обновления.</p>
<a class="btn btn-primary" :href="urls.client">
Скачать лаунчер <span v-html="arrowSvg"></span>
</a>
<span class="portal-file-note">{{ client.filename }}</span>
</article>
</div>
</section>
<section id="characters" class="portal-section">
<div class="portal-section__head">
<div>
<span class="eyebrow">Мои персонажи</span>
<h2 class="h-serif">Герои аккаунта</h2>
</div>
<span class="portal-section__count">{{ characters.length }} {{ characters.length === 1 ? 'герой' : 'героев' }}</span>
</div>
<div v-if="characters.length" class="portal-character-grid">
<article v-for="character in characters" :key="character.name" class="portal-card portal-character-card">
<div class="portal-character-card__head">
<span class="portal-character-level"><span>{{ character.level }}</span></span>
<span :class="['portal-online', { online: character.online }]">
{{ character.online ? 'Онлайн' : 'Оффлайн' }}
</span>
</div>
<h3 class="h-serif">{{ character.name }}</h3>
<p>{{ character.race }} · {{ character.class }}</p>
<dl>
<div><dt>Пол</dt><dd>{{ character.gender }}</dd></div>
<div><dt>Золото</dt><dd>{{ character.money }}</dd></div>
<div><dt>Время в игре</dt><dd>{{ character.played }}</dd></div>
</dl>
</article>
</div>
<article v-else class="portal-card portal-empty">
<span class="portal-empty__moon">☾</span>
<h3 class="h-serif">Персонажей пока нет</h3>
<p>Создай первого героя в игре — он появится здесь автоматически.</p>
<a class="btn btn-primary" :href="urls.client">Скачать лаунчер</a>
</article>
</section>
<section id="wallet" class="portal-section">
<div class="portal-section__head">
<div>
<span class="eyebrow">Слёзы Элуны</span>
<h2 class="h-serif">Баланс и операции</h2>
</div>
</div>
<div class="portal-grid portal-grid--wallet">
<article class="portal-card portal-wallet-card corners">
<span class="portal-card-label">Доступно сейчас</span>
<div class="portal-balance">{{ wallet.formatted_amount }}</div>
<p>Слёз Элуны</p>
<form v-if="wallet.deposit_enabled" class="portal-deposit" method="POST" :action="urls.deposit">
<input type="hidden" name="_token" :value="csrfToken" />
<label class="portal-field">
<span>Сумма пополнения, ₽</span>
<input
type="number"
name="amount"
:min="wallet.minimum_deposit"
:max="wallet.maximum_deposit"
step="0.01"
:value="wallet.deposit_amount"
required
/>
<small v-if="sharedErrors.amount" class="portal-field-error">{{ sharedErrors.amount }}</small>
</label>
<button class="btn btn-primary" type="submit">
Пополнить <span v-html="arrowSvg"></span>
</button>
</form>
<div v-if="wallet.deposit_enabled" class="portal-rate">
<span>1 ₽ = {{ wallet.tears_per_ruble }} Слёз Элуны</span>
<span>От {{ wallet.minimum_deposit }} до {{ wallet.formatted_maximum_deposit }} ₽</span>
</div>
</article>
<article class="portal-card portal-history-card">
<span class="portal-card-label">Последние операции</span>
<div v-if="wallet.transactions.length" class="portal-history">
<div v-for="transaction in wallet.transactions" :key="transaction.id" class="portal-history__row">
<span>
<strong>{{ transaction.label }}</strong>
<small>{{ transaction.created_at }}</small>
</span>
<strong :class="{ positive: transaction.is_positive }">
{{ transaction.is_positive ? '+' : '' }}{{ transaction.formatted_amount }}
</strong>
</div>
</div>
<div v-else class="portal-history-empty">Операций пока нет.</div>
</article>
</div>
</section>
<section id="security" class="portal-section">
<div class="portal-section__head">
<div>
<span class="eyebrow">Безопасность</span>
<h2 class="h-serif">Сменить пароль</h2>
</div>
</div>
<div class="portal-card portal-security-card corners">
<div class="portal-security-card__copy">
<h3 class="h-serif">Защити свой аккаунт</h3>
<p>После обновления новый пароль сразу начнёт действовать и в игре, и в личном кабинете.</p>
<ul>
<li>От 8 до 32 символов</li>
<li>Хотя бы одна буква и одна цифра</li>
<li>Не используй пароль от почты</li>
</ul>
</div>
<form class="portal-password-form" @submit.prevent="updatePassword">
<label :class="['portal-field', { 'has-error': passwordForm.errors.current_password }]">
<span>Текущий пароль</span>
<input v-model="passwordForm.current_password" type="password" autocomplete="current-password" maxlength="32" required />
<small v-if="passwordForm.errors.current_password">{{ passwordForm.errors.current_password }}</small>
</label>
<label :class="['portal-field', { 'has-error': passwordForm.errors.password }]">
<span>Новый пароль</span>
<input v-model="passwordForm.password" type="password" autocomplete="new-password" maxlength="32" required />
<small v-if="passwordForm.errors.password">{{ passwordForm.errors.password }}</small>
</label>
<label :class="['portal-field', { 'has-error': passwordForm.errors.password }]">
<span>Повтори новый пароль</span>
<input v-model="passwordForm.password_confirmation" type="password" autocomplete="new-password" maxlength="32" required />
</label>
<button class="btn btn-primary portal-submit" type="submit" :disabled="passwordForm.processing">
{{ passwordForm.processing ? 'Обновляем…' : 'Обновить пароль' }}
<span v-html="arrowSvg"></span>
</button>
</form>
</div>
</section>
</div>
</div>
</PortalLayout>
</template>