Files
LogiFlow/client/src/pages/AuthPage.tsx
T
Claude 5d3e5357a0 perf(client): pages chargées à la demande et un seul appel /api/user
- Routes en React.lazy (sauf connexion et accueil) avec un Suspense dans
  la zone de contenu ; recharts n'est plus préchargé. JS initial :
  1,72 Mo -> 625 Ko (183 Ko gzip). Rechargement automatique unique si un
  ancien fichier JS a disparu après un déploiement, et ErrorBoundary par
  page pour garder le menu affiché en cas d'erreur.
- Authentification unifiée sur le cache React Query ['/api/user'] : un
  seul GET /api/user pour toute l'application (au lieu de 4 à 5 par
  page), plus de rechargement complet après la connexion.
- Pas de nouvel essai sur les erreurs 4xx ; fournisseurs et magasins en
  cache 5 minutes ; valeur du StoreContext mémoïsée et redimensionnement
  qui ne re-rend que lorsque le palier d'écran change.
- Pages : requêtes inutilisées ou en double supprimées, appels lancés
  seulement une fois l'utilisateur connu, invalidations ciblées au lieu
  de rechargements complets, filtres et tris mémoïsés, anciennes données
  gardées (et estompées) pendant un changement de filtre ou de mois,
  recherche DLC mobile temporisée, vérifications de factures à
  concurrence bornée, navigation interne sans rechargement de la page.
- index.html : script de bannière Replit retiré, lang="fr".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
2026-10-03 04:29:51 +00:00

304 lines
11 KiB
TypeScript

import { useState, useEffect } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useAuthUnified } from "@/hooks/useAuthUnified";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { Store, LogIn, User, Info } from "lucide-react";
import { useLocation } from "wouter";
// Fonction pour détecter l'environnement
const getEnvironment = () => {
const hostname = window.location.hostname;
const isDev = hostname.includes('replit.dev') || hostname === 'localhost';
return isDev ? 'development' : 'production';
};
export default function AuthPage() {
const [, setLocation] = useLocation();
const { user, isLoading, refreshAuth, forceAuthRefresh, isAuthenticated } = useAuthUnified();
const { toast } = useToast();
const [showDefaultCredentials, setShowDefaultCredentials] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
const [loginData, setLoginData] = useState({
username: "",
password: "",
});
const currentEnvironment = getEnvironment();
// Check if admin user still has default password
const { data: defaultCredentialsCheck } = useQuery({
queryKey: ['/api/default-credentials-check'],
queryFn: async () => {
try {
const response = await apiRequest("/api/default-credentials-check", "GET");
return response;
} catch (error) {
// If the endpoint doesn't exist or fails, assume we should show credentials
return { showDefault: true };
}
},
});
// Fonction de login simple pour production
const handleProductionLogin = async (data: typeof loginData) => {
setIsSubmitting(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(data),
});
if (response.ok) {
toast({
title: "Connexion réussie",
description: "Bienvenue dans LogiFlow",
});
// L'utilisateur est relu une fois et placé dans le cache partagé : toute
// l'application bascule directement sur le tableau de bord, sans attente
// ni rechargement complet
const refreshedUserData = await forceAuthRefresh();
if (refreshedUserData && refreshedUserData.id) {
setLocation("/", { replace: true });
} else {
// Session pas encore visible : rechargement complet comme auparavant
window.location.href = "/";
}
} else {
const errorData = await response.json().catch(() => ({}));
toast({
title: "Erreur de connexion",
description: errorData.message || "Identifiant ou mot de passe incorrect",
variant: "destructive",
});
}
} catch (error: any) {
toast({
title: "Erreur de connexion",
description: error.message || "Une erreur est survenue",
variant: "destructive",
});
} finally {
setIsSubmitting(false);
}
};
// Mutation complexe pour développement
const loginMutation = useMutation({
mutationFn: async (data: typeof loginData) => {
const response = await apiRequest("/api/login", "POST", data);
return response;
},
onSuccess: async (loginResult) => {
if (import.meta.env.DEV) {
console.log('✅ Login successful, refreshing auth state...');
}
toast({
title: "Connexion réussie",
description: "Bienvenue dans LogiFlow",
});
// Force invalidation of auth queries and refresh auth state
queryClient.invalidateQueries({ queryKey: ['/api/user'] });
queryClient.invalidateQueries({ queryKey: ['/api/default-credentials-check'] });
// Force immediate authentication state refresh
const refreshedUserData = await forceAuthRefresh();
if (import.meta.env.DEV) {
console.log('🔄 Force auth refresh result:', refreshedUserData);
}
if (refreshedUserData && refreshedUserData.id) {
if (import.meta.env.DEV) {
console.log('🔄 User data confirmed, forcing redirect...');
}
setLocation("/");
} else {
setTimeout(() => {
forceAuthRefresh().then((retryUserData) => {
if (retryUserData && retryUserData.id) {
setLocation("/");
}
});
}, 500);
}
},
onError: (error: any) => {
toast({
title: "Erreur de connexion",
description: error.message || "Identifiant ou mot de passe incorrect",
variant: "destructive",
});
},
});
useEffect(() => {
if (defaultCredentialsCheck) {
setShowDefaultCredentials(defaultCredentialsCheck.showDefault);
}
}, [defaultCredentialsCheck]);
// Redirect if already authenticated
useEffect(() => {
if (!isLoading && isAuthenticated && user) {
if (import.meta.env.DEV) {
console.log('🔄 User authenticated, redirecting to dashboard...', { user: user.username, authenticated: isAuthenticated });
}
// Force immediate redirect to home page
setLocation("/");
}
}, [isLoading, isAuthenticated, user, setLocation]);
const handleLogin = (e: React.FormEvent) => {
e.preventDefault();
if (currentEnvironment === 'production') {
// Utiliser la méthode simple pour production
handleProductionLogin(loginData);
} else {
// Utiliser React Query pour développement
loginMutation.mutate(loginData);
}
};
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-gray-600">Chargement...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 flex">
{/* Left side - Auth forms */}
<div className="flex-1 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div className="text-center">
<div className="flex justify-center">
<div className="w-16 h-16 bg-primary rounded-lg flex items-center justify-center">
<Store className="w-8 h-8 text-white" />
</div>
</div>
<h2 className="mt-6 text-3xl font-extrabold text-gray-900">
LogiFlow
</h2>
<p className="mt-2 text-sm text-gray-600">
Gestion des commandes et livraisons
</p>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<LogIn className="w-5 h-5" />
Connexion
</CardTitle>
<CardDescription>
Connectez-vous à votre compte LogiFlow
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleLogin} className="space-y-4">
<div>
<Label htmlFor="login-username">Identifiant</Label>
<Input
id="login-username"
type="text"
value={loginData.username}
onChange={(e) => setLoginData({...loginData, username: e.target.value})}
required
placeholder="Votre identifiant"
/>
</div>
<div>
<Label htmlFor="login-password">Mot de passe</Label>
<Input
id="login-password"
type="password"
value={loginData.password}
onChange={(e) => setLoginData({...loginData, password: e.target.value})}
required
placeholder="••••••••"
/>
</div>
<Button
type="submit"
className="w-full"
disabled={currentEnvironment === 'production' ? isSubmitting : loginMutation.isPending}
>
{(currentEnvironment === 'production' ? isSubmitting : loginMutation.isPending) ? "Connexion..." : "Se connecter"}
</Button>
</form>
</CardContent>
</Card>
{/* Default credentials info - only show if admin password hasn't been changed */}
{showDefaultCredentials && (
<Card className="mt-4 bg-blue-50 border-blue-200">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-blue-800 text-sm">
<Info className="w-4 h-4" />
Première connexion
</CardTitle>
</CardHeader>
<CardContent className="pt-0">
<div className="text-sm text-blue-700 space-y-1">
<p><strong>Identifiant :</strong> admin</p>
<p><strong>Mot de passe :</strong> admin</p>
<p className="text-xs text-blue-600 mt-2">
Changez le mot de passe après votre première connexion
</p>
</div>
</CardContent>
</Card>
)}
</div>
</div>
{/* Right side - Hero section */}
<div className="hidden lg:block relative w-0 flex-1">
<div className="absolute inset-0 bg-gradient-to-br from-primary to-primary/80 flex items-center justify-center">
<div className="text-center text-white max-w-md">
<Store className="w-20 h-20 mx-auto mb-6 opacity-90" />
<h1 className="text-4xl font-bold mb-4">
Gérez vos commandes efficacement
</h1>
<p className="text-xl opacity-90 mb-8">
LogiFlow centralise la gestion de vos commandes et livraisons across tous vos magasins
</p>
<div className="space-y-4 text-left">
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>Calendrier centralisé</span>
</div>
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>Gestion multi-magasins</span>
</div>
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-white rounded-full"></div>
<span>Suivi en temps réel</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
}