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
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
This commit is contained in:
236
components/houses/HousesSection.tsx
Normal file
236
components/houses/HousesSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user