From 281fb764c4b618bf2590911b09bc13f4db25ebdd Mon Sep 17 00:00:00 2001 From: streaper2 Date: Sun, 26 Apr 2026 22:46:32 +0200 Subject: [PATCH] feat: implement authentication system with email verification, user management dashboard, and administrative business controls --- admin/package-lock.json | 2 + admin/package.json | 1 - admin/prisma/schema.prisma | 8 +- admin/src/app/actions/countries.ts | 17 +- admin/src/app/actions/settings.ts | 26 +- admin/src/app/actions/user.ts | 16 + admin/src/app/clients/page.tsx | 110 ---- admin/src/app/countries/page.tsx | 108 +++- admin/src/app/entrepreneurs/page.tsx | 145 ----- admin/src/app/settings/page.tsx | 607 ++++++++++++-------- admin/src/app/users/page.tsx | 298 ++++++++++ admin/src/components/ManualVerifyButton.tsx | 41 ++ admin/src/components/Sidebar.tsx | 5 +- app/api/auth/forgot-password/route.ts | 32 +- app/api/auth/login/route.ts | 8 + app/api/auth/register/route.ts | 47 +- app/api/auth/verify/route.ts | 35 ++ app/login/page.tsx | 27 +- prisma/schema.prisma | 27 +- 19 files changed, 999 insertions(+), 561 deletions(-) delete mode 100644 admin/src/app/clients/page.tsx delete mode 100644 admin/src/app/entrepreneurs/page.tsx create mode 100644 admin/src/app/users/page.tsx create mode 100644 admin/src/components/ManualVerifyButton.tsx create mode 100644 app/api/auth/verify/route.ts diff --git a/admin/package-lock.json b/admin/package-lock.json index 12db3eb..22892bb 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -55,6 +55,7 @@ "jiti": "^2.6.1", "lucide-react": "^0.554.0", "next": "^16.1.6", + "nodemailer": "^8.0.6", "pg": "^8.19.0", "postcss": "^8.5.10", "prisma": "6.19.3", @@ -67,6 +68,7 @@ "typescript": "~5.8.2" }, "devDependencies": { + "@types/nodemailer": "^8.0.0", "concurrently": "^9.2.1" }, "engines": { diff --git a/admin/package.json b/admin/package.json index fe1bb42..3c3fcdb 100644 --- a/admin/package.json +++ b/admin/package.json @@ -13,7 +13,6 @@ "@prisma/client": "6.19.3", "@prisma/config": "6.19.3", "@types/bcryptjs": "^2.4.6", - "afrohub": "file:..", "bcryptjs": "^3.0.3", "dotenv": "^17.4.1", "lucide-react": "^1.8.0", diff --git a/admin/prisma/schema.prisma b/admin/prisma/schema.prisma index 69380dc..fcf806e 100644 --- a/admin/prisma/schema.prisma +++ b/admin/prisma/schema.prisma @@ -15,6 +15,8 @@ model User { password String role UserRole @default(VISITOR) avatar String? + emailVerified Boolean @default(false) + verificationToken String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt bio String? @@ -312,9 +314,11 @@ model SiteSetting { homeBlogSubtitle String @default("Toutes les actualités de l'entrepreneuriat africain.") homeBlogCount Int @default(3) homeBlogSelection String @default("latest") - homeBlogIds String[] @default([]) - homeBlogCategories String[] @default([]) homeCategories String[] @default(["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"]) + resetPasswordSubject String @default("Réinitialisation de votre mot de passe - Afrohub") + resetPasswordTemplate String @default("

{siteName}

Bonjour {name},

Vous avez demandé la réinitialisation de votre mot de passe. Cliquez sur le bouton ci-dessous pour choisir un nouveau mot de passe :

Réinitialiser mon mot de passe

Ce lien expirera dans 1 heure. Si vous n'avez pas demandé cette réinitialisation, vous pouvez ignorer cet email.


© 2025 {siteName}. Tous droits réservés.

") @db.Text + verifyEmailSubject String @default("Vérification de votre compte - Afrohub") + verifyEmailTemplate String @default("

{siteName}

Bonjour {name},

Merci de vous être inscrit sur {siteName}. Pour finaliser la création de votre compte, merci de vérifier votre adresse email en cliquant sur le bouton ci-dessous :

Vérifier mon compte

Si vous n'avez pas créé de compte sur notre plateforme, vous pouvez ignorer cet email.


© 2025 {siteName}. Tous droits réservés.

") @db.Text updatedAt DateTime @updatedAt } diff --git a/admin/src/app/actions/countries.ts b/admin/src/app/actions/countries.ts index 31712d3..c3e656a 100644 --- a/admin/src/app/actions/countries.ts +++ b/admin/src/app/actions/countries.ts @@ -14,13 +14,14 @@ export async function getCountries() { } } -export async function addCountry(data: { name: string, code: string, flag?: string }) { +export async function addCountry(data: { name: string, code: string, flag?: string, description?: string }) { try { const country = await prisma.country.create({ data: { name: data.name, code: data.code.toUpperCase(), flag: data.flag || null, + description: data.description || null, isActive: true } }); @@ -58,3 +59,17 @@ export async function toggleCountryStatus(id: string, isActive: boolean) { return { success: false }; } } + +export async function updateCountryDescription(id: string, description: string) { + try { + await prisma.country.update({ + where: { id }, + data: { description } + }); + revalidatePath('/countries'); + return { success: true }; + } catch (error) { + console.error('Error updating country description:', error); + return { success: false, error: 'Erreur lors de la mise à jour.' }; + } +} diff --git a/admin/src/app/actions/settings.ts b/admin/src/app/actions/settings.ts index ffa50a1..3ade872 100644 --- a/admin/src/app/actions/settings.ts +++ b/admin/src/app/actions/settings.ts @@ -28,7 +28,31 @@ export async function getSiteSettings() { homeBlogSelection: "latest", homeBlogIds: [], homeBlogCategories: [], - homeCategories: ["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"] + homeCategories: ["Technologie & IT", "Agriculture & Agrobusiness", "Mode & Textile", "Cosmétique & Beauté"], + resetPasswordSubject: "Réinitialisation de votre mot de passe - Afrohub", + resetPasswordTemplate: `
+

{siteName}

+

Bonjour {name},

+

Vous avez demandé la réinitialisation de votre mot de passe. Cliquez sur le bouton ci-dessous pour choisir un nouveau mot de passe :

+
+ Réinitialiser mon mot de passe +
+

Ce lien expirera dans 1 heure. Si vous n'avez pas demandé cette réinitialisation, vous pouvez ignorer cet email.

+
+

© 2025 {siteName}. Tous droits réservés.

+
`, + verifyEmailSubject: "Vérification de votre compte - Afrohub", + verifyEmailTemplate: `
+

{siteName}

+

Bonjour {name},

+

Merci de vous être inscrit sur {siteName}. Pour finaliser la création de votre compte, merci de vérifier votre adresse email en cliquant sur le bouton ci-dessous :

+
+ Vérifier mon compte +
+

Si vous n'avez pas créé de compte sur notre plateforme, vous pouvez ignorer cet email.

+
+

© 2025 {siteName}. Tous droits réservés.

+
` }; if (!settings) return defaultSettings; diff --git a/admin/src/app/actions/user.ts b/admin/src/app/actions/user.ts index 225285d..dd2acbd 100644 --- a/admin/src/app/actions/user.ts +++ b/admin/src/app/actions/user.ts @@ -25,3 +25,19 @@ export async function getClients() { return { success: false, error: "Erreur lors de la récupération des clients" }; } } + +export async function verifyUserEmail(userId: string) { + try { + await prisma.user.update({ + where: { id: userId }, + data: { + emailVerified: true, + verificationToken: null + } + }); + return { success: true }; + } catch (error) { + console.error("Failed to verify user email:", error); + return { success: false, error: "Erreur lors de la vérification de l'email" }; + } +} diff --git a/admin/src/app/clients/page.tsx b/admin/src/app/clients/page.tsx deleted file mode 100644 index 3fb92c4..0000000 --- a/admin/src/app/clients/page.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { getClients } from "@/app/actions/user"; -import { User, Mail, Phone, MapPin } from "lucide-react"; -import ClientActions from "@/components/ClientActions"; - -export default async function ClientsPage() { - const result = await getClients(); - const clients = result.success ? (result.data || []) : []; - - return ( -
-
-
-

Gestion des Clients

-

Liste des utilisateurs n'ayant pas encore de boutique active.

-
-
- Total Clients: - {clients.length} -
-
- -
-
- - - - - - - - - - - - {clients.length === 0 ? ( - - - - ) : ( - clients.map((client: any) => ( - - - - - - - - )) - )} - -
UtilisateurContactLocalisationStatutAction
-
- -

Aucun client trouvé.

-
-
-
-
- {client.avatar ? ( - {client.name} - ) : ( - client.name.charAt(0) - )} -
-
-

{client.name}

-

ID: {client.id.split('-')[0]}...

-
-
-
-
-
- - {client.email} -
- {client.phone && ( -
- - {client.phone} -
- )} -
-
-
- - {client.location || "Non spécifié"} -
-
-
- {client.isSuspended ? ( - <> - Compte Suspendu - - {client.suspensionReason} - - - ) : client.businesses ? ( - Shop Inactif - ) : ( - Actif - )} -
-
- -
-
-
-
- ); -} diff --git a/admin/src/app/countries/page.tsx b/admin/src/app/countries/page.tsx index 15882b4..49d1b2d 100644 --- a/admin/src/app/countries/page.tsx +++ b/admin/src/app/countries/page.tsx @@ -1,14 +1,16 @@ "use client"; import React, { useState, useEffect, useTransition } from 'react'; -import { getCountries, addCountry, deleteCountry, toggleCountryStatus } from '@/app/actions/countries'; -import { Globe, Plus, Trash2, MapPin, Search, Loader2, Flag } from 'lucide-react'; +import { getCountries, addCountry, deleteCountry, toggleCountryStatus, updateCountryDescription } from '@/app/actions/countries'; +import { Globe, Plus, Trash2, MapPin, Search, Loader2, Flag, Edit2, Check, X } from 'lucide-react'; import { toast } from 'react-hot-toast'; export default function CountriesPage() { const [countries, setCountries] = useState([]); const [loading, setLoading] = useState(true); const [isAdding, setIsAdding] = useState(false); + const [editingId, setEditingId] = useState(null); + const [editDesc, setEditDesc] = useState(""); const [isPending, startTransition] = useTransition(); const loadData = async () => { @@ -27,9 +29,10 @@ export default function CountriesPage() { const name = formData.get('name') as string; const code = formData.get('code') as string; const flag = formData.get('flag') as string; + const description = formData.get('description') as string; startTransition(async () => { - const result = await addCountry({ name, code, flag }); + const result = await addCountry({ name, code, flag, description }); if (result.success) { toast.success("Pays ajouté avec succès"); setIsAdding(false); @@ -55,10 +58,22 @@ export default function CountriesPage() { const handleToggleStatus = async (id: string, current: boolean) => { const result = await toggleCountryStatus(id, !current); if (result.success) { + toast.success(!current ? "Pays activé" : "Pays désactivé"); loadData(); } }; + const handleSaveDescription = async (id: string) => { + const result = await updateCountryDescription(id, editDesc); + if (result.success) { + toast.success("Description mise à jour"); + setEditingId(null); + loadData(); + } else { + toast.error("Erreur lors de la mise à jour"); + } + }; + if (loading) { return (
@@ -85,26 +100,34 @@ export default function CountriesPage() { {isAdding && (

Nouveau Pays

-
-
- - + +
+
+ + +
+
+ + +
+
+ + +
+
- - + +