Sage100/src/pages/auth/Login.tsx
2026-01-20 11:06:26 +03:00

272 lines
11 KiB
TypeScript

import AuthLayout from "@/components/layout/AuthLayout";
import { motion } from 'framer-motion';
import { useState } from 'react'
import { loginInterface } from '@/types/userInterface'
import { Link, useNavigate } from "react-router-dom";
import { sageService } from "@/service/sageService";
import { useToast } from "@/components/ui/use-toast";
import { Button } from '@/components/ui/buttonTsx';
import AuthInput from '@/components/ui/AuthInput';
import { Checkbox } from '@/components/ui/checkbox';
import { ArrowRight, Building2, Eye, EyeOff, Loader2, Lock, Mail, ShieldCheck } from "lucide-react";
import { useAppDispatch } from "@/store/hooks";
import { authMe } from "@/store/features/user/thunk";
export default function Login() {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { toast } = useToast();
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [loginData, setLoginData] = useState<loginInterface>({ email: "", password: "", rememberMe: false });
const [errors, setErrors] = useState({}) as any;
const [errorFom, setErrorForm] = useState('')
const validateForm = () => {
const newErrors = {} as any;
if (!loginData.email) {
newErrors.email = "L'adresse email est requise";
} else if (!/\S+@\S+\.\S+/.test(loginData.email)) {
newErrors.email = "Format d'email invalide";
}
if (!loginData.password) {
newErrors.password = "Le mot de passe est requis";
} else if (loginData.password.length < 8) {
newErrors.password = "Le mot de passe doit contenir au moins 8 caractères";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setIsLoading(true);
try {
const response = await sageService.login(loginData);
const access_token = response.access_token;
const refresh_token = response.refresh_token;
const expires_in = response.expires_in;
if (access_token) {
document.cookie = `access_token=${access_token}; path=/; max-age=${expires_in}`;
document.cookie = `refresh_token=${refresh_token}; path=/; max-age=${expires_in * 2}`;
try {
await dispatch(authMe()).unwrap();
navigate("/home");
} catch (error) {
setErrorForm("Impossible de vérifier votre accès. Veuillez réessayer.");
}
} else {
setErrors("Email ou mot de passe incorrect")
setErrorForm("Email ou mot de passe incorrect")
}
setIsLoading(false);
} catch (err: any) {
setErrors(`${err.response.data.detail || "Email ou mot de passe incorrect!"}`);
setErrorForm(`${err.response.data.detail || "Email ou mot de passe incorrect!"}`);
setIsLoading(false);
} finally {
setIsLoading(false);
}
};
const handleSSOLogin = (provider : any) => {
toast({
title: "Redirection SSO",
description: `Connexion avec ${provider} en cours...`,
});
// Implementation would go here
};
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } }
};
return (
<AuthLayout>
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="w-full max-w-[420px]"
>
{/* Mobile Logo */}
<div className="lg:hidden flex justify-center mb-8">
<div className="w-12 h-12 rounded-xl bg-[#338660] flex items-center justify-center shadow-lg shadow-[#338660]/30">
<Building2 className="w-7 h-7 text-white" />
</div>
</div>
{/* Header */}
<motion.div variants={itemVariants} className="text-center mb-10">
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">Bon retour</h2>
<p className="text-gray-500 dark:text-gray-400">
Connectez-vous pour accéder à votre espace
</p>
</motion.div>
{/* SSO Buttons */}
<motion.div variants={itemVariants} className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
<button
onClick={() => handleSSOLogin('Microsoft')}
className="flex items-center justify-center gap-3 px-4 py-3 bg-[#2F2F2F] hover:bg-[#1a1a1a] text-white rounded-xl font-medium transition-all hover:shadow-lg hover:-translate-y-0.5 duration-200 border border-transparent"
>
<svg className="w-5 h-5" viewBox="0 0 23 23">
<path fill="#f35325" d="M1 1h10v10H1z"/>
<path fill="#81bc06" d="M12 1h10v10H12z"/>
<path fill="#05a6f0" d="M1 12h10v10H1z"/>
<path fill="#ffba08" d="M12 12h10v10H12z"/>
</svg>
<span>Microsoft</span>
</button>
<button
onClick={() => handleSSOLogin('Google')}
className="flex items-center justify-center gap-3 px-4 py-3 bg-white hover:bg-gray-50 text-gray-700 rounded-xl font-medium transition-all hover:shadow-lg hover:-translate-y-0.5 duration-200 border border-gray-200"
>
<svg className="w-5 h-5" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.26.81-.58z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
<span>Google</span>
</button>
</motion.div>
<motion.div variants={itemVariants} className="relative mb-8">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-gray-200 dark:border-gray-800" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-white dark:bg-gray-950 text-gray-500">ou avec email</span>
</div>
</motion.div>
{/* Login Form */}
{errorFom && (
<div className="p-3 text-sm text-red-600 bg-red-50 rounded-lg">
{errorFom}
</div>
)}
<motion.form variants={itemVariants} onSubmit={handleLogin} className="space-y-6">
<AuthInput
icon={Mail}
type="email"
label="Adresse email"
placeholder="nom@entreprise.com"
value={loginData.email}
onChange={(e) => setLoginData({...loginData, email: e.target.value})}
error={errors.email}
/>
<AuthInput
icon={Lock}
type={showPassword ? "text" : "password"}
label="Mot de passe"
placeholder="••••••••"
value={loginData.password}
onChange={(e) => setLoginData({...loginData, password: e.target.value})}
error={errors.password}
rightElement={
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="text-gray-400 hover:text-gray-600 focus:outline-none"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
}
/>
<div className="flex items-center justify-between pt-2">
<div className="flex items-center space-x-2">
<Checkbox
id="remember"
checked={loginData.rememberMe}
onCheckedChange={(checked) =>
setLoginData({
...loginData,
rememberMe: checked === true
})
}
/>
<label
htmlFor="remember"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-600 dark:text-gray-400 cursor-pointer"
>
Se souvenir de moi
</label>
</div>
<Link
to="/forgot"
className="text-sm font-medium text-[#338660] hover:text-[#2A6F4F] hover:underline"
>
Mot de passe oublié ?
</Link>
</div>
<Button
type="submit"
disabled={isLoading}
className="w-full h-12 text-base font-bold bg-[#338660] hover:bg-[#2A6F4F] transition-all duration-300 shadow-[0_4px_14px_0_rgba(51,134,96,0.39)] hover:shadow-[0_6px_20px_rgba(51,134,96,0.23)] hover:-translate-y-0.5 rounded-xl"
>
{isLoading ? (
<span className="flex items-center gap-2">
<Loader2 className="w-5 h-5 animate-spin" />
Connexion en cours...
</span>
) : (
<span className="flex items-center gap-2">
Se connecter
<ArrowRight className="w-5 h-5" />
</span>
)}
</Button>
</motion.form>
{/* Footer Trust Elements */}
<motion.div variants={itemVariants} className="mt-12 text-center space-y-4">
<div className="flex items-center justify-center gap-2 text-xs text-gray-500 bg-gray-50 dark:bg-gray-900/50 py-2 px-4 rounded-full mx-auto">
<ShieldCheck className="w-3.5 h-3.5 text-[#338660]" />
<span>Connexion chiffrée SSL Données sécurisées</span>
</div>
<p className="text-xs text-gray-400">
En vous connectant, vous acceptez nos{' '}
<Link to="/" className="text-gray-500 hover:text-[#338660] underline">Conditions</Link>
{' '}et{' '}
<Link to="/privacy" className="text-gray-500 hover:text-[#338660] underline">Politique de confidentialité</Link>.
</p>
</motion.div>
</motion.div>
</AuthLayout>
)
}