1. Système d'Analytics (Nouveauté Majeure)
Modèle de Données : Ajout de la table AnalyticsEvent dans prisma/schema.prisma pour suivre les vues de pages, les clics et le temps de rétention (DWELL_TIME). API Backend : Création de la route app/api/analytics/route.ts pour enregistrer les événements en base de données. Tracking Client : Création du composant AnalyticsTracker.tsx (intégré dans le layout principal) qui capture automatiquement : Le changement de page (Page Views). Les clics sur les boutons et liens importants. Le temps passé sur chaque page. 2. Intégration Base de Données (PostgreSQL/Prisma) Fiches Entreprises : Mise à jour de app/directory/[id]/page.tsx pour récupérer les données réelles depuis PostgreSQL via Prisma au lieu d'utiliser les données de test (mockData). Navigation Dashboard : Correction du bouton "Voir ma fiche" dans le tableau de bord pour qu'il redirige correctement vers le profil public de l'entrepreneur. 3. Administration & Metrics Nouvelle Application Admin : Initialisation d'un dossier admin/ (application Next.js séparée) destinée à la gestion de la plateforme et à la visualisation des statistiques (Metrics & Analytics). 4. Authentification & Inscription Nouvelle Page : Création de app/register/page.tsx pour permettre l'inscription des nouveaux utilisateurs. Gestion de Session : Amélioration de UserProvider.tsx pour une meilleure gestion de l'état utilisateur à travers l'application. 5. Maintenance & Types Mise à jour des types globaux dans types.ts et rafraîchissement du package-lock.json suite à l'ajout de dépendances.
This commit is contained in:
203
app/register/page.tsx
Normal file
203
app/register/page.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { User, Mail, Lock, ArrowRight, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
|
||||
const RegisterPage = () => {
|
||||
const router = useRouter();
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
setError('');
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError('Les mots de passe ne correspondent pas');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
password: formData.password
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.error || "Une erreur est survenue");
|
||||
}
|
||||
|
||||
setSuccess(true);
|
||||
setTimeout(() => {
|
||||
router.push('/login');
|
||||
}, 2000);
|
||||
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 bg-[radial-gradient(circle_at_top_right,_var(--color-brand-100),_transparent_40%),_radial-gradient(circle_at_bottom_left,_var(--color-brand-50),_transparent_40%)]">
|
||||
<div className="max-w-md w-full">
|
||||
<div className="text-center mb-10">
|
||||
<Link href="/" className="inline-flex items-center justify-center h-16 w-16 bg-brand-600 rounded-2xl shadow-lg shadow-brand-200 text-white font-bold text-3xl mb-6 hover:scale-105 transition-transform">
|
||||
A
|
||||
</Link>
|
||||
<h2 className="text-4xl font-extrabold text-dark-900 font-serif mb-2">Rejoignez l'aventure</h2>
|
||||
<p className="text-gray-600">Créez votre compte pour propulser votre entreprise</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/80 backdrop-blur-xl p-8 rounded-3xl shadow-2xl border border-white shadow-brand-100/50">
|
||||
{success ? (
|
||||
<div className="text-center py-10 animate-in fade-in zoom-in duration-500">
|
||||
<div className="inline-flex items-center justify-center h-20 w-20 bg-green-100 text-green-600 rounded-full mb-6">
|
||||
<CheckCircle size={40} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">Compte créé !</h3>
|
||||
<p className="text-gray-600">Redirection vers la page de connexion...</p>
|
||||
</div>
|
||||
) : (
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
{error && (
|
||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 rounded-r-lg flex items-center gap-3 text-red-700 animate-in slide-in-from-top-2 duration-300">
|
||||
<AlertCircle size={20} className="shrink-0" />
|
||||
<p className="text-sm font-medium">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold text-gray-700 ml-1">Nom complet</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-brand-600 transition-colors">
|
||||
<User size={18} />
|
||||
</div>
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
className="block w-full pl-10 pr-3 py-3 border border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand-500/20 focus:border-brand-500 transition-all text-gray-900 placeholder-gray-400"
|
||||
placeholder="Ex: Jean Dupont"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold text-gray-700 ml-1">Adresse email</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-brand-600 transition-colors">
|
||||
<Mail size={18} />
|
||||
</div>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="block w-full pl-10 pr-3 py-3 border border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand-500/20 focus:border-brand-500 transition-all text-gray-900 placeholder-gray-400"
|
||||
placeholder="votre@email.com"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold text-gray-700 ml-1">Mot de passe</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-brand-600 transition-colors">
|
||||
<Lock size={18} />
|
||||
</div>
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
className="block w-full pl-10 pr-3 py-3 border border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand-500/20 focus:border-brand-500 transition-all text-gray-900 placeholder-gray-400"
|
||||
placeholder="••••••••"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold text-gray-700 ml-1">Confirmer le mot de passe</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-brand-600 transition-colors">
|
||||
<Lock size={18} />
|
||||
</div>
|
||||
<input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
required
|
||||
className="block w-full pl-10 pr-3 py-3 border border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand-500/20 focus:border-brand-500 transition-all text-gray-900 placeholder-gray-400"
|
||||
placeholder="••••••••"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="group relative w-full flex justify-center items-center py-3.5 px-4 border border-transparent text-sm font-bold rounded-xl text-white bg-brand-600 hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-500 transition-all shadow-lg shadow-brand-200 disabled:opacity-70 disabled:cursor-not-allowed overflow-hidden"
|
||||
>
|
||||
{loading ? (
|
||||
<div className="h-5 w-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
S'inscrire
|
||||
<ArrowRight className="ml-2 group-hover:translate-x-1 transition-transform" size={18} />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="text-center mt-6">
|
||||
<p className="text-gray-600 text-sm">
|
||||
Déjà un compte ?{' '}
|
||||
<Link href="/login" className="font-bold text-brand-600 hover:text-brand-700 transition-colors underline-offset-4 hover:underline">
|
||||
Se connecter
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mt-8 text-center text-xs text-gray-400 uppercase tracking-widest font-semibold">
|
||||
Afropreunariat © 2026
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterPage;
|
||||
Reference in New Issue
Block a user