421 lines
12 KiB
Vue
421 lines
12 KiB
Vue
<template>
|
||
<div class="page">
|
||
<h1>✏️ Редактирование профиля</h1>
|
||
|
||
<div class="avatar-section">
|
||
<h3>Фотография профиля</h3>
|
||
<AvatarUpload :user="user" :show-actions="true" @avatar-updated="onAvatarUpdated" />
|
||
</div>
|
||
|
||
<div v-if="loading && !user" class="loading">Загрузка...</div>
|
||
|
||
<form v-else @submit.prevent="handleSubmit" class="profile-edit-form">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="firstName">Имя *</label>
|
||
<input id="firstName" v-model="formData.firstName" type="text" class="form-input"
|
||
placeholder="Введите ваше имя" required :disabled="loading">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="lastName">Фамилия *</label>
|
||
<input id="lastName" v-model="formData.lastName" type="text" class="form-input"
|
||
placeholder="Введите вашу фамилию" required :disabled="loading">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="phone">Телефон</label>
|
||
<input id="phone" v-model="formData.phone" type="tel" class="form-input" placeholder="+7 (XXX) XXX-XX-XX"
|
||
:disabled="loading">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="email">Email *</label>
|
||
<input id="email" v-model="formData.email" type="email" class="form-input" placeholder="example@mail.ru"
|
||
required readonly disabled style="background-color: #f5f5f5; cursor: not-allowed;">
|
||
<small style="color: #666; font-size: 0.9rem;">
|
||
Email нельзя изменить. Для смены email обратитесь в поддержку.
|
||
</small>
|
||
</div>
|
||
|
||
<div v-if="!user.email_verified" class="verification-alert">
|
||
<p>Ваш email не подтвержден</p>
|
||
<button @click="resendVerification" :disabled="resending">
|
||
{{ resending ? 'Отправка...' : 'Отправить подтверждение' }}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="experience">Уровень подготовки</label>
|
||
<select id="experience" v-model="formData.experience" class="form-input" :disabled="loading">
|
||
<option value="">Выберите уровень</option>
|
||
<option value="beginner">Начинающий (0-6 месяцев)</option>
|
||
<option value="intermediate">Любитель (6-24 месяцев)</option>
|
||
<option value="advanced">Опытный (2+ лет)</option>
|
||
<option value="professional">Профессионал</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="goals">Цели</label>
|
||
<select id="goals" v-model="formData.goals" class="form-input" :disabled="loading">
|
||
<option value="">Выберите цель</option>
|
||
<option value="health">Улучшить здоровье</option>
|
||
<option value="weight">Сбросить вес</option>
|
||
<option value="first5k">Пробежать первые 5 км</option>
|
||
<option value="first10k">Пробежать первые 10 км</option>
|
||
<option value="halfMarathon">Подготовиться к полумарафону</option>
|
||
<option value="marathon">Подготовиться к марафону</option>
|
||
<option value="improve">Улучшить результаты</option>
|
||
<option value="social">Общение и компания</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group checkbox-group">
|
||
<label class="checkbox-label">
|
||
<input v-model="formData.newsletter" type="checkbox" class="checkbox" :disabled="loading">
|
||
<span class="checkmark"></span>
|
||
Хочу получать новости о тренировках и мероприятиях
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary" :disabled="loading || !isFormChanged">
|
||
{{ loading ? 'Сохранение...' : '💾 Сохранить изменения' }}
|
||
</button>
|
||
|
||
<button type="button" class="btn btn-secondary" @click="cancelEdit" :disabled="loading">
|
||
❌ Отмена
|
||
</button>
|
||
</div>
|
||
|
||
<div v-if="error" class="error-message">
|
||
{{ error }}
|
||
</div>
|
||
|
||
<div v-if="success" class="success-message">
|
||
✅ Профиль успешно обновлен!
|
||
</div>
|
||
</form>
|
||
|
||
<div class="navigation-actions">
|
||
<button class="btn btn-secondary" @click="$router.push('/profile')">← Назад к профилю</button>
|
||
<button class="btn btn-secondary" @click="$router.push('/')">🏠 На главную</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { useAuthStore } from '../stores/auth'
|
||
import AvatarUpload from '../components/AvatarUpload.vue'
|
||
|
||
export default {
|
||
name: 'ProfileEdit',
|
||
components: {
|
||
AvatarUpload
|
||
},
|
||
setup() {
|
||
const authStore = useAuthStore()
|
||
return { authStore }
|
||
},
|
||
data() {
|
||
return {
|
||
formData: {
|
||
firstName: '',
|
||
lastName: '',
|
||
email: '', // Только для отображения, не для изменения
|
||
phone: '',
|
||
experience: '',
|
||
goals: '',
|
||
newsletter: false
|
||
},
|
||
originalData: {},
|
||
loading: false,
|
||
error: '',
|
||
success: false
|
||
}
|
||
},
|
||
computed: {
|
||
user() {
|
||
return this.authStore.user
|
||
},
|
||
isFormChanged() {
|
||
// Исключаем email из сравнения, так как он не изменяется
|
||
const formDataCopy = { ...this.formData }
|
||
const originalDataCopy = { ...this.originalData }
|
||
delete formDataCopy.email
|
||
delete originalDataCopy.email
|
||
return JSON.stringify(formDataCopy) !== JSON.stringify(originalDataCopy)
|
||
}
|
||
},
|
||
methods: {
|
||
handleFirstInteraction() {
|
||
if (!this.hasInteracted) {
|
||
this.hasInteracted = true
|
||
this.showContent()
|
||
clearTimeout(this.autoShowTimeout)
|
||
}
|
||
},
|
||
showContent() {
|
||
this.isContentVisible = true
|
||
// Эмитим событие для показа хедера
|
||
this.$emit('show-header')
|
||
},
|
||
async onAvatarUpdated() {
|
||
// Обновляем данные пользователя после загрузки аватара
|
||
await this.authStore.fetchProfile()
|
||
this.initializeForm()
|
||
},
|
||
initializeForm() {
|
||
if (this.user) {
|
||
this.formData = {
|
||
firstName: this.user.firstName || '',
|
||
lastName: this.user.lastName || '',
|
||
email: this.user.email || '', // Только для отображения
|
||
phone: this.user.phone || '',
|
||
experience: this.user.experience || '',
|
||
goals: this.user.goals || '',
|
||
newsletter: this.user.newsletter || false
|
||
}
|
||
this.originalData = { ...this.formData }
|
||
}
|
||
},
|
||
async handleSubmit() {
|
||
this.loading = true
|
||
this.error = ''
|
||
this.success = false
|
||
|
||
try {
|
||
// Выделяем данные в отдельный объект
|
||
const profileData = {
|
||
firstName: this.formData.firstName,
|
||
lastName: this.formData.lastName,
|
||
phone: this.formData.phone,
|
||
experience: this.formData.experience,
|
||
goals: this.formData.goals,
|
||
newsletter: this.formData.newsletter
|
||
};
|
||
|
||
// debug only
|
||
console.log("dubug profile Data = " + profileData)
|
||
|
||
// Используем метод updateProfile из authStore
|
||
// Отправляем на обновление профиль
|
||
const result = await this.authStore.updateProfile(profileData);
|
||
|
||
if (result.success) {
|
||
this.originalData = { ...this.formData }
|
||
this.success = true
|
||
|
||
// Принудительно обновляем профиль в сторе
|
||
await this.authStore.fetchProfile()
|
||
} else {
|
||
this.error = result.error
|
||
}
|
||
|
||
} catch (err) {
|
||
this.error = err.response?.data?.message || 'Ошибка обновления профиля'
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
cancelEdit() {
|
||
this.initializeForm()
|
||
this.error = ''
|
||
this.success = false
|
||
}
|
||
},
|
||
mounted() {
|
||
if (this.user) {
|
||
this.initializeForm()
|
||
} else {
|
||
// Если пользователь не загружен, загружаем данные
|
||
this.loading = true
|
||
this.authStore.fetchProfile().finally(() => {
|
||
this.loading = false
|
||
this.initializeForm()
|
||
})
|
||
}
|
||
window.addEventListener('scroll', this.handleFirstInteraction, { passive: true, once: true })
|
||
window.addEventListener('click', this.handleFirstInteraction, { once: true })
|
||
window.addEventListener('touchstart', this.handleFirstInteraction, { once: true })
|
||
this.autoShowTimeout = setTimeout(() => {
|
||
if (!this.hasInteracted) {
|
||
this.showContent()
|
||
}
|
||
}, 3000)
|
||
},
|
||
beforeUnmount() {
|
||
// Убираем обработчики при размонтировании
|
||
window.removeEventListener('scroll', this.handleFirstInteraction)
|
||
window.removeEventListener('click', this.handleFirstInteraction)
|
||
window.removeEventListener('touchstart', this.handleFirstInteraction)
|
||
clearTimeout(this.autoShowTimeout)
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.profile-edit-form {
|
||
max-width: 500px;
|
||
margin: 2rem auto;
|
||
background: white;
|
||
padding: 2rem;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
padding: 12px;
|
||
border: 2px solid #e1e5e9;
|
||
border-radius: 6px;
|
||
font-size: 1rem;
|
||
transition: border-color 0.3s;
|
||
}
|
||
|
||
.form-input:focus {
|
||
outline: none;
|
||
border-color: #2e8b57;
|
||
}
|
||
|
||
.form-input:disabled {
|
||
background-color: #f5f5f5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.checkbox-group {
|
||
margin: 1.5rem 0;
|
||
}
|
||
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
cursor: pointer;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.checkbox {
|
||
margin-right: 10px;
|
||
margin-top: 3px;
|
||
}
|
||
|
||
.checkmark {
|
||
width: 18px;
|
||
height: 18px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 3px;
|
||
margin-right: 10px;
|
||
margin-top: 2px;
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.checkbox:checked+.checkmark {
|
||
background-color: #2e8b57;
|
||
border-color: #2e8b57;
|
||
}
|
||
|
||
.checkbox:checked+.checkmark::after {
|
||
content: '✓';
|
||
color: white;
|
||
position: absolute;
|
||
top: -2px;
|
||
left: 2px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
gap: 1rem;
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.btn-primary {
|
||
flex: 1;
|
||
background-color: #2e8b57;
|
||
color: white;
|
||
padding: 12px;
|
||
}
|
||
|
||
.btn-primary:hover:not(:disabled) {
|
||
background-color: #26734a;
|
||
}
|
||
|
||
.btn-primary:disabled {
|
||
background-color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.success-message {
|
||
background-color: #efe;
|
||
color: #2e8b57;
|
||
padding: 12px;
|
||
border-radius: 6px;
|
||
margin-top: 1rem;
|
||
border-left: 4px solid #2e8b57;
|
||
text-align: center;
|
||
}
|
||
|
||
.error-message {
|
||
background-color: #fee;
|
||
color: #c33;
|
||
padding: 12px;
|
||
border-radius: 6px;
|
||
margin-top: 1rem;
|
||
border-left: 4px solid #c33;
|
||
}
|
||
|
||
.navigation-actions {
|
||
display: flex;
|
||
gap: 1rem;
|
||
justify-content: center;
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
font-size: 1.1rem;
|
||
color: #666;
|
||
}
|
||
|
||
/* Адаптивность */
|
||
@media (max-width: 600px) {
|
||
.form-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.profile-edit-form {
|
||
padding: 1.5rem;
|
||
margin: 1rem;
|
||
}
|
||
|
||
.form-actions {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.navigation-actions {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
}
|
||
</style> |