164 lines
4.6 KiB
Vue
164 lines
4.6 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import AppAvatar from '@/components/AppAvatar.vue';
|
|
import ImageCropperDialog from '@/components/ImageCropperDialog.vue';
|
|
import { useUserProfileStore } from '@/stores/userProfileStore.js';
|
|
|
|
const userProfileStore = useUserProfileStore();
|
|
const { t } = useI18n();
|
|
const isPortraitDialogOpen = ref(false);
|
|
const isSavingPortrait = ref(false);
|
|
|
|
const email = computed(() => userProfileStore.user?.email || t('userSettings.noEmail'));
|
|
const alias = computed(() => userProfileStore.alias);
|
|
const fullname = computed(() => userProfileStore.fullname);
|
|
|
|
async function savePortrait(result) {
|
|
isSavingPortrait.value = true;
|
|
|
|
try {
|
|
await userProfileStore.changePortrait(result.file);
|
|
isPortraitDialogOpen.value = false;
|
|
} finally {
|
|
isSavingPortrait.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section class="page-shell">
|
|
<div class="page-header">
|
|
<div class="eyebrow">{{ t('userSettings.eyebrow') }}</div>
|
|
<h1>{{ t('userSettings.title') }}</h1>
|
|
<p>{{ t('userSettings.description') }}</p>
|
|
</div>
|
|
|
|
<div class="panel hero-panel">
|
|
<div class="hero-identity">
|
|
<AppAvatar
|
|
:name="alias"
|
|
:src="userProfileStore.portraitUrl"
|
|
size="lg"
|
|
/>
|
|
<div>
|
|
<strong>{{ alias }}</strong>
|
|
<span>{{ fullname }}</span>
|
|
<small>{{ email }}</small>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
class="primary-button"
|
|
@click="isPortraitDialogOpen = true"
|
|
>
|
|
{{ t('userSettings.updatePortrait') }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="panel-heading">
|
|
<strong>{{ t('userSettings.accountDetails') }}</strong>
|
|
<span>{{ t('userSettings.accountDetailsDescription') }}</span>
|
|
</div>
|
|
|
|
<div class="details-grid">
|
|
<div class="detail-row">
|
|
<span>{{ t('userSettings.alias') }}</span>
|
|
<strong>{{ alias }}</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>{{ t('userSettings.fullName') }}</span>
|
|
<strong>{{ fullname }}</strong>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span>{{ t('userSettings.email') }}</span>
|
|
<strong>{{ email }}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ImageCropperDialog
|
|
v-model="isPortraitDialogOpen"
|
|
:title="t('userSettings.cropperTitle')"
|
|
:confirm-label="t('userSettings.savePortrait')"
|
|
:upload-label="t('userSettings.choosePortrait')"
|
|
:is-saving="isSavingPortrait"
|
|
@save="savePortrait"
|
|
/>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page-shell {
|
|
@apply flex flex-col gap-6;
|
|
}
|
|
|
|
.eyebrow {
|
|
@apply text-xs font-bold uppercase tracking-[0.24em];
|
|
color: #0f766e;
|
|
}
|
|
|
|
.page-header h1 {
|
|
@apply mt-2 text-4xl font-black;
|
|
color: #172033;
|
|
}
|
|
|
|
.page-header p,
|
|
.panel-heading span,
|
|
.hero-identity span,
|
|
.hero-identity small,
|
|
.detail-row span {
|
|
@apply text-sm leading-6;
|
|
color: #526178;
|
|
}
|
|
|
|
.panel {
|
|
@apply flex flex-col gap-5 rounded-[1.75rem] border p-5;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
border-color: rgba(23, 32, 51, 0.08);
|
|
}
|
|
|
|
.hero-panel {
|
|
@apply flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between;
|
|
}
|
|
|
|
.hero-identity {
|
|
@apply flex items-center gap-4;
|
|
}
|
|
|
|
.hero-identity strong,
|
|
.panel-heading strong,
|
|
.detail-row strong {
|
|
color: #172033;
|
|
}
|
|
|
|
.hero-identity strong {
|
|
@apply text-2xl font-black;
|
|
}
|
|
|
|
.panel-heading {
|
|
@apply flex flex-col gap-2;
|
|
}
|
|
|
|
.panel-heading strong {
|
|
@apply text-lg font-black;
|
|
}
|
|
|
|
.details-grid {
|
|
@apply grid gap-4 md:grid-cols-2;
|
|
}
|
|
|
|
.detail-row {
|
|
@apply flex flex-col gap-1 rounded-[1.25rem] border p-4;
|
|
background: #fffaf2;
|
|
border-color: rgba(23, 32, 51, 0.08);
|
|
}
|
|
|
|
.primary-button {
|
|
@apply inline-flex items-center justify-center gap-2 rounded-full px-5 py-3 text-sm font-bold transition;
|
|
background: #172033;
|
|
color: #fffaf2;
|
|
}
|
|
</style>
|