Spaces:
Sleeping
Sleeping
| import { useState } from 'react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { login } from '../services/auth' | |
| import { Button } from '@/components/ui/button' | |
| import { | |
| User, | |
| Lock, | |
| ArrowRight, | |
| Globe, | |
| ShieldCheck, | |
| Zap, | |
| Loader2 | |
| } from 'lucide-react' | |
| const Login = ({ onLoginSuccess, onNavigateToRegister, onNavigateToLanding }) => { | |
| const [username, setUsername] = useState('') | |
| const [password, setPassword] = useState('') | |
| const [error, setError] = useState('') | |
| const [loading, setLoading] = useState(false) | |
| const handleSubmit = async (e) => { | |
| e.preventDefault() | |
| setError('') | |
| setLoading(true) | |
| try { | |
| const data = await login(username, password) | |
| localStorage.setItem('token', data.token) | |
| localStorage.setItem('user', JSON.stringify(data.user)) | |
| onLoginSuccess(data.user) | |
| } catch (err) { | |
| setError('Identifiants incorrects. Veuillez réessayer.') | |
| } finally { | |
| setLoading(false) | |
| } | |
| } | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center relative overflow-hidden bg-background"> | |
| {/* Background Mesh Gradients */} | |
| <div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-primary/20 rounded-full blur-[120px] animate-pulse" /> | |
| <div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-secondary/20 rounded-full blur-[120px] animate-pulse" style={{ animationDelay: '2s' }} /> | |
| <div className="relative z-10 w-full max-w-6xl px-4 flex flex-col lg:flex-row items-center gap-16"> | |
| {/* Left Side: Branding & Info */} | |
| <motion.div | |
| initial={{ opacity: 0, x: -50 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| transition={{ duration: 0.8, ease: "easeOut" }} | |
| className="flex-1 text-center lg:text-left hidden lg:block" | |
| > | |
| <div className="inline-flex items-center space-x-3 px-4 py-2 rounded-full bg-primary/10 border border-primary/20 mb-8"> | |
| <Zap className="h-4 w-4 text-secondary" /> | |
| <span className="text-xs font-black uppercase tracking-widest text-secondary">Intelligence de Données Africaines</span> | |
| </div> | |
| <h1 className="text-6xl xl:text-7xl font-black text-foreground leading-tight mb-6"> | |
| Reconnectez-vous à <br /> | |
| <span className="text-[#fcd34d]">l'Afrique de Demain</span> | |
| </h1> | |
| <p className="text-xl text-muted-foreground font-medium max-w-xl mb-10"> | |
| Accédez à la plateforme d'analyse de données la plus avancée du continent. | |
| Visualisez, analysez et agissez en temps réel. | |
| </p> | |
| <div className="grid grid-cols-2 gap-6 max-w-md"> | |
| <div className="p-6 rounded-3xl bg-primary/10 border border-primary/20 "> | |
| <ShieldCheck className="h-8 w-8 text-primary mb-4" /> | |
| <h3 className="text-foreground font-bold">Sécurisé</h3> | |
| <p className="text-muted-foreground text-sm">Protection de données de niveau bancaire.</p> | |
| </div> | |
| <div className="p-6 rounded-3xl bg-primary/10 border border-primary/20 "> | |
| <Globe className="h-8 w-8 text-secondary mb-4" /> | |
| <h3 className="text-foreground font-bold">Continental</h3> | |
| <p className="text-muted-foreground text-sm">Couverture totale des 54 pays.</p> | |
| </div> | |
| </div> | |
| </motion.div> | |
| {/* Right Side: Login Card */} | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.9 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| transition={{ duration: 0.6, delay: 0.2 }} | |
| className="w-full max-w-[480px]" | |
| > | |
| <div className="glass rounded-[3rem] p-10 md:p-12 border border-primary/30 shadow-2xl relative overflow-hidden"> | |
| <div className="absolute top-0 right-0 p-8 opacity-5 pointer-events-none"> | |
| <Lock className="w-32 h-32 text-foreground" /> | |
| </div> | |
| <div className="relative z-10"> | |
| <div className="mb-10 text-center lg:text-left"> | |
| <div className="lg:hidden flex justify-center mb-6"> | |
| <div className="p-3 bg-primary rounded-2xl shadow-lg"> | |
| <Globe className="h-8 w-8 text-white" /> | |
| </div> | |
| </div> | |
| <h2 className="text-3xl font-black text-foreground mb-2">Bienvenue</h2> | |
| <p className="text-muted-foreground font-medium">Entrez vos identifiants pour continuer</p> | |
| </div> | |
| <form onSubmit={handleSubmit} className="space-y-6"> | |
| <div className="space-y-4"> | |
| <div className="relative group"> | |
| <div className="absolute inset-y-0 left-4 flex items-center pointer-events-none"> | |
| <User className="h-5 w-5 text-muted-foreground/30 group-focus-within:text-primary transition-colors" /> | |
| </div> | |
| <input | |
| type="text" | |
| required | |
| placeholder="Email ou Nom d'utilisateur" | |
| value={username} | |
| onChange={(e) => setUsername(e.target.value)} | |
| className="w-full pl-12 pr-4 py-4 bg-primary/10 border border-primary/20 rounded-2xl text-foreground placeholder:text-muted-foreground/40 focus:ring-4 focus:ring-primary/10 focus:border-primary/30 outline-none transition-all font-medium" | |
| /> | |
| </div> | |
| <div className="relative group"> | |
| <div className="absolute inset-y-0 left-4 flex items-center pointer-events-none"> | |
| <Lock className="h-5 w-5 text-muted-foreground/30 group-focus-within:text-secondary transition-colors" /> | |
| </div> | |
| <input | |
| type="password" | |
| required | |
| placeholder="Mot de passe" | |
| value={password} | |
| onChange={(e) => setPassword(e.target.value)} | |
| className="w-full pl-12 pr-4 py-4 bg-primary/10 border border-primary/20 rounded-2xl text-foreground placeholder:text-muted-foreground/40 focus:ring-4 focus:ring-secondary/10 focus:border-secondary/30 outline-none transition-all font-medium" | |
| /> | |
| </div> | |
| </div> | |
| <AnimatePresence mode="wait"> | |
| {error && ( | |
| <motion.div | |
| initial={{ opacity: 0, height: 0 }} | |
| animate={{ opacity: 1, height: 'auto' }} | |
| exit={{ opacity: 0, height: 0 }} | |
| className="p-4 bg-red-500/10 border border-red-500/20 rounded-2xl text-red-400 text-sm font-bold text-center" | |
| > | |
| {error} | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| <div className="flex items-center justify-between text-sm"> | |
| <label className="flex items-center space-x-2 cursor-pointer group"> | |
| <input type="checkbox" className="w-4 h-4 rounded border-border bg-card text-primary focus:ring-primary/20" /> | |
| <span className="text-muted-foreground group-hover:text-foreground transition-colors">Se souvenir de moi</span> | |
| </label> | |
| <button type="button" className="text-primary font-black hover:text-primary/80 transition-colors">Oublié ?</button> | |
| </div> | |
| <Button | |
| type="submit" | |
| disabled={loading} | |
| className="w-full py-7 rounded-2xl bg-primary hover:bg-[#15803d] text-white font-black text-lg shadow-xl shadow-primary/20 hover:shadow-primary/40 transition-all hover:scale-[1.02] active:scale-[0.98]" | |
| > | |
| {loading ? ( | |
| <Loader2 className="h-6 w-6 animate-spin" /> | |
| ) : ( | |
| <span className="flex items-center"> | |
| Se connecter <ArrowRight className="ml-2 h-5 w-5" /> | |
| </span> | |
| )} | |
| </Button> | |
| </form> | |
| <div className="mt-10 text-center space-y-4"> | |
| <p className="text-muted-foreground font-medium"> | |
| Pas encore de compte ?{' '} | |
| <button | |
| onClick={onNavigateToRegister} | |
| className="text-foreground font-black hover:text-secondary transition-colors underline underline-offset-4" | |
| > | |
| Créer un profil | |
| </button> | |
| </p> | |
| <button | |
| onClick={onNavigateToLanding} | |
| className="text-muted-foreground/40 hover:text-muted-foreground text-sm font-bold transition-colors" | |
| > | |
| ← Retour à l'accueil | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </motion.div> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| export default Login | |