From 69f23db55dedd1a66d462338aa287cbebef3fc0e Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 21 Aug 2025 12:54:48 +0200 Subject: [PATCH] feat: handling SSR on home page --- app/login/page.tsx | 176 ++++++++++- app/page.tsx | 385 ++++--------------------- components/home/action-section.tsx | 16 + components/home/category-breakdown.tsx | 66 +++++ components/home/category-card.tsx | 168 +++++++++++ components/home/client-wrapper.tsx | 36 +++ components/home/index.ts | 9 + components/home/loading-page.tsx | 17 ++ components/home/radar-section.tsx | 27 ++ components/home/skill-progress.tsx | 36 +++ components/home/welcome-header.tsx | 24 ++ components/home/welcome-screen.tsx | 74 +++++ lib/score-utils.ts | 51 ++++ lib/server-auth.ts | 85 ++++++ 14 files changed, 828 insertions(+), 342 deletions(-) create mode 100644 components/home/action-section.tsx create mode 100644 components/home/category-breakdown.tsx create mode 100644 components/home/category-card.tsx create mode 100644 components/home/client-wrapper.tsx create mode 100644 components/home/index.ts create mode 100644 components/home/loading-page.tsx create mode 100644 components/home/radar-section.tsx create mode 100644 components/home/skill-progress.tsx create mode 100644 components/home/welcome-header.tsx create mode 100644 components/home/welcome-screen.tsx create mode 100644 lib/score-utils.ts create mode 100644 lib/server-auth.ts diff --git a/app/login/page.tsx b/app/login/page.tsx index 6e2fc77..da96d1f 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -5,7 +5,16 @@ import { useRouter } from "next/navigation"; import { ProfileForm } from "@/components/profile-form"; import { AuthService } from "@/lib/auth-utils"; import { UserProfile, Team } from "@/lib/types"; -import { Code2 } from "lucide-react"; +import { Code2, LogOut, Edit, X, Home } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import Link from "next/link"; interface LoginPageProps {} @@ -13,17 +22,16 @@ export default function LoginPage({}: LoginPageProps) { const [teams, setTeams] = useState([]); const [loading, setLoading] = useState(true); const [authenticating, setAuthenticating] = useState(false); + const [currentUser, setCurrentUser] = useState(null); + const [isEditing, setIsEditing] = useState(false); const router = useRouter(); useEffect(() => { async function initialize() { try { // Vérifier si l'utilisateur est déjà connecté - const currentUser = await AuthService.getCurrentUser(); - if (currentUser) { - router.push("/"); - return; - } + const user = await AuthService.getCurrentUser(); + setCurrentUser(user); // Charger les équipes const teamsResponse = await fetch("/api/teams"); @@ -39,13 +47,20 @@ export default function LoginPage({}: LoginPageProps) { } initialize(); - }, [router]); + }, []); const handleSubmit = async (profile: UserProfile) => { setAuthenticating(true); try { await AuthService.login(profile); - router.push("/"); + if (isEditing) { + // Si on modifie le profil existant, mettre à jour l'état + setCurrentUser(profile); + setIsEditing(false); + } else { + // Si c'est un nouveau login, rediriger + router.push("/"); + } } catch (error) { console.error("Login failed:", error); // Vous pouvez ajouter une notification d'erreur ici @@ -54,6 +69,24 @@ export default function LoginPage({}: LoginPageProps) { } }; + const handleLogout = async () => { + try { + await AuthService.logout(); + setCurrentUser(null); + setIsEditing(false); + } catch (error) { + console.error("Logout failed:", error); + } + }; + + const handleEdit = () => { + setIsEditing(true); + }; + + const handleCancelEdit = () => { + setIsEditing(false); + }; + if (loading) { return (
@@ -72,6 +105,100 @@ export default function LoginPage({}: LoginPageProps) { ); } + // Si l'utilisateur est connecté et qu'on ne modifie pas + if (currentUser && !isEditing) { + const currentTeam = teams.find((t) => t.id === currentUser.teamId); + + return ( +
+
+
+ +
+
+
+ + + PeakSkills + +
+ +

+ Vous êtes connecté +

+

+ Gérez votre profil ou retournez à l'application +

+
+ +
+ {/* Informations utilisateur */} + + + Vos informations + + Profil actuellement connecté + + + +
+
+ +

{currentUser.firstName}

+
+
+ +

{currentUser.lastName}

+
+
+
+ +

+ {currentTeam?.name || "Équipe non trouvée"} +

+
+
+
+ + {/* Actions */} +
+ + + +
+
+
+
+ ); + } + + // Sinon, afficher le formulaire (nouvel utilisateur ou modification) return (
@@ -87,11 +214,26 @@ export default function LoginPage({}: LoginPageProps) {

- Bienvenue sur PeakSkills + {isEditing + ? "Modifier vos informations" + : "Bienvenue sur PeakSkills"}

- Évaluez vos compétences techniques et suivez votre progression + {isEditing + ? "Mettez à jour vos informations personnelles" + : "Évaluez vos compétences techniques et suivez votre progression"}

+ + {isEditing && ( + + )}
@@ -100,11 +242,21 @@ export default function LoginPage({}: LoginPageProps) {
-

Connexion en cours...

+

+ {isEditing + ? "Mise à jour en cours..." + : "Connexion en cours..."} +

)} - +
diff --git a/app/page.tsx b/app/page.tsx index 7818270..68cfe4a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,352 +1,77 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useEvaluation } from "@/hooks/use-evaluation"; -import { SkillsRadarChart } from "@/components/radar-chart"; +import { redirect } from "next/navigation"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; + isUserAuthenticated, + getServerUserEvaluation, + getServerSkillCategories, + getServerTeams, +} from "@/lib/server-auth"; import { generateRadarData } from "@/lib/evaluation-utils"; -import { useUser } from "@/hooks/use-user-context"; -import { TechIcon } from "@/components/icons/tech-icon"; -import Link from "next/link"; -import { Code2, ChevronDown, ChevronRight, ExternalLink } from "lucide-react"; -import { getCategoryIcon } from "@/lib/category-icons"; +import { + WelcomeHeader, + RadarSection, + CategoryBreakdown, + ActionSection, + ClientWrapper, + WelcomeScreen, +} from "@/components/home"; -// Fonction pour déterminer la couleur du badge selon le niveau moyen -function getScoreColors(score: number) { - if (score >= 2.5) { - // Expert/Maîtrise (violet) - return { - bg: "bg-violet-500/20", - border: "border-violet-500/30", - text: "text-violet-400", - gradient: "from-violet-500 to-violet-400", - }; - } else if (score >= 1.5) { - // Autonome (vert) - return { - bg: "bg-green-500/20", - border: "border-green-500/30", - text: "text-green-400", - gradient: "from-green-500 to-green-400", - }; - } else if (score >= 0.5) { - // Non autonome (orange/amber) - return { - bg: "bg-amber-500/20", - border: "border-amber-500/30", - text: "text-amber-400", - gradient: "from-amber-500 to-amber-400", - }; - } else { - // Jamais pratiqué (rouge) - return { - bg: "bg-red-500/20", - border: "border-red-500/30", - text: "text-red-400", - gradient: "from-red-500 to-red-400", - }; - } -} +export default async function HomePage() { + // Vérifier l'authentification + const isAuthenticated = await isUserAuthenticated(); -export default function HomePage() { - const { userEvaluation, skillCategories, teams, loading } = useEvaluation(); - - const { setUserInfo } = useUser(); - const [expandedCategory, setExpandedCategory] = useState(null); - - // Update user info in navigation when user evaluation is loaded - useEffect(() => { - if (userEvaluation) { - const teamName = - teams.find((t) => t.id === userEvaluation.profile.teamId)?.name || ""; - setUserInfo({ - firstName: userEvaluation.profile.firstName, - lastName: userEvaluation.profile.lastName, - teamName, - }); - } else { - setUserInfo(null); - } - }, [userEvaluation, teams, setUserInfo]); - - if (loading) { - return ( -
-
-
- -
-
-
-
-

Chargement...

-
-
-
-
- ); + // Si pas de cookie d'authentification, rediriger vers login + if (!isAuthenticated) { + redirect("/login"); } + // Charger les données côté serveur + const [userEvaluation, skillCategories, teams] = await Promise.all([ + getServerUserEvaluation(), + getServerSkillCategories(), + getServerTeams(), + ]); + + // Si pas d'évaluation, afficher l'écran d'accueil if (!userEvaluation) { - return ( -
-
-
- -
-
-
-
-

- Redirection vers la page de connexion... -

-
-
-
-
- ); + return ; } + // Générer les données radar côté serveur const radarData = generateRadarData( userEvaluation.evaluations, skillCategories ); + // Tout en server-side, seul le ClientWrapper gère setUserInfo return ( -
- {/* Background Effects */} -
-
-
+ +
+ {/* Background Effects */} +
+
+
-
- {/* Header */} -
-
- - - Tableau de bord - +
+ {/* Header */} + + + {/* Main Content Grid */} +
+ {/* Radar Chart */} + + + {/* Category Breakdown */} +
-

- Bonjour {userEvaluation.profile.firstName} ! -

- -

- Voici un aperçu de vos compétences techniques -

-
- - {/* Main Content Grid */} -
- {/* Radar Chart */} -
-
-

- Vue d'ensemble de vos compétences -

-

- Radar chart représentant votre niveau par catégorie -

-
-
- -
-
- - {/* Category Breakdown */} -
-
-

- Détail par catégorie -

-

- Vue détaillée de votre progression dans chaque domaine -

-
-
- {radarData.map((category) => { - const categoryEval = userEvaluation.evaluations.find( - (e) => e.category === category.category - ); - const skillsCount = categoryEval?.selectedSkillIds?.length || 0; - const evaluatedCount = - categoryEval?.skills.filter((s) => s.level !== null).length || - 0; - const isExpanded = expandedCategory === category.category; - const currentSkillCategory = skillCategories.find( - (sc) => sc.category === category.category - ); - const CategoryIcon = currentSkillCategory - ? getCategoryIcon(currentSkillCategory.icon) - : null; - - return ( -
-
- setExpandedCategory( - isExpanded ? null : category.category - ) - } - > -
-
- {isExpanded ? ( - - ) : ( - - )} - {CategoryIcon && ( - - )} -

- {category.category} -

-
- {skillsCount > 0 ? ( - (() => { - const colors = getScoreColors(category.score); - return ( -
- - {Math.round((category.score / 3) * 100)}% - -
- ); - })() - ) : ( -
- - Aucune - -
- )} -
-
- {skillsCount > 0 - ? `${evaluatedCount}/${skillsCount} compétences sélectionnées évaluées` - : "Aucune compétence sélectionnée"} -
- {skillsCount > 0 ? ( -
- {(() => { - const colors = getScoreColors(category.score); - return ( -
- ); - })()} -
- ) : ( -
- )} -
- - {/* Expanded Skills */} - {isExpanded && categoryEval && currentSkillCategory && ( -
-
- {categoryEval.selectedSkillIds.map((skillId) => { - const skill = currentSkillCategory.skills.find( - (s) => s.id === skillId - ); - if (!skill) return null; - - const skillEval = categoryEval.skills.find( - (s) => s.skillId === skillId - ); - return ( -
-
- - - {skill.name} - -
-
- {skillEval?.level && ( - - {skillEval.level === "never" && - "Jamais utilisé"} - {skillEval.level === "not-autonomous" && - "Non autonome"} - {skillEval.level === "autonomous" && - "Autonome"} - {skillEval.level === "expert" && "Expert"} - - )} -
-
- ); - })} -
- -
- -
-
- )} -
- ); - })} -
-
-
- - {/* Action Button */} -
- + {/* Action Button */} +
-
+ ); } diff --git a/components/home/action-section.tsx b/components/home/action-section.tsx new file mode 100644 index 0000000..ceab51d --- /dev/null +++ b/components/home/action-section.tsx @@ -0,0 +1,16 @@ +import { Button } from "@/components/ui/button"; +import Link from "next/link"; + +export function ActionSection() { + return ( +
+ +
+ ); +} diff --git a/components/home/category-breakdown.tsx b/components/home/category-breakdown.tsx new file mode 100644 index 0000000..9ad3a51 --- /dev/null +++ b/components/home/category-breakdown.tsx @@ -0,0 +1,66 @@ +import { CategoryCard } from "./category-card"; + +interface CategoryBreakdownProps { + radarData: Array<{ + category: string; + score: number; + maxScore: number; + }>; + userEvaluation: { + evaluations: Array<{ + category: string; + selectedSkillIds: string[]; + skills: Array<{ + skillId: string; + level: string | null; + }>; + }>; + }; + skillCategories: Array<{ + category: string; + icon: string; + skills: Array<{ + id: string; + name: string; + icon?: string; + }>; + }>; +} + +export function CategoryBreakdown({ + radarData, + userEvaluation, + skillCategories, +}: CategoryBreakdownProps) { + return ( +
+
+

+ Détail par catégorie +

+

+ Vue détaillée de votre progression dans chaque domaine +

+
+
+ {radarData.map((category) => { + const categoryEval = userEvaluation.evaluations.find( + (e) => e.category === category.category + ); + const skillCategory = skillCategories.find( + (sc) => sc.category === category.category + ); + + return ( + + ); + })} +
+
+ ); +} diff --git a/components/home/category-card.tsx b/components/home/category-card.tsx new file mode 100644 index 0000000..56dcd99 --- /dev/null +++ b/components/home/category-card.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { ChevronDown, ChevronRight, ExternalLink } from "lucide-react"; +import { getCategoryIcon } from "@/lib/category-icons"; +import { getScoreColors } from "@/lib/score-utils"; +import { SkillProgress } from "./skill-progress"; +import Link from "next/link"; + +interface CategoryCardProps { + category: { + category: string; + score: number; + maxScore: number; + }; + categoryEval?: { + category: string; + selectedSkillIds: string[]; + skills: Array<{ + skillId: string; + level: string | null; + }>; + }; + skillCategory?: { + category: string; + icon: string; + skills: Array<{ + id: string; + name: string; + icon?: string; + }>; + }; +} + +export function CategoryCard({ + category, + categoryEval, + skillCategory, +}: CategoryCardProps) { + const [isExpanded, setIsExpanded] = useState(false); + + const skillsCount = categoryEval?.selectedSkillIds?.length || 0; + const evaluatedCount = + categoryEval?.skills.filter((s) => s.level !== null).length || 0; + + const CategoryIcon = skillCategory + ? getCategoryIcon(skillCategory.icon) + : null; + + return ( +
+
setIsExpanded(!isExpanded)} + > +
+
+ {isExpanded ? ( + + ) : ( + + )} + {CategoryIcon && } +

+ {category.category} +

+
+ {skillsCount > 0 ? ( + (() => { + const colors = getScoreColors(category.score); + return ( +
+ + {Math.round((category.score / 3) * 100)}% + +
+ ); + })() + ) : ( +
+ Aucune +
+ )} +
+
+ {skillsCount > 0 + ? `${evaluatedCount}/${skillsCount} compétences sélectionnées évaluées` + : "Aucune compétence sélectionnée"} +
+ {skillsCount > 0 ? ( +
+ {(() => { + const colors = getScoreColors(category.score); + return ( +
+ ); + })()} +
+ ) : ( +
+ )} +
+ + {/* Expanded Skills */} + {isExpanded && ( +
+ {categoryEval && skillCategory && skillsCount > 0 ? ( +
+ {categoryEval.selectedSkillIds.map((skillId) => { + const skill = skillCategory.skills.find( + (s) => s.id === skillId + ); + if (!skill) return null; + + const skillEval = categoryEval.skills.find( + (s) => s.skillId === skillId + ); + return ( + + ); + })} +
+ ) : ( +
+

+ Aucune compétence sélectionnée dans cette catégorie +

+
+ )} + +
0 ? "mt-3 pt-3 border-t border-white/10" : "" + }`} + > + +
+
+ )} +
+ ); +} diff --git a/components/home/client-wrapper.tsx b/components/home/client-wrapper.tsx new file mode 100644 index 0000000..fc84471 --- /dev/null +++ b/components/home/client-wrapper.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useEffect } from "react"; +import { useUser } from "@/hooks/use-user-context"; +import { UserEvaluation, Team } from "@/lib/types"; + +interface ClientWrapperProps { + userEvaluation: UserEvaluation | null; + teams: Team[]; + children: React.ReactNode; +} + +export function ClientWrapper({ + userEvaluation, + teams, + children, +}: ClientWrapperProps) { + const { setUserInfo } = useUser(); + + // Update user info in navigation when user evaluation is loaded + useEffect(() => { + if (userEvaluation) { + const teamName = + teams.find((t) => t.id === userEvaluation.profile.teamId)?.name || ""; + setUserInfo({ + firstName: userEvaluation.profile.firstName, + lastName: userEvaluation.profile.lastName, + teamName, + }); + } else { + setUserInfo(null); + } + }, [userEvaluation, teams, setUserInfo]); + + return <>{children}; +} diff --git a/components/home/index.ts b/components/home/index.ts new file mode 100644 index 0000000..e766f0c --- /dev/null +++ b/components/home/index.ts @@ -0,0 +1,9 @@ +export { LoadingPage } from "./loading-page"; +export { WelcomeHeader } from "./welcome-header"; +export { RadarSection } from "./radar-section"; +export { CategoryBreakdown } from "./category-breakdown"; +export { CategoryCard } from "./category-card"; +export { SkillProgress } from "./skill-progress"; +export { ActionSection } from "./action-section"; +export { ClientWrapper } from "./client-wrapper"; +export { WelcomeScreen } from "./welcome-screen"; diff --git a/components/home/loading-page.tsx b/components/home/loading-page.tsx new file mode 100644 index 0000000..393f855 --- /dev/null +++ b/components/home/loading-page.tsx @@ -0,0 +1,17 @@ +export function LoadingPage() { + return ( +
+
+
+ +
+
+
+
+

Chargement...

+
+
+
+
+ ); +} diff --git a/components/home/radar-section.tsx b/components/home/radar-section.tsx new file mode 100644 index 0000000..5a8f7be --- /dev/null +++ b/components/home/radar-section.tsx @@ -0,0 +1,27 @@ +import { SkillsRadarChart } from "@/components/radar-chart"; + +interface RadarSectionProps { + radarData: Array<{ + category: string; + score: number; + maxScore: number; + }>; +} + +export function RadarSection({ radarData }: RadarSectionProps) { + return ( +
+
+

+ Vue d'ensemble de vos compétences +

+

+ Radar chart représentant votre niveau par catégorie +

+
+
+ +
+
+ ); +} diff --git a/components/home/skill-progress.tsx b/components/home/skill-progress.tsx new file mode 100644 index 0000000..ab29e77 --- /dev/null +++ b/components/home/skill-progress.tsx @@ -0,0 +1,36 @@ +import { TechIcon } from "@/components/icons/tech-icon"; +import { getSkillLevelLabel } from "@/lib/score-utils"; + +interface SkillProgressProps { + skill: { + id: string; + name: string; + icon?: string; + }; + skillEval?: { + skillId: string; + level: string | null; + }; +} + +export function SkillProgress({ skill, skillEval }: SkillProgressProps) { + return ( +
+
+ + {skill.name} +
+
+ {skillEval?.level && ( + + {getSkillLevelLabel(skillEval.level)} + + )} +
+
+ ); +} diff --git a/components/home/welcome-header.tsx b/components/home/welcome-header.tsx new file mode 100644 index 0000000..2844595 --- /dev/null +++ b/components/home/welcome-header.tsx @@ -0,0 +1,24 @@ +import { Code2 } from "lucide-react"; + +interface WelcomeHeaderProps { + firstName: string; +} + +export function WelcomeHeader({ firstName }: WelcomeHeaderProps) { + return ( +
+
+ + + Tableau de bord + +
+ +

Bonjour {firstName} !

+ +

+ Voici un aperçu de vos compétences techniques +

+
+ ); +} diff --git a/components/home/welcome-screen.tsx b/components/home/welcome-screen.tsx new file mode 100644 index 0000000..a4cccb3 --- /dev/null +++ b/components/home/welcome-screen.tsx @@ -0,0 +1,74 @@ +import { Code2, ArrowRight, Target } from "lucide-react"; +import Link from "next/link"; + +export function WelcomeScreen() { + return ( +
+ {/* Background Effects */} +
+
+
+ +
+
+
+ + + PeakSkills + +
+ +

+ Bienvenue ! Commencez votre parcours +

+

+ Vous êtes connecté avec succès. Il est temps d'évaluer vos + compétences techniques pour obtenir une vue d'ensemble personnalisée + de votre expertise. +

+ +
+
+
+ +

+ Évaluez vos compétences +

+

+ Sélectionnez vos domaines d'expertise et évaluez votre niveau +

+
+
+
+

+ Visualisez vos forces +

+

+ Obtenez un radar chart personnalisé de vos compétences +

+
+
+
+

+ Suivez votre progression +

+

+ Mettez à jour vos évaluations au fil de votre évolution +

+
+
+
+ + + Commencer l'évaluation + + +
+
+
+ ); +} + diff --git a/lib/score-utils.ts b/lib/score-utils.ts new file mode 100644 index 0000000..e03b424 --- /dev/null +++ b/lib/score-utils.ts @@ -0,0 +1,51 @@ +// Fonction pour déterminer la couleur du badge selon le niveau moyen +export function getScoreColors(score: number) { + if (score >= 2.5) { + // Expert/Maîtrise (violet) + return { + bg: "bg-violet-500/20", + border: "border-violet-500/30", + text: "text-violet-400", + gradient: "from-violet-500 to-violet-400", + }; + } else if (score >= 1.5) { + // Autonome (vert) + return { + bg: "bg-green-500/20", + border: "border-green-500/30", + text: "text-green-400", + gradient: "from-green-500 to-green-400", + }; + } else if (score >= 0.5) { + // Non autonome (orange/amber) + return { + bg: "bg-amber-500/20", + border: "border-amber-500/30", + text: "text-amber-400", + gradient: "from-amber-500 to-amber-400", + }; + } else { + // Jamais pratiqué (rouge) + return { + bg: "bg-red-500/20", + border: "border-red-500/30", + text: "text-red-400", + gradient: "from-red-500 to-red-400", + }; + } +} + +export function getSkillLevelLabel(level: string): string { + switch (level) { + case "never": + return "Jamais utilisé"; + case "not-autonomous": + return "Non autonome"; + case "autonomous": + return "Autonome"; + case "expert": + return "Expert"; + default: + return ""; + } +} diff --git a/lib/server-auth.ts b/lib/server-auth.ts new file mode 100644 index 0000000..8f900ba --- /dev/null +++ b/lib/server-auth.ts @@ -0,0 +1,85 @@ +import { cookies } from "next/headers"; +import { COOKIE_NAME } from "./auth-utils"; +import { EvaluationService } from "@/services/evaluation-service"; +import { TeamsService } from "@/services/teams-service"; +import { SkillsService } from "@/services/skills-service"; +import { SkillCategory, Team } from "./types"; + +/** + * Récupère l'ID utilisateur depuis le cookie côté serveur + */ +export async function getUserIdFromCookie(): Promise { + const cookieStore = await cookies(); + const userIdCookie = cookieStore.get("peakSkills_userId"); + + if (!userIdCookie?.value) { + return null; + } + + const userId = parseInt(userIdCookie.value); + return isNaN(userId) ? null : userId; +} + +/** + * Récupère l'évaluation complète de l'utilisateur côté serveur + */ +export async function getServerUserEvaluation() { + const userId = await getUserIdFromCookie(); + + if (!userId) { + return null; + } + + try { + const evaluationService = new EvaluationService(); + + // Récupérer d'abord le profil utilisateur + const userProfile = await evaluationService.getUserById(userId); + + if (!userProfile) { + return null; + } + + // Puis charger l'évaluation avec le profil + const userEvaluation = await evaluationService.loadUserEvaluation( + userProfile + ); + + return userEvaluation; + } catch (error) { + console.error("Failed to get user evaluation:", error); + return null; + } +} + +/** + * Charge les catégories de compétences côté serveur depuis PostgreSQL + */ +export async function getServerSkillCategories(): Promise { + try { + return await SkillsService.getSkillCategories(); + } catch (error) { + console.error("Failed to load skill categories:", error); + return []; + } +} + +/** + * Charge les équipes côté serveur depuis PostgreSQL + */ +export async function getServerTeams(): Promise { + try { + return await TeamsService.getTeams(); + } catch (error) { + console.error("Failed to load teams:", error); + return []; + } +} + +/** + * Vérifie simplement si l'utilisateur est authentifié via le cookie + */ +export async function isUserAuthenticated(): Promise { + const userId = await getUserIdFromCookie(); + return !!userId; +}