diff --git a/client/src/App.tsx b/client/src/App.tsx index 5ea27ba..07ba43a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,9 +1,11 @@ -import { Switch, Route } from "wouter"; +import { Switch, Route, Redirect, useLocation } from "wouter"; +import { useEffect } from "react"; import { queryClient } from "./lib/queryClient"; -import { QueryClientProvider } from "@tanstack/react-query"; +import { QueryClientProvider, useQuery } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import NotFound from "@/pages/not-found"; +import Login from "@/pages/login"; import Dashboard from "@/pages/dashboard"; import NewPost from "@/pages/new-post"; import Calendar from "@/pages/calendar"; @@ -13,19 +15,67 @@ import AI from "@/pages/ai"; import Analytics from "@/pages/analytics"; import History from "@/pages/history"; import Settings from "@/pages/settings"; +import SqlAdmin from "@/pages/sql"; +import UsersAdmin from "@/pages/users-admin"; + +function ProtectedRoute({ component: Component, adminOnly = false }: { component: React.ComponentType; adminOnly?: boolean }) { + const [, setLocation] = useLocation(); + + const { data: session, isLoading } = useQuery({ + queryKey: ["/api/auth/session"], + retry: false, + }); + + useEffect(() => { + if (!isLoading && !session) { + setLocation("/login"); + } + }, [isLoading, session, setLocation]); + + if (isLoading) { + return ( +
+
+
+

Chargement...

+
+
+ ); + } + + if (!session) { + return null; + } + + if (adminOnly && (session as any).role !== "admin") { + return ( +
+
+

Accès refusé

+

Cette page est réservée aux administrateurs.

+
+
+ ); + } + + return ; +} function Router() { return ( - - - - - - - - - + + {() => } + {() => } + {() => } + {() => } + {() => } + {() => } + {() => } + {() => } + {() => } + {() => } + {() => } ); diff --git a/client/src/components/sidebar.tsx b/client/src/components/sidebar.tsx index 6247a70..31ca9ac 100644 --- a/client/src/components/sidebar.tsx +++ b/client/src/components/sidebar.tsx @@ -1,8 +1,38 @@ -import { Home, PlusCircle, Calendar, Images, Users, Bot, BarChart3, Clock, Settings } from "lucide-react"; +import { Home, PlusCircle, Calendar, Images, Users, Bot, BarChart3, Clock, Settings, Database, UserCog, LogOut } from "lucide-react"; import { Link, useLocation } from "wouter"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { useToast } from "@/hooks/use-toast"; +import { Button } from "@/components/ui/button"; export default function Sidebar() { const [location] = useLocation(); + const [, setLocation] = useLocation(); + const { toast } = useToast(); + + // Charger la session utilisateur + const { data: session } = useQuery({ + queryKey: ["/api/auth/session"], + retry: false, + }); + + const isAdmin = (session as any)?.role === "admin"; + + // Mutation pour déconnexion + const logoutMutation = useMutation({ + mutationFn: async () => { + const res = await apiRequest("POST", "/api/auth/logout", {}); + return await res.json(); + }, + onSuccess: () => { + queryClient.clear(); + setLocation("/login"); + toast({ + title: "Déconnecté", + description: "Vous avez été déconnecté avec succès", + }); + }, + }); const navItems = [ { icon: Home, label: "Tableau de bord", href: "/", badge: null }, @@ -94,15 +124,77 @@ export default function Sidebar() { -
- - - Paramètres - +
+ {isAdmin && ( + <> +
+

Administration

+
+ + + Utilisateurs + + + + SQL + + + + Paramètres + + + )} + +
+ +
+ + {session && ( +
+

Connecté en tant que

+

{(session as any).username}

+

{(session as any).role}

+
+ )}
); diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx new file mode 100644 index 0000000..ab24eee --- /dev/null +++ b/client/src/pages/login.tsx @@ -0,0 +1,113 @@ +import { useState } from "react"; +import { useLocation } from "wouter"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { useToast } from "@/hooks/use-toast"; +import { apiRequest } from "@/lib/queryClient"; +import { Lock, User } from "lucide-react"; + +export default function Login() { + const [, setLocation] = useLocation(); + const { toast } = useToast(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + try { + const response = await apiRequest("POST", "/api/auth/login", { username, password }); + const data = await response.json(); + + toast({ + title: "Connexion réussie", + description: `Bienvenue ${data.username}`, + }); + + // Rediriger vers le dashboard + setLocation("/"); + } catch (error: any) { + toast({ + variant: "destructive", + title: "Erreur de connexion", + description: error.message || "Nom d'utilisateur ou mot de passe incorrect", + }); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + + Social Flow + + Connectez-vous à votre compte pour gérer vos publications + + + +
+
+ +
+ + setUsername(e.target.value)} + className="pl-10" + required + disabled={isLoading} + data-testid="input-username" + /> +
+
+ +
+ +
+ + setPassword(e.target.value)} + className="pl-10" + required + disabled={isLoading} + data-testid="input-password" + /> +
+
+ + +
+ +
+

Identifiants par défaut :

+

admin / admin

+
+
+
+
+ ); +} diff --git a/client/src/pages/sql.tsx b/client/src/pages/sql.tsx new file mode 100644 index 0000000..53b02a6 --- /dev/null +++ b/client/src/pages/sql.tsx @@ -0,0 +1,248 @@ +import { useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import Sidebar from "@/components/sidebar"; +import TopBar from "@/components/topbar"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/hooks/use-toast"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { Database, Download, Play } from "lucide-react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; + +export default function SqlAdmin() { + const [sidebarOpen, setSidebarOpen] = useState(false); + const { toast } = useToast(); + const [sqlQuery, setSqlQuery] = useState(""); + const [queryResult, setQueryResult] = useState(null); + + // Charger la liste des tables + const { data: tablesData } = useQuery({ + queryKey: ["/api/sql/tables"], + }); + + // Mutation pour exécuter une requête SQL + const executeSqlMutation = useMutation({ + mutationFn: async (query: string) => { + const res = await apiRequest("POST", "/api/sql/execute", { query }); + return await res.json(); + }, + onSuccess: (data) => { + setQueryResult(data); + if (data.success) { + toast({ + title: "Requête exécutée", + description: "La requête SQL a été exécutée avec succès", + }); + } + }, + onError: (error: any) => { + toast({ + variant: "destructive", + title: "Erreur SQL", + description: error.message || "Erreur lors de l'exécution de la requête", + }); + }, + }); + + const handleExecuteQuery = () => { + if (!sqlQuery.trim()) { + toast({ + variant: "destructive", + title: "Erreur", + description: "Veuillez entrer une requête SQL", + }); + return; + } + executeSqlMutation.mutate(sqlQuery); + }; + + const handleDownloadResult = () => { + if (!queryResult?.result) return; + + const json = JSON.stringify(queryResult.result, null, 2); + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `sql-result-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); + }; + + const insertTable = (tableName: string) => { + setSqlQuery(`SELECT * FROM ${tableName} LIMIT 10;`); + }; + + return ( +
+ {sidebarOpen && ( +
setSidebarOpen(false)} + /> + )} + +
+ +
+ +
+ setSidebarOpen(!sidebarOpen)} /> + +
+
+

+ + Administration SQL +

+

+ Exécutez des requêtes SQL et gérez votre base de données +

+
+ +
+ {/* Liste des tables */} + + + Tables disponibles + + Cliquez pour insérer dans la requête + + + +
+ {(tablesData as any)?.tables?.map((table: any) => ( + + ))} +
+
+
+ + {/* Éditeur SQL et résultats */} +
+ + + Éditeur SQL + + Entrez votre requête SQL ci-dessous + + + +