Refactor HousesPage and HouseManagement components: Introduce TypeScript types for house and invitation data structures to enhance type safety. Update data serialization logic for improved clarity and maintainability. Refactor UI components for better readability and consistency, including adjustments to conditional rendering and styling in HouseManagement. Optimize fetch logic in HousesSection with useCallback for performance improvements.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m25s

This commit is contained in:
Julien Froidefond
2025-12-18 08:50:14 +01:00
parent 1b82bd9ee6
commit f5dab3cb95
4 changed files with 195 additions and 85 deletions

View File

@@ -91,7 +91,9 @@ export default function HouseManagement({
const fetchRequests = async () => {
if (!house || !isAdmin) return;
try {
const response = await fetch(`/api/houses/${house.id}/requests?status=PENDING`);
const response = await fetch(
`/api/houses/${house.id}/requests?status=PENDING`
);
if (response.ok) {
const data = await response.json();
setRequests(data);
@@ -101,10 +103,13 @@ export default function HouseManagement({
}
};
fetchRequests();
}, [house?.id, isAdmin]);
}, [house, isAdmin]);
const handleDelete = () => {
if (!house || !confirm("Êtes-vous sûr de vouloir supprimer cette maison ?")) {
if (
!house ||
!confirm("Êtes-vous sûr de vouloir supprimer cette maison ?")
) {
return;
}
@@ -168,11 +173,17 @@ export default function HouseManagement({
if (!house) {
return (
<Card className="p-6">
<h2 className="text-lg sm:text-xl font-bold mb-4" style={{ color: "var(--foreground)" }}>
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Ma Maison
</h2>
<p className="text-sm mb-4" style={{ color: "var(--muted-foreground)" }}>
Vous n'êtes membre d'aucune maison pour le moment.
<p
className="text-sm mb-4"
style={{ color: "var(--muted-foreground)" }}
>
Vous n&apos;êtes membre d&apos;aucune maison pour le moment.
</p>
</Card>
);
@@ -180,27 +191,30 @@ export default function HouseManagement({
return (
<div className="space-y-6">
<Card
<Card
className="p-4 sm:p-6"
style={{
borderColor: `color-mix(in srgb, var(--accent) 40%, var(--border))`,
borderWidth: "2px",
boxShadow: `0 0 20px color-mix(in srgb, var(--accent) 10%, transparent)`
boxShadow: `0 0 20px color-mix(in srgb, var(--accent) 10%, transparent)`,
}}
>
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-4">
<div className="flex-1 min-w-0">
<h3
<h3
className="text-xl sm:text-2xl font-bold mb-2 break-words"
style={{
style={{
color: "var(--accent)",
textShadow: `0 0 10px color-mix(in srgb, var(--accent) 30%, transparent)`
textShadow: `0 0 10px color-mix(in srgb, var(--accent) 30%, transparent)`,
}}
>
{house.name}
</h3>
{house.description && (
<p className="text-sm mt-2 break-words" style={{ color: "var(--muted-foreground)" }}>
<p
className="text-sm mt-2 break-words"
style={{ color: "var(--muted-foreground)" }}
>
{house.description}
</p>
)}
@@ -217,22 +231,40 @@ export default function HouseManagement({
{isEditing ? "Annuler" : "Modifier"}
</Button>
{isOwner && (
<Button onClick={handleDelete} variant="danger" size="sm" className="flex-1 sm:flex-none">
<Button
onClick={handleDelete}
variant="danger"
size="sm"
className="flex-1 sm:flex-none"
>
Supprimer
</Button>
)}
</>
)}
{!isOwner && (
<Button onClick={handleLeave} variant="danger" size="sm" className="flex-1 sm:flex-none">
<Button
onClick={handleLeave}
variant="danger"
size="sm"
className="flex-1 sm:flex-none"
>
Quitter
</Button>
)}
</div>
</div>
{error && <Alert variant="error" className="mb-4">{error}</Alert>}
{success && <Alert variant="success" className="mb-4">{success}</Alert>}
{error && (
<Alert variant="error" className="mb-4">
{error}
</Alert>
)}
{success && (
<Alert variant="success" className="mb-4">
{success}
</Alert>
)}
{isEditing ? (
<HouseForm
@@ -245,12 +277,12 @@ export default function HouseManagement({
/>
) : (
<div>
<h4
<h4
className="text-sm font-semibold uppercase tracking-wider mb-3"
style={{
style={{
color: "var(--primary)",
borderBottom: `2px solid color-mix(in srgb, var(--primary) 30%, transparent)`,
paddingBottom: "0.5rem"
paddingBottom: "0.5rem",
}}
>
Membres ({house.memberships?.length ?? 0})
@@ -258,21 +290,25 @@ export default function HouseManagement({
<div className="space-y-2">
{(house.memberships || []).map((membership) => {
const isCurrentUser = membership.user.id === session?.user?.id;
const roleColor =
membership.role === "OWNER" ? "var(--accent)" :
membership.role === "ADMIN" ? "var(--primary)" :
"var(--muted-foreground)";
const roleColor =
membership.role === "OWNER"
? "var(--accent)"
: membership.role === "ADMIN"
? "var(--primary)"
: "var(--muted-foreground)";
return (
<div
key={membership.id}
className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-3 rounded"
style={{
backgroundColor: isCurrentUser
style={{
backgroundColor: isCurrentUser
? "color-mix(in srgb, var(--primary) 10%, var(--card-hover))"
: "var(--card-hover)",
borderLeft: `3px solid ${roleColor}`,
borderColor: isCurrentUser ? "var(--primary)" : "transparent"
borderColor: isCurrentUser
? "var(--primary)"
: "transparent",
}}
>
<div className="flex items-center gap-2 min-w-0 flex-1">
@@ -285,16 +321,18 @@ export default function HouseManagement({
/>
)}
<div className="min-w-0">
<span
<span
className="font-semibold block sm:inline"
style={{
color: isCurrentUser ? "var(--primary)" : "var(--foreground)"
style={{
color: isCurrentUser
? "var(--primary)"
: "var(--foreground)",
}}
>
{membership.user.username}
{isCurrentUser && " (Vous)"}
</span>
<span
<span
className="text-xs block sm:inline sm:ml-2"
style={{ color: "var(--muted-foreground)" }}
>
@@ -309,43 +347,55 @@ export default function HouseManagement({
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span
<span
className="text-xs uppercase px-2 py-1 rounded font-bold"
style={{
style={{
color: roleColor,
backgroundColor: `color-mix(in srgb, ${roleColor} 15%, transparent)`,
border: `1px solid color-mix(in srgb, ${roleColor} 30%, transparent)`
border: `1px solid color-mix(in srgb, ${roleColor} 30%, transparent)`,
}}
>
{membership.role === "OWNER" && "👑 "}
{membership.role}
</span>
{isAdmin &&
!isCurrentUser &&
{isAdmin &&
!isCurrentUser &&
(isOwner || membership.role === "MEMBER") &&
membership.role !== "OWNER" && (
<Button
onClick={() => {
if (confirm(`Êtes-vous sûr de vouloir retirer ${membership.user.username} de la maison ?`)) {
startTransition(async () => {
const result = await removeMember(house.id, membership.user.id);
if (result.success) {
// Rafraîchir le score dans le header (le membre retiré perd des points)
window.dispatchEvent(new Event("refreshUserScore"));
onUpdate?.();
} else {
setError(result.error || "Erreur lors du retrait du membre");
}
});
}
}}
disabled={isPending}
variant="danger"
size="sm"
>
Retirer
</Button>
)}
<Button
onClick={() => {
if (
confirm(
`Êtes-vous sûr de vouloir retirer ${membership.user.username} de la maison ?`
)
) {
startTransition(async () => {
const result = await removeMember(
house.id,
membership.user.id
);
if (result.success) {
// Rafraîchir le score dans le header (le membre retiré perd des points)
window.dispatchEvent(
new Event("refreshUserScore")
);
onUpdate?.();
} else {
setError(
result.error ||
"Erreur lors du retrait du membre"
);
}
});
}
}}
disabled={isPending}
variant="danger"
size="sm"
>
Retirer
</Button>
)}
</div>
</div>
);
@@ -411,15 +461,15 @@ export default function HouseManagement({
{isAdmin && pendingRequests.length > 0 && (
<Card className="p-4 sm:p-6">
<h2
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{
style={{
color: "var(--purple)",
borderBottom: `2px solid color-mix(in srgb, var(--purple) 30%, transparent)`,
paddingBottom: "0.5rem"
paddingBottom: "0.5rem",
}}
>
Demandes d'adhésion
Demandes d&apos;adhésion
</h2>
<RequestList requests={pendingRequests} onUpdate={onUpdate} />
</Card>
@@ -427,4 +477,3 @@ export default function HouseManagement({
</div>
);
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import { useSession } from "next-auth/react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
@@ -78,7 +78,7 @@ export default function HousesSection({
const [showCreateForm, setShowCreateForm] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const fetchHouses = async () => {
const fetchHouses = useCallback(async () => {
try {
const params = new URLSearchParams();
if (searchTerm) {
@@ -94,7 +94,7 @@ export default function HousesSection({
} catch (error) {
console.error("Error fetching houses:", error);
}
};
}, [searchTerm]);
const fetchMyHouse = async () => {
try {
@@ -129,9 +129,13 @@ export default function HousesSection({
}, 300);
return () => clearTimeout(timeout);
} else {
fetchHouses();
// Utiliser un timeout pour éviter setState synchrone dans effect
const timeout = setTimeout(() => {
fetchHouses();
}, 0);
return () => clearTimeout(timeout);
}
}, [searchTerm]);
}, [searchTerm, fetchHouses]);
const handleUpdate = () => {
fetchMyHouse();
@@ -165,15 +169,24 @@ export default function HousesSection({
<>
{invitations.length > 0 && (
<Card className="p-4 sm:p-6">
<h2 className="text-lg sm:text-xl font-bold mb-4" style={{ color: "var(--foreground)" }}>
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Mes Invitations
</h2>
<InvitationList invitations={invitations} onUpdate={handleUpdate} />
<InvitationList
invitations={invitations}
onUpdate={handleUpdate}
/>
</Card>
)}
<Card className="p-4 sm:p-6">
<h2 className="text-lg sm:text-xl font-bold mb-4" style={{ color: "var(--foreground)" }}>
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Ma Maison
</h2>
{myHouse ? (
@@ -194,8 +207,12 @@ export default function HousesSection({
/>
) : (
<div>
<p className="text-sm mb-4 break-words" style={{ color: "var(--muted-foreground)" }}>
Vous n'êtes membre d'aucune maison. Créez-en une ou demandez à rejoindre une maison existante.
<p
className="text-sm mb-4 break-words"
style={{ color: "var(--muted-foreground)" }}
>
Vous n&apos;êtes membre d&apos;aucune maison. Créez-en
une ou demandez à rejoindre une maison existante.
</p>
<Button
onClick={() => setShowCreateForm(true)}
@@ -213,7 +230,10 @@ export default function HousesSection({
)}
<Card className="p-4 sm:p-6">
<h2 className="text-lg sm:text-xl font-bold mb-4" style={{ color: "var(--foreground)" }}>
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Toutes les Maisons
</h2>
<div className="mb-4">
@@ -243,4 +263,3 @@ export default function HousesSection({
</BackgroundSection>
);
}