All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m49s
305 lines
10 KiB
TypeScript
305 lines
10 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useTransition } from "react";
|
||
import { useSession } from "next-auth/react";
|
||
import {
|
||
createChallenge,
|
||
acceptChallenge,
|
||
cancelChallenge,
|
||
} from "@/actions/challenges/create";
|
||
import { Button, Card, SectionTitle, Alert } from "@/components/ui";
|
||
import ChallengeCard from "./ChallengeCard";
|
||
import ChallengeForm from "./ChallengeForm";
|
||
|
||
interface User {
|
||
id: string;
|
||
username: string;
|
||
avatar: string | null;
|
||
score: number;
|
||
level: number;
|
||
}
|
||
|
||
interface Challenge {
|
||
id: string;
|
||
challenger: {
|
||
id: string;
|
||
username: string;
|
||
avatar: string | null;
|
||
};
|
||
challenged: {
|
||
id: string;
|
||
username: string;
|
||
avatar: string | null;
|
||
};
|
||
title: string;
|
||
description: string;
|
||
pointsReward: number;
|
||
status: string;
|
||
adminComment: string | null;
|
||
winner?: {
|
||
id: string;
|
||
username: string;
|
||
} | null;
|
||
createdAt: string;
|
||
acceptedAt: string | null;
|
||
completedAt: string | null;
|
||
}
|
||
|
||
interface ChallengesSectionProps {
|
||
initialChallenges: Challenge[];
|
||
initialUsers: User[];
|
||
backgroundImage: string;
|
||
}
|
||
|
||
export default function ChallengesSection({
|
||
initialChallenges,
|
||
initialUsers,
|
||
backgroundImage,
|
||
}: ChallengesSectionProps) {
|
||
const { data: session } = useSession();
|
||
const [challenges, setChallenges] = useState<Challenge[]>(initialChallenges);
|
||
const [users] = useState<User[]>(initialUsers);
|
||
const [showCreateForm, setShowCreateForm] = useState(false);
|
||
const [isPending, startTransition] = useTransition();
|
||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||
const [showExamples, setShowExamples] = useState(false);
|
||
|
||
const fetchChallenges = async () => {
|
||
try {
|
||
const response = await fetch("/api/challenges");
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setChallenges(data);
|
||
}
|
||
} catch (error) {
|
||
console.error("Error fetching challenges:", error);
|
||
}
|
||
};
|
||
|
||
const handleCreateChallenge = (data: {
|
||
challengedId: string;
|
||
title: string;
|
||
description: string;
|
||
pointsReward: number;
|
||
}) => {
|
||
startTransition(async () => {
|
||
const result = await createChallenge(data);
|
||
|
||
if (result.success) {
|
||
setSuccessMessage("Défi créé avec succès !");
|
||
setShowCreateForm(false);
|
||
fetchChallenges();
|
||
setTimeout(() => setSuccessMessage(null), 5000);
|
||
} else {
|
||
setErrorMessage(result.error || "Erreur lors de la création du défi");
|
||
setTimeout(() => setErrorMessage(null), 5000);
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleAcceptChallenge = (challengeId: string) => {
|
||
startTransition(async () => {
|
||
const result = await acceptChallenge(challengeId);
|
||
|
||
if (result.success) {
|
||
setSuccessMessage(
|
||
"Défi accepté ! En attente de désignation du gagnant."
|
||
);
|
||
fetchChallenges();
|
||
setTimeout(() => setSuccessMessage(null), 5000);
|
||
} else {
|
||
setErrorMessage(result.error || "Erreur lors de l'acceptation");
|
||
setTimeout(() => setErrorMessage(null), 5000);
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleCancelChallenge = (challengeId: string) => {
|
||
startTransition(async () => {
|
||
const result = await cancelChallenge(challengeId);
|
||
|
||
if (result.success) {
|
||
setSuccessMessage("Défi annulé");
|
||
fetchChallenges();
|
||
setTimeout(() => setSuccessMessage(null), 5000);
|
||
} else {
|
||
setErrorMessage(result.error || "Erreur lors de l'annulation");
|
||
setTimeout(() => setErrorMessage(null), 5000);
|
||
}
|
||
});
|
||
};
|
||
|
||
return (
|
||
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
|
||
{/* Background Image */}
|
||
<div
|
||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||
style={{
|
||
backgroundImage: `url('${backgroundImage}')`,
|
||
}}
|
||
>
|
||
{/* Dark overlay for readability */}
|
||
<div
|
||
className="absolute inset-0 bg-gradient-to-b"
|
||
style={{
|
||
background: `linear-gradient(to bottom,
|
||
color-mix(in srgb, var(--background) 70%, transparent),
|
||
color-mix(in srgb, var(--background) 60%, transparent),
|
||
color-mix(in srgb, var(--background) 80%, transparent)
|
||
)`,
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
<div className="relative z-10 w-full max-w-6xl mx-auto px-4 sm:px-8 py-16">
|
||
<SectionTitle variant="gradient" size="md" className="mb-8 text-center">
|
||
DÉFIS ENTRE JOUEURS
|
||
</SectionTitle>
|
||
|
||
{successMessage && (
|
||
<Alert variant="success" className="mb-4">
|
||
{successMessage}
|
||
</Alert>
|
||
)}
|
||
{errorMessage && (
|
||
<Alert variant="error" className="mb-4">
|
||
{errorMessage}
|
||
</Alert>
|
||
)}
|
||
|
||
<div className="mb-6 flex justify-center">
|
||
<Button
|
||
onClick={() => setShowCreateForm(!showCreateForm)}
|
||
variant="primary"
|
||
size="md"
|
||
>
|
||
{showCreateForm ? "Annuler" : "Créer un défi"}
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Create Form */}
|
||
{showCreateForm && (
|
||
<ChallengeForm
|
||
users={users}
|
||
onSubmit={handleCreateChallenge}
|
||
onCancel={() => setShowCreateForm(false)}
|
||
isPending={isPending}
|
||
/>
|
||
)}
|
||
|
||
{/* Challenges List */}
|
||
{challenges.length === 0 ? (
|
||
<Card variant="dark" className="p-6 text-center">
|
||
<p className="text-gray-400">
|
||
Vous n'avez aucun défi pour le moment.
|
||
</p>
|
||
</Card>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{challenges.map((challenge) => (
|
||
<ChallengeCard
|
||
key={challenge.id}
|
||
challenge={challenge}
|
||
currentUserId={session?.user?.id}
|
||
onAccept={handleAcceptChallenge}
|
||
onCancel={handleCancelChallenge}
|
||
isPending={isPending}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Examples Section */}
|
||
<Card variant="dark" className="mt-8">
|
||
<button
|
||
onClick={() => setShowExamples(!showExamples)}
|
||
className="w-full flex items-center justify-between p-4 text-left"
|
||
>
|
||
<h3 className="text-lg font-bold text-pixel-gold">
|
||
💡 Exemples de défis
|
||
</h3>
|
||
<span className="text-pixel-gold text-xl">
|
||
{showExamples ? "−" : "+"}
|
||
</span>
|
||
</button>
|
||
{showExamples && (
|
||
<div className="px-4 pb-4 space-y-4 border-t border-[var(--border)] pt-4">
|
||
<div className="space-y-3">
|
||
<div className="p-4 bg-black/40 rounded border border-[var(--border)]">
|
||
<h4 className="font-bold text-pixel-gold mb-2">
|
||
Qui participera à plus d'événements ce mois ?
|
||
</h4>
|
||
<p className="text-sm text-gray-300">
|
||
Le joueur qui participe au plus grand nombre
|
||
d'événements organisés ce mois remporte le défi. Les
|
||
événements doivent être validés par un admin pour compter.
|
||
</p>
|
||
<div className="mt-2 text-xs text-gray-400">
|
||
Points suggérés: 150
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4 bg-black/40 rounded border border-[var(--border)]">
|
||
<h4 className="font-bold text-pixel-gold mb-2">
|
||
Premier à atteindre le niveau 10
|
||
</h4>
|
||
<p className="text-sm text-gray-300">
|
||
Le premier joueur à atteindre le niveau 10 remporte le défi.
|
||
Le niveau est calculé automatiquement selon le score total.
|
||
</p>
|
||
<div className="mt-2 text-xs text-gray-400">
|
||
Points suggérés: 200
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4 bg-black/40 rounded border border-[var(--border)]">
|
||
<h4 className="font-bold text-pixel-gold mb-2">
|
||
Meilleur feedback sur un événement
|
||
</h4>
|
||
<p className="text-sm text-gray-300">
|
||
Le joueur qui donne le feedback le plus détaillé et
|
||
constructif sur un événement remporte le défi. L'admin
|
||
désignera le gagnant selon la qualité du feedback.
|
||
</p>
|
||
<div className="mt-2 text-xs text-gray-400">
|
||
Points suggérés: 100
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4 bg-black/40 rounded border border-[var(--border)]">
|
||
<h4 className="font-bold text-pixel-gold mb-2">
|
||
Plus grand nombre de points gagnés cette semaine
|
||
</h4>
|
||
<p className="text-sm text-gray-300">
|
||
Le joueur qui accumule le plus de points cette semaine
|
||
remporte le défi. Seuls les points gagnés après
|
||
l'acceptation du défi comptent.
|
||
</p>
|
||
<div className="mt-2 text-xs text-gray-400">
|
||
Points suggérés: 250
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-4 bg-black/40 rounded border border-[var(--border)]">
|
||
<h4 className="font-bold text-pixel-gold mb-2">
|
||
Défi créatif : meilleure bio de profil
|
||
</h4>
|
||
<p className="text-sm text-gray-300">
|
||
Le joueur avec la bio de profil la plus créative et
|
||
originale remporte le défi. L'admin désignera le
|
||
gagnant selon l'originalité et la qualité de la bio.
|
||
</p>
|
||
<div className="mt-2 text-xs text-gray-400">
|
||
Points suggérés: 120
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|