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.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m43s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m43s
This commit is contained in:
@@ -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<Request[]>(initialRequests);
|
||||
const [invitations, setInvitations] = useState<HouseInvitation[]>([]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(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 && (
|
||||
<span className="ml-2 text-xs normal-case" style={{ color: "var(--muted-foreground)" }}>
|
||||
• {invitations.length} invitation{invitations.length > 1 ? "s" : ""} en cours
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{(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({
|
||||
})}
|
||||
</div>
|
||||
|
||||
{isAdmin && invitations.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h5
|
||||
className="text-xs font-semibold uppercase tracking-wider mb-2"
|
||||
style={{
|
||||
color: "var(--primary)",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
Invitations en cours
|
||||
</h5>
|
||||
<div className="space-y-2">
|
||||
{invitations
|
||||
.filter((inv) => inv.status === "PENDING")
|
||||
.map((invitation) => (
|
||||
<div
|
||||
key={invitation.id}
|
||||
className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-3 rounded"
|
||||
style={{
|
||||
backgroundColor: "var(--card-hover)",
|
||||
borderLeft: `3px solid var(--primary)`,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
{invitation.invitee.avatar && (
|
||||
<img
|
||||
src={invitation.invitee.avatar}
|
||||
alt={invitation.invitee.username}
|
||||
className="w-8 h-8 rounded-full flex-shrink-0 border-2"
|
||||
style={{ borderColor: "var(--primary)" }}
|
||||
/>
|
||||
)}
|
||||
<div className="min-w-0">
|
||||
<span
|
||||
className="font-semibold block sm:inline"
|
||||
style={{ color: "var(--foreground)" }}
|
||||
>
|
||||
{invitation.invitee.username}
|
||||
</span>
|
||||
<span
|
||||
className="text-xs block sm:inline sm:ml-2"
|
||||
style={{ color: "var(--muted-foreground)" }}
|
||||
>
|
||||
Invité par {invitation.inviter.username}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<span
|
||||
className="text-xs uppercase px-2 py-1 rounded font-bold"
|
||||
style={{
|
||||
color: "var(--primary)",
|
||||
backgroundColor: `color-mix(in srgb, var(--primary) 15%, transparent)`,
|
||||
border: `1px solid color-mix(in srgb, var(--primary) 30%, transparent)`,
|
||||
}}
|
||||
>
|
||||
En attente
|
||||
</span>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (
|
||||
confirm(
|
||||
`Êtes-vous sûr de vouloir annuler l'invitation pour ${invitation.invitee.username} ?`
|
||||
)
|
||||
) {
|
||||
startTransition(async () => {
|
||||
const result = await cancelInvitation(
|
||||
invitation.id
|
||||
);
|
||||
if (result.success) {
|
||||
window.dispatchEvent(
|
||||
new Event("refreshInvitations")
|
||||
);
|
||||
handleUpdate();
|
||||
} else {
|
||||
setError(
|
||||
result.error ||
|
||||
"Erreur lors de l'annulation"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={isPending}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAdmin && (
|
||||
<div className="mt-4">
|
||||
{showInviteForm ? (
|
||||
@@ -471,7 +620,7 @@ export default function HouseManagement({
|
||||
>
|
||||
Demandes d'adhésion
|
||||
</h2>
|
||||
<RequestList requests={pendingRequests} onUpdate={onUpdate} />
|
||||
<RequestList requests={pendingRequests} onUpdate={handleUpdate} />
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user