Files
got-gaming/components/challenges/ChallengesSection.tsx

459 lines
15 KiB
TypeScript

"use client";
import { useEffect, useState, useTransition } from "react";
import { useSession } from "next-auth/react";
import {
createChallenge,
acceptChallenge,
cancelChallenge,
} from "@/actions/challenges/create";
import {
Button,
Card,
SectionTitle,
Input,
Textarea,
Alert,
} from "@/components/ui";
import { Avatar } from "@/components/ui";
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 {
backgroundImage: string;
}
export default function ChallengesSection({
backgroundImage,
}: ChallengesSectionProps) {
const { data: session } = useSession();
const [challenges, setChallenges] = useState<Challenge[]>([]);
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [showCreateForm, setShowCreateForm] = useState(false);
const [isPending, startTransition] = useTransition();
// Form state
const [challengedId, setChallengedId] = useState("");
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [pointsReward, setPointsReward] = useState(100);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
fetchChallenges();
fetchUsers();
}, []);
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);
} finally {
setLoading(false);
}
};
const fetchUsers = async () => {
try {
const response = await fetch("/api/users");
if (response.ok) {
const data = await response.json();
setUsers(data);
}
} catch (error) {
console.error("Error fetching users:", error);
}
};
const handleCreateChallenge = () => {
if (!challengedId || !title || !description) {
setErrorMessage("Veuillez remplir tous les champs");
setTimeout(() => setErrorMessage(null), 5000);
return;
}
startTransition(async () => {
const result = await createChallenge({
challengedId,
title,
description,
pointsReward,
});
if (result.success) {
setSuccessMessage("Défi créé avec succès !");
setShowCreateForm(false);
setChallengedId("");
setTitle("");
setDescription("");
setPointsReward(100);
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) => {
if (!confirm("Êtes-vous sûr de vouloir annuler ce défi ?")) {
return;
}
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);
}
});
};
const getStatusLabel = (status: string) => {
switch (status) {
case "PENDING":
return "En attente d'acceptation";
case "ACCEPTED":
return "En cours - En attente de désignation du gagnant";
case "COMPLETED":
return "Complété";
case "REJECTED":
return "Rejeté";
case "CANCELLED":
return "Annulé";
default:
return status;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case "PENDING":
return "text-yellow-400";
case "ACCEPTED":
return "text-blue-400";
case "COMPLETED":
return "text-green-400";
case "REJECTED":
return "text-red-400";
case "CANCELLED":
return "text-gray-400";
default:
return "text-gray-300";
}
};
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 && (
<Card variant="dark" className="p-6 mb-8">
<h2 className="text-xl font-bold text-pixel-gold mb-4">
Créer un nouveau défi
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Défier qui ?
</label>
<select
value={challengedId}
onChange={(e) => setChallengedId(e.target.value)}
className="w-full p-2 bg-black/60 border border-pixel-gold/30 rounded text-gray-300"
>
<option value="">Sélectionner un joueur</option>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.username} (Lv.{user.level} - {user.score} pts)
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Titre du défi
</label>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Ex: Qui participera à plus d'événements ce mois ?"
/>
</div>
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Description
</label>
<Textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Décrivez les règles du défi..."
rows={4}
/>
</div>
<div>
<label className="block text-sm font-bold text-pixel-gold mb-2">
Points à gagner (défaut: 100)
</label>
<Input
type="number"
value={pointsReward}
onChange={(e) =>
setPointsReward(parseInt(e.target.value) || 100)
}
min={1}
max={1000}
/>
</div>
<Button
onClick={handleCreateChallenge}
variant="primary"
disabled={isPending || !challengedId || !title || !description}
className="w-full"
>
{isPending ? "Création..." : "Créer le défi"}
</Button>
</div>
</Card>
)}
{/* Challenges List */}
{loading ? (
<div className="text-center text-pixel-gold py-8">Chargement...</div>
) : challenges.length === 0 ? (
<Card variant="dark" className="p-6 text-center">
<p className="text-gray-400">
Vous n&apos;avez aucun défi pour le moment.
</p>
</Card>
) : (
<div className="space-y-4">
{challenges.map((challenge) => {
const currentUserId = session?.user?.id;
const isChallenger = challenge.challenger.id === currentUserId;
const isChallenged = challenge.challenged.id === currentUserId;
const canAccept = challenge.status === "PENDING" && isChallenged;
const canCancel =
(challenge.status === "PENDING" ||
challenge.status === "ACCEPTED") &&
(isChallenger || isChallenged);
return (
<Card key={challenge.id} variant="dark" className="p-6">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="text-lg font-bold text-pixel-gold">
{challenge.title}
</h3>
<span
className={`text-xs px-2 py-1 rounded ${getStatusColor(
challenge.status
)} bg-black/40`}
>
{getStatusLabel(challenge.status)}
</span>
</div>
<p className="text-gray-300 mb-4">
{challenge.description}
</p>
<div className="flex items-center gap-4 mb-2">
<div className="flex items-center gap-2">
<Avatar
src={challenge.challenger.avatar}
username={challenge.challenger.username}
size="sm"
/>
<span className="text-sm text-gray-300">
{challenge.challenger.username}
</span>
</div>
<span className="text-gray-500">VS</span>
<div className="flex items-center gap-2">
<Avatar
src={challenge.challenged.avatar}
username={challenge.challenged.username}
size="sm"
/>
<span className="text-sm text-gray-300">
{challenge.challenged.username}
</span>
</div>
</div>
<div className="text-sm text-gray-400">
Récompense:{" "}
<span className="text-pixel-gold font-bold">
{challenge.pointsReward} points
</span>
</div>
{challenge.winner && (
<div className="text-sm text-green-400 mt-2">
🏆 Gagnant: {challenge.winner.username}
</div>
)}
{challenge.adminComment && (
<div className="text-xs text-gray-500 mt-2 italic">
Admin: {challenge.adminComment}
</div>
)}
<div className="text-xs text-gray-500 mt-2">
Créé le:{" "}
{new Date(challenge.createdAt).toLocaleDateString(
"fr-FR"
)}
{challenge.acceptedAt &&
` • Accepté le: ${new Date(challenge.acceptedAt).toLocaleDateString("fr-FR")}`}
{challenge.completedAt &&
` • Complété le: ${new Date(challenge.completedAt).toLocaleDateString("fr-FR")}`}
</div>
</div>
<div className="flex flex-col gap-2">
{canAccept && (
<Button
onClick={() => handleAcceptChallenge(challenge.id)}
variant="primary"
size="sm"
disabled={isPending}
>
Accepter
</Button>
)}
{canCancel && (
<Button
onClick={() => handleCancelChallenge(challenge.id)}
variant="secondary"
size="sm"
disabled={isPending}
>
Annuler
</Button>
)}
</div>
</div>
</Card>
);
})}
</div>
)}
</div>
</section>
);
}