diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.manus/db/db-query-1763414434598.json b/.manus/db/db-query-1763414434598.json deleted file mode 100644 index 138c8d8..0000000 --- a/.manus/db/db-query-1763414434598.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "query": "SELECT id, userId, LEFT(token, 20) as token_preview, expiresAt, used, createdAt FROM passwordResetTokens ORDER BY createdAt DESC LIMIT 5;", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT id, userId, LEFT(token, 20) as token_preview, expiresAt, used, createdAt FROM passwordResetTokens ORDER BY createdAt DESC LIMIT 5;", - "rows": [ - { - "id": "1", - "userId": "1", - "token_preview": "e6626bd6ba7eabccd094", - "expiresAt": "2025-11-18 21:19:50", - "used": "0", - "createdAt": "2025-11-17 21:19:50" - } - ], - "messages": [], - "stdout": "id\tuserId\ttoken_preview\texpiresAt\tused\tcreatedAt\n1\t1\te6626bd6ba7eabccd094\t2025-11-18 21:19:50\t0\t2025-11-17 21:19:50\n", - "stderr": "", - "execution_time_ms": 901 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763417332805.json b/.manus/db/db-query-1763417332805.json deleted file mode 100644 index cb7dc39..0000000 --- a/.manus/db/db-query-1763417332805.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "query": "SELECT * FROM emailTemplates;", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT * FROM emailTemplates;", - "rows": [], - "messages": [], - "stdout": "", - "stderr": "", - "execution_time_ms": 57 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763417422542.json b/.manus/db/db-query-1763417422542.json deleted file mode 100644 index 19d7239..0000000 --- a/.manus/db/db-query-1763417422542.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "query": "INSERT INTO emailTemplates (type, name, logoUrl, primaryColor, headerBgColor, headerTextColor, headerTitle, footerText, active) VALUES \n('inscription', 'Confirmation d''inscription', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1),\n('teaser', 'Email teaser', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1),\n('rappel', 'Rappel J-7', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1),\n('reset_password', 'Réinitialisation de mot de passe', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1);", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute INSERT INTO emailTemplates (type, name, logoUrl, primaryColor, headerBgColor, headerTextColor, headerTitle, footerText, active) VALUES \n('inscription', 'Confirmation d''inscription', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1),\n('teaser', 'Email teaser', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1),\n('rappel', 'Rappel J-7', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1),\n('reset_password', 'Réinitialisation de mot de passe', NULL, '#2563eb', '#2563eb', '#ffffff', 'Formation Manager Itinova', 'Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.', 1);", - "rows": [], - "messages": [], - "stdout": "", - "stderr": "", - "execution_time_ms": 64 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763546134103.json b/.manus/db/db-query-1763546134103.json deleted file mode 100644 index 6e1ab9a..0000000 --- a/.manus/db/db-query-1763546134103.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "query": "UPDATE users \nSET password = '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n role = 'admin',\n emailVerified = TRUE\nWHERE email = 'o.pareige@itinova.org';", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute UPDATE users \nSET password = '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n role = 'admin',\n emailVerified = TRUE\nWHERE email = 'o.pareige@itinova.org';", - "rows": [], - "messages": [], - "stdout": "", - "stderr": "", - "execution_time_ms": 414 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763546215617.json b/.manus/db/db-query-1763546215617.json deleted file mode 100644 index 373e4a2..0000000 --- a/.manus/db/db-query-1763546215617.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "query": "SELECT id, email, name, role, password, emailVerified, isActive FROM users WHERE email = 'o.pareige@itinova.org';", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT id, email, name, role, password, emailVerified, isActive FROM users WHERE email = 'o.pareige@itinova.org';", - "rows": [ - { - "id": "1", - "email": "o.pareige@itinova.org", - "name": "olivier pareige", - "role": "admin", - "password": "$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK", - "emailVerified": "1", - "isActive": "1" - } - ], - "messages": [], - "stdout": "id\temail\tname\trole\tpassword\temailVerified\tisActive\n1\to.pareige@itinova.org\tolivier pareige\tadmin\t$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK\t1\t1\n", - "stderr": "", - "execution_time_ms": 49 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763546239968.json b/.manus/db/db-query-1763546239968.json deleted file mode 100644 index 6250a6f..0000000 --- a/.manus/db/db-query-1763546239968.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "query": "DELETE FROM users WHERE email = 'o.pareige@itinova.org';\n\nINSERT INTO users (email, password, name, role, emailVerified, isActive, createdAt, updatedAt, lastSignedIn)\nVALUES (\n 'o.pareige@itinova.org',\n '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n 'Olivier Pareige',\n 'admin',\n 1,\n 1,\n NOW(),\n NOW(),\n NOW()\n);", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute DELETE FROM users WHERE email = 'o.pareige@itinova.org';\n\nINSERT INTO users (email, password, name, role, emailVerified, isActive, createdAt, updatedAt, lastSignedIn)\nVALUES (\n 'o.pareige@itinova.org',\n '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n 'Olivier Pareige',\n 'admin',\n 1,\n 1,\n NOW(),\n NOW(),\n NOW()\n);", - "rows": [], - "messages": [], - "stdout": "", - "stderr": "", - "execution_time_ms": 71 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763546569851.json b/.manus/db/db-query-1763546569851.json deleted file mode 100644 index b328c16..0000000 --- a/.manus/db/db-query-1763546569851.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "query": "SELECT id, email, password, LENGTH(password) as password_length FROM users WHERE email = 'o.pareige@itinova.org';", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT id, email, password, LENGTH(password) as password_length FROM users WHERE email = 'o.pareige@itinova.org';", - "rows": [ - { - "id": "2010001", - "email": "o.pareige@itinova.org", - "password": "$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK", - "password_length": "60" - } - ], - "messages": [], - "stdout": "id\temail\tpassword\tpassword_length\n2010001\to.pareige@itinova.org\t$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK\t60\n", - "stderr": "", - "execution_time_ms": 141 -} \ No newline at end of file diff --git a/.manus/db/db-query-1763546726252.json b/.manus/db/db-query-1763546726252.json deleted file mode 100644 index f5aeda1..0000000 --- a/.manus/db/db-query-1763546726252.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "query": "UPDATE users SET password = '$2b$10$wtfrg13y5qw/VZKdQfcs4epTfZT2Hje5eomHggJEfSLp9m9ZGlk2O' WHERE email = 'o.pareige@itinova.org';", - "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute UPDATE users SET password = '$2b$10$wtfrg13y5qw/VZKdQfcs4epTfZT2Hje5eomHggJEfSLp9m9ZGlk2O' WHERE email = 'o.pareige@itinova.org';", - "rows": [], - "messages": [], - "stdout": "", - "stderr": "", - "execution_time_ms": 58 -} \ No newline at end of file diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md deleted file mode 100644 index 4afb8fb..0000000 --- a/DOCUMENTATION.md +++ /dev/null @@ -1,285 +0,0 @@ -# Documentation - Gestion des Formations Manager Itinova - -## Vue d'ensemble - -L'application **Gestion des Formations Manager Itinova** est une solution complète développée pour gérer l'organisation interne des formations destinées à environ 150 apprenants entre février et octobre 2026, puis 3 sessions par an. Elle permet à la DRH (assistante formation, responsable Développement RH) et à la Directrice Qualité et Transformation de gérer efficacement les inscriptions, les sessions, et les communications automatisées. - -## Fonctionnalités principales - -### 1. Gestion des formations - -L'application permet de créer et gérer plusieurs formations en parallèle. Chaque formation dispose d'un lien unique d'inscription qui peut être partagé avec les apprenants. - -**Caractéristiques :** -- Création de formations avec nom, description et lien unique personnalisé -- Activation/désactivation des formations -- Génération automatique de liens d'inscription distincts pour chaque formation - -### 2. Gestion des sessions - -Les sessions sont les instances concrètes d'une formation avec des dates, un lieu et une capacité définis. - -**Caractéristiques :** -- Création de sessions avec dates de début et fin -- Définition du lieu de formation -- Capacité maximale fixée à 12 participants par session -- Date de blocage automatique à J-15 avant le début de la session -- Statuts de session : ouverte, bloquée, terminée - -### 3. Gestion des apprenants - -L'application maintient une base de données des apprenants avec leurs informations essentielles. - -**Informations collectées :** -- Nom et prénom -- Email professionnel -- Code établissement - -### 4. Système d'inscription - -Les apprenants peuvent s'inscrire via un lien unique propre à chaque formation. Le système applique automatiquement toutes les contraintes définies. - -**Validations automatiques :** -- Vérification de la capacité maximale (12 participants) -- Prévention des doubles inscriptions -- Blocage des inscriptions et désinscriptions à J-15 -- Gestion automatique de la liste d'attente si la session est complète - -### 5. Invitations et communications automatiques - -Dès qu'un apprenant s'inscrit, le système génère et envoie automatiquement une invitation Outlook avec tous les détails de la formation. - -**Fonctionnalités d'envoi :** -- **Email de confirmation** : envoyé immédiatement après l'inscription avec invitation Outlook (.ics) en pièce jointe -- **Invitation Outlook** : fichier .ics avec statut "occupé" incluant le lieu de formation -- **Email teaser** : peut être envoyé manuellement par les administrateurs à tous les inscrits d'une session -- **Email de rappel J-7** : peut être envoyé manuellement 7 jours avant la formation - -### 6. Exports et feuilles de présence - -Les administrateurs peuvent exporter les listes d'inscrits dans différents formats pour faciliter la gestion administrative. - -**Formats disponibles :** -- **Export Excel** : liste complète des inscrits avec toutes leurs informations -- **Export PDF** : liste des inscrits au format PDF -- **Feuille de présence PDF** : document prêt à imprimer avec nom, prénom, code établissement et colonne signature - -## Guide d'utilisation - -### Pour les administrateurs - -#### Connexion - -1. Accédez à l'application via l'URL fournie -2. Cliquez sur "Se connecter" -3. Utilisez vos identifiants Manus OAuth -4. Vous serez redirigé vers le tableau de bord administrateur - -#### Créer une formation - -1. Dans le menu latéral, cliquez sur **Formations** -2. Cliquez sur le bouton **Nouvelle formation** -3. Remplissez les informations : - - Nom de la formation (ex: "Manager Itinova") - - Description (optionnelle) - - Lien unique (ex: "manager-itinova-2026") - - Cochez "Formation active" pour la rendre disponible -4. Cliquez sur **Créer** -5. Le lien d'inscription sera généré automatiquement : `/inscription/[votre-lien-unique]` - -#### Créer une session - -1. Dans le menu latéral, cliquez sur **Sessions** -2. Cliquez sur le bouton **Nouvelle session** -3. Remplissez les informations : - - Sélectionnez la formation associée - - Nom de la session (ex: "Session 1 - Février 2026") - - Date et heure de début - - Date et heure de fin - - Lieu de la formation (sera inclus dans l'invitation Outlook) - - Capacité maximale (par défaut 12) - - Date de blocage (J-15 avant le début) - - Statut (ouverte, bloquée, terminée) -4. Cliquez sur **Créer** - -#### Gérer les inscriptions - -1. Dans le menu **Sessions**, cliquez sur l'icône **Utilisateurs** à côté d'une session -2. Vous verrez : - - Le nombre de places confirmées sur la capacité totale - - Le nombre de personnes en liste d'attente - - La liste complète des inscrits avec leurs informations -3. Actions disponibles : - - Modifier le statut d'une inscription (confirmée, liste d'attente, annulée) - - Exporter la liste en Excel ou PDF - - Générer une feuille de présence - - Envoyer des emails groupés (teaser ou rappel J-7) - -#### Envoyer des emails groupés - -1. Accédez à la page des inscrits d'une session -2. Cliquez sur **Envoyer teaser** pour envoyer un email d'introduction à tous les inscrits confirmés -3. Cliquez sur **Envoyer rappel J-7** pour envoyer un rappel 7 jours avant la formation -4. Un message de confirmation indiquera le nombre d'emails envoyés avec succès - -#### Exporter les données - -1. Accédez à la page des inscrits d'une session -2. Cliquez sur : - - **Exporter Excel** : télécharge un fichier .xlsx avec toutes les informations - - **Exporter PDF** : télécharge un fichier PDF avec la liste des inscrits - - **Feuille de présence** : télécharge un PDF prêt à imprimer pour la signature des participants - -### Pour les apprenants - -#### S'inscrire à une formation - -1. Cliquez sur le lien d'inscription fourni par la DRH (ex: `https://[domaine]/inscription/manager-itinova-2026`) -2. Consultez les sessions disponibles avec : - - Les dates et horaires - - Le lieu de formation - - Le nombre de places disponibles - - Le statut (disponible, complète, fermée) -3. Sélectionnez la session souhaitée en cliquant dessus -4. Remplissez vos informations : - - Nom - - Prénom - - Email professionnel - - Code établissement -5. Cliquez sur **Confirmer mon inscription** -6. Vous recevrez immédiatement : - - Un email de confirmation - - Une invitation Outlook (.ics) à ajouter à votre calendrier - -#### Que se passe-t-il après l'inscription ? - -- **Si votre inscription est confirmée** : vous recevrez une invitation Outlook qui bloquera automatiquement votre agenda avec le statut "occupé" -- **Si la session est complète** : vous serez placé en liste d'attente et contacté si une place se libère -- **15 jours avant la formation** : les inscriptions et désinscriptions seront bloquées -- **7 jours avant la formation** : vous recevrez un email de rappel avec tous les détails pratiques - -## Gestion des rôles et permissions - -L'application utilise un système de rôles pour contrôler l'accès aux fonctionnalités : - -### Rôle Administrateur - -Accordé aux membres de la DRH (assistante formation, responsable Développement RH, Directrice Qualité et Transformation, stagiaire). - -**Permissions :** -- Accès complet au tableau de bord administrateur -- Création, modification et suppression de formations -- Création, modification et suppression de sessions -- Gestion des apprenants -- Consultation et modification des inscriptions -- Envoi d'emails groupés -- Export des données - -### Rôle Utilisateur - -Les apprenants qui s'inscrivent via le lien public. - -**Permissions :** -- Accès au formulaire d'inscription public -- Consultation des sessions disponibles -- Inscription à une session (dans les limites définies) - -## Contraintes et règles métier - -### Capacité des sessions - -Chaque session est limitée à **12 participants maximum**. Au-delà, les apprenants sont automatiquement placés en liste d'attente. - -### Blocage à J-15 - -Les inscriptions et désinscriptions sont automatiquement bloquées **15 jours avant le début de la session**. Cette règle garantit la stabilité des groupes et évite les désistements de dernière minute. - -### Prévention des doubles inscriptions - -Le système empêche un apprenant de s'inscrire plusieurs fois à la même session. Si l'apprenant existe déjà dans la base de données (même email), ses informations sont réutilisées. - -### Statuts des inscriptions - -- **Confirmée** : l'apprenant a une place garantie dans la session -- **Liste d'attente** : la session est complète, l'apprenant sera contacté si une place se libère -- **Annulée** : l'inscription a été annulée par l'apprenant ou l'administrateur - -## Architecture technique - -### Technologies utilisées - -L'application est construite avec une architecture moderne full-stack : - -**Frontend :** -- React 19 avec TypeScript -- Tailwind CSS 4 pour le design -- tRPC pour la communication type-safe avec le backend -- Wouter pour le routage -- shadcn/ui pour les composants d'interface - -**Backend :** -- Node.js avec Express -- tRPC pour les API type-safe -- Drizzle ORM pour l'accès à la base de données -- MySQL/TiDB comme base de données - -**Fonctionnalités :** -- Génération de fichiers ICS (iCalendar) pour Outlook -- Export Excel avec xlsx -- Export PDF avec jsPDF -- Système d'emails automatisés - -### Base de données - -Le schéma de base de données comprend 5 tables principales : - -1. **users** : utilisateurs authentifiés (administrateurs) -2. **formations** : catalogue des formations -3. **sessions** : instances de formations avec dates et lieux -4. **apprenants** : base de données des participants -5. **inscriptions** : relation entre apprenants et sessions avec statuts - -## Confidentialité et sécurité - -L'application respecte les principes de confidentialité des données : - -- **Authentification sécurisée** : via Manus OAuth pour les administrateurs -- **Contrôle d'accès** : seuls les administrateurs peuvent accéder aux données sensibles -- **Protection des données** : les informations personnelles des apprenants sont stockées de manière sécurisée -- **Emails professionnels** : seuls les emails professionnels sont collectés - -## Support et maintenance - -### Contact - -Pour toute question ou problème technique, contactez le service RH ou l'équipe de développement. - -### Évolutions futures - -L'application est conçue pour être évolutive. Des fonctionnalités supplémentaires peuvent être ajoutées selon les besoins : - -- Intégration avec un vrai service SMTP pour l'envoi d'emails -- Tableau de bord analytique avec statistiques de participation -- Système de notation et d'évaluation post-formation -- Gestion des formateurs et de leurs disponibilités -- Notifications push pour les rappels - -## Notes importantes - -### Emails simulés - -Dans la version actuelle, les emails sont **simulés** et affichés dans les logs du serveur. Pour une utilisation en production, il est nécessaire d'intégrer un service SMTP réel (comme SendGrid, Mailgun, ou un serveur SMTP d'entreprise). - -### Personnalisation - -Les templates d'emails peuvent être personnalisés dans le fichier `server/emailService.ts` pour correspondre à la charte graphique de l'entreprise. - -### Sauvegarde des données - -Il est recommandé de mettre en place une stratégie de sauvegarde régulière de la base de données pour prévenir toute perte de données. - ---- - -**Version de la documentation :** 1.0 -**Date de dernière mise à jour :** 2026-01-17 -**Auteur :** Manus AI diff --git a/EMAIL_SETUP.md b/EMAIL_SETUP.md deleted file mode 100644 index bc8a0ef..0000000 --- a/EMAIL_SETUP.md +++ /dev/null @@ -1,109 +0,0 @@ -# Configuration de l'envoi d'emails - -## État actuel - -Par défaut, l'application **simule** l'envoi d'emails en créant des notifications pour le propriétaire du projet. Les emails ne sont pas réellement envoyés aux utilisateurs finaux. - -## Configuration de l'envoi réel d'emails - -Pour activer l'envoi réel d'emails, vous devez configurer un service d'envoi d'emails externe. L'application supporte **Resend**, un service moderne et simple d'utilisation. - -### Étape 1 : Créer un compte Resend - -1. Allez sur [https://resend.com](https://resend.com) -2. Créez un compte gratuit (100 emails/jour inclus) -3. Vérifiez votre adresse email - -### Étape 2 : Obtenir une clé API - -1. Dans le tableau de bord Resend, allez dans **API Keys** -2. Cliquez sur **Create API Key** -3. Donnez un nom à votre clé (ex: "Manager Itinova Production") -4. Copiez la clé API générée (elle commence par `re_`) - -### Étape 3 : Configurer votre domaine (optionnel mais recommandé) - -Pour envoyer des emails depuis votre propre domaine (ex: `noreply@itinova.org`) : - -1. Dans Resend, allez dans **Domains** -2. Cliquez sur **Add Domain** -3. Entrez votre domaine (ex: `itinova.org`) -4. Suivez les instructions pour ajouter les enregistrements DNS -5. Attendez la vérification (quelques minutes à quelques heures) - -**Note:** Sans domaine vérifié, les emails seront envoyés depuis `noreply@manusvm.computer` - -### Étape 4 : Ajouter les secrets dans Manus - -1. Ouvrez l'interface de gestion de votre projet Manus -2. Allez dans **Settings** → **Secrets** -3. Ajoutez les secrets suivants : - -| Nom du secret | Valeur | Description | -|---------------|--------|-------------| -| `RESEND_API_KEY` | `re_xxxxx...` | Votre clé API Resend | -| `RESEND_FROM_EMAIL` | `noreply@itinova.org` | Adresse email d'envoi (doit être vérifiée dans Resend) | - -4. Sauvegardez les modifications -5. Redémarrez l'application (elle détectera automatiquement la nouvelle configuration) - -### Étape 5 : Tester l'envoi - -1. Allez dans **Utilisateurs** -2. Cliquez sur le bouton de réinitialisation de mot de passe pour un utilisateur -3. Vérifiez que l'email a bien été reçu - -## Vérification de la configuration - -Vous pouvez vérifier si l'envoi d'emails réel est activé en consultant les logs du serveur : - -- **Emails simulés** : `[Email] RESEND_API_KEY non configurée, email simulé` -- **Emails réels** : `[Email] Email envoyé via Resend: ` - -## Alternatives à Resend - -Si vous préférez utiliser un autre service d'envoi d'emails, vous pouvez modifier le fichier `server/_core/emailSender.ts` pour intégrer : - -- **SendGrid** : Service populaire avec 100 emails/jour gratuits -- **Mailgun** : Service robuste avec 5000 emails/mois gratuits -- **AWS SES** : Service Amazon très économique pour gros volumes -- **Votre propre serveur SMTP** : Configuration personnalisée - -## Limites et quotas - -### Plan gratuit Resend -- 100 emails par jour -- 3000 emails par mois -- Support email uniquement - -### Plans payants Resend -- À partir de 20$/mois pour 50 000 emails -- Support prioritaire -- Webhooks et analytics avancés - -## Dépannage - -### Les emails n'arrivent pas - -1. Vérifiez que `RESEND_API_KEY` est correctement configurée -2. Vérifiez que `RESEND_FROM_EMAIL` utilise un domaine vérifié -3. Consultez les logs du serveur pour voir les erreurs -4. Vérifiez le dossier spam/courrier indésirable -5. Consultez le tableau de bord Resend pour voir l'état des envois - -### Erreur "Domain not verified" - -Vous devez vérifier votre domaine dans Resend en ajoutant les enregistrements DNS requis. - -### Quota dépassé - -Si vous dépassez le quota gratuit, vous devrez : -- Attendre le lendemain (quota journalier) -- Passer à un plan payant -- Utiliser un autre service d'envoi - -## Support - -Pour toute question sur la configuration de l'envoi d'emails, consultez : -- [Documentation Resend](https://resend.com/docs) -- [API Reference Resend](https://resend.com/docs/api-reference) diff --git a/GUIDE_DEPLOIEMENT_LINUX.md b/GUIDE_DEPLOIEMENT_LINUX.md deleted file mode 100644 index ff2ac1e..0000000 --- a/GUIDE_DEPLOIEMENT_LINUX.md +++ /dev/null @@ -1,600 +0,0 @@ -# Guide de déploiement - Formation Manager Itinova sur serveur Linux - -## Vue d'ensemble - -Ce guide vous accompagne dans le déploiement de l'application Formation Manager Itinova sur un serveur Linux (Ubuntu/Debian) avec authentification locale email/mot de passe (sans OAuth Manus). - -**Durée estimée** : 1-2 heures -**Niveau** : Intermédiaire - ---- - -## Prérequis - -### Logiciels requis - -- **Système** : Ubuntu 22.04 LTS ou Debian 11+ -- **Node.js** : Version 22.x -- **pnpm** : Version 10.x -- **MySQL** : Version 8.0+ ou MariaDB 10.6+ -- **Nginx** : Pour le reverse proxy (optionnel mais recommandé) - -### Accès nécessaires - -- Accès SSH au serveur -- Droits sudo -- Accès à la base de données MySQL - ---- - -## Étape 1 : Préparation du serveur - -### 1.1 Mise à jour du système - -```bash -sudo apt update -sudo apt upgrade -y -``` - -### 1.2 Installation de Node.js 22.x - -```bash -# Installer Node.js via NodeSource -curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - -sudo apt install -y nodejs - -# Vérifier l'installation -node --version # Doit afficher v22.x.x -``` - -### 1.3 Installation de pnpm - -```bash -npm install -g pnpm -pnpm --version # Doit afficher 10.x.x -``` - -### 1.4 Installation de MySQL - -```bash -sudo apt install -y mysql-server -sudo mysql_secure_installation -``` - ---- - -## Étape 2 : Configuration de la base de données - -### 2.1 Créer la base de données et l'utilisateur - -```bash -sudo mysql -``` - -Dans MySQL : - -```sql -CREATE DATABASE formation_manager CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE USER 'formation_user'@'localhost' IDENTIFIED BY 'VOTRE_MOT_DE_PASSE_SECURISE'; -GRANT ALL PRIVILEGES ON formation_manager.* TO 'formation_user'@'localhost'; -FLUSH PRIVILEGES; -EXIT; -``` - -### 2.2 Tester la connexion - -```bash -mysql -u formation_user -p formation_manager -# Entrez le mot de passe, puis EXIT -``` - ---- - -## Étape 3 : Déploiement de l'application - -### 3.1 Créer le répertoire de l'application - -```bash -sudo mkdir -p /var/www/formation-manager-itinova -sudo chown -R $USER:$USER /var/www/formation-manager-itinova -cd /var/www/formation-manager-itinova -``` - -### 3.2 Cloner ou copier les fichiers - -**Option A : Via Git (recommandé)** - -```bash -git clone https://github.com/votre-repo/formation-manager-itinova.git . -``` - -**Option B : Via upload manuel** - -Transférez les fichiers via SCP/SFTP vers `/var/www/formation-manager-itinova/` - -### 3.3 Installer les dépendances - -```bash -cd /var/www/formation-manager-itinova -pnpm install -``` - ---- - -## Étape 4 : Configuration de l'application - -### 4.1 Créer le fichier .env - -```bash -nano /var/www/formation-manager-itinova/.env -``` - -**Contenu du fichier .env :** - -```env -# Base de données -DATABASE_URL=mysql://formation_user:VOTRE_MOT_DE_PASSE@localhost:3306/formation_manager - -# Sécurité JWT (générez une chaîne aléatoire longue) -JWT_SECRET=CHANGEZ_CECI_PAR_UNE_CHAINE_ALEATOIRE_TRES_LONGUE_ET_SECURISEE - -# Configuration de l'application -NODE_ENV=production -VITE_APP_TITLE=Formation Manager Itinova -VITE_APP_LOGO=/logo.svg - -# Email (optionnel - pour Resend) -RESEND_API_KEY= -RESEND_FROM_EMAIL= - -# Port de l'application (par défaut 3000) -PORT=3000 -``` - -**Générer un JWT_SECRET sécurisé :** - -```bash -openssl rand -base64 64 -``` - -Copiez le résultat dans `JWT_SECRET`. - -### 4.2 Sécuriser le fichier .env - -```bash -chmod 600 /var/www/formation-manager-itinova/.env -``` - -### 4.3 Initialiser la base de données - -```bash -cd /var/www/formation-manager-itinova -pnpm db:push -``` - -Cette commande va créer toutes les tables nécessaires. - ---- - -## Étape 5 : Compilation de l'application - -### 5.1 Compiler le frontend et le backend - -```bash -cd /var/www/formation-manager-itinova -pnpm build -``` - -### 5.2 Vérifier que la compilation a réussi - -```bash -ls -la /var/www/formation-manager-itinova/dist/ -``` - -Vous devriez voir un fichier `index.js`. - ---- - -## Étape 6 : Configuration du service systemd - -### 6.1 Créer le fichier de service - -```bash -sudo nano /etc/systemd/system/formation-manager.service -``` - -**Contenu du fichier :** - -```ini -[Unit] -Description=Formation Manager Itinova -After=network.target mysql.service - -[Service] -Type=simple -User=www-data -WorkingDirectory=/var/www/formation-manager-itinova -Environment="NODE_ENV=production" -Environment="HOME=/var/www/formation-manager-itinova" -Environment="PNPM_HOME=/var/www/formation-manager-itinova/.pnpm" -EnvironmentFile=/var/www/formation-manager-itinova/.env -ExecStart=/usr/bin/pnpm start -Restart=always -RestartSec=10 -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target -``` - -### 6.2 Configurer les permissions - -```bash -sudo chown -R www-data:www-data /var/www/formation-manager-itinova -sudo mkdir -p /var/www/.local/share/pnpm -sudo chown -R www-data:www-data /var/www/.local -``` - -### 6.3 Activer et démarrer le service - -```bash -sudo systemctl daemon-reload -sudo systemctl enable formation-manager -sudo systemctl start formation-manager -``` - -### 6.4 Vérifier le statut - -```bash -sudo systemctl status formation-manager -``` - -### 6.5 Voir les logs - -```bash -sudo journalctl -u formation-manager -f -``` - ---- - -## Étape 7 : Configuration de Nginx (optionnel mais recommandé) - -### 7.1 Installer Nginx - -```bash -sudo apt install -y nginx -``` - -### 7.2 Créer la configuration du site - -```bash -sudo nano /etc/nginx/sites-available/formation-manager -``` - -**Contenu du fichier :** - -```nginx -server { - listen 80; - server_name votre-domaine.com; # Changez par votre domaine - - # Logs - access_log /var/log/nginx/formation-manager-access.log; - error_log /var/log/nginx/formation-manager-error.log; - - # Proxy vers l'application Node.js - location / { - proxy_pass http://localhost:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } -} -``` - -### 7.3 Activer le site - -```bash -sudo ln -s /etc/nginx/sites-available/formation-manager /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl restart nginx -``` - -### 7.4 Configurer le pare-feu - -```bash -sudo ufw allow 'Nginx Full' -sudo ufw allow OpenSSH -sudo ufw enable -``` - ---- - -## Étape 8 : Configuration HTTPS avec Let's Encrypt (recommandé) - -### 8.1 Installer Certbot - -```bash -sudo apt install -y certbot python3-certbot-nginx -``` - -### 8.2 Obtenir un certificat SSL - -```bash -sudo certbot --nginx -d votre-domaine.com -``` - -Suivez les instructions à l'écran. - -### 8.3 Renouvellement automatique - -```bash -sudo systemctl status certbot.timer -``` - -Le renouvellement est automatique. - ---- - -## Étape 9 : Créer le premier utilisateur administrateur - -### 9.1 Se connecter à MySQL - -```bash -mysql -u formation_user -p formation_manager -``` - -### 9.2 Créer un utilisateur admin - -```sql --- Générer un hash bcrypt pour le mot de passe "admin123" (à changer immédiatement) --- Hash bcrypt de "admin123" : $2b$10$rKZhVJQX7mEWQxGqF8vQZOQxqxqxqxqxqxqxqxqxqxqxqxqxqxqxq - -INSERT INTO users (email, password, name, role, emailVerified, isActive, createdAt, updatedAt, lastSignedIn) -VALUES ( - 'admin@itinova.org', - '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK', -- Mot de passe: admin123 - 'Administrateur', - 'admin', - TRUE, - TRUE, - NOW(), - NOW(), - NOW() -); - -EXIT; -``` - -**⚠️ IMPORTANT** : Changez le mot de passe immédiatement après la première connexion ! - ---- - -## Étape 10 : Vérification et tests - -### 10.1 Accéder à l'application - -Ouvrez votre navigateur et accédez à : -- **HTTP** : `http://votre-domaine.com` -- **HTTPS** : `https://votre-domaine.com` - -### 10.2 Se connecter - -- **Email** : `admin@itinova.org` -- **Mot de passe** : `admin123` - -### 10.3 Changer le mot de passe - -1. Connectez-vous -2. Allez dans "Utilisateurs" -3. Modifiez votre profil -4. Changez le mot de passe - ---- - -## Maintenance - -### Voir les logs - -```bash -sudo journalctl -u formation-manager -f -``` - -### Redémarrer l'application - -```bash -sudo systemctl restart formation-manager -``` - -### Mettre à jour l'application - -```bash -cd /var/www/formation-manager-itinova -git pull # Si vous utilisez Git -pnpm install -pnpm build -sudo systemctl restart formation-manager -``` - -### Sauvegarder la base de données - -```bash -mysqldump -u formation_user -p formation_manager > backup_$(date +%Y%m%d_%H%M%S).sql -``` - -### Restaurer la base de données - -```bash -mysql -u formation_user -p formation_manager < backup_YYYYMMDD_HHMMSS.sql -``` - ---- - -## Dépannage - -### L'application ne démarre pas - -1. Vérifier les logs : - ```bash - sudo journalctl -u formation-manager -n 100 --no-pager - ``` - -2. Vérifier que le fichier .env existe et est correct : - ```bash - cat /var/www/formation-manager-itinova/.env - ``` - -3. Vérifier que la base de données est accessible : - ```bash - mysql -u formation_user -p formation_manager - ``` - -### Erreur de permissions - -```bash -sudo chown -R www-data:www-data /var/www/formation-manager-itinova -sudo mkdir -p /var/www/.local/share/pnpm -sudo chown -R www-data:www-data /var/www/.local -``` - -### Erreur "drizzle-kit: not found" - -```bash -cd /var/www/formation-manager-itinova -pnpm install -``` - -### Port 3000 déjà utilisé - -Modifiez le port dans `.env` : -```env -PORT=3001 -``` - -Puis redémarrez : -```bash -sudo systemctl restart formation-manager -``` - ---- - -## Sécurité - -### Recommandations - -1. **Changez immédiatement le mot de passe admin par défaut** -2. **Utilisez HTTPS** (Let's Encrypt gratuit) -3. **Configurez un pare-feu** (ufw) -4. **Mettez à jour régulièrement** le système et l'application -5. **Sauvegardez régulièrement** la base de données -6. **Limitez l'accès SSH** (clés SSH, désactiver root login) -7. **Surveillez les logs** régulièrement - -### Sauvegardes automatiques - -Créez un script de sauvegarde : - -```bash -sudo nano /usr/local/bin/backup-formation-manager.sh -``` - -Contenu : - -```bash -#!/bin/bash -BACKUP_DIR="/var/backups/formation-manager" -DATE=$(date +%Y%m%d_%H%M%S) - -mkdir -p $BACKUP_DIR - -# Sauvegarder la base de données -mysqldump -u formation_user -p'VOTRE_MOT_DE_PASSE' formation_manager > $BACKUP_DIR/db_$DATE.sql - -# Compresser -gzip $BACKUP_DIR/db_$DATE.sql - -# Garder seulement les 30 derniers jours -find $BACKUP_DIR -name "db_*.sql.gz" -mtime +30 -delete - -echo "Sauvegarde terminée : $BACKUP_DIR/db_$DATE.sql.gz" -``` - -Rendre exécutable : - -```bash -sudo chmod +x /usr/local/bin/backup-formation-manager.sh -``` - -Ajouter au cron (tous les jours à 2h du matin) : - -```bash -sudo crontab -e -``` - -Ajouter : - -``` -0 2 * * * /usr/local/bin/backup-formation-manager.sh >> /var/log/backup-formation-manager.log 2>&1 -``` - ---- - -## Support - -Pour toute question ou problème : - -1. Consultez les logs : `sudo journalctl -u formation-manager -f` -2. Vérifiez la documentation : `/var/www/formation-manager-itinova/README.md` -3. Contactez le support technique - ---- - -## Annexe : Commandes utiles - -```bash -# Statut du service -sudo systemctl status formation-manager - -# Démarrer le service -sudo systemctl start formation-manager - -# Arrêter le service -sudo systemctl stop formation-manager - -# Redémarrer le service -sudo systemctl restart formation-manager - -# Voir les logs en temps réel -sudo journalctl -u formation-manager -f - -# Voir les 100 dernières lignes de logs -sudo journalctl -u formation-manager -n 100 --no-pager - -# Tester la connexion à la base de données -mysql -u formation_user -p formation_manager - -# Vérifier la configuration Nginx -sudo nginx -t - -# Recharger Nginx -sudo systemctl reload nginx - -# Vérifier l'utilisation des ressources -htop - -# Vérifier l'espace disque -df -h - -# Vérifier les processus Node.js -ps aux | grep node -``` - ---- - -**Fin du guide de déploiement** diff --git a/MIGRATION_AUTH_LOCALE.md b/MIGRATION_AUTH_LOCALE.md deleted file mode 100644 index 5207ecf..0000000 --- a/MIGRATION_AUTH_LOCALE.md +++ /dev/null @@ -1,260 +0,0 @@ -# Migration OAuth Manus vers Authentification Locale - -## État de la migration - -**Date** : 18 novembre 2024 -**Statut** : ⚠️ **En cours** (80% complété) - ---- - -## ✅ Modifications complétées - -### 1. Base de données - -- ✅ Schéma `users` modifié : - - Ajout du champ `email` (unique, not null) - - Ajout du champ `password` (hash bcrypt, not null) - - Ajout du champ `emailVerified` (boolean, default false) - - Suppression du champ `openId` - - Suppression du champ `loginMethod` - -### 2. Backend - -- ✅ Installation de `bcrypt` pour le hashage des mots de passe -- ✅ Création des helpers d'authentification (`server/_core/auth.ts`) : - - `hashPassword()` - Hashe un mot de passe avec bcrypt - - `verifyPassword()` - Vérifie un mot de passe - - `generateToken()` - Génère un token JWT - - `verifyToken()` - Vérifie un token JWT - -- ✅ Modification du fichier `server/db.ts` : - - `createUser()` - Crée un utilisateur avec email/password - - `getUserByEmail()` - Récupère un utilisateur par email - - `getUserById()` - Récupère un utilisateur par ID - - `updateUserLastSignedIn()` - Met à jour le dernier login - -- ✅ Modification du fichier `server/routers.ts` : - - Procédure `auth.register` - Inscription avec email/password - - Procédure `auth.login` - Connexion avec email/password - - Procédure `auth.logout` - Déconnexion (inchangée) - -- ✅ Modification du fichier `server/_core/context.ts` : - - Remplacement de l'authentification OAuth par JWT - - Lecture du token depuis le cookie - - Vérification et décodage du token - - Récupération de l'utilisateur depuis la base de données - -### 3. Frontend - -- ✅ Création de la page `Login.tsx` : - - Formulaire de connexion (email + mot de passe) - - Validation des champs - - Gestion des erreurs - - Redirection après connexion - - Lien vers la page d'inscription - -- ✅ Création de la page `Register.tsx` : - - Formulaire d'inscription (nom, email, mot de passe, confirmation) - - Validation des champs (minimum 6 caractères) - - Vérification de la correspondance des mots de passe - - Gestion des erreurs - - Redirection après inscription - - Lien vers la page de connexion - -- ✅ Modification du fichier `App.tsx` : - - Ajout des routes `/login` et `/register` - -- ✅ Modification du fichier `const.ts` : - - Remplacement de `getLoginUrl()` pour pointer vers `/login` - -### 4. Documentation - -- ✅ Création du guide de déploiement Linux complet (`GUIDE_DEPLOIEMENT_LINUX.md`) -- ✅ Création de ce document de migration - ---- - -## ⚠️ Modifications restantes - -### 1. Correction des erreurs - -- ❌ **Corriger les doublons dans `server/db.ts`** - - Il reste des fonctions dupliquées qui causent des erreurs de compilation - - Nettoyer complètement les anciennes fonctions OAuth - -### 2. Page de gestion des utilisateurs - -- ❌ **Modifier `AdminUsers.tsx`** : - - Remplacer le champ `openId` par `password` dans le formulaire de création - - Ajouter un champ de changement de mot de passe dans le formulaire d'édition - - Adapter les mutations pour utiliser email au lieu d'openId - -### 3. Hooks d'authentification - -- ❌ **Vérifier `useAuth()`** : - - S'assurer qu'il fonctionne avec la nouvelle authentification JWT - - Pas de modification normalement nécessaire car il utilise `trpc.auth.me` - -### 4. Suppression du code OAuth - -- ❌ **Supprimer les fichiers OAuth inutilisés** : - - `server/_core/sdk.ts` (si existe) - - `server/_core/oauth.ts` (si existe) - - Routes OAuth dans `server/_core/index.ts` - -### 5. Variables d'environnement - -- ❌ **Nettoyer le fichier `.env`** : - - Supprimer `OAUTH_SERVER_URL` - - Supprimer `VITE_OAUTH_PORTAL_URL` - - Supprimer `VITE_APP_ID` - - Supprimer `OWNER_OPEN_ID` - - Garder uniquement : - - `DATABASE_URL` - - `JWT_SECRET` - - `VITE_APP_TITLE` - - `VITE_APP_LOGO` - - Variables email (optionnelles) - ---- - -## 🚀 Prochaines étapes pour finaliser - -### Étape 1 : Corriger les erreurs de compilation - -```bash -cd /home/ubuntu/formation-manager-itinova - -# Vérifier les erreurs -pnpm check -``` - -Corriger manuellement les doublons dans `server/db.ts`. - -### Étape 2 : Modifier la page AdminUsers - -1. Ouvrir `client/src/pages/AdminUsers.tsx` -2. Remplacer le champ `openId` par `password` dans le formulaire de création -3. Ajouter un champ de changement de mot de passe (optionnel) dans l'édition - -### Étape 3 : Tester localement - -```bash -# Compiler -pnpm build - -# Démarrer -pnpm start -``` - -Tester : -1. Inscription d'un nouvel utilisateur -2. Connexion avec cet utilisateur -3. Déconnexion -4. Gestion des utilisateurs (admin) - -### Étape 4 : Créer le premier utilisateur admin - -Après déploiement, exécuter ce SQL : - -```sql -INSERT INTO users (email, password, name, role, emailVerified, isActive, createdAt, updatedAt, lastSignedIn) -VALUES ( - 'admin@itinova.org', - '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK', -- Mot de passe: admin123 - 'Administrateur', - 'admin', - TRUE, - TRUE, - NOW(), - NOW(), - NOW() -); -``` - -⚠️ **Changez immédiatement le mot de passe après la première connexion !** - -### Étape 5 : Déployer sur le serveur - -Suivre le guide `GUIDE_DEPLOIEMENT_LINUX.md`. - ---- - -## 📋 Checklist de déploiement - -### Avant le déploiement - -- [ ] Toutes les erreurs de compilation sont corrigées -- [ ] Les tests locaux passent (inscription, connexion, déconnexion) -- [ ] Le fichier `.env` est configuré correctement -- [ ] La base de données est accessible -- [ ] Un utilisateur admin est créé en base de données - -### Pendant le déploiement - -- [ ] Les fichiers sont copiés sur le serveur -- [ ] Les dépendances sont installées (`pnpm install`) -- [ ] La base de données est initialisée (`pnpm db:push`) -- [ ] L'application est compilée (`pnpm build`) -- [ ] Le service systemd est configuré -- [ ] Le service démarre sans erreur - -### Après le déploiement - -- [ ] L'application est accessible via le navigateur -- [ ] La connexion fonctionne -- [ ] L'inscription fonctionne -- [ ] Le tableau de bord admin est accessible -- [ ] Les fonctionnalités de gestion des utilisateurs fonctionnent -- [ ] Le mot de passe admin par défaut a été changé - ---- - -## 🔧 Dépannage - -### Erreur "Email ou mot de passe incorrect" - -- Vérifier que l'utilisateur existe en base de données -- Vérifier que le mot de passe est bien hashé avec bcrypt -- Vérifier les logs du serveur - -### Erreur "User is not defined" - -- Vérifier que le token JWT est valide -- Vérifier que le cookie est bien envoyé -- Vérifier que `context.ts` récupère bien l'utilisateur - -### Erreur "Cannot read property 'id' of null" - -- L'utilisateur n'est pas connecté -- Rediriger vers `/login` - -### L'application ne démarre pas - -- Vérifier les logs : `sudo journalctl -u formation-manager -f` -- Vérifier le fichier `.env` -- Vérifier la connexion à la base de données - ---- - -## 📚 Ressources - -- Guide de déploiement : `GUIDE_DEPLOIEMENT_LINUX.md` -- Documentation bcrypt : https://www.npmjs.com/package/bcrypt -- Documentation JWT : https://jwt.io/ -- Documentation tRPC : https://trpc.io/ - ---- - -## 🆘 Support - -En cas de problème : - -1. Consulter les logs du serveur -2. Vérifier la base de données -3. Consulter ce document de migration -4. Consulter le guide de déploiement - ---- - -**Note** : Cette migration remplace complètement OAuth Manus par une authentification locale. L'application devient totalement autonome et ne dépend plus de services externes pour l'authentification. diff --git a/client/src/App.tsx b/client/src/App.tsx index e9c58e5..5c7a610 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,35 +5,12 @@ import { Route, Switch } from "wouter"; import ErrorBoundary from "./components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; import Home from "./pages/Home"; -import AdminRapportPublicCible from "./pages/AdminRapportPublicCible"; -import Admin from "./pages/Admin"; -import AdminFormations from "./pages/AdminFormations"; -import AdminSequences from "./pages/AdminSequences"; -import AdminApprenants from "./pages/AdminApprenants"; -import AdminSequenceInscrits from "./pages/AdminSequenceInscrits"; -import AdminUsers from "./pages/AdminUsers"; -import AdminEmailTemplates from "./pages/AdminEmailTemplates"; -import AdminEmailConfig from "./pages/AdminEmailConfig"; -import Inscription from "./pages/Inscription"; -import Login from "./pages/Login"; -import Register from "./pages/Register"; function Router() { + // make sure to consider if you need authentication for certain routes return ( - - - - - - - - - - - - {/* Final fallback route */} @@ -41,10 +18,18 @@ function Router() { ); } +// NOTE: About Theme +// - First choose a default theme according to your design style (dark or light bg), than change color palette in index.css +// to keep consistent foreground/background color across components +// - If you want to make theme switchable, pass `switchable` ThemeProvider and use `useTheme` hook + function App() { return ( - + diff --git a/client/src/_core/hooks/useAuth.ts b/client/src/_core/hooks/useAuth.ts index 9a63901..dcef9bd 100644 --- a/client/src/_core/hooks/useAuth.ts +++ b/client/src/_core/hooks/useAuth.ts @@ -36,8 +36,6 @@ export function useAuth(options?: UseAuthOptions) { } throw error; } finally { - // Supprimer le token de localStorage - localStorage.removeItem('auth_token'); utils.auth.me.setData(undefined, null); await utils.auth.me.invalidate(); } diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx index 93c4b67..11ed6d0 100644 --- a/client/src/components/DashboardLayout.tsx +++ b/client/src/components/DashboardLayout.tsx @@ -21,21 +21,15 @@ import { } from "@/components/ui/sidebar"; import { APP_LOGO, APP_TITLE, getLoginUrl } from "@/const"; import { useIsMobile } from "@/hooks/useMobile"; -import { LayoutDashboard, LogOut, PanelLeft, Users, GraduationCap, Calendar, BarChart3, UserCog, Mail, Settings } from "lucide-react"; +import { LayoutDashboard, LogOut, PanelLeft, Users } from "lucide-react"; import { CSSProperties, useEffect, useRef, useState } from "react"; import { useLocation } from "wouter"; import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton'; import { Button } from "./ui/button"; const menuItems = [ - { icon: LayoutDashboard, label: "Tableau de bord", path: "/admin" }, - { icon: GraduationCap, label: "Formations", path: "/admin/formations" }, - { icon: Calendar, label: "Séquences", path: "/admin/sequences" }, - { icon: Users, label: "Apprenants", path: "/admin/apprenants" }, - { icon: UserCog, label: "Utilisateurs", path: "/admin/users" }, - { icon: Mail, label: "Templates d'emails", path: "/admin/email-templates" }, - { icon: Settings, label: "Configuration SMTP", path: "/admin/email-config" }, - { icon: BarChart3, label: "Rapport Public Cible", path: "/admin/rapport-public-cible" }, + { icon: LayoutDashboard, label: "Page 1", path: "/" }, + { icon: Users, label: "Page 2", path: "/some-path" }, ]; const SIDEBAR_WIDTH_KEY = "sidebar-width"; @@ -79,7 +73,7 @@ export default function DashboardLayout({

{APP_TITLE}

- Veuillez vous connecter pour continuer + Please sign in to continue

@@ -90,34 +84,7 @@ export default function DashboardLayout({ size="lg" className="w-full shadow-lg hover:shadow-xl transition-all" > - Se connecter - - - - ); - } - - // Vérifier que l'utilisateur est administrateur - if (user.role !== 'admin') { - return ( -
-
-
-
-

Accès refusé

-

- Vous n'avez pas les permissions nécessaires pour accéder à cette page. -

-
-
-
@@ -288,7 +255,7 @@ function DashboardLayoutContent({ className="cursor-pointer text-destructive focus:text-destructive" > - Déconnexion + Sign out diff --git a/client/src/const.ts b/client/src/const.ts index 414e678..08f9c00 100644 --- a/client/src/const.ts +++ b/client/src/const.ts @@ -4,5 +4,18 @@ export const APP_TITLE = import.meta.env.VITE_APP_TITLE || "App"; export const APP_LOGO = "https://placehold.co/128x128/E1E7EF/1F2937?text=App"; -// Retourne l'URL de la page de connexion locale -export const getLoginUrl = () => "/login"; +// Generate login URL at runtime so redirect URI reflects the current origin. +export const getLoginUrl = () => { + const oauthPortalUrl = import.meta.env.VITE_OAUTH_PORTAL_URL; + const appId = import.meta.env.VITE_APP_ID; + const redirectUri = `${window.location.origin}/api/oauth/callback`; + const state = btoa(redirectUri); + + const url = new URL(`${oauthPortalUrl}/app-auth`); + url.searchParams.set("appId", appId); + url.searchParams.set("redirectUri", redirectUri); + url.searchParams.set("state", state); + url.searchParams.set("type", "signIn"); + + return url.toString(); +}; diff --git a/client/src/main.tsx b/client/src/main.tsx index 67e518e..8adf6f5 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -43,22 +43,9 @@ const trpcClient = trpc.createClient({ url: "/api/trpc", transformer: superjson, fetch(input, init) { - // Récupérer le token depuis localStorage - const token = localStorage.getItem('auth_token'); - - const headers: HeadersInit = { - ...(init?.headers || {}), - }; - - // Ajouter le header Authorization si un token existe - if (token) { - headers['Authorization'] = `Bearer ${token}`; - } - return globalThis.fetch(input, { ...(init ?? {}), credentials: "include", - headers, }); }, }), diff --git a/client/src/pages/Admin.tsx b/client/src/pages/Admin.tsx deleted file mode 100644 index 614a545..0000000 --- a/client/src/pages/Admin.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import DashboardLayout from "@/components/DashboardLayout"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { trpc } from "@/lib/trpc"; -import { GraduationCap, Calendar, Users, CheckCircle, PieChart as PieChartIcon } from "lucide-react"; -import { Skeleton } from "@/components/ui/skeleton"; -import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts"; -import { useMemo } from "react"; - -export default function Admin() { - const { data: formations, isLoading: loadingFormations } = trpc.formations.list.useQuery(); - const { data: sequences, isLoading: loadingSessions } = trpc.sequences.list.useQuery(); - const { data: apprenants, isLoading: loadingApprenants } = trpc.apprenants.list.useQuery(); - - // Statistiques par fonction - const statsFonction = useMemo(() => { - if (!apprenants) return []; - - const directeurs = apprenants.filter(a => a.fonction === "directeur").length; - const chefsService = apprenants.filter(a => a.fonction === "chef_service").length; - const autres = apprenants.filter(a => a.fonction === "autre").length; - - return [ - { name: "Directeurs", value: directeurs, color: "#3b82f6" }, - { name: "Chefs de service", value: chefsService, color: "#10b981" }, - { name: "Autres", value: autres, color: "#f59e0b" }, - ].filter(item => item.value > 0); - }, [apprenants]); - - const stats = [ - { - title: "Formations actives", - value: formations?.filter(f => f.actif).length || 0, - icon: , - loading: loadingFormations, - }, - { - title: "Séquences ouvertes", - value: sequences?.filter(s => s.statut === "ouverte").length || 0, - icon: , - loading: loadingSessions, - }, - { - title: "Apprenants inscrits", - value: apprenants?.length || 0, - icon: , - loading: loadingApprenants, - }, - { - title: "Séquences terminées", - value: sequences?.filter(s => s.statut === "terminee").length || 0, - icon: , - loading: loadingSessions, - }, - ]; - - return ( - -
-
-

Tableau de bord

-

- Gestion des formations Manager Itinova -

-
- -
- {stats.map((stat, index) => ( - - - - {stat.title} - - {stat.icon} - - - {stat.loading ? ( - - ) : ( -
{stat.value}
- )} -
-
- ))} -
- - {/* Graphique de répartition par fonction */} - {statsFonction.length > 0 && ( - - - - - Répartition des apprenants par fonction - - - Distribution des {apprenants?.length || 0} apprenants selon leur fonction - - - - {loadingApprenants ? ( -
- -
- ) : ( -
- - - `${name}: ${(percent * 100).toFixed(0)}%`} - outerRadius={100} - fill="#8884d8" - dataKey="value" - > - {statsFonction.map((entry, index) => ( - - ))} - - - - - -
- )} -
-
- )} - - - - Bienvenue dans l'espace d'administration - - Gérez vos formations, sequences et apprenants depuis le menu de navigation. - - - -
-

Fonctionnalités principales :

-
    -
  • Créer et gérer les formations avec liens d'inscription uniques
  • -
  • Organiser les sequences avec limitation à 12 participants
  • -
  • Gérer les inscriptions avec blocage automatique à J-15
  • -
  • Exporter les listes d'inscrits en PDF ou Excel
  • -
  • Générer des invitations Outlook et envoyer des emails automatiques
  • -
-
-
-
-
-
- ); -} diff --git a/client/src/pages/AdminApprenants.tsx b/client/src/pages/AdminApprenants.tsx deleted file mode 100644 index 7ac7ca8..0000000 --- a/client/src/pages/AdminApprenants.tsx +++ /dev/null @@ -1,331 +0,0 @@ -import DashboardLayout from "@/components/DashboardLayout"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { trpc } from "@/lib/trpc"; -import { Plus, Pencil, Trash2, Search, SlidersHorizontal } from "lucide-react"; -import { useState, useMemo } from "react"; -import { toast } from "sonner"; -import { Skeleton } from "@/components/ui/skeleton"; - -export default function AdminApprenants() { - const [open, setOpen] = useState(false); - const [editingId, setEditingId] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); - const [filterFonction, setFilterFonction] = useState("all"); - const [formData, setFormData] = useState({ - nom: "", - prenom: "", - email: "", - codeEtablissement: "", - fonction: "autre" as "directeur" | "chef_service" | "autre", - }); - - const utils = trpc.useUtils(); - const { data: apprenants, isLoading } = trpc.apprenants.list.useQuery(); - - const createMutation = trpc.apprenants.create.useMutation({ - onSuccess: () => { - utils.apprenants.list.invalidate(); - toast.success("Apprenant créé avec succès"); - setOpen(false); - resetForm(); - }, - onError: (error) => { - toast.error("Erreur lors de la création : " + error.message); - }, - }); - - const updateMutation = trpc.apprenants.update.useMutation({ - onSuccess: () => { - utils.apprenants.list.invalidate(); - toast.success("Apprenant mis à jour avec succès"); - setOpen(false); - resetForm(); - }, - onError: (error) => { - toast.error("Erreur lors de la mise à jour : " + error.message); - }, - }); - - const deleteMutation = trpc.apprenants.delete.useMutation({ - onSuccess: () => { - utils.apprenants.list.invalidate(); - toast.success("Apprenant supprimé avec succès"); - }, - onError: (error) => { - toast.error("Erreur lors de la suppression : " + error.message); - }, - }); - - const resetForm = () => { - setFormData({ nom: "", prenom: "", email: "", codeEtablissement: "", fonction: "autre" }); - setEditingId(null); - }; - - const handleEdit = (apprenant: any) => { - setFormData({ - nom: apprenant.nom, - prenom: apprenant.prenom, - email: apprenant.email, - codeEtablissement: apprenant.codeEtablissement, - fonction: apprenant.fonction || "autre", - }); - setEditingId(apprenant.id); - setOpen(true); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (editingId) { - updateMutation.mutate({ id: editingId, ...formData }); - } else { - createMutation.mutate(formData); - } - }; - - const handleDelete = (id: number) => { - if (confirm("Êtes-vous sûr de vouloir supprimer cet apprenant ?")) { - deleteMutation.mutate({ id }); - } - }; - - // Filtrer les apprenants - const filteredApprenants = useMemo(() => { - if (!apprenants) return []; - - return apprenants.filter(apprenant => { - const matchesSearch = searchTerm === "" || - apprenant.nom.toLowerCase().includes(searchTerm.toLowerCase()) || - apprenant.prenom.toLowerCase().includes(searchTerm.toLowerCase()) || - apprenant.email.toLowerCase().includes(searchTerm.toLowerCase()) || - apprenant.codeEtablissement.toLowerCase().includes(searchTerm.toLowerCase()); - - const matchesFonction = filterFonction === "all" || apprenant.fonction === filterFonction; - - return matchesSearch && matchesFonction; - }); - }, [apprenants, searchTerm, filterFonction]); - - return ( - -
-
-
-

Apprenants

-

- Gérez les apprenants et leurs informations -

-
- { - setOpen(isOpen); - if (!isOpen) resetForm(); - }}> - - - - -
- - - {editingId ? "Modifier l'apprenant" : "Nouvel apprenant"} - - - Ajoutez les informations de l'apprenant - - -
-
-
- - setFormData({ ...formData, nom: e.target.value })} - placeholder="Dupont" - required - /> -
-
- - setFormData({ ...formData, prenom: e.target.value })} - placeholder="Jean" - required - /> -
-
-
- - setFormData({ ...formData, email: e.target.value })} - placeholder="jean.dupont@example.com" - required - /> -
-
- - setFormData({ ...formData, codeEtablissement: e.target.value })} - placeholder="ETB001" - required - /> -
-
- - -
-
- - - - -
-
-
-
- - {/* Filtres et recherche */} - - - - - Recherche - - - -
-
- -
- - setSearchTerm(e.target.value)} - className="pl-8" - /> -
-
- -
- - -
-
- - {/* Compteur de résultats */} -
- {filteredApprenants.length} apprenant(s) trouvé(s) -
-
-
- - - - Liste des apprenants - - Gérez les apprenants inscrits aux formations - - - - {isLoading ? ( -
- - - -
- ) : filteredApprenants && filteredApprenants.length > 0 ? ( - - - - Nom - Prénom - Email - Code établissement - Fonction - Actions - - - - {filteredApprenants.map((apprenant) => ( - - {apprenant.nom} - {apprenant.prenom} - {apprenant.email} - {apprenant.codeEtablissement} - - {apprenant.fonction === "directeur" ? "Directeur" : - apprenant.fonction === "chef_service" ? "Chef de service" : "Autre"} - - -
- - -
-
-
- ))} -
-
- ) : ( -
- Aucun apprenant pour le moment. Créez-en un pour commencer. -
- )} -
-
-
-
- ); -} diff --git a/client/src/pages/AdminEmailConfig.tsx b/client/src/pages/AdminEmailConfig.tsx deleted file mode 100644 index 915f337..0000000 --- a/client/src/pages/AdminEmailConfig.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import { useEffect, useState } from "react"; -import DashboardLayout from "@/components/DashboardLayout"; -import { trpc } from "@/lib/trpc"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { toast } from "sonner"; -import { Switch } from "@/components/ui/switch"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { AlertCircle, CheckCircle2, Mail, Send } from "lucide-react"; - -export default function AdminEmailConfig() { - const { data: config, isLoading, refetch } = trpc.emailConfig.get.useQuery(); - const upsertMutation = trpc.emailConfig.upsert.useMutation(); - const testEmailMutation = trpc.emailConfig.testEmail.useMutation(); - - const [formData, setFormData] = useState({ - provider: "resend", - apiKey: "", - fromEmail: "", - fromName: "Formation Manager Itinova", - mode: "simulation" as "simulation" | "production", - domainVerified: false, - }); - - const [testEmail, setTestEmail] = useState(""); - - // Charger la configuration existante - useEffect(() => { - if (config) { - setFormData({ - provider: config.provider, - apiKey: config.apiKey || "", - fromEmail: config.fromEmail, - fromName: config.fromName, - mode: config.mode, - domainVerified: config.domainVerified, - }); - } - }, [config]); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - upsertMutation.mutate(formData, { - onSuccess: () => { - toast.success("Configuration enregistrée avec succès"); - refetch(); - }, - onError: (error) => { - toast.error(`Erreur: ${error.message}`); - }, - }); - }; - - const handleTestEmail = () => { - if (!testEmail) { - toast.error("Veuillez saisir une adresse email"); - return; - } - - testEmailMutation.mutate( - { toEmail: testEmail }, - { - onSuccess: (data) => { - if (data.success) { - toast.success(data.message || "Email de test envoyé avec succès"); - } else { - toast.error(data.message || "Échec de l'envoi de l'email de test"); - } - }, - onError: (error) => { - toast.error(`Erreur: ${error.message}`); - }, - } - ); - }; - - if (isLoading) { - return ( - -
-

Chargement...

-
-
- ); - } - - return ( - -
-
-

Configuration des emails

-

- Configurez l'envoi d'emails via Resend et basculez entre mode simulation et production -

-
- -
- {/* Statut actuel */} - - - - - Statut actuel - - - -
-
-
-

Mode d'envoi

-

- {formData.mode === "simulation" - ? "Les emails sont simulés et envoyés comme notifications" - : "Les emails sont envoyés via Resend"} -

-
-
- {formData.mode === "production" ? "Production" : "Simulation"} -
-
- - {formData.apiKey && ( - - - - Clé API Resend configurée - - - )} - - {!formData.apiKey && ( - - - - Aucune clé API configurée. Les emails seront simulés. - - - )} -
-
-
- - {/* Formulaire de configuration */} - - - Configuration Resend - - Configurez votre compte Resend pour envoyer de vrais emails - - - -
-
- - setFormData({ ...formData, apiKey: e.target.value })} - /> -

- Obtenez votre clé API sur{" "} - - resend.com/api-keys - -

-
- -
- - setFormData({ ...formData, fromEmail: e.target.value })} - required - /> -

- L'adresse email doit être vérifiée dans votre compte Resend -

-
- -
- - setFormData({ ...formData, fromName: e.target.value })} - required - /> -
- -
-
- -

- Activer l'envoi réel d'emails (désactiver pour simuler) -

-
- - setFormData({ ...formData, mode: checked ? "production" : "simulation" }) - } - /> -
- -
- -
-
-
-
- - {/* Test d'envoi */} - - - - - Test d'envoi - - - Envoyez un email de test pour vérifier votre configuration - - - -
-
- - setTestEmail(e.target.value)} - /> -
- - - - {formData.mode === "simulation" && ( - - - - Mode simulation actif. L'email de test sera simulé et envoyé comme notification au propriétaire. - - - )} -
-
-
- - {/* Documentation */} - - - Guide de configuration - - -
-
-

1. Créer un compte Resend

-

- Inscrivez-vous gratuitement sur{" "} - - resend.com - {" "} - (100 emails/jour gratuits) -

-
- -
-

2. Vérifier votre domaine

-

- Ajoutez et vérifiez votre domaine dans les paramètres Resend pour pouvoir envoyer des emails -

-
- -
-

3. Obtenir la clé API

-

- Générez une clé API dans la section "API Keys" de votre compte Resend -

-
- -
-

4. Configurer et tester

-

- Saisissez vos informations ci-dessus, enregistrez, puis testez l'envoi avant de passer en mode production -

-
-
-
-
-
-
-
- ); -} diff --git a/client/src/pages/AdminEmailTemplates.tsx b/client/src/pages/AdminEmailTemplates.tsx deleted file mode 100644 index 148ebc9..0000000 --- a/client/src/pages/AdminEmailTemplates.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import { useState, useEffect } from "react"; -import { trpc } from "@/lib/trpc"; -import DashboardLayout from "@/components/DashboardLayout"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { toast } from "sonner"; -import { Mail, Eye, Save, RotateCcw } from "lucide-react"; - -export default function AdminEmailTemplates() { - const [selectedType, setSelectedType] = useState("inscription"); - const [formData, setFormData] = useState({ - type: "inscription", - name: "", - logoUrl: "", - primaryColor: "#2563eb", - headerBgColor: "#2563eb", - headerTextColor: "#ffffff", - headerTitle: "Formation Manager Itinova", - footerText: "", - active: true, - }); - - const { data: templates, refetch } = trpc.emailTemplates.list.useQuery(); - const { data: currentTemplate } = trpc.emailTemplates.getByType.useQuery( - { type: selectedType }, - { enabled: !!selectedType } - ); - - const upsertMutation = trpc.emailTemplates.upsert.useMutation({ - onSuccess: () => { - toast.success("Template enregistré avec succès"); - refetch(); - }, - onError: (error) => { - toast.error(`Erreur: ${error.message}`); - }, - }); - - const initMutation = trpc.emailTemplates.initializeDefaults.useMutation({ - onSuccess: () => { - toast.success("Templates par défaut initialisés"); - refetch(); - }, - onError: (error) => { - toast.error(`Erreur: ${error.message}`); - }, - }); - - // Charger le template sélectionné - useEffect(() => { - if (currentTemplate) { - setFormData({ - type: currentTemplate.type, - name: currentTemplate.name, - logoUrl: currentTemplate.logoUrl || "", - primaryColor: currentTemplate.primaryColor, - headerBgColor: currentTemplate.headerBgColor, - headerTextColor: currentTemplate.headerTextColor, - headerTitle: currentTemplate.headerTitle, - footerText: currentTemplate.footerText || "", - active: currentTemplate.active, - }); - } - }, [currentTemplate]); - - const handleSave = () => { - upsertMutation.mutate({ - ...formData, - logoUrl: formData.logoUrl || null, - footerText: formData.footerText || null, - }); - }; - - const handleReset = () => { - if (currentTemplate) { - setFormData({ - type: currentTemplate.type, - name: currentTemplate.name, - logoUrl: currentTemplate.logoUrl || "", - primaryColor: currentTemplate.primaryColor, - headerBgColor: currentTemplate.headerBgColor, - headerTextColor: currentTemplate.headerTextColor, - headerTitle: currentTemplate.headerTitle, - footerText: currentTemplate.footerText || "", - active: currentTemplate.active, - }); - } - }; - - const handleInitDefaults = () => { - if (confirm("Initialiser les templates par défaut ? Cela ne modifiera pas les templates existants.")) { - initMutation.mutate(); - } - }; - - // Générer la prévisualisation HTML - const generatePreview = () => { - return ` - - - - - - - -
-
- ${formData.logoUrl ? `Logo` : ''} -

${formData.headerTitle}

-
-
-

Exemple de contenu

-

Bonjour,

-

Ceci est un exemple de prévisualisation de votre template d'email personnalisé.

-
-

Information importante

-

Ce bloc utilise la couleur principale que vous avez choisie.

-
- -

Cordialement,
L'équipe Formation Manager Itinova

-
- -
- - - `.trim(); - }; - - const templateTypes = [ - { value: "inscription", label: "Confirmation d'inscription" }, - { value: "teaser", label: "Email teaser" }, - { value: "rappel", label: "Rappel J-7" }, - { value: "reset_password", label: "Réinitialisation de mot de passe" }, - ]; - - return ( - -
-

Templates d'emails

-

- Personnalisez l'apparence de vos emails automatiques -

-
- - {(!templates || templates.length === 0) && ( - - - - - Initialisation requise - - - Aucun template trouvé. Initialisez les templates par défaut pour commencer. - - - - - - - )} - -
- {/* Éditeur */} - - - Éditeur de template - - Modifiez les paramètres du template sélectionné - - - -
- - -
- -
- - setFormData({ ...formData, name: e.target.value })} - placeholder="Ex: Confirmation d'inscription" - /> -
- -
- - setFormData({ ...formData, logoUrl: e.target.value })} - placeholder="https://exemple.com/logo.png" - /> -
- -
-
- -
- setFormData({ ...formData, headerBgColor: e.target.value })} - className="w-16 h-10 p-1" - /> - setFormData({ ...formData, headerBgColor: e.target.value })} - placeholder="#2563eb" - /> -
-
- -
- -
- setFormData({ ...formData, headerTextColor: e.target.value })} - className="w-16 h-10 p-1" - /> - setFormData({ ...formData, headerTextColor: e.target.value })} - placeholder="#ffffff" - /> -
-
-
- -
- -
- setFormData({ ...formData, primaryColor: e.target.value })} - className="w-16 h-10 p-1" - /> - setFormData({ ...formData, primaryColor: e.target.value })} - placeholder="#2563eb" - /> -
-

- Utilisée pour les boutons et les accents -

-
- -
- - setFormData({ ...formData, headerTitle: e.target.value })} - placeholder="Formation Manager Itinova" - /> -
- -
- -