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 { 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 */}
<div className="w-24 h-24 rounded-md overflow-hidden flex-shrink-0">
{mission.logoUrl ? (
<img
<Image
src={mission.logoUrl}
alt={mission.name}
width={96}
height={96}
className="w-full h-full object-cover rounded-md"
onError={(e) => {
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 = `<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">

View File

@ -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() {
<div className="flex justify-center items-center p-6 flex-grow">
<div className="w-48 h-48 relative">
{mission.logoUrl ? (
<img
<Image
src={mission.logoUrl}
alt={mission.name}
width={192}
height={192}
className="w-full h-full object-cover rounded-md"
onError={(e) => {
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}
<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 { Calendar } from "@/components/calendar";
import { News } from "@/components/news";
import { Duties } from "@/components/flow";
import { Email } from "@/components/email";
import { Parole } from "@/components/parole";
import { useSession } from "next-auth/react";
import { useEffect, useState } from "react";
import { HomeLogoutCheck } from "@/components/home-logout-check";
export default function Home() {
const { data: session, status } = useSession();
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) {
// Loading skeleton components
function QuoteCardSkeleton() {
return (
<main className="h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-gray-900"></div>
</main>
<div className="col-span-3 animate-pulse">
<div className="h-48 bg-gray-200 rounded-lg"></div>
</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 (
<>
<HomeLogoutCheck />
<main className="h-screen overflow-auto">
<div className="container mx-auto p-4 mt-12">
{/* First row */}
<div className="grid grid-cols-12 gap-4 mb-4">
<Suspense fallback={<QuoteCardSkeleton />}>
<div className="col-span-3">
<QuoteCard />
</div>
</Suspense>
<Suspense fallback={<CalendarSkeleton />}>
<div className="col-span-3">
<Calendar />
</div>
</Suspense>
<Suspense fallback={<NewsSkeleton />}>
<div className="col-span-3">
<News />
</div>
</Suspense>
<Suspense fallback={<DutiesSkeleton />}>
<div className="col-span-3">
<Duties />
</div>
</Suspense>
</div>
{/* Second row */}
<div className="grid grid-cols-12 gap-4">
<Suspense fallback={<EmailSkeleton />}>
<div className="col-span-6">
<Email />
</div>
</Suspense>
<Suspense fallback={<ParoleSkeleton />}>
<div className="col-span-6">
<Parole />
</div>
</Suspense>
</div>
</div>
</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 { 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 });

View File

@ -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('; ')
}
]
}