Add house leaderboard feature: Integrate house leaderboard functionality in LeaderboardPage and LeaderboardSection components. Update userStatsService to fetch house leaderboard data, and enhance UI to display house rankings, scores, and member details. Update Prisma schema to include house-related models and relationships, and seed database with initial house data.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled

This commit is contained in:
Julien Froidefond
2025-12-17 13:35:18 +01:00
parent cb02b494f4
commit 85ee812ab1
36 changed files with 5422 additions and 13 deletions

View File

@@ -0,0 +1,236 @@
"use client";
import { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import SectionTitle from "@/components/ui/SectionTitle";
import BackgroundSection from "@/components/ui/BackgroundSection";
import HouseCard from "./HouseCard";
import HouseForm from "./HouseForm";
import HouseManagement from "./HouseManagement";
import InvitationList from "./InvitationList";
import Input from "@/components/ui/Input";
interface House {
id: string;
name: string;
description: string | null;
creator: {
id: string;
username: string;
avatar: string | null;
};
memberships?: Array<{
id: string;
role: string;
user: {
id: string;
username: string;
avatar: string | null;
score?: number;
level?: number;
};
}>;
_count?: {
memberships: number;
};
}
interface User {
id: string;
username: string;
avatar: string | null;
}
interface HousesSectionProps {
initialHouses?: House[];
initialMyHouse?: House | null;
initialUsers?: User[];
initialInvitations?: Array<{
id: string;
house: {
id: string;
name: string;
};
inviter: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}>;
backgroundImage: string;
}
export default function HousesSection({
initialHouses = [],
initialMyHouse = null,
initialUsers = [],
initialInvitations = [],
backgroundImage,
}: HousesSectionProps) {
const { data: session } = useSession();
const [houses, setHouses] = useState<House[]>(initialHouses);
const [myHouse, setMyHouse] = useState<House | null>(initialMyHouse);
const [invitations, setInvitations] = useState(initialInvitations);
const [showCreateForm, setShowCreateForm] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const fetchHouses = async () => {
try {
const params = new URLSearchParams();
if (searchTerm) {
params.append("search", searchTerm);
}
params.append("include", "members,creator");
const response = await fetch(`/api/houses?${params}`);
if (response.ok) {
const data = await response.json();
setHouses(data);
}
} catch (error) {
console.error("Error fetching houses:", error);
}
};
const fetchMyHouse = async () => {
try {
const response = await fetch("/api/houses/my-house");
if (response.ok) {
const data = await response.json();
setMyHouse(data);
} else if (response.status === 404) {
setMyHouse(null);
}
} catch (error) {
console.error("Error fetching my house:", error);
}
};
const fetchInvitations = async () => {
try {
const response = await fetch("/api/invitations?status=PENDING");
if (response.ok) {
const data = await response.json();
setInvitations(data);
}
} catch (error) {
console.error("Error fetching invitations:", error);
}
};
useEffect(() => {
if (searchTerm) {
const timeout = setTimeout(() => {
fetchHouses();
}, 300);
return () => clearTimeout(timeout);
} else {
fetchHouses();
}
}, [searchTerm]);
const handleUpdate = () => {
fetchMyHouse();
fetchHouses();
fetchInvitations();
};
const filteredHouses = houses.filter((house) => {
if (!myHouse) return true;
return house.id !== myHouse.id;
});
return (
<BackgroundSection backgroundImage={backgroundImage}>
{/* Title Section */}
<SectionTitle
variant="gradient"
size="lg"
subtitle="Rejoignez une maison ou créez la vôtre"
className="mb-12 overflow-hidden"
>
MAISONS
</SectionTitle>
<div className="space-y-4 sm:space-y-6">
{session?.user && (
<>
{invitations.length > 0 && (
<Card className="p-4 sm:p-6">
<SectionTitle>Mes Invitations</SectionTitle>
<InvitationList invitations={invitations} onUpdate={handleUpdate} />
</Card>
)}
<Card className="p-4 sm:p-6">
<SectionTitle>Ma Maison</SectionTitle>
{myHouse ? (
<HouseManagement
house={myHouse}
users={initialUsers}
onUpdate={handleUpdate}
/>
) : (
<div>
{showCreateForm ? (
<HouseForm
onSuccess={() => {
setShowCreateForm(false);
handleUpdate();
}}
onCancel={() => setShowCreateForm(false)}
/>
) : (
<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>
<Button
onClick={() => setShowCreateForm(true)}
variant="primary"
className="w-full sm:w-auto"
>
Créer une maison
</Button>
</div>
)}
</div>
)}
</Card>
</>
)}
<Card className="p-4 sm:p-6">
<SectionTitle>Toutes les Maisons</SectionTitle>
<div className="mb-4">
<Input
placeholder="Rechercher une maison..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
{filteredHouses.length === 0 ? (
<p className="text-sm" style={{ color: "var(--muted-foreground)" }}>
Aucune maison trouvée
</p>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{filteredHouses.map((house) => (
<HouseCard
key={house.id}
house={house}
onRequestSent={handleUpdate}
/>
))}
</div>
)}
</Card>
</div>
</BackgroundSection>
);
}