152 lines
5.7 KiB
Vue
152 lines
5.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useUserStore } from '@/stores/user'
|
|
import GlassCard from '@/components/ui/GlassCard.vue'
|
|
import CoinChip from '@/components/ui/CoinChip.vue'
|
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
|
import AchievementBadge from '@/components/ui/AchievementBadge.vue'
|
|
import DataTable from '@/components/ui/DataTable.vue'
|
|
import EmptyState from '@/components/ui/EmptyState.vue'
|
|
|
|
const auth = useAuthStore()
|
|
const userStore = useUserStore()
|
|
const user = computed(() => auth.user!)
|
|
|
|
const userMetaLine = computed(() => {
|
|
const parts: string[] = []
|
|
if (user.value.institute) parts.push(user.value.institute)
|
|
if (user.value.direction) parts.push(user.value.direction)
|
|
return parts.join(' ')
|
|
})
|
|
|
|
const userYearLine = computed(() => {
|
|
const year = user.value.year ?? 0
|
|
return Number.isFinite(year) && year > 0 ? `${year} курс` : ''
|
|
})
|
|
const interestTags = ref([
|
|
{ label: '#ML', active: true },
|
|
{ label: '#ИИ', active: true },
|
|
{ label: '#дизайн', active: false },
|
|
{ label: '#право', active: false },
|
|
{ label: '#биоинформатика', active: false },
|
|
{ label: '#маркетинг', active: true },
|
|
])
|
|
|
|
const notificationSettings = ref({ email: true })
|
|
|
|
const historyColumns = [
|
|
{ key: 'date', label: 'Дата' },
|
|
{ key: 'description', label: 'Описание' },
|
|
{ key: 'amount', label: 'Монеты', align: 'right' },
|
|
]
|
|
|
|
onMounted(() => {
|
|
void userStore.fetchStudentData(user.value.id)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="profile page-content">
|
|
<div class="header">
|
|
<h1 class="page-title">Профиль пользователя</h1>
|
|
<CoinChip :amount="user.coins" />
|
|
</div>
|
|
|
|
<div class="profile-grid">
|
|
<GlassCard>
|
|
<div class="user-info">
|
|
<div class="avatar">👤</div>
|
|
<div>
|
|
<div class="name">{{ user.name }}</div>
|
|
<div class="email">{{ user.email }}</div>
|
|
<div v-if="userMetaLine" class="meta">{{ userMetaLine }}</div>
|
|
<div v-if="userYearLine" class="meta">{{ userYearLine }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="level">
|
|
<div class="level-header">
|
|
<span>Уровень {{ user.level }}</span>
|
|
<span>{{ user.xp }} / 200 XP</span>
|
|
</div>
|
|
<ProgressBar :value="user.xp ?? 0" :max="200" />
|
|
</div>
|
|
<div class="tags">
|
|
<div class="section-title">Интересы</div>
|
|
<div class="tags-grid">
|
|
<button
|
|
v-for="tag in interestTags"
|
|
:key="tag.label"
|
|
class="tag-chip"
|
|
:class="{ active: tag.active }"
|
|
@click="tag.active = !tag.active"
|
|
>
|
|
{{ tag.label }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
<GlassCard>
|
|
<div class="section-title">Настройки уведомлений</div>
|
|
<div class="settings">
|
|
<label class="setting">
|
|
<input type="checkbox" v-model="notificationSettings.email" />
|
|
Email уведомления
|
|
</label>
|
|
</div>
|
|
<div class="section-title">Достижения</div>
|
|
<EmptyState
|
|
v-if="!userStore.achievements.length"
|
|
title="Достижений пока нет"
|
|
subtitle="Они появятся после посещений, отзывов и начислений."
|
|
/>
|
|
<div v-else class="achievements">
|
|
<AchievementBadge
|
|
v-for="a in userStore.achievements.slice(0, 3)"
|
|
:key="a.id"
|
|
:icon="a.icon"
|
|
:title="a.title"
|
|
:description="a.description"
|
|
:unlocked="a.unlocked"
|
|
:unlockedAt="a.unlockedAt"
|
|
:coins="a.coins"
|
|
/>
|
|
</div>
|
|
</GlassCard>
|
|
</div>
|
|
|
|
<GlassCard>
|
|
<div class="section-title">История начисления монет</div>
|
|
<EmptyState
|
|
v-if="!userStore.coinHistory.length"
|
|
title="История монет пуста"
|
|
subtitle="Начисления появятся после активностей на платформе."
|
|
/>
|
|
<DataTable :columns="historyColumns" :rows="userStore.coinHistory">
|
|
<template #amount="{ value }">
|
|
<span :class="value > 0 ? 'positive' : 'negative'">{{ value > 0 ? `+${value}` : value }}</span>
|
|
</template>
|
|
</DataTable>
|
|
</GlassCard>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.profile { display: flex; flex-direction: column; gap: 20px; }
|
|
.header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
|
.profile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }
|
|
.user-info { display: flex; gap: 14px; align-items: center; margin-bottom: 16px; }
|
|
.avatar { font-size: 38px; background: rgba(34,197,94,0.15); border-radius: 16px; padding: 12px; }
|
|
.name { font-weight: 700; font-size: 18px; }
|
|
.email, .meta { font-size: 13px; color: var(--color-text-secondary); }
|
|
.level { margin: 16px 0; }
|
|
.level-header { display: flex; justify-content: space-between; font-size: 12px; color: var(--color-text-secondary); margin-bottom: 6px; }
|
|
.tags-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
|
.settings { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
|
.setting { font-size: 13px; color: var(--color-text-secondary); display: flex; gap: 8px; align-items: center; }
|
|
.achievements { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }
|
|
.positive { color: #166534; font-weight: 600; }
|
|
.negative { color: #991B1B; font-weight: 600; }
|
|
</style>
|