Add admin challenge management features: Implement functions for canceling and reactivating challenges, enhance error handling, and update the ChallengeManagement component to support these actions. Update API to retrieve all challenge statuses for admin and improve UI to display active challenges count.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m16s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m16s
This commit is contained in:
72
components/navigation/ChallengeBadge.tsx
Normal file
72
components/navigation/ChallengeBadge.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface ChallengeBadgeProps {
|
||||
initialCount?: number;
|
||||
onNavigate?: () => void;
|
||||
}
|
||||
|
||||
export default function ChallengeBadge({
|
||||
initialCount = 0,
|
||||
onNavigate,
|
||||
}: ChallengeBadgeProps) {
|
||||
const [count, setCount] = useState(initialCount);
|
||||
|
||||
useEffect(() => {
|
||||
// Récupérer le nombre de défis actifs
|
||||
const fetchActiveCount = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/challenges/active-count");
|
||||
const data = await response.json();
|
||||
setCount(data.count || 0);
|
||||
} catch (error) {
|
||||
console.error("Error fetching active challenges count:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchActiveCount();
|
||||
|
||||
// Rafraîchir toutes les 30 secondes
|
||||
const interval = setInterval(fetchActiveCount, 30000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href="/challenges"
|
||||
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} défi${count > 1 ? "s" : ""} actif${count > 1 ? "s" : ""}`
|
||||
: "Défis"
|
||||
}
|
||||
>
|
||||
<span>DÉFIS</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import PlayerStats from "@/components/profile/PlayerStats";
|
||||
import { Button, ThemeToggle } from "@/components/ui";
|
||||
import ChallengeBadge from "./ChallengeBadge";
|
||||
|
||||
interface UserData {
|
||||
username: string;
|
||||
@@ -20,11 +21,13 @@ interface UserData {
|
||||
interface NavigationProps {
|
||||
initialUserData?: UserData | null;
|
||||
initialIsAdmin?: boolean;
|
||||
initialActiveChallengesCount?: number;
|
||||
}
|
||||
|
||||
export default function Navigation({
|
||||
initialUserData,
|
||||
initialIsAdmin,
|
||||
initialActiveChallengesCount = 0,
|
||||
}: NavigationProps) {
|
||||
const { data: session } = useSession();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
@@ -114,19 +117,7 @@ export default function Navigation({
|
||||
LEADERBOARD
|
||||
</Link>
|
||||
{isAuthenticated && (
|
||||
<Link
|
||||
href="/challenges"
|
||||
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)")
|
||||
}
|
||||
>
|
||||
DÉFIS
|
||||
</Link>
|
||||
<ChallengeBadge initialCount={initialActiveChallengesCount} />
|
||||
)}
|
||||
{isAdmin && (
|
||||
<Link
|
||||
@@ -287,20 +278,10 @@ export default function Navigation({
|
||||
LEADERBOARD
|
||||
</Link>
|
||||
{isAuthenticated && (
|
||||
<Link
|
||||
href="/challenges"
|
||||
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)")
|
||||
}
|
||||
>
|
||||
DÉFIS
|
||||
</Link>
|
||||
<ChallengeBadge
|
||||
initialCount={initialActiveChallengesCount}
|
||||
onNavigate={() => setIsMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<Link
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { auth } from "@/lib/auth";
|
||||
import { userService } from "@/services/users/user.service";
|
||||
import { challengeService } from "@/services/challenges/challenge.service";
|
||||
import Navigation from "./Navigation";
|
||||
|
||||
interface UserData {
|
||||
@@ -17,6 +18,7 @@ export default async function NavigationWrapper() {
|
||||
|
||||
let userData: UserData | null = null;
|
||||
const isAdmin = session?.user?.role === "ADMIN";
|
||||
let activeChallengesCount = 0;
|
||||
|
||||
if (session?.user?.id) {
|
||||
const user = await userService.getUserById(session.user.id, {
|
||||
@@ -32,7 +34,17 @@ export default async function NavigationWrapper() {
|
||||
if (user) {
|
||||
userData = user;
|
||||
}
|
||||
|
||||
// Récupérer le nombre de défis actifs
|
||||
activeChallengesCount =
|
||||
await challengeService.getActiveChallengesCount(session.user.id);
|
||||
}
|
||||
|
||||
return <Navigation initialUserData={userData} initialIsAdmin={isAdmin} />;
|
||||
return (
|
||||
<Navigation
|
||||
initialUserData={userData}
|
||||
initialIsAdmin={isAdmin}
|
||||
initialActiveChallengesCount={activeChallengesCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user