feat: add dashboard, projects, and persons pages with reusable components

Implemented the following:
- `DashboardPage`: displays an overview of stats, recent projects, and tabs for future analytics/reports.
- `ProjectsPage` and `PersonsPage`: include searchable tables, actions, and mobile-friendly card views.
- Integrated reusable components like `AuthLoading`, `DropdownMenu`, `Table`, and `Card`.
This commit is contained in:
2025-05-16 14:43:14 +02:00
parent 753669c622
commit cab80e6aef
31 changed files with 5850 additions and 100 deletions

View File

@@ -0,0 +1,79 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Loader2 } from "lucide-react";
import { useAuth } from "@/lib/auth-context";
export default function CallbackPage() {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
const { login, user } = useAuth();
useEffect(() => {
async function handleCallback() {
try {
// Get the code from the URL query parameters
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (!code) {
throw new Error('No authorization code found in the URL');
}
// Use the auth context to login
await login(code);
// Check if there's a stored callbackUrl
const callbackUrl = sessionStorage.getItem('callbackUrl');
// Clear the stored callbackUrl
sessionStorage.removeItem('callbackUrl');
// Redirect based on role and callbackUrl
if (callbackUrl) {
// For admin routes, check if user has admin role
if (callbackUrl.startsWith('/admin') && user?.role !== 'ADMIN') {
router.push('/dashboard');
} else {
router.push(callbackUrl);
}
} else {
// Default redirects if no callbackUrl
if (user && user.role === 'ADMIN') {
router.push('/admin');
} else {
router.push('/dashboard');
}
}
} catch (err) {
console.error("Authentication error:", err);
setError("Une erreur est survenue lors de l'authentification. Veuillez réessayer.");
}
}
handleCallback();
}, [router]);
if (error) {
return (
<div className="flex min-h-screen flex-col items-center justify-center p-4 text-center">
<div className="mb-4 text-red-500">{error}</div>
<a
href="/auth/login"
className="text-primary hover:underline"
>
Retour à la page de connexion
</a>
</div>
);
}
return (
<div className="flex min-h-screen flex-col items-center justify-center p-4 text-center">
<Loader2 className="mb-4 h-8 w-8 animate-spin text-primary" />
<h1 className="mb-2 text-xl font-semibold">Authentification en cours...</h1>
<p className="text-muted-foreground">Vous allez être redirigé vers l'application.</p>
</div>
);
}

View File

@@ -0,0 +1,74 @@
"use client";
import { useState } from "react";
import { Github } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
export default function LoginPage() {
const [isLoading, setIsLoading] = useState(false);
const handleGitHubLogin = async () => {
setIsLoading(true);
try {
// Get the callbackUrl from the URL if present
const urlParams = new URLSearchParams(window.location.search);
const callbackUrl = urlParams.get('callbackUrl');
// Use the API service to get the GitHub OAuth URL
const { url } = await import('@/lib/api').then(module =>
module.authAPI.getGitHubOAuthUrl()
);
// Store the callbackUrl in sessionStorage to use after authentication
if (callbackUrl) {
sessionStorage.setItem('callbackUrl', callbackUrl);
}
// Redirect to GitHub OAuth page
window.location.href = url;
} catch (error) {
console.error('Login error:', error);
setIsLoading(false);
// You could add error handling UI here
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-background p-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1 text-center">
<CardTitle className="text-2xl font-bold">Connexion</CardTitle>
<CardDescription>
Connectez-vous pour accéder à l&apos;application de création de groupes
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Button
variant="outline"
onClick={handleGitHubLogin}
disabled={isLoading}
className="w-full"
>
{isLoading ? (
<span className="flex items-center gap-2">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
Connexion en cours...
</span>
) : (
<span className="flex items-center gap-2">
<Github className="h-5 w-5" />
Se connecter avec GitHub
</span>
)}
</Button>
</CardContent>
<CardFooter className="flex flex-col text-center text-sm text-muted-foreground">
<p>
En vous connectant, vous acceptez nos conditions d&apos;utilisation et notre politique de confidentialité.
</p>
</CardFooter>
</Card>
</div>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { Loader2 } from "lucide-react";
import { useAuth } from "@/lib/auth-context";
export default function LogoutPage() {
const router = useRouter();
const { logout } = useAuth();
useEffect(() => {
async function handleLogout() {
try {
// Use the auth context to logout
await logout();
// Note: The auth context handles clearing localStorage and redirecting
} catch (error) {
console.error('Logout error:', error);
// Even if there's an error, still redirect to login
router.push('/auth/login');
}
}
handleLogout();
}, [router]);
return (
<div className="flex min-h-screen flex-col items-center justify-center p-4 text-center">
<Loader2 className="mb-4 h-8 w-8 animate-spin text-primary" />
<h1 className="mb-2 text-xl font-semibold">Déconnexion en cours...</h1>
<p className="text-muted-foreground">Vous allez être redirigé vers la page de connexion.</p>
</div>
);
}