Files
tp/main_dc/bbvue/src/views/ProfileEdit.vue
T

421 lines
12 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<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>