187 lines
5.5 KiB
Vue
187 lines
5.5 KiB
Vue
<template>
|
||
<div class="page-container">
|
||
<!-- ================================= -->
|
||
<!-- Authenticated User Dashboard -->
|
||
<!-- ================================= -->
|
||
<div v-if="isAuthenticated && user" class="dashboard-content">
|
||
|
||
<h1>Ваши цели на сегодня</h1>
|
||
<p class="text-color-light">Цели обновляются раз в сутки. Получаемые бонусы усиливаются, если посещать сайт ежедневно.</p>
|
||
|
||
<div class="streak-section">
|
||
<div class="streak-card" :class="{ 'active-streak': user.dailyStreak === 1 }">
|
||
<h2>x1</h2>
|
||
<p>Базовые</p>
|
||
</div>
|
||
<div class="streak-card" :class="{ 'active-streak': user.dailyStreak === 2 }">
|
||
<h2>x2</h2>
|
||
<p>Двойные</p>
|
||
</div>
|
||
<div class="streak-card" :class="{ 'active-streak': user.dailyStreak >= 3 }">
|
||
<h2>x3</h2>
|
||
<p>Тройные</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="habits-section">
|
||
<h2>Привычки</h2>
|
||
<div v-if="habitsPending">Загрузка привычек...</div>
|
||
<div v-else-if="habitsError">Не удалось загрузить привычки.</div>
|
||
<div v-else-if="habits && habits.length > 0">
|
||
<HabitCard
|
||
v-for="habit in habits"
|
||
:key="habit.id"
|
||
:habit="habit"
|
||
:is-submitting-habit="isSubmittingHabit"
|
||
:exploding-habit-id="explodingHabitId"
|
||
@complete="completeHabit"
|
||
/>
|
||
</div>
|
||
<div v-else>
|
||
<p>У вас еще нет привычек. Перейдите на страницу <NuxtLink to="/habits">Мои привычки</NuxtLink>, чтобы создать их.</p>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ================================= -->
|
||
<!-- Anonymous User Onboarding Funnel -->
|
||
<!-- ================================= -->
|
||
<div v-else-if="isAnonymous" class="onboarding-container">
|
||
<OnboardingFunnel />
|
||
</div>
|
||
|
||
<!-- ================================= -->
|
||
<!-- New/Unidentified User Welcome -->
|
||
<!-- ================================= -->
|
||
<div v-else class="welcome-content">
|
||
<h1>Добро пожаловать в SmurfHabits!</h1>
|
||
<p class="text-color-light">Отслеживайте свои привычки и развивайте свою деревню.</p>
|
||
<div class="auth-buttons">
|
||
<button @click="startOnboarding" class="btn btn-primary">Начать онбординг</button>
|
||
<NuxtLink to="/login" class="btn btn-secondary">У меня уже есть аккаунт</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
|
||
// Use the refactored auth composable
|
||
const { user, isAuthenticated, isAnonymous, updateUser, startOnboarding } = useAuth();
|
||
const api = useApi();
|
||
|
||
// --- Habits Data (This part only runs for authenticated users but is fine to leave here) ---
|
||
const { data: habits, pending: habitsPending, error: habitsError, refresh: refreshHabits } = await useFetch('/api/habits', {
|
||
lazy: true,
|
||
server: false,
|
||
});
|
||
|
||
// --- Actions & UI State ---
|
||
const isSubmittingHabit = ref(false);
|
||
const explodingHabitId = ref(null);
|
||
|
||
const completeHabit = async (habitId) => { // Removed event param since it's handled by HabitCard
|
||
if (isSubmittingHabit.value) return;
|
||
isSubmittingHabit.value = true;
|
||
|
||
try {
|
||
const response = await api(`/api/habits/${habitId}/complete`, { method: 'POST' });
|
||
if (updateUser && response) {
|
||
updateUser({
|
||
coins: response.updatedCoins,
|
||
exp: response.updatedExp,
|
||
});
|
||
}
|
||
|
||
const habit = habits.value.find(h => h.id === habitId);
|
||
if (habit) {
|
||
// Optimistically update the completions. This assumes the API call is successful.
|
||
if (!habit.completions) {
|
||
habit.completions = [];
|
||
}
|
||
habit.completions.push({
|
||
id: Math.random(), // Temporary ID for reactivity
|
||
habitId: habitId,
|
||
date: new Date().toISOString(),
|
||
});
|
||
}
|
||
|
||
explodingHabitId.value = habitId;
|
||
setTimeout(() => {
|
||
explodingHabitId.value = null;
|
||
}, 1000);
|
||
|
||
} catch (err) {
|
||
alert(err.data?.message || 'Failed to complete habit.');
|
||
} finally {
|
||
isSubmittingHabit.value = false;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.dashboard-content, .welcome-content, .onboarding-container {
|
||
text-align: center;
|
||
}
|
||
|
||
.welcome-content {
|
||
padding: 40px 0;
|
||
}
|
||
|
||
.streak-section {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
margin: 24px 0 32px 0;
|
||
}
|
||
|
||
.streak-card {
|
||
background: #f8f9fa;
|
||
border: 2px solid var(--border-color);
|
||
border-radius: 12px;
|
||
padding: 16px 20px;
|
||
width: 110px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.streak-card h2 {
|
||
margin: 0 0 5px 0;
|
||
font-size: 2em;
|
||
color: var(--text-color-light);
|
||
}
|
||
|
||
.streak-card p {
|
||
margin: 0;
|
||
font-size: 0.9em;
|
||
color: var(--text-color-light);
|
||
}
|
||
|
||
.active-streak {
|
||
border-color: var(--primary-color);
|
||
background-color: #f0f5ff;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||
transform: translateY(-4px);
|
||
}
|
||
|
||
.active-streak h2 {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.active-streak p {
|
||
color: var(--text-color);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.habits-section {
|
||
margin: 40px 0;
|
||
}
|
||
|
||
.auth-buttons {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
margin-top: 32px;
|
||
}
|
||
</style> |