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>
This commit is contained in:
jeremy bayse
2026-04-29 07:01:41 +02:00
commit 26c6d8031c
150 changed files with 19863 additions and 0 deletions

View File

@@ -0,0 +1,279 @@
<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>

View File

@@ -0,0 +1,108 @@
<script setup>
import DangerButton from '@/Components/DangerButton.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import Modal from '@/Components/Modal.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
import { nextTick, ref } from 'vue';
const confirmingUserDeletion = ref(false);
const passwordInput = ref(null);
const form = useForm({
password: '',
});
const confirmUserDeletion = () => {
confirmingUserDeletion.value = true;
nextTick(() => passwordInput.value.focus());
};
const deleteUser = () => {
form.delete(route('profile.destroy'), {
preserveScroll: true,
onSuccess: () => closeModal(),
onError: () => passwordInput.value.focus(),
onFinish: () => form.reset(),
});
};
const closeModal = () => {
confirmingUserDeletion.value = false;
form.clearErrors();
form.reset();
};
</script>
<template>
<section class="space-y-6">
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
Delete Account
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Once your account is deleted, all of its resources and data will
be permanently deleted. Before deleting your account, please
download any data or information that you wish to retain.
</p>
</header>
<DangerButton @click="confirmUserDeletion">Delete Account</DangerButton>
<Modal :show="confirmingUserDeletion" @close="closeModal">
<div class="p-6">
<h2
class="text-lg font-medium text-gray-900 dark:text-gray-100"
>
Are you sure you want to delete your account?
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Once your account is deleted, all of its resources and data
will be permanently deleted. Please enter your password to
confirm you would like to permanently delete your account.
</p>
<div class="mt-6">
<InputLabel
for="password"
value="Password"
class="sr-only"
/>
<TextInput
id="password"
ref="passwordInput"
v-model="form.password"
type="password"
class="mt-1 block w-3/4"
placeholder="Password"
@keyup.enter="deleteUser"
/>
<InputError :message="form.errors.password" class="mt-2" />
</div>
<div class="mt-6 flex justify-end">
<SecondaryButton @click="closeModal">
Cancel
</SecondaryButton>
<DangerButton
class="ms-3"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
@click="deleteUser"
>
Delete Account
</DangerButton>
</div>
</div>
</Modal>
</section>
</template>

View File

@@ -0,0 +1,104 @@
<script setup>
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Link, useForm, usePage } from '@inertiajs/vue3';
const user = usePage().props.auth.user;
const form = 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',
});
const palettes = [
{ id: 'mint', label: 'Menthe', swatch: '#7bbfb5' },
{ id: 'lilac', label: 'Lilas', swatch: '#9b8ec4' },
{ id: 'peach', label: 'Pêche', swatch: '#d4826a' },
];
const types = [
{ id: 'type1', label: 'Type 1' },
{ id: 'type2', label: 'Type 2' },
{ id: 'gestational', label: 'Gestationnel' },
{ id: 'other', label: 'Autre' },
];
</script>
<template>
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Préférences Diabetix</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Personnalisez votre prénom, votre type de diabète, votre plage cible (mg/dL) et le thème de l'application.
</p>
</header>
<form @submit.prevent="form.patch(route('profile.diabetix'), { preserveScroll: true })" class="mt-6 space-y-6">
<div>
<InputLabel for="first_name" value="Prénom" />
<TextInput id="first_name" v-model="form.first_name" type="text" class="mt-1 block w-full" autocomplete="given-name" />
<InputError :message="form.errors.first_name" class="mt-2" />
</div>
<div>
<InputLabel value="Type de diabète" />
<div class="mt-2 flex flex-wrap gap-2">
<button v-for="t in types" :key="t.id" type="button" @click="form.diabetes_type = t.id"
:class="[
'rounded-full border px-4 py-1.5 text-sm transition',
form.diabetes_type === t.id
? 'border-emerald-500 bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-200'
: 'border-gray-300 text-gray-600 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-700',
]">{{ t.label }}</button>
</div>
<InputError :message="form.errors.diabetes_type" class="mt-2" />
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<InputLabel for="target_min" value="Cible min (mg/dL)" />
<TextInput id="target_min" v-model.number="form.target_min" type="number" min="50" max="140" class="mt-1 block w-full" />
<InputError :message="form.errors.target_min" class="mt-2" />
</div>
<div>
<InputLabel for="target_max" value="Cible max (mg/dL)" />
<TextInput id="target_max" v-model.number="form.target_max" type="number" min="120" max="300" class="mt-1 block w-full" />
<InputError :message="form.errors.target_max" class="mt-2" />
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
Valeurs par défaut OMS / consensus international : 70180 mg/dL.
</p>
<div>
<InputLabel value="Thème de couleur" />
<div class="mt-2 flex gap-3">
<button v-for="p in palettes" :key="p.id" type="button" @click="form.palette = p.id"
:class="[
'flex items-center gap-2 rounded-full border px-4 py-2 text-sm transition',
form.palette === p.id
? 'border-gray-900 dark:border-gray-100'
: 'border-gray-300 hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700',
]">
<span class="block h-4 w-4 rounded-full" :style="{ background: p.swatch }" />
<span>{{ p.label }}</span>
</button>
</div>
<InputError :message="form.errors.palette" class="mt-2" />
</div>
<div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Enregistrer</PrimaryButton>
<Transition enter-from-class="opacity-0" leave-to-class="opacity-0" class="transition ease-in-out">
<p v-if="form.recentlySuccessful" class="text-sm text-gray-600 dark:text-gray-400">Préférences enregistrées.</p>
</Transition>
<Link :href="route('dashboard')" class="ml-auto text-sm text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-100">← Retour à l'app</Link>
</div>
</form>
</section>
</template>

View File

@@ -0,0 +1,122 @@
<script setup>
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
const passwordInput = ref(null);
const currentPasswordInput = ref(null);
const form = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const updatePassword = () => {
form.put(route('password.update'), {
preserveScroll: true,
onSuccess: () => form.reset(),
onError: () => {
if (form.errors.password) {
form.reset('password', 'password_confirmation');
passwordInput.value.focus();
}
if (form.errors.current_password) {
form.reset('current_password');
currentPasswordInput.value.focus();
}
},
});
};
</script>
<template>
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
Update Password
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Ensure your account is using a long, random password to stay
secure.
</p>
</header>
<form @submit.prevent="updatePassword" class="mt-6 space-y-6">
<div>
<InputLabel for="current_password" value="Current Password" />
<TextInput
id="current_password"
ref="currentPasswordInput"
v-model="form.current_password"
type="password"
class="mt-1 block w-full"
autocomplete="current-password"
/>
<InputError
:message="form.errors.current_password"
class="mt-2"
/>
</div>
<div>
<InputLabel for="password" value="New Password" />
<TextInput
id="password"
ref="passwordInput"
v-model="form.password"
type="password"
class="mt-1 block w-full"
autocomplete="new-password"
/>
<InputError :message="form.errors.password" class="mt-2" />
</div>
<div>
<InputLabel
for="password_confirmation"
value="Confirm Password"
/>
<TextInput
id="password_confirmation"
v-model="form.password_confirmation"
type="password"
class="mt-1 block w-full"
autocomplete="new-password"
/>
<InputError
:message="form.errors.password_confirmation"
class="mt-2"
/>
</div>
<div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Save</PrimaryButton>
<Transition
enter-active-class="transition ease-in-out"
enter-from-class="opacity-0"
leave-active-class="transition ease-in-out"
leave-to-class="opacity-0"
>
<p
v-if="form.recentlySuccessful"
class="text-sm text-gray-600 dark:text-gray-400"
>
Saved.
</p>
</Transition>
</div>
</form>
</section>
</template>

View File

@@ -0,0 +1,112 @@
<script setup>
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Link, useForm, usePage } from '@inertiajs/vue3';
defineProps({
mustVerifyEmail: {
type: Boolean,
},
status: {
type: String,
},
});
const user = usePage().props.auth.user;
const form = useForm({
name: user.name,
email: user.email,
});
</script>
<template>
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
Profile Information
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Update your account's profile information and email address.
</p>
</header>
<form
@submit.prevent="form.patch(route('profile.update'))"
class="mt-6 space-y-6"
>
<div>
<InputLabel for="name" value="Name" />
<TextInput
id="name"
type="text"
class="mt-1 block w-full"
v-model="form.name"
required
autofocus
autocomplete="name"
/>
<InputError class="mt-2" :message="form.errors.name" />
</div>
<div>
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autocomplete="username"
/>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div v-if="mustVerifyEmail && user.email_verified_at === null">
<p class="mt-2 text-sm text-gray-800 dark:text-gray-200">
Your email address is unverified.
<Link
:href="route('verification.send')"
method="post"
as="button"
class="rounded-md text-sm text-gray-600 underline hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:text-gray-400 dark:hover:text-gray-100 dark:focus:ring-offset-gray-800"
>
Click here to re-send the verification email.
</Link>
</p>
<div
v-show="status === 'verification-link-sent'"
class="mt-2 text-sm font-medium text-green-600 dark:text-green-400"
>
A new verification link has been sent to your email address.
</div>
</div>
<div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Save</PrimaryButton>
<Transition
enter-active-class="transition ease-in-out"
enter-from-class="opacity-0"
leave-active-class="transition ease-in-out"
leave-to-class="opacity-0"
>
<p
v-if="form.recentlySuccessful"
class="text-sm text-gray-600 dark:text-gray-400"
>
Saved.
</p>
</Transition>
</div>
</form>
</section>
</template>