diff --git a/app/mission-tab/[missionId]/page.tsx b/app/mission-tab/[missionId]/page.tsx index d8eeb80..7fb3e52 100644 --- a/app/mission-tab/[missionId]/page.tsx +++ b/app/mission-tab/[missionId]/page.tsx @@ -6,6 +6,7 @@ import { FileIcon, Calendar, Eye, MapPin, Users, Clock, ThumbsUp, Languages, Arr import { useToast } from "@/components/ui/use-toast"; import { useParams, useRouter } from "next/navigation"; import Link from "next/link"; +import Image from "next/image"; // Define types for mission details interface User { @@ -219,9 +220,11 @@ export default function MissionTabDetailPage() { {/* Display logo instead of Participate button */}
{mission.logoUrl ? ( - {mission.name} { console.error("Logo failed to load:", { @@ -238,9 +241,13 @@ export default function MissionTabDetailPage() { parent.classList.add('flex'); parent.classList.add('items-center'); parent.classList.add('justify-center'); - parent.innerHTML = `${mission.name.slice(0, 2).toUpperCase()}`; + 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')} /> ) : (
diff --git a/app/missions/page.tsx b/app/missions/page.tsx index 300d1aa..b7c5ec4 100644 --- a/app/missions/page.tsx +++ b/app/missions/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import { Search, Lock, Plus } from "lucide-react"; import { Input } from "@/components/ui/input"; import Link from "next/link"; +import Image from "next/image"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; import { getPublicUrl } from "@/lib/s3"; @@ -235,9 +236,11 @@ export default function MissionsPage() {
{mission.logoUrl ? ( - {mission.name} { console.error("Logo failed to load:", { @@ -254,6 +257,7 @@ export default function MissionsPage() { (fallbackDiv as HTMLElement).style.display = 'flex'; } }} + unoptimized={mission.logoUrl?.includes('localhost') || mission.logoUrl?.includes('127.0.0.1')} /> ) : null}
+
+
+ ); +} - 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; - } +function CalendarSkeleton() { + return ( +
+
+
+ ); +} - if (status !== "loading") { - setIsLoading(false); - } - }, [status]); +function NewsSkeleton() { + return ( +
+
+
+ ); +} - // 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')); +function DutiesSkeleton() { + return ( +
+
+
+ ); +} + +function EmailSkeleton() { + return ( +
+
+
+ ); +} + +function ParoleSkeleton() { + return ( +
+
+
+ ); +} + +export default async function Home() { + // Check authentication on server side + const session = await getServerSession(authOptions); - if (isLoading || justLoggedOut || logoutCookie) { - return ( -
-
-
- ); + if (!session) { + redirect("/signin"); } return ( -
-
- {/* First row */} -
-
- + <> + +
+
+ {/* First row */} +
+ }> +
+ +
+
+ }> +
+ +
+
+ }> +
+ +
+
+ }> +
+ +
+
-
- -
-
- -
-
- + + {/* Second row */} +
+ }> +
+ +
+
+ }> +
+ +
+
- - {/* Second row */} -
-
- -
-
- -
-
-
-
+
+ ); } diff --git a/components/home-logout-check.tsx b/components/home-logout-check.tsx new file mode 100644 index 0000000..59c2897 --- /dev/null +++ b/components/home-logout-check.tsx @@ -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; +} diff --git a/lib/services/caldav-sync.ts b/lib/services/caldav-sync.ts index a82960b..3f3a8b7 100644 --- a/lib/services/caldav-sync.ts +++ b/lib/services/caldav-sync.ts @@ -1,3 +1,4 @@ +// @ts-ignore - webdav package types may not be available import { createClient, WebDAVClient } from 'webdav'; import { prisma } from '@/lib/prisma'; import { logger } from '@/lib/logger'; @@ -54,12 +55,12 @@ export async function discoverInfomaniakCalendars( logger.debug('[CALDAV] Found items in root directory', { 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[] = []; - for (const item of items) { + for (const item of items as any[]) { // Skip non-directories, root, and special directories like /principals if (item.type !== 'directory' || item.filename === '/' || item.filename === '/principals') { logger.debug('[CALDAV] Skipping item', { filename: item.filename, type: item.type }); diff --git a/next.config.mjs b/next.config.mjs index 5c7e92b..5165e91 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,13 +1,28 @@ /** @type {import('next').NextConfig} */ const nextConfig = { eslint: { - ignoreDuringBuilds: true, + ignoreDuringBuilds: false, // ✅ Réactivé - corriger les erreurs avant build + dirs: ['app', 'components', 'lib'], // Limiter aux dossiers pertinents }, typescript: { - ignoreBuildErrors: true, + ignoreBuildErrors: false, // ✅ Réactivé - corriger les erreurs avant build }, 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: { webpackBuildWorker: true, @@ -22,9 +37,46 @@ const nextConfig = { { source: '/:path*', 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', - 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('; ') } ] }