Implement house points system: Add houseJoinPoints, houseLeavePoints, and houseCreatePoints to SitePreferences model and update related services. Enhance house management features to award and deduct points for house creation, membership removal, and leaving a house. Update environment configuration for PostgreSQL and adjust UI components to reflect new functionalities.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled

This commit is contained in:
Julien Froidefond
2025-12-18 08:48:31 +01:00
parent 12bc44e3ac
commit 1b82bd9ee6
23 changed files with 1026 additions and 113 deletions

View File

@@ -0,0 +1,73 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
interface InvitationBadgeProps {
initialCount?: number;
onNavigate?: () => void;
}
export default function InvitationBadge({
initialCount = 0,
onNavigate,
}: InvitationBadgeProps) {
const [count, setCount] = useState(initialCount);
// Utiliser le count initial (déjà récupéré côté serveur)
useEffect(() => {
setCount(initialCount);
}, [initialCount]);
// Écouter les événements de refresh des invitations (déclenché après acceptation/refus)
useEffect(() => {
const handleRefreshInvitations = async () => {
try {
const response = await fetch("/api/invitations/pending-count");
const data = await response.json();
setCount(data.count || 0);
} catch (error) {
console.error("Error fetching pending invitations count:", error);
}
};
window.addEventListener("refreshInvitations", handleRefreshInvitations);
return () => {
window.removeEventListener("refreshInvitations", handleRefreshInvitations);
};
}, []);
return (
<Link
href="/houses"
onClick={onNavigate}
className={`inline-flex items-center gap-1.5 transition text-xs font-normal uppercase tracking-widest ${
onNavigate ? "py-2" : ""
}`}
style={{ color: "var(--foreground)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.color = "var(--accent-color)")
}
onMouseLeave={(e) => (e.currentTarget.style.color = "var(--foreground)")}
title={
count > 0
? `${count} action${count > 1 ? "s" : ""} en attente (invitations et demandes)`
: "Maisons"
}
>
<span>MAISONS</span>
{count > 0 && (
<span
className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-full text-[10px] font-bold leading-none"
style={{
backgroundColor: "var(--accent)",
color: "var(--background)",
}}
>
{count > 9 ? "9+" : count}
</span>
)}
</Link>
);
}

View File

@@ -7,6 +7,7 @@ import { usePathname } from "next/navigation";
import PlayerStats from "@/components/profile/PlayerStats";
import { Button, ThemeToggle } from "@/components/ui";
import ChallengeBadge from "./ChallengeBadge";
import InvitationBadge from "./InvitationBadge";
interface UserData {
username: string;
@@ -23,12 +24,14 @@ interface NavigationProps {
initialUserData?: UserData | null;
initialIsAdmin?: boolean;
initialActiveChallengesCount?: number;
initialPendingInvitationsCount?: number;
}
export default function Navigation({
initialUserData,
initialIsAdmin,
initialActiveChallengesCount = 0,
initialPendingInvitationsCount = 0,
}: NavigationProps) {
const { data: session } = useSession();
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -119,19 +122,7 @@ export default function Navigation({
</Link>
{isAuthenticated && (
<>
<Link
href="/houses"
className="transition text-xs font-normal uppercase tracking-widest"
style={{ color: "var(--foreground)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.color = "var(--accent-color)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.color = "var(--foreground)")
}
>
MAISONS
</Link>
<InvitationBadge initialCount={initialPendingInvitationsCount} />
<ChallengeBadge initialCount={initialActiveChallengesCount} />
</>
)}
@@ -295,20 +286,10 @@ export default function Navigation({
</Link>
{isAuthenticated && (
<>
<Link
href="/houses"
onClick={() => setIsMenuOpen(false)}
className="transition text-xs font-normal uppercase tracking-widest py-2"
style={{ color: "var(--foreground)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.color = "var(--accent-color)")
}
onMouseLeave={(e) =>
(e.currentTarget.style.color = "var(--foreground)")
}
>
MAISONS
</Link>
<InvitationBadge
initialCount={initialPendingInvitationsCount}
onNavigate={() => setIsMenuOpen(false)}
/>
<ChallengeBadge
initialCount={initialActiveChallengesCount}
onNavigate={() => setIsMenuOpen(false)}

View File

@@ -1,6 +1,7 @@
import { auth } from "@/lib/auth";
import { userService } from "@/services/users/user.service";
import { challengeService } from "@/services/challenges/challenge.service";
import { houseService } from "@/services/houses/house.service";
import Navigation from "./Navigation";
interface UserData {
@@ -20,10 +21,11 @@ export default async function NavigationWrapper() {
let userData: UserData | null = null;
const isAdmin = session?.user?.role === "ADMIN";
let activeChallengesCount = 0;
let pendingHouseActionsCount = 0;
if (session?.user?.id) {
// Paralléliser les appels DB
const [user, count] = await Promise.all([
const [user, challengesCount, houseActionsCount] = await Promise.all([
userService.getUserById(session.user.id, {
username: true,
avatar: true,
@@ -35,13 +37,15 @@ export default async function NavigationWrapper() {
score: true,
}),
challengeService.getActiveChallengesCount(session.user.id),
houseService.getPendingHouseActionsCount(session.user.id),
]);
if (user) {
userData = user;
}
activeChallengesCount = count;
activeChallengesCount = challengesCount;
pendingHouseActionsCount = houseActionsCount;
}
return (
@@ -49,6 +53,7 @@ export default async function NavigationWrapper() {
initialUserData={userData}
initialIsAdmin={isAdmin}
initialActiveChallengesCount={activeChallengesCount}
initialPendingInvitationsCount={pendingHouseActionsCount}
/>
);
}