Files
Diabetix_v2/app/resources/js/Pages/Profile/Edit.vue
jeremy bayse 26c6d8031c Initial commit — Diabetix V2
Application Laravel 12 + Inertia + Vue 3 + Tailwind.
Fonctionnalités : dashboard glycémique, saisie de mesures, courbe SVG,
statistiques (jour/semaine/mois/trimestre), défis & badges, chat coach IA
(Gemini), paramètres profil avec palette de couleurs, pages auth redessinées,
emails transactionnels via Resend avec thème Diabetix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 07:01:41 +02:00

280 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<script setup>
import { ref, nextTick } from 'vue';
import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
import DiabetixLayout from '@/Layouts/DiabetixLayout.vue';
defineProps({
mustVerifyEmail: Boolean,
status: String,
});
const user = usePage().props.auth.user;
// Formulaire infos compte
const accountForm = useForm({
name: user.name,
email: user.email,
});
// Formulaire préférences Diabetix
const diabetixForm = useForm({
first_name: user.first_name ?? '',
diabetes_type: user.diabetes_type ?? 'type2',
target_min: user.target_min ?? 70,
target_max: user.target_max ?? 180,
palette: user.palette ?? 'mint',
});
// Formulaire mot de passe
const passwordForm = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const passwordRef = ref(null);
const currentPasswordRef = ref(null);
function updatePassword() {
passwordForm.put(route('password.update'), {
preserveScroll: true,
onSuccess: () => passwordForm.reset(),
onError: () => {
if (passwordForm.errors.password) {
passwordForm.reset('password', 'password_confirmation');
passwordRef.value?.focus();
}
if (passwordForm.errors.current_password) {
passwordForm.reset('current_password');
currentPasswordRef.value?.focus();
}
},
});
}
// Suppression compte
const deleteOpen = ref(false);
const deletePasswordRef = ref(null);
const deleteForm = useForm({ password: '' });
function openDelete() {
deleteOpen.value = true;
nextTick(() => deletePasswordRef.value?.focus());
}
function confirmDelete() {
deleteForm.delete(route('profile.destroy'), {
preserveScroll: true,
onError: () => deletePasswordRef.value?.focus(),
onFinish: () => deleteForm.reset(),
});
}
const palettes = [
{ id: 'mint', label: 'Menthe', swatch: '#7bbfb5' },
{ id: 'lilac', label: 'Lilas', swatch: '#9b8ec4' },
{ id: 'peach', label: 'Pêche', swatch: '#d4826a' },
];
const diabetesTypes = [
{ id: 'type1', label: 'Type 1' },
{ id: 'type2', label: 'Type 2' },
{ id: 'gestational', label: 'Gestationnel' },
{ id: 'other', label: 'Autre' },
];
function inputStyle(tok) {
return {
width: '100%', padding: '11px 14px', borderRadius: '12px',
border: '1.5px solid ' + tok.border, background: tok.bg,
fontSize: '14px', color: tok.text, outline: 'none', boxSizing: 'border-box',
};
}
function chipStyle(active, tok) {
return {
padding: '7px 14px', borderRadius: '20px',
border: '1.5px solid ' + (active ? tok.primary : tok.border),
background: active ? tok.light : tok.white,
color: active ? tok.dark : tok.muted,
fontSize: '12px', fontWeight: active ? 600 : 400, cursor: 'pointer',
};
}
function btnPrimary(tok, disabled) {
return {
padding: '13px 20px', background: tok.primary, color: '#fff',
borderRadius: '14px', border: 'none', fontSize: '13px', fontWeight: 600,
cursor: disabled ? 'not-allowed' : 'pointer', opacity: disabled ? 0.6 : 1,
};
}
</script>
<template>
<Head title="Paramètres" />
<DiabetixLayout v-slot="{ tok, font }">
<!-- Header -->
<div :style="{ background: tok.white, padding: '14px 20px 18px', borderBottom: '1px solid ' + tok.border }">
<div style="display:flex;align-items:center;gap:10px;">
<Link :href="route('dashboard')" :style="{ color: tok.muted, fontSize: '20px', textDecoration: 'none', lineHeight: 1 }"></Link>
<div :style="{ fontFamily: font.title, fontSize: '20px', fontWeight: 600, color: tok.text }">Paramètres</div>
</div>
</div>
<div style="padding:14px 20px;display:flex;flex-direction:column;gap:14px;">
<!-- Préférences Diabetix -->
<div :style="{ background: tok.white, borderRadius: '20px', padding: '20px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
<div :style="{ fontSize: '11px', fontWeight: 700, color: tok.muted, letterSpacing: '0.8px', textTransform: 'uppercase', marginBottom: '16px' }">Mes préférences</div>
<form @submit.prevent="diabetixForm.patch(route('profile.diabetix'), { preserveScroll: true })">
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px' }">Prénom</label>
<input v-model="diabetixForm.first_name" type="text" :style="inputStyle(tok)" placeholder="Marie" />
<span v-if="diabetixForm.errors.first_name" :style="{ fontSize: '11px', color: '#c43' }">{{ diabetixForm.errors.first_name }}</span>
</div>
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '8px' }">Type de diabète</label>
<div style="display:flex;flex-wrap:wrap;gap:6px;">
<button v-for="t in diabetesTypes" :key="t.id" type="button" @click="diabetixForm.diabetes_type = t.id" :style="chipStyle(diabetixForm.diabetes_type === t.id, tok)">{{ t.label }}</button>
</div>
</div>
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '8px' }">Plage cible glycémique (mg/dL)</label>
<div style="display:flex;gap:10px;align-items:center;">
<input v-model.number="diabetixForm.target_min" type="number" min="50" max="140" :style="{ ...inputStyle(tok), textAlign: 'center', width: '80px' }" />
<span :style="{ color: tok.muted, fontSize: '13px' }"></span>
<input v-model.number="diabetixForm.target_max" type="number" min="120" max="300" :style="{ ...inputStyle(tok), textAlign: 'center', width: '80px' }" />
<span :style="{ color: tok.muted, fontSize: '11px' }">mg/dL</span>
</div>
<span v-if="diabetixForm.errors.target_min || diabetixForm.errors.target_max" :style="{ fontSize: '11px', color: '#c43' }">{{ diabetixForm.errors.target_min || diabetixForm.errors.target_max }}</span>
</div>
<div style="margin-bottom:20px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '8px' }">Thème de couleur</label>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button v-for="p in palettes" :key="p.id" type="button" @click="diabetixForm.palette = p.id"
:style="{
display: 'flex', alignItems: 'center', gap: '7px',
padding: '8px 14px', borderRadius: '20px',
border: '1.5px solid ' + (diabetixForm.palette === p.id ? tok.text : tok.border),
background: diabetixForm.palette === p.id ? tok.light : tok.white,
fontSize: '12px', fontWeight: diabetixForm.palette === p.id ? 600 : 400,
color: tok.text, cursor: 'pointer',
}">
<span :style="{ width: '12px', height: '12px', borderRadius: '50%', background: p.swatch, display: 'inline-block' }" />
{{ p.label }}
</button>
</div>
</div>
<div style="display:flex;align-items:center;gap:12px;">
<button type="submit" :disabled="diabetixForm.processing" :style="btnPrimary(tok, diabetixForm.processing)">
{{ diabetixForm.processing ? '…' : 'Enregistrer' }}
</button>
<span v-if="diabetixForm.recentlySuccessful" :style="{ fontSize: '12px', color: tok.primary }"> Enregistré</span>
</div>
</form>
</div>
<!-- Infos compte -->
<div :style="{ background: tok.white, borderRadius: '20px', padding: '20px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
<div :style="{ fontSize: '11px', fontWeight: 700, color: tok.muted, letterSpacing: '0.8px', textTransform: 'uppercase', marginBottom: '16px' }">Mon compte</div>
<form @submit.prevent="accountForm.patch(route('profile.update'), { preserveScroll: true })">
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px' }">Nom complet</label>
<input v-model="accountForm.name" type="text" required :style="inputStyle(tok)" />
<span v-if="accountForm.errors.name" :style="{ fontSize: '11px', color: '#c43' }">{{ accountForm.errors.name }}</span>
</div>
<div style="margin-bottom:20px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px' }">Adresse e-mail</label>
<input v-model="accountForm.email" type="email" required :style="inputStyle(tok)" />
<span v-if="accountForm.errors.email" :style="{ fontSize: '11px', color: '#c43' }">{{ accountForm.errors.email }}</span>
</div>
<div style="display:flex;align-items:center;gap:12px;">
<button type="submit" :disabled="accountForm.processing" :style="btnPrimary(tok, accountForm.processing)">
{{ accountForm.processing ? '…' : 'Mettre à jour' }}
</button>
<span v-if="accountForm.recentlySuccessful" :style="{ fontSize: '12px', color: tok.primary }"> Enregistré</span>
</div>
</form>
</div>
<!-- Mot de passe -->
<div :style="{ background: tok.white, borderRadius: '20px', padding: '20px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
<div :style="{ fontSize: '11px', fontWeight: 700, color: tok.muted, letterSpacing: '0.8px', textTransform: 'uppercase', marginBottom: '16px' }">Mot de passe</div>
<form @submit.prevent="updatePassword">
<div style="margin-bottom:12px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px' }">Mot de passe actuel</label>
<input ref="currentPasswordRef" v-model="passwordForm.current_password" type="password" autocomplete="current-password" :style="inputStyle(tok)" />
<span v-if="passwordForm.errors.current_password" :style="{ fontSize: '11px', color: '#c43' }">{{ passwordForm.errors.current_password }}</span>
</div>
<div style="margin-bottom:12px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px' }">Nouveau mot de passe</label>
<input ref="passwordRef" v-model="passwordForm.password" type="password" autocomplete="new-password" :style="inputStyle(tok)" />
<span v-if="passwordForm.errors.password" :style="{ fontSize: '11px', color: '#c43' }">{{ passwordForm.errors.password }}</span>
</div>
<div style="margin-bottom:20px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px' }">Confirmer le nouveau mot de passe</label>
<input v-model="passwordForm.password_confirmation" type="password" autocomplete="new-password" :style="inputStyle(tok)" />
<span v-if="passwordForm.errors.password_confirmation" :style="{ fontSize: '11px', color: '#c43' }">{{ passwordForm.errors.password_confirmation }}</span>
</div>
<div style="display:flex;align-items:center;gap:12px;">
<button type="submit" :disabled="passwordForm.processing" :style="btnPrimary(tok, passwordForm.processing)">
{{ passwordForm.processing ? '…' : 'Changer le mot de passe' }}
</button>
<span v-if="passwordForm.recentlySuccessful" :style="{ fontSize: '12px', color: tok.primary }"> Modifié</span>
</div>
</form>
</div>
<!-- Session -->
<div :style="{ background: tok.white, borderRadius: '20px', padding: '20px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
<div :style="{ fontSize: '11px', fontWeight: 700, color: tok.muted, letterSpacing: '0.8px', textTransform: 'uppercase', marginBottom: '16px' }">Session</div>
<Link :href="route('logout')" method="post" as="button"
:style="{ ...btnPrimary(tok, false), background: tok.bgAlt, color: tok.text, boxShadow: 'none', width: '100%', marginBottom: '10px', display: 'block', textAlign: 'center', textDecoration: 'none' }">
Se déconnecter
</Link>
<button @click="openDelete"
:style="{ width: '100%', padding: '13px', background: 'transparent', border: '1.5px solid #fb9999', borderRadius: '14px', color: '#c43', fontSize: '13px', fontWeight: 600, cursor: 'pointer' }">
Supprimer mon compte
</button>
</div>
</div>
<!-- Modal suppression -->
<div v-if="deleteOpen" @click.self="deleteOpen = false"
:style="{ position: 'fixed', inset: 0, zIndex: 50, background: 'rgba(0,0,0,0.45)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }">
<div :style="{ width: '100%', maxWidth: '440px', background: tok.white, borderRadius: '24px 24px 0 0', padding: '6px 20px 36px', boxShadow: '0 -8px 40px rgba(0,0,0,0.18)' }">
<div :style="{ width: '40px', height: '4px', borderRadius: '2px', background: tok.border, margin: '12px auto 20px' }" />
<div :style="{ fontFamily: font.title, fontSize: '18px', fontWeight: 600, color: tok.text, marginBottom: '8px' }">Supprimer mon compte</div>
<p :style="{ fontSize: '13px', color: tok.muted, marginBottom: '20px', lineHeight: 1.5 }">
Cette action est irréversible. Toutes vos données (mesures, défis, badges) seront définitivement supprimées. Confirmez votre mot de passe pour continuer.
</p>
<input ref="deletePasswordRef" v-model="deleteForm.password" type="password" placeholder="Mot de passe"
:style="{ ...inputStyle(tok), marginBottom: '8px' }" @keyup.enter="confirmDelete" />
<span v-if="deleteForm.errors.password" :style="{ fontSize: '11px', color: '#c43', display: 'block', marginBottom: '10px' }">{{ deleteForm.errors.password }}</span>
<div style="display:flex;gap:10px;margin-top:12px;">
<button @click="deleteOpen = false"
:style="{ flex: 1, padding: '13px', background: tok.bgAlt, border: 'none', borderRadius: '14px', fontSize: '13px', color: tok.text, fontWeight: 600, cursor: 'pointer' }">
Annuler
</button>
<button @click="confirmDelete" :disabled="deleteForm.processing"
:style="{ flex: 1, padding: '13px', background: '#c43', border: 'none', borderRadius: '14px', fontSize: '13px', color: '#fff', fontWeight: 600, cursor: 'pointer', opacity: deleteForm.processing ? 0.6 : 1 }">
Supprimer
</button>
</div>
</div>
</div>
</DiabetixLayout>
</template>