From 0b56d625ecd466b4710790fafa6edbbf968f79c5 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 18 Dec 2025 09:16:13 +0100 Subject: [PATCH] Enhance HouseManagement and HousesPage components: Introduce invitation management features, including fetching and displaying pending invitations. Refactor data handling and UI updates for improved user experience and maintainability. Optimize state management with useCallback and useEffect for better performance. --- app/houses/page.tsx | 187 ++++++++++++++------------ components/houses/HouseManagement.tsx | 165 +++++++++++++++++++++-- 2 files changed, 261 insertions(+), 91 deletions(-) diff --git a/app/houses/page.tsx b/app/houses/page.tsx index c33f898..74ab996 100644 --- a/app/houses/page.tsx +++ b/app/houses/page.tsx @@ -5,7 +5,11 @@ import NavigationWrapper from "@/components/navigation/NavigationWrapper"; import HousesSection from "@/components/houses/HousesSection"; import { houseService } from "@/services/houses/house.service"; import { prisma } from "@/services/database"; -import type { House, HouseMembership, HouseInvitation } from "@/prisma/generated/prisma/client"; +import type { + House, + HouseMembership, + HouseInvitation, +} from "@/prisma/generated/prisma/client"; export const dynamic = "force-dynamic"; @@ -17,15 +21,17 @@ type HouseWithRelations = House & { avatar: string | null; } | null; creatorId?: string; - memberships?: Array; + memberships?: Array< + HouseMembership & { + user: { + id: string; + username: string; + avatar: string | null; + score: number | null; + level: number | null; + }; + } + >; }; type InvitationWithRelations = HouseInvitation & { @@ -47,10 +53,39 @@ export default async function HousesPage() { redirect("/login"); } - const [housesData, myHouseData, invitationsData, users, backgroundImage] = await Promise.all([ - // Récupérer les maisons - houseService.getAllHouses({ - include: { + const [housesData, myHouseData, invitationsData, users, backgroundImage] = + await Promise.all([ + // Récupérer les maisons + houseService.getAllHouses({ + include: { + memberships: { + include: { + user: { + select: { + id: true, + username: true, + avatar: true, + score: true, + level: true, + }, + }, + }, + orderBy: [ + { role: "asc" }, // OWNER, ADMIN, MEMBER + { user: { score: "desc" } }, // Puis par score décroissant + ], + }, + creator: { + select: { + id: true, + username: true, + avatar: true, + }, + }, + }, + }), + // Récupérer la maison de l'utilisateur + houseService.getUserHouse(session.user.id, { memberships: { include: { user: { @@ -63,10 +98,6 @@ export default async function HousesPage() { }, }, }, - orderBy: [ - { role: "asc" }, // OWNER, ADMIN, MEMBER - { user: { score: "desc" } }, // Puis par score décroissant - ], }, creator: { select: { @@ -75,78 +106,66 @@ export default async function HousesPage() { avatar: true, }, }, - }, - }), - // Récupérer la maison de l'utilisateur - houseService.getUserHouse(session.user.id, { - memberships: { - include: { - user: { - select: { - id: true, - username: true, - avatar: true, - score: true, - level: true, - }, + }), + // Récupérer les invitations de l'utilisateur + houseService.getUserInvitations(session.user.id, "PENDING"), + // Récupérer tous les utilisateurs sans maison pour les invitations + prisma.user.findMany({ + where: { + houseMemberships: { + none: {}, }, }, - }, - creator: { select: { id: true, username: true, avatar: true, }, - }, - }), - // Récupérer les invitations de l'utilisateur - houseService.getUserInvitations(session.user.id, "PENDING"), - // Récupérer tous les utilisateurs sans maison pour les invitations - prisma.user.findMany({ - where: { - houseMemberships: { - none: {}, + orderBy: { + username: "asc", }, - }, - select: { - id: true, - username: true, - avatar: true, - }, - orderBy: { - username: "asc", - }, - }), - getBackgroundImage("challenges", "/got-2.jpg"), - ]); + }), + getBackgroundImage("challenges", "/got-2.jpg"), + ]); // Sérialiser les données pour le client - const houses = (housesData as HouseWithRelations[]).map((house: HouseWithRelations) => ({ - id: house.id, - name: house.name, - description: house.description, - creator: house.creator || { id: house.creatorId || "", username: "Unknown", avatar: null }, - memberships: (house.memberships || []).map((m) => ({ - id: m.id, - role: m.role, - user: { - id: m.user.id, - username: m.user.username, - avatar: m.user.avatar, - score: m.user.score ?? 0, - level: m.user.level ?? 1, + const houses = (housesData as HouseWithRelations[]).map( + (house: HouseWithRelations) => ({ + id: house.id, + name: house.name, + description: house.description, + creator: house.creator || { + id: house.creatorId || "", + username: "Unknown", + avatar: null, }, - })), - })); + memberships: (house.memberships || []).map((m) => ({ + id: m.id, + role: m.role, + user: { + id: m.user.id, + username: m.user.username, + avatar: m.user.avatar, + score: m.user.score ?? 0, + level: m.user.level ?? 1, + }, + })), + }) + ); const myHouse = myHouseData ? { id: myHouseData.id, name: myHouseData.name, description: myHouseData.description, - creator: (myHouseData as HouseWithRelations).creator || { id: (myHouseData as HouseWithRelations).creatorId || "", username: "Unknown", avatar: null }, - memberships: ((myHouseData as HouseWithRelations).memberships || []).map((m) => ({ + creator: (myHouseData as HouseWithRelations).creator || { + id: (myHouseData as HouseWithRelations).creatorId || "", + username: "Unknown", + avatar: null, + }, + memberships: ( + (myHouseData as HouseWithRelations).memberships || [] + ).map((m) => ({ id: m.id, role: m.role, user: { @@ -160,16 +179,18 @@ export default async function HousesPage() { } : null; - const invitations = (invitationsData as InvitationWithRelations[]).map((inv: InvitationWithRelations) => ({ - id: inv.id, - house: { - id: inv.house.id, - name: inv.house.name, - }, - inviter: inv.inviter, - status: inv.status, - createdAt: inv.createdAt.toISOString(), - })); + const invitations = (invitationsData as InvitationWithRelations[]).map( + (inv: InvitationWithRelations) => ({ + id: inv.id, + house: { + id: inv.house.id, + name: inv.house.name, + }, + inviter: inv.inviter, + status: inv.status, + createdAt: inv.createdAt.toISOString(), + }) + ); return (
diff --git a/components/houses/HouseManagement.tsx b/components/houses/HouseManagement.tsx index 1511634..6612596 100644 --- a/components/houses/HouseManagement.tsx +++ b/components/houses/HouseManagement.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useTransition } from "react"; +import { useState, useEffect, useTransition, useCallback } from "react"; import { useSession } from "next-auth/react"; import Card from "@/components/ui/Card"; import Button from "@/components/ui/Button"; @@ -8,7 +8,7 @@ import HouseForm from "./HouseForm"; import RequestList from "./RequestList"; import Alert from "@/components/ui/Alert"; import { deleteHouse, leaveHouse, removeMember } from "@/actions/houses/update"; -import { inviteUser } from "@/actions/houses/invitations"; +import { inviteUser, cancelInvitation } from "@/actions/houses/invitations"; interface House { id: string; @@ -38,6 +38,22 @@ interface User { avatar: string | null; } +interface HouseInvitation { + id: string; + invitee: { + id: string; + username: string; + avatar: string | null; + }; + inviter: { + id: string; + username: string; + avatar: string | null; + }; + status: string; + createdAt: string; +} + interface HouseManagementProps { house: House | null; users?: User[]; @@ -76,6 +92,7 @@ export default function HouseManagement({ const [showInviteForm, setShowInviteForm] = useState(false); const [selectedUserId, setSelectedUserId] = useState(""); const [requests, setRequests] = useState(initialRequests); + const [invitations, setInvitations] = useState([]); const [isPending, startTransition] = useTransition(); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); @@ -105,6 +122,34 @@ export default function HouseManagement({ fetchRequests(); }, [house, isAdmin]); + const fetchInvitations = useCallback(async () => { + if (!house || !isAdmin) return; + try { + const response = await fetch( + `/api/houses/${house.id}/invitations?status=PENDING` + ); + if (response.ok) { + const data = await response.json(); + setInvitations(data); + } + } catch (error) { + console.error("Error fetching invitations:", error); + } + }, [house, isAdmin]); + + useEffect(() => { + // Utiliser un timeout pour éviter l'appel synchrone de setState dans l'effect + const timeout = setTimeout(() => { + fetchInvitations(); + }, 0); + return () => clearTimeout(timeout); + }, [fetchInvitations]); + + const handleUpdate = useCallback(() => { + fetchInvitations(); + onUpdate?.(); + }, [fetchInvitations, onUpdate]); + const handleDelete = () => { if ( !house || @@ -119,7 +164,7 @@ export default function HouseManagement({ if (result.success) { // Rafraîchir le score dans le header (le créateur perd des points) window.dispatchEvent(new Event("refreshUserScore")); - onUpdate?.(); + handleUpdate(); } else { setError(result.error || "Erreur lors de la suppression"); } @@ -136,7 +181,7 @@ export default function HouseManagement({ const result = await leaveHouse(house.id); if (result.success) { window.dispatchEvent(new Event("refreshUserScore")); - onUpdate?.(); + handleUpdate(); } else { setError(result.error || "Erreur lors de la sortie"); } @@ -157,7 +202,9 @@ export default function HouseManagement({ setSuccess("Invitation envoyée"); setShowInviteForm(false); setSelectedUserId(""); - onUpdate?.(); + // Rafraîchir la liste des invitations + await fetchInvitations(); + handleUpdate(); } else { setError(result.error || "Erreur lors de l'envoi de l'invitation"); } @@ -271,7 +318,7 @@ export default function HouseManagement({ house={house} onSuccess={() => { setIsEditing(false); - onUpdate?.(); + handleUpdate(); }} onCancel={() => setIsEditing(false)} /> @@ -286,6 +333,11 @@ export default function HouseManagement({ }} > Membres ({house.memberships?.length ?? 0}) + {isAdmin && invitations.length > 0 && ( + + • {invitations.length} invitation{invitations.length > 1 ? "s" : ""} en cours + + )}
{(house.memberships || []).map((membership) => { @@ -379,7 +431,7 @@ export default function HouseManagement({ window.dispatchEvent( new Event("refreshUserScore") ); - onUpdate?.(); + handleUpdate(); } else { setError( result.error || @@ -402,6 +454,103 @@ export default function HouseManagement({ })}
+ {isAdmin && invitations.length > 0 && ( +
+
+ Invitations en cours +
+
+ {invitations + .filter((inv) => inv.status === "PENDING") + .map((invitation) => ( +
+
+ {invitation.invitee.avatar && ( + {invitation.invitee.username} + )} +
+ + {invitation.invitee.username} + + + Invité par {invitation.inviter.username} + +
+
+
+ + En attente + + +
+
+ ))} +
+
+ )} + {isAdmin && (
{showInviteForm ? ( @@ -471,7 +620,7 @@ export default function HouseManagement({ > Demandes d'adhésion - + )}