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

320 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
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();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
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();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
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="fixed 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="xl"
subtitle="Défiez vos collègues et gagnez des points"
className="mb-16"
>
DÉFIS ENTRE JOUEURS
</SectionTitle>
<p className="text-gray-400 text-sm max-w-2xl mx-auto text-center mb-16">
Créez des défis personnalisés, acceptez ceux de vos collègues et
remportez des récompenses en points pour monter dans le classement
</p>
{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&apos;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&apos;événements ce mois ?
</h4>
<p className="text-sm text-gray-300">
Le joueur qui participe au plus grand nombre
d&apos;é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&apos;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&apos;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&apos;admin désignera le
gagnant selon l&apos;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>
);
}