feat: add mobile application structure with emotion picker and API integration
- Added favicon and icon assets for the mobile app. - Implemented EmotionPicker component for selecting emotions with child options. - Created hooks for fetching emotions, feeds, and rapport data. - Established API service for handling requests and authentication. - Integrated Zustand for state management of user authentication. - Defined color palette for the application. - Set up TypeScript types for various entities including User, Emotion, and Feed. - Configured Babel and TypeScript for the mobile project. - Added package.json with necessary dependencies for Expo and React Native.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Plus, Edit2, Trash2, Eye, EyeOff } from 'lucide-react';
|
||||
import { Plus, Edit2, Trash2 } from 'lucide-react';
|
||||
import api from '@/lib/api';
|
||||
import type { Feed } from '@/types';
|
||||
import Card from '@/components/ui/Card';
|
||||
@@ -69,8 +69,9 @@ export default function AdminContenusPage() {
|
||||
}
|
||||
setModalOpen(false);
|
||||
await fetchFeeds();
|
||||
} catch (err: any) {
|
||||
toast.error(err.response?.data?.message || 'Erreur');
|
||||
} catch (err: unknown) {
|
||||
const axiosError = err as import('axios').AxiosError<{ message?: string }>;
|
||||
toast.error(axiosError.response?.data?.message || 'Erreur');
|
||||
} finally {
|
||||
setFormLoading(false);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Users, FileText, Heart, TrendingUp } from 'lucide-react';
|
||||
import { Users, FileText, Heart } from 'lucide-react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import api from '@/lib/api';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Plus, Edit2, Trash2, Eye, EyeOff } from 'lucide-react';
|
||||
import { Plus, Edit2, Trash2 } from 'lucide-react';
|
||||
import api from '@/lib/api';
|
||||
import type { Emotion } from '@/types';
|
||||
import Card from '@/components/ui/Card';
|
||||
@@ -81,8 +81,9 @@ export default function AdminEmotionsPage() {
|
||||
}
|
||||
setModalOpen(false);
|
||||
await fetchEmotions();
|
||||
} catch (err: any) {
|
||||
toast.error(err.response?.data?.message || 'Erreur');
|
||||
} catch (err: unknown) {
|
||||
const axiosError = err as import('axios').AxiosError<{ message?: string }>;
|
||||
toast.error(axiosError.response?.data?.message || 'Erreur');
|
||||
} finally {
|
||||
setFormLoading(false);
|
||||
}
|
||||
|
||||
@@ -64,8 +64,9 @@ export default function AdminUtilisateursPage() {
|
||||
setNewUser({ nom: '', prenom: '', email: '', password: '', password_confirmation: '', role_id: 2 });
|
||||
await fetchUsers();
|
||||
toast.success('Utilisateur créé');
|
||||
} catch (err: any) {
|
||||
toast.error(err.response?.data?.message || 'Erreur lors de la création');
|
||||
} catch (err: unknown) {
|
||||
const axiosError = err as import('axios').AxiosError<{ message?: string }>;
|
||||
toast.error(axiosError.response?.data?.message || 'Erreur lors de la création');
|
||||
} finally {
|
||||
setFormLoading(false);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ export default function ConnexionPage() {
|
||||
await login(email, password);
|
||||
toast.success('Connexion réussie !');
|
||||
router.push('/dashboard');
|
||||
} catch (error: any) {
|
||||
const message = error?.response?.data?.message || 'Identifiants incorrects';
|
||||
} catch (error: unknown) {
|
||||
const axiosError = error as import('axios').AxiosError<{ message?: string }>;
|
||||
const message = axiosError.response?.data?.message || 'Identifiants incorrects';
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -83,6 +84,7 @@ export default function ConnexionPage() {
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
aria-label={showPassword ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
|
||||
@@ -36,8 +36,9 @@ export default function InscriptionPage() {
|
||||
await register(form);
|
||||
toast.success('Inscription réussie ! Bienvenue sur CESIZen.');
|
||||
router.push('/dashboard');
|
||||
} catch (error: any) {
|
||||
const data = error?.response?.data;
|
||||
} catch (error: unknown) {
|
||||
const axiosError = error as import('axios').AxiosError<{ message?: string; errors?: Record<string, string[]> }>;
|
||||
const data = axiosError.response?.data;
|
||||
if (data?.errors) {
|
||||
setErrors(data.errors);
|
||||
} else {
|
||||
@@ -143,6 +144,7 @@ export default function InscriptionPage() {
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
aria-label={showPassword ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
@@ -181,8 +183,8 @@ export default function InscriptionPage() {
|
||||
/>
|
||||
<label htmlFor="consentement_rgpd" className="text-sm text-gray-600 dark:text-gray-400">
|
||||
J'accepte le traitement de mes données personnelles conformément à la{' '}
|
||||
<Link href="/informations" className="text-malachite-600 dark:text-malachite-400 hover:underline">
|
||||
politique de confidentialite
|
||||
<Link href="/politique-de-confidentialite" className="text-malachite-600 dark:text-malachite-400 hover:underline">
|
||||
politique de confidentialité
|
||||
</Link>
|
||||
. Mes données sont chiffrées et je peux supprimer mon compte à tout moment.
|
||||
</label>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Heart, BarChart3, Plus, BookOpen, TrendingUp } from 'lucide-react';
|
||||
import { Heart, BarChart3, Plus, BookOpen } from 'lucide-react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useSaisies } from '@/hooks/useTracker';
|
||||
import { useRapport } from '@/hooks/useRapport';
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Plus, Filter, Calendar } from 'lucide-react';
|
||||
import { Plus, Filter } from 'lucide-react';
|
||||
import { useSaisies, useDeleteSaisie } from '@/hooks/useTracker';
|
||||
import { useEmotions } from '@/hooks/useEmotions';
|
||||
import TrackerTimeline from '@/components/features/TrackerTimeline';
|
||||
import EditSaisieModal from '@/components/features/EditSaisieModal';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Skeleton from '@/components/ui/Skeleton';
|
||||
import toast from 'react-hot-toast';
|
||||
import type { SaisieTracker } from '@/types';
|
||||
|
||||
export default function JournalPage() {
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
@@ -18,6 +20,7 @@ export default function JournalPage() {
|
||||
date_fin?: string;
|
||||
emotion_id?: number;
|
||||
}>({});
|
||||
const [editingSaisie, setEditingSaisie] = useState<SaisieTracker | null>(null);
|
||||
|
||||
const { data: saisies, isLoading } = useSaisies(filters);
|
||||
const { data: emotions } = useEmotions();
|
||||
@@ -134,9 +137,17 @@ export default function JournalPage() {
|
||||
) : (
|
||||
<TrackerTimeline
|
||||
saisies={saisies || []}
|
||||
onEdit={(saisie) => setEditingSaisie(saisie)}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Modale d'édition */}
|
||||
<EditSaisieModal
|
||||
saisie={editingSaisie}
|
||||
isOpen={!!editingSaisie}
|
||||
onClose={() => setEditingSaisie(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,8 +37,9 @@ export default function ProfilPage() {
|
||||
await fetchUser();
|
||||
setEditing(false);
|
||||
toast.success('Profil mis à jour');
|
||||
} catch (err: any) {
|
||||
toast.error(err.response?.data?.message || 'Erreur lors de la mise à jour');
|
||||
} catch (err: unknown) {
|
||||
const axiosError = err as import('axios').AxiosError<{ message?: string }>;
|
||||
toast.error(axiosError.response?.data?.message || 'Erreur lors de la mise à jour');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -52,8 +53,9 @@ export default function ProfilPage() {
|
||||
setChangingPassword(false);
|
||||
setPasswordForm({ ancien_mot_de_passe: '', password: '', password_confirmation: '' });
|
||||
toast.success('Mot de passe modifié');
|
||||
} catch (err: any) {
|
||||
toast.error(err.response?.data?.message || 'Erreur lors du changement');
|
||||
} catch (err: unknown) {
|
||||
const axiosError = err as import('axios').AxiosError<{ message?: string }>;
|
||||
toast.error(axiosError.response?.data?.message || 'Erreur lors du changement');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -65,8 +67,9 @@ export default function ProfilPage() {
|
||||
await api.delete('/profil');
|
||||
toast.success('Compte supprimé');
|
||||
logout();
|
||||
} catch (err: any) {
|
||||
toast.error(err.response?.data?.message || 'Erreur lors de la suppression');
|
||||
} catch (err: unknown) {
|
||||
const axiosError = err as import('axios').AxiosError<{ message?: string }>;
|
||||
toast.error(axiosError.response?.data?.message || 'Erreur lors de la suppression');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
import { Phone } from 'lucide-react';
|
||||
|
||||
export default function ContactsUrgencePage() {
|
||||
const contacts = [
|
||||
{
|
||||
name: 'Numéro national de prévention du suicide',
|
||||
number: '3114',
|
||||
description:
|
||||
'Numéro national disponible 24h/24 et 7j/7. Appel gratuit et confidentiel pour les personnes en détresse psychologique et leur entourage.',
|
||||
},
|
||||
{
|
||||
name: 'SOS Amitié',
|
||||
number: '09 72 39 40 50',
|
||||
description:
|
||||
'Écoute et soutien pour les personnes en souffrance, solitude ou détresse morale. Service disponible 24h/24.',
|
||||
},
|
||||
{
|
||||
name: 'Fil Santé Jeunes',
|
||||
number: '0 800 235 236',
|
||||
description:
|
||||
'Ligne d\u2019écoute gratuite et anonyme pour les jeunes de 12 à 25 ans. Du lundi au dimanche de 9h à 23h.',
|
||||
},
|
||||
{
|
||||
name: 'SAMU',
|
||||
number: '15',
|
||||
description:
|
||||
'Service d\u2019aide médicale urgente. En cas d\u2019urgence vitale, appelez immédiatement le 15 ou le 112.',
|
||||
},
|
||||
{
|
||||
name: 'SOS Médecins',
|
||||
number: '3624',
|
||||
description:
|
||||
'Service de médecins disponible pour des consultations à domicile, 24h/24 et 7j/7.',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-4 py-12">
|
||||
<h1 className="font-display text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Contacts d'urgence
|
||||
</h1>
|
||||
<p className="text-lg text-gray-500 dark:text-gray-400 mb-8">
|
||||
Si vous ou un proche traversez une période difficile, n'hésitez pas à
|
||||
contacter l'un de ces services. Vous n'êtes pas seul(e).
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{contacts.map((contact) => (
|
||||
<Card key={contact.number}>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 w-12 h-12 rounded-full bg-red-50 dark:bg-red-950/50 flex items-center justify-center">
|
||||
<Phone size={20} className="text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="font-display text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
||||
{contact.name}
|
||||
</h2>
|
||||
<a
|
||||
href={`tel:${contact.number.replace(/\s/g, '')}`}
|
||||
className="inline-block text-xl font-bold text-malachite-600 dark:text-malachite-400 mb-2 hover:underline"
|
||||
>
|
||||
{contact.number}
|
||||
</a>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 leading-relaxed">
|
||||
{contact.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card className="mt-8 bg-red-50 dark:bg-red-950/30 border-red-100 dark:border-red-900">
|
||||
<p className="text-sm text-red-700 dark:text-red-400 text-center leading-relaxed">
|
||||
<strong>En cas d'urgence vitale</strong>, appelez immédiatement le{' '}
|
||||
<strong>15</strong> (SAMU) ou le <strong>112</strong> (numéro d'urgence
|
||||
européen).
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { ArrowLeft, Calendar, User } from 'lucide-react';
|
||||
import { useFeed } from '@/hooks/useFeeds';
|
||||
import Skeleton from '@/components/ui/Skeleton';
|
||||
@@ -52,10 +53,13 @@ export default function ArticlePage() {
|
||||
|
||||
{feed.image_url && (
|
||||
<div className="relative w-full h-64 sm:h-80 rounded-2xl overflow-hidden bg-gray-100 dark:bg-gray-800 mb-8">
|
||||
<img
|
||||
<Image
|
||||
src={feed.image_url}
|
||||
alt={feed.titre}
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-full h-full object-cover"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { BookOpen, Calendar } from 'lucide-react';
|
||||
import { useFeeds } from '@/hooks/useFeeds';
|
||||
import Card from '@/components/ui/Card';
|
||||
@@ -39,10 +40,13 @@ export default function InformationsPage() {
|
||||
<Card hover className="h-full">
|
||||
{feed.image_url && (
|
||||
<div className="relative w-full h-48 mb-4 rounded-xl overflow-hidden bg-gray-100 dark:bg-gray-800">
|
||||
<img
|
||||
<Image
|
||||
src={feed.image_url}
|
||||
alt={feed.titre}
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-full h-full object-cover"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
|
||||
export default function MentionsLegalesPage() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-4 py-12">
|
||||
<h1 className="font-display text-4xl font-bold text-gray-900 dark:text-white mb-8">
|
||||
Mentions légales
|
||||
</h1>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Éditeur de l'application
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
CESIZen est une application développée dans le cadre d'un projet
|
||||
pédagogique au sein de{' '}
|
||||
<strong>CESI École d'Ingénieurs</strong>. Cette application est
|
||||
réalisée à but éducatif et n'a pas de vocation commerciale.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Hébergement
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
L'application est hébergée dans le cadre de l'infrastructure
|
||||
pédagogique de CESI. Les données sont stockées sur des serveurs
|
||||
sécurisés situés en France.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Propriété intellectuelle
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
L'ensemble des contenus présents sur CESIZen (textes, images,
|
||||
graphismes, logo, icônes) sont la propriété exclusive de leurs auteurs
|
||||
respectifs et sont protégés par les lois françaises et internationales
|
||||
relatives à la propriété intellectuelle.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Responsabilité
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
CESIZen est un outil de suivi du bien-être et ne se substitue en aucun
|
||||
cas à un avis médical professionnel. En cas de détresse psychologique,
|
||||
veuillez contacter les services d'urgence ou un professionnel de
|
||||
santé.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Contact
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
Pour toute question relative aux mentions légales, vous pouvez nous
|
||||
contacter via l'adresse suivante :{' '}
|
||||
<span className="text-malachite-600 dark:text-malachite-400">
|
||||
contact@cesizen.fr
|
||||
</span>
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import Card from '@/components/ui/Card';
|
||||
|
||||
export default function PolitiqueConfidentialitePage() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-4 py-12">
|
||||
<h1 className="font-display text-4xl font-bold text-gray-900 dark:text-white mb-8">
|
||||
Politique de confidentialité
|
||||
</h1>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Conformité RGPD
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
CESIZen s'engage à respecter le Règlement Général sur la Protection
|
||||
des Données (RGPD) et la loi Informatique et Libertés. La protection
|
||||
de vos données personnelles est une priorité.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Données collectées
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed mb-3">
|
||||
Dans le cadre de l'utilisation de CESIZen, les données suivantes
|
||||
sont collectées :
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<li>Adresse e-mail</li>
|
||||
<li>Nom et prénom</li>
|
||||
<li>Données relatives aux émotions enregistrées</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Finalité du traitement
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
Ces données sont collectées uniquement dans le but de fournir un suivi
|
||||
personnalisé du bien-être émotionnel. Elles ne sont ni vendues, ni
|
||||
partagées avec des tiers à des fins commerciales.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Durée de conservation
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
Les données personnelles sont conservées pendant toute la durée
|
||||
d'utilisation du compte. En cas de suppression du compte, les
|
||||
données sont effacées dans un délai de 30 jours.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Sécurité des données
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
Les données sont chiffrées en transit (HTTPS) et au repos. Les mots de
|
||||
passe sont hachés avec des algorithmes sécurisés. L'accès aux
|
||||
données est strictement limité aux personnes habilitées.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<h2 className="font-display text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Vos droits
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed mb-3">
|
||||
Conformément au RGPD, vous disposez des droits suivants :
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<li>
|
||||
<strong>Droit d'accès :</strong> obtenir une copie de vos
|
||||
données personnelles
|
||||
</li>
|
||||
<li>
|
||||
<strong>Droit de rectification :</strong> corriger des données
|
||||
inexactes ou incomplètes
|
||||
</li>
|
||||
<li>
|
||||
<strong>Droit de suppression :</strong> demander l'effacement
|
||||
de vos données personnelles
|
||||
</li>
|
||||
<li>
|
||||
<strong>Droit à la portabilité :</strong> récupérer vos données dans
|
||||
un format structuré
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed mt-3">
|
||||
Pour exercer ces droits, contactez-nous à l'adresse :{' '}
|
||||
<span className="text-malachite-600 dark:text-malachite-400">
|
||||
contact@cesizen.fr
|
||||
</span>
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { SaisieTracker } from '@/types';
|
||||
import { useUpdateSaisie } from '@/hooks/useTracker';
|
||||
import EmotionPicker from '@/components/features/EmotionPicker';
|
||||
import Slider from '@/components/ui/Slider';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import Button from '@/components/ui/Button';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface EditSaisieModalProps {
|
||||
saisie: SaisieTracker | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function EditSaisieModal({ saisie, isOpen, onClose }: EditSaisieModalProps) {
|
||||
const updateSaisie = useUpdateSaisie();
|
||||
const [emotionId, setEmotionId] = useState<number | undefined>();
|
||||
const [intensite, setIntensite] = useState(5);
|
||||
const [note, setNote] = useState('');
|
||||
const [dateSaisie, setDateSaisie] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (saisie) {
|
||||
setEmotionId(saisie.emotion_id);
|
||||
setIntensite(saisie.intensite);
|
||||
setNote(saisie.note || '');
|
||||
setDateSaisie(saisie.date_saisie.split('T')[0]);
|
||||
}
|
||||
}, [saisie]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!saisie || !emotionId) return;
|
||||
|
||||
try {
|
||||
await updateSaisie.mutateAsync({
|
||||
id: saisie.id,
|
||||
emotion_id: emotionId,
|
||||
intensite,
|
||||
note: note.trim() || undefined,
|
||||
date_saisie: dateSaisie,
|
||||
});
|
||||
toast.success('Saisie modifiée !');
|
||||
onClose();
|
||||
} catch {
|
||||
toast.error('Erreur lors de la modification');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Modifier la saisie" size="lg">
|
||||
<div className="space-y-6">
|
||||
{/* Émotion */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Émotion
|
||||
</label>
|
||||
<EmotionPicker value={emotionId} onChange={setEmotionId} />
|
||||
</div>
|
||||
|
||||
{/* Intensité */}
|
||||
<div>
|
||||
<Slider
|
||||
value={intensite}
|
||||
onChange={setIntensite}
|
||||
min={1}
|
||||
max={10}
|
||||
label="Intensité de l'émotion"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
||||
<span>Faible</span>
|
||||
<span>Modérée</span>
|
||||
<span>Forte</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
|
||||
Date de la saisie
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={dateSaisie}
|
||||
onChange={(e) => setDateSaisie(e.target.value)}
|
||||
className="w-full rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-candlelight-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Note */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
|
||||
Note (optionnel)
|
||||
</label>
|
||||
<textarea
|
||||
value={note}
|
||||
onChange={(e) => setNote(e.target.value)}
|
||||
placeholder="Décrivez ce que vous ressentez..."
|
||||
rows={3}
|
||||
className="w-full rounded-xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-candlelight-500 placeholder:text-gray-400 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<Button variant="ghost" onClick={onClose}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
loading={updateSaisie.isPending}
|
||||
disabled={!emotionId}
|
||||
>
|
||||
Enregistrer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ interface AuthState {
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
(set) => ({
|
||||
user: null,
|
||||
token: null,
|
||||
isAuthenticated: false,
|
||||
|
||||
Reference in New Issue
Block a user