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,55 @@
<script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue';
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 { Head, useForm } from '@inertiajs/vue3';
const form = useForm({
password: '',
});
const submit = () => {
form.post(route('password.confirm'), {
onFinish: () => form.reset(),
});
};
</script>
<template>
<GuestLayout>
<Head title="Confirm Password" />
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
This is a secure area of the application. Please confirm your
password before continuing.
</div>
<form @submit.prevent="submit">
<div>
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="current-password"
autofocus
/>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="mt-4 flex justify-end">
<PrimaryButton
class="ms-4"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Confirm
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View File

@@ -0,0 +1,60 @@
<script setup>
import { Head, Link, useForm } from '@inertiajs/vue3';
import { tokens, FONT } from '@/Components/Diabetix/palette.js';
defineProps({ status: String });
const tok = tokens('mint');
const font = FONT;
const form = useForm({ email: '' });
function submit() {
form.post(route('password.email'));
}
</script>
<template>
<Head title="Mot de passe oublié" />
<div :style="{ minHeight: '100vh', background: '#eeece8', fontFamily: font.body, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }">
<div :style="{ width: '100%', maxWidth: '400px' }">
<div style="text-align:center;margin-bottom:32px;">
<div :style="{ width: '64px', height: '64px', borderRadius: '50%', background: tok.light, border: '2px solid ' + tok.primary, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '28px', margin: '0 auto 14px' }">🔑</div>
<div :style="{ fontFamily: font.title, fontSize: '24px', fontWeight: 700, color: tok.text }">Mot de passe oublié</div>
<div :style="{ fontSize: '13px', color: tok.muted, marginTop: '6px', lineHeight: 1.5 }">Saisissez votre e-mail nous vous enverrons un lien pour réinitialiser votre mot de passe.</div>
</div>
<div :style="{ background: tok.white, borderRadius: '24px', padding: '28px 24px', boxShadow: '0 4px 24px rgba(42,53,51,0.10)' }">
<div v-if="status" :style="{ background: tok.light, borderRadius: '12px', padding: '10px 14px', fontSize: '13px', color: tok.dark, marginBottom: '20px' }">
{{ status }}
</div>
<form @submit.prevent="submit">
<div style="margin-bottom:24px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Adresse e-mail</label>
<input v-model="form.email" type="email" required autofocus autocomplete="username"
:style="{ width: '100%', padding: '12px 14px', borderRadius: '14px', border: '1.5px solid ' + tok.border, background: tok.bg, fontSize: '15px', color: tok.text, outline: 'none', boxSizing: 'border-box' }" />
<span v-if="form.errors.email" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.email }}</span>
</div>
<button type="submit" :disabled="form.processing"
:style="{
width: '100%', padding: '15px', background: tok.primary, color: '#fff',
borderRadius: '16px', border: 'none', fontSize: '15px', fontWeight: 700,
cursor: form.processing ? 'not-allowed' : 'pointer',
opacity: form.processing ? 0.6 : 1,
boxShadow: '0 6px 18px rgba(123,191,181,0.35)',
}">
{{ form.processing ? 'Envoi…' : 'Envoyer le lien' }}
</button>
</form>
</div>
<div style="text-align:center;margin-top:20px;">
<Link :href="route('login')" :style="{ fontSize: '13px', color: tok.primary, fontWeight: 600, textDecoration: 'none' }"> Retour à la connexion</Link>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,110 @@
<script setup>
import { Head, Link, useForm } from '@inertiajs/vue3';
import { tokens, FONT } from '@/Components/Diabetix/palette.js';
defineProps({
canResetPassword: Boolean,
status: String,
});
const tok = tokens('mint');
const font = FONT;
const form = useForm({
email: '',
password: '',
remember: false,
});
function submit() {
form.post(route('login'), {
onFinish: () => form.reset('password'),
});
}
function inputStyle() {
return {
width: '100%', padding: '12px 14px', borderRadius: '14px',
border: '1.5px solid ' + tok.border, background: tok.bg,
fontSize: '15px', color: tok.text, outline: 'none', boxSizing: 'border-box',
};
}
</script>
<template>
<Head title="Connexion" />
<div :style="{ minHeight: '100vh', background: '#eeece8', fontFamily: font.body, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }">
<div :style="{ width: '100%', maxWidth: '400px' }">
<!-- Logo / titre -->
<div style="text-align:center;margin-bottom:32px;">
<div :style="{ width: '64px', height: '64px', borderRadius: '50%', background: tok.light, border: '2px solid ' + tok.primary, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '28px', margin: '0 auto 14px' }">🩺</div>
<div :style="{ fontFamily: font.title, fontSize: '26px', fontWeight: 700, color: tok.text }">Diabetix</div>
<div :style="{ fontSize: '13px', color: tok.muted, marginTop: '4px' }">Votre coach glycémique personnel</div>
</div>
<!-- Carte -->
<div :style="{ background: tok.white, borderRadius: '24px', padding: '28px 24px', boxShadow: '0 4px 24px rgba(42,53,51,0.10)' }">
<div v-if="status" :style="{ background: tok.light, borderRadius: '12px', padding: '10px 14px', fontSize: '13px', color: tok.dark, marginBottom: '20px' }">
{{ status }}
</div>
<form @submit.prevent="submit">
<div style="margin-bottom:16px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Adresse e-mail</label>
<input v-model="form.email" type="email" required autofocus autocomplete="username"
:style="inputStyle()"
:class="form.errors.email ? 'border-red-400' : ''" />
<span v-if="form.errors.email" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.email }}</span>
</div>
<div style="margin-bottom:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.7px' }">Mot de passe</label>
<Link v-if="canResetPassword" :href="route('password.request')"
:style="{ fontSize: '11px', color: tok.primary, textDecoration: 'none' }">
Mot de passe oublié ?
</Link>
</div>
<input v-model="form.password" type="password" required autocomplete="current-password"
:style="inputStyle()" />
<span v-if="form.errors.password" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.password }}</span>
</div>
<!-- Se souvenir -->
<label style="display:flex;align-items:center;gap:10px;margin-bottom:24px;cursor:pointer;">
<span :style="{
width: '20px', height: '20px', borderRadius: '6px', flexShrink: 0,
border: '1.5px solid ' + (form.remember ? tok.primary : tok.border),
background: form.remember ? tok.primary : tok.white,
display: 'flex', alignItems: 'center', justifyContent: 'center',
}" @click="form.remember = !form.remember">
<span v-if="form.remember" style="color:#fff;font-size:12px;font-weight:700;"></span>
</span>
<span :style="{ fontSize: '13px', color: tok.muted }">Se souvenir de moi</span>
</label>
<button type="submit" :disabled="form.processing"
:style="{
width: '100%', padding: '15px', background: tok.primary, color: '#fff',
borderRadius: '16px', border: 'none', fontSize: '15px', fontWeight: 700,
cursor: form.processing ? 'not-allowed' : 'pointer',
opacity: form.processing ? 0.6 : 1,
boxShadow: '0 6px 18px rgba(123,191,181,0.35)',
}">
{{ form.processing ? 'Connexion…' : 'Se connecter' }}
</button>
</form>
</div>
<!-- Lien inscription -->
<div style="text-align:center;margin-top:20px;">
<span :style="{ fontSize: '13px', color: tok.muted }">Pas encore de compte ? </span>
<Link :href="route('register')" :style="{ fontSize: '13px', color: tok.primary, fontWeight: 600, textDecoration: 'none' }">Créer un compte</Link>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,91 @@
<script setup>
import { Head, Link, useForm } from '@inertiajs/vue3';
import { tokens, FONT } from '@/Components/Diabetix/palette.js';
const tok = tokens('mint');
const font = FONT;
const form = useForm({
name: '',
email: '',
password: '',
password_confirmation: '',
});
function submit() {
form.post(route('register'), {
onFinish: () => form.reset('password', 'password_confirmation'),
});
}
function inputStyle() {
return {
width: '100%', padding: '12px 14px', borderRadius: '14px',
border: '1.5px solid ' + tok.border, background: tok.bg,
fontSize: '15px', color: tok.text, outline: 'none', boxSizing: 'border-box',
};
}
</script>
<template>
<Head title="Créer un compte" />
<div :style="{ minHeight: '100vh', background: '#eeece8', fontFamily: font.body, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }">
<div :style="{ width: '100%', maxWidth: '400px' }">
<!-- Logo -->
<div style="text-align:center;margin-bottom:32px;">
<div :style="{ width: '64px', height: '64px', borderRadius: '50%', background: tok.light, border: '2px solid ' + tok.primary, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '28px', margin: '0 auto 14px' }">🩺</div>
<div :style="{ fontFamily: font.title, fontSize: '26px', fontWeight: 700, color: tok.text }">Diabetix</div>
<div :style="{ fontSize: '13px', color: tok.muted, marginTop: '4px' }">Créez votre compte gratuitement</div>
</div>
<!-- Carte -->
<div :style="{ background: tok.white, borderRadius: '24px', padding: '28px 24px', boxShadow: '0 4px 24px rgba(42,53,51,0.10)' }">
<form @submit.prevent="submit">
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Nom complet</label>
<input v-model="form.name" type="text" required autofocus autocomplete="name" :style="inputStyle()" />
<span v-if="form.errors.name" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.name }}</span>
</div>
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Adresse e-mail</label>
<input v-model="form.email" type="email" required autocomplete="username" :style="inputStyle()" />
<span v-if="form.errors.email" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.email }}</span>
</div>
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Mot de passe</label>
<input v-model="form.password" type="password" required autocomplete="new-password" :style="inputStyle()" />
<span v-if="form.errors.password" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.password }}</span>
</div>
<div style="margin-bottom:24px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Confirmer le mot de passe</label>
<input v-model="form.password_confirmation" type="password" required autocomplete="new-password" :style="inputStyle()" />
<span v-if="form.errors.password_confirmation" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.password_confirmation }}</span>
</div>
<button type="submit" :disabled="form.processing"
:style="{
width: '100%', padding: '15px', background: tok.primary, color: '#fff',
borderRadius: '16px', border: 'none', fontSize: '15px', fontWeight: 700,
cursor: form.processing ? 'not-allowed' : 'pointer',
opacity: form.processing ? 0.6 : 1,
boxShadow: '0 6px 18px rgba(123,191,181,0.35)',
}">
{{ form.processing ? 'Création…' : 'Créer mon compte' }}
</button>
</form>
</div>
<!-- Lien connexion -->
<div style="text-align:center;margin-top:20px;">
<span :style="{ fontSize: '13px', color: tok.muted }">Déjà un compte ? </span>
<Link :href="route('login')" :style="{ fontSize: '13px', color: tok.primary, fontWeight: 600, textDecoration: 'none' }">Se connecter</Link>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,82 @@
<script setup>
import { Head, useForm } from '@inertiajs/vue3';
import { tokens, FONT } from '@/Components/Diabetix/palette.js';
const props = defineProps({
email: { type: String, required: true },
token: { type: String, required: true },
});
const tok = tokens('mint');
const font = FONT;
const form = useForm({
token: props.token,
email: props.email,
password: '',
password_confirmation: '',
});
function submit() {
form.post(route('password.store'), {
onFinish: () => form.reset('password', 'password_confirmation'),
});
}
function inputStyle() {
return {
width: '100%', padding: '12px 14px', borderRadius: '14px',
border: '1.5px solid ' + tok.border, background: tok.bg,
fontSize: '15px', color: tok.text, outline: 'none', boxSizing: 'border-box',
};
}
</script>
<template>
<Head title="Réinitialiser le mot de passe" />
<div :style="{ minHeight: '100vh', background: '#eeece8', fontFamily: font.body, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }">
<div :style="{ width: '100%', maxWidth: '400px' }">
<div style="text-align:center;margin-bottom:32px;">
<div :style="{ width: '64px', height: '64px', borderRadius: '50%', background: tok.light, border: '2px solid ' + tok.primary, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '28px', margin: '0 auto 14px' }">🔒</div>
<div :style="{ fontFamily: font.title, fontSize: '24px', fontWeight: 700, color: tok.text }">Nouveau mot de passe</div>
<div :style="{ fontSize: '13px', color: tok.muted, marginTop: '4px' }">Choisissez un mot de passe sécurisé.</div>
</div>
<div :style="{ background: tok.white, borderRadius: '24px', padding: '28px 24px', boxShadow: '0 4px 24px rgba(42,53,51,0.10)' }">
<form @submit.prevent="submit">
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Adresse e-mail</label>
<input v-model="form.email" type="email" required autocomplete="username" :style="inputStyle()" />
<span v-if="form.errors.email" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.email }}</span>
</div>
<div style="margin-bottom:14px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Nouveau mot de passe</label>
<input v-model="form.password" type="password" required autofocus autocomplete="new-password" :style="inputStyle()" />
<span v-if="form.errors.password" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.password }}</span>
</div>
<div style="margin-bottom:24px;">
<label :style="{ fontSize: '11px', color: tok.muted, fontWeight: 600, display: 'block', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.7px' }">Confirmer le mot de passe</label>
<input v-model="form.password_confirmation" type="password" required autocomplete="new-password" :style="inputStyle()" />
<span v-if="form.errors.password_confirmation" :style="{ fontSize: '11px', color: '#c43', marginTop: '4px', display: 'block' }">{{ form.errors.password_confirmation }}</span>
</div>
<button type="submit" :disabled="form.processing"
:style="{
width: '100%', padding: '15px', background: tok.primary, color: '#fff',
borderRadius: '16px', border: 'none', fontSize: '15px', fontWeight: 700,
cursor: form.processing ? 'not-allowed' : 'pointer',
opacity: form.processing ? 0.6 : 1,
boxShadow: '0 6px 18px rgba(123,191,181,0.35)',
}">
{{ form.processing ? 'Enregistrement…' : 'Réinitialiser le mot de passe' }}
</button>
</form>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,61 @@
<script setup>
import { computed } from 'vue';
import GuestLayout from '@/Layouts/GuestLayout.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import { Head, Link, useForm } from '@inertiajs/vue3';
const props = defineProps({
status: {
type: String,
},
});
const form = useForm({});
const submit = () => {
form.post(route('verification.send'));
};
const verificationLinkSent = computed(
() => props.status === 'verification-link-sent',
);
</script>
<template>
<GuestLayout>
<Head title="Email Verification" />
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
Thanks for signing up! Before getting started, could you verify your
email address by clicking on the link we just emailed to you? If you
didn't receive the email, we will gladly send you another.
</div>
<div
class="mb-4 text-sm font-medium text-green-600 dark:text-green-400"
v-if="verificationLinkSent"
>
A new verification link has been sent to the email address you
provided during registration.
</div>
<form @submit.prevent="submit">
<div class="mt-4 flex items-center justify-between">
<PrimaryButton
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Resend Verification Email
</PrimaryButton>
<Link
:href="route('logout')"
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"
>Log Out</Link
>
</div>
</form>
</GuestLayout>
</template>