Fondation

This commit is contained in:
alma 2026-01-16 22:55:13 +01:00
parent 04de49aad5
commit 5744fd9724
6 changed files with 200 additions and 69 deletions

View File

@ -6,6 +6,7 @@ import { FileIcon, Calendar, Eye, MapPin, Users, Clock, ThumbsUp, Languages, Arr
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
// Define types for mission details // Define types for mission details
interface User { interface User {
@ -219,9 +220,11 @@ export default function MissionTabDetailPage() {
{/* Display logo instead of Participate button */} {/* Display logo instead of Participate button */}
<div className="w-24 h-24 rounded-md overflow-hidden flex-shrink-0"> <div className="w-24 h-24 rounded-md overflow-hidden flex-shrink-0">
{mission.logoUrl ? ( {mission.logoUrl ? (
<img <Image
src={mission.logoUrl} src={mission.logoUrl}
alt={mission.name} alt={mission.name}
width={96}
height={96}
className="w-full h-full object-cover rounded-md" className="w-full h-full object-cover rounded-md"
onError={(e) => { onError={(e) => {
console.error("Logo failed to load:", { console.error("Logo failed to load:", {
@ -238,9 +241,13 @@ export default function MissionTabDetailPage() {
parent.classList.add('flex'); parent.classList.add('flex');
parent.classList.add('items-center'); parent.classList.add('items-center');
parent.classList.add('justify-center'); parent.classList.add('justify-center');
parent.innerHTML = `<span className="text-3xl font-medium text-gray-500">${mission.name.slice(0, 2).toUpperCase()}</span>`; const fallback = document.createElement('span');
fallback.className = 'text-3xl font-medium text-gray-500';
fallback.textContent = mission.name.slice(0, 2).toUpperCase();
parent.appendChild(fallback);
} }
}} }}
unoptimized={mission.logoUrl?.includes('localhost') || mission.logoUrl?.includes('127.0.0.1')}
/> />
) : ( ) : (
<div className="bg-gray-100 w-full h-full flex items-center justify-center"> <div className="bg-gray-100 w-full h-full flex items-center justify-center">

View File

@ -4,6 +4,7 @@ import { useState, useEffect } from "react";
import { Search, Lock, Plus } from "lucide-react"; import { Search, Lock, Plus } from "lucide-react";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { getPublicUrl } from "@/lib/s3"; import { getPublicUrl } from "@/lib/s3";
@ -235,9 +236,11 @@ export default function MissionsPage() {
<div className="flex justify-center items-center p-6 flex-grow"> <div className="flex justify-center items-center p-6 flex-grow">
<div className="w-48 h-48 relative"> <div className="w-48 h-48 relative">
{mission.logoUrl ? ( {mission.logoUrl ? (
<img <Image
src={mission.logoUrl} src={mission.logoUrl}
alt={mission.name} alt={mission.name}
width={192}
height={192}
className="w-full h-full object-cover rounded-md" className="w-full h-full object-cover rounded-md"
onError={(e) => { onError={(e) => {
console.error("Logo failed to load:", { console.error("Logo failed to load:", {
@ -254,6 +257,7 @@ export default function MissionsPage() {
(fallbackDiv as HTMLElement).style.display = 'flex'; (fallbackDiv as HTMLElement).style.display = 'flex';
} }
}} }}
unoptimized={mission.logoUrl?.includes('localhost') || mission.logoUrl?.includes('127.0.0.1')}
/> />
) : null} ) : null}
<div <div

View File

@ -1,77 +1,116 @@
"use client"; import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/options";
import { redirect } from "next/navigation";
import { Suspense } from "react";
import { QuoteCard } from "@/components/quote-card"; import { QuoteCard } from "@/components/quote-card";
import { Calendar } from "@/components/calendar"; import { Calendar } from "@/components/calendar";
import { News } from "@/components/news"; import { News } from "@/components/news";
import { Duties } from "@/components/flow"; import { Duties } from "@/components/flow";
import { Email } from "@/components/email"; import { Email } from "@/components/email";
import { Parole } from "@/components/parole"; import { Parole } from "@/components/parole";
import { useSession } from "next-auth/react"; import { HomeLogoutCheck } from "@/components/home-logout-check";
import { useEffect, useState } from "react";
export default function Home() { // Loading skeleton components
const { data: session, status } = useSession(); function QuoteCardSkeleton() {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Check if logout is in progress - if so, redirect immediately
const justLoggedOut = sessionStorage.getItem('just_logged_out') === 'true';
const logoutCookie = document.cookie.split(';').some(c => c.trim().startsWith('logout_in_progress=true'));
if (justLoggedOut || logoutCookie) {
// Clear the flags and redirect
sessionStorage.removeItem('just_logged_out');
document.cookie = 'logout_in_progress=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC';
window.location.href = '/signin?logout=true';
return;
}
if (status !== "loading") {
setIsLoading(false);
}
}, [status]);
// Don't render widgets if logout is in progress
const justLoggedOut = sessionStorage.getItem('just_logged_out') === 'true';
const logoutCookie = document.cookie.split(';').some(c => c.trim().startsWith('logout_in_progress=true'));
if (isLoading || justLoggedOut || logoutCookie) {
return ( return (
<main className="h-screen flex items-center justify-center"> <div className="col-span-3 animate-pulse">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div> <div className="h-48 bg-gray-200 rounded-lg"></div>
</main> </div>
); );
}
function CalendarSkeleton() {
return (
<div className="col-span-3 animate-pulse">
<div className="h-48 bg-gray-200 rounded-lg"></div>
</div>
);
}
function NewsSkeleton() {
return (
<div className="col-span-3 animate-pulse">
<div className="h-48 bg-gray-200 rounded-lg"></div>
</div>
);
}
function DutiesSkeleton() {
return (
<div className="col-span-3 animate-pulse">
<div className="h-48 bg-gray-200 rounded-lg"></div>
</div>
);
}
function EmailSkeleton() {
return (
<div className="col-span-6 animate-pulse">
<div className="h-96 bg-gray-200 rounded-lg"></div>
</div>
);
}
function ParoleSkeleton() {
return (
<div className="col-span-6 animate-pulse">
<div className="h-96 bg-gray-200 rounded-lg"></div>
</div>
);
}
export default async function Home() {
// Check authentication on server side
const session = await getServerSession(authOptions);
if (!session) {
redirect("/signin");
} }
return ( return (
<>
<HomeLogoutCheck />
<main className="h-screen overflow-auto"> <main className="h-screen overflow-auto">
<div className="container mx-auto p-4 mt-12"> <div className="container mx-auto p-4 mt-12">
{/* First row */} {/* First row */}
<div className="grid grid-cols-12 gap-4 mb-4"> <div className="grid grid-cols-12 gap-4 mb-4">
<Suspense fallback={<QuoteCardSkeleton />}>
<div className="col-span-3"> <div className="col-span-3">
<QuoteCard /> <QuoteCard />
</div> </div>
</Suspense>
<Suspense fallback={<CalendarSkeleton />}>
<div className="col-span-3"> <div className="col-span-3">
<Calendar /> <Calendar />
</div> </div>
</Suspense>
<Suspense fallback={<NewsSkeleton />}>
<div className="col-span-3"> <div className="col-span-3">
<News /> <News />
</div> </div>
</Suspense>
<Suspense fallback={<DutiesSkeleton />}>
<div className="col-span-3"> <div className="col-span-3">
<Duties /> <Duties />
</div> </div>
</Suspense>
</div> </div>
{/* Second row */} {/* Second row */}
<div className="grid grid-cols-12 gap-4"> <div className="grid grid-cols-12 gap-4">
<Suspense fallback={<EmailSkeleton />}>
<div className="col-span-6"> <div className="col-span-6">
<Email /> <Email />
</div> </div>
</Suspense>
<Suspense fallback={<ParoleSkeleton />}>
<div className="col-span-6"> <div className="col-span-6">
<Parole /> <Parole />
</div> </div>
</Suspense>
</div> </div>
</div> </div>
</main> </main>
</>
); );
} }

View File

@ -0,0 +1,28 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
/**
* Client component to handle logout state check
* This checks sessionStorage and cookies for logout flags
* and redirects if logout is in progress
*/
export function HomeLogoutCheck() {
const router = useRouter();
useEffect(() => {
// Check if logout is in progress - if so, redirect immediately
const justLoggedOut = sessionStorage.getItem('just_logged_out') === 'true';
const logoutCookie = document.cookie.split(';').some(c => c.trim().startsWith('logout_in_progress=true'));
if (justLoggedOut || logoutCookie) {
// Clear the flags and redirect
sessionStorage.removeItem('just_logged_out');
document.cookie = 'logout_in_progress=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC';
router.push('/signin?logout=true');
}
}, [router]);
return null;
}

View File

@ -1,3 +1,4 @@
// @ts-ignore - webdav package types may not be available
import { createClient, WebDAVClient } from 'webdav'; import { createClient, WebDAVClient } from 'webdav';
import { prisma } from '@/lib/prisma'; import { prisma } from '@/lib/prisma';
import { logger } from '@/lib/logger'; import { logger } from '@/lib/logger';
@ -54,12 +55,12 @@ export async function discoverInfomaniakCalendars(
logger.debug('[CALDAV] Found items in root directory', { logger.debug('[CALDAV] Found items in root directory', {
count: items.length, count: items.length,
items: items.map(item => ({ filename: item.filename, type: item.type, basename: item.basename })) items: items.map((item: any) => ({ filename: item.filename, type: item.type, basename: item.basename }))
}); });
const calendars: CalDAVCalendar[] = []; const calendars: CalDAVCalendar[] = [];
for (const item of items) { for (const item of items as any[]) {
// Skip non-directories, root, and special directories like /principals // Skip non-directories, root, and special directories like /principals
if (item.type !== 'directory' || item.filename === '/' || item.filename === '/principals') { if (item.type !== 'directory' || item.filename === '/' || item.filename === '/principals') {
logger.debug('[CALDAV] Skipping item', { filename: item.filename, type: item.type }); logger.debug('[CALDAV] Skipping item', { filename: item.filename, type: item.type });

View File

@ -1,13 +1,28 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
eslint: { eslint: {
ignoreDuringBuilds: true, ignoreDuringBuilds: false, // ✅ Réactivé - corriger les erreurs avant build
dirs: ['app', 'components', 'lib'], // Limiter aux dossiers pertinents
}, },
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: false, // ✅ Réactivé - corriger les erreurs avant build
}, },
images: { images: {
unoptimized: true, unoptimized: false, // ✅ Activé l'optimisation d'images
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
{
protocol: 'http',
hostname: 'localhost',
port: '9000', // MinIO local
},
],
}, },
experimental: { experimental: {
webpackBuildWorker: true, webpackBuildWorker: true,
@ -22,9 +37,46 @@ const nextConfig = {
{ {
source: '/:path*', source: '/:path*',
headers: [ headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{ {
key: 'Content-Security-Policy', key: 'Content-Security-Policy',
value: "frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net" value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'", // ⚠️ À restreindre davantage si possible
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https: http://localhost:9000",
"font-src 'self' data:",
"connect-src 'self' https://*.slm-lab.net https://*.microsoft.com https://*.microsoftonline.com wss://*.slm-lab.net",
"frame-src 'self' https://*.slm-lab.net",
"frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net",
].join('; ')
} }
] ]
} }