Enhance UI components and animations: Introduce a shimmer animation effect in globals.css, refactor FeedbackPageClient, LoginPage, RegisterPage, and AdminPanel components to utilize new UI components for improved consistency and maintainability. Update event and feedback handling in EventsPageSection and FeedbackModal, ensuring a cohesive user experience across the application.

This commit is contained in:
Julien Froidefond
2025-12-12 16:44:57 +01:00
parent db01c25de7
commit 99a475736b
32 changed files with 2242 additions and 1389 deletions

View File

@@ -1,7 +1,7 @@
"use client";
import { useState, useRef, useTransition, type ChangeEvent } from "react";
import Avatar from "./Avatar";
import { Avatar, Input, Textarea, Button, Alert, Card, BackgroundSection, SectionTitle, ProgressBar } from "@/components/ui";
import { updateProfile } from "@/actions/profile/update-profile";
import { updatePassword } from "@/actions/profile/update-password";
@@ -170,53 +170,19 @@ export default function ProfileForm({
: "from-red-700 to-red-900";
return (
<section className="relative w-full min-h-screen flex flex-col items-center justify-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 from-black/70 via-black/60 to-black/80"></div>
</div>
{/* Content */}
<div className="relative z-10 w-full max-w-4xl mx-auto px-8 py-16">
<BackgroundSection backgroundImage={backgroundImage}>
<div className="w-full max-w-4xl mx-auto px-8">
{/* Title Section */}
<div className="text-center mb-12">
<h1 className="text-5xl md:text-7xl font-gaming font-black mb-4 tracking-tight">
<span
className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent"
style={{
textShadow: "0 0 30px rgba(218, 165, 32, 0.5)",
}}
>
PROFIL
</span>
</h1>
<div className="text-pixel-gold text-lg md:text-xl font-gaming-subtitle font-semibold flex items-center justify-center gap-2 tracking-wide">
<span></span>
<span>Gérez votre profil</span>
<span></span>
</div>
</div>
<SectionTitle variant="gradient" size="lg" subtitle="Gérez votre profil" className="mb-12">
PROFIL
</SectionTitle>
{/* Profile Card */}
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg overflow-hidden backdrop-blur-sm">
<Card variant="default" className="overflow-hidden">
<form onSubmit={handleSubmit} className="p-8 space-y-8">
{/* Messages */}
{error && (
<div className="bg-red-900/50 border border-red-500/50 text-red-400 px-4 py-3 rounded text-sm">
{error}
</div>
)}
{success && (
<div className="bg-green-900/50 border border-green-500/50 text-green-400 px-4 py-3 rounded text-sm">
{success}
</div>
)}
{error && <Alert variant="error">{error}</Alert>}
{success && <Alert variant="success">{success}</Alert>}
{/* Avatar Section */}
<div className="flex flex-col items-center gap-4">
@@ -281,51 +247,38 @@ export default function ProfileForm({
className="hidden"
id="avatar-upload"
/>
<label
htmlFor="avatar-upload"
className="px-4 py-2 border border-pixel-gold/50 bg-black/40 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition cursor-pointer inline-block"
>
{uploadingAvatar
? "Upload en cours..."
: "Upload un avatar custom"}
<label htmlFor="avatar-upload">
<Button variant="primary" size="md" as="span" className="cursor-pointer">
{uploadingAvatar ? "Upload en cours..." : "Upload un avatar custom"}
</Button>
</label>
</div>
</div>
{/* Username Field */}
<div>
<label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2">
Nom d&apos;utilisateur
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition"
required
minLength={3}
maxLength={20}
/>
<p className="text-gray-500 text-xs mt-1">3-20 caractères</p>
</div>
<Input
type="text"
label="Nom d'utilisateur"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
minLength={3}
maxLength={20}
className="bg-black/40"
/>
<p className="text-gray-500 text-xs mt-1">3-20 caractères</p>
{/* Bio Field */}
<div>
<label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2">
Bio
</label>
<textarea
value={bio || ""}
onChange={(e) => setBio(e.target.value)}
className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition resize-none"
rows={4}
maxLength={500}
placeholder="Parlez-nous de vous..."
/>
<p className="text-gray-500 text-xs mt-1">
{(bio || "").length}/500 caractères
</p>
</div>
<Textarea
label="Bio"
value={bio || ""}
onChange={(e) => setBio(e.target.value)}
rows={4}
maxLength={500}
showCharCount
placeholder="Parlez-nous de vous..."
className="bg-black/40"
/>
{/* Character Class Selection */}
<div>
@@ -458,36 +411,23 @@ export default function ProfileForm({
</div>
{/* HP Bar */}
<div className="mb-4">
<div className="flex justify-between text-xs text-gray-400 mb-1">
<span>HP</span>
<span>
{profile.hp} / {profile.maxHp}
</span>
</div>
<div className="relative h-3 bg-gray-900 border border-gray-700 rounded overflow-hidden">
<div
className={`absolute inset-0 bg-gradient-to-r ${hpColor} transition-all duration-1000 ease-out`}
style={{ width: `${hpPercentage}%` }}
/>
</div>
</div>
<ProgressBar
value={profile.hp}
max={profile.maxHp}
variant="hp"
showLabel
label="HP"
className="mb-4"
/>
{/* XP Bar */}
<div>
<div className="flex justify-between text-xs text-gray-400 mb-1">
<span>XP</span>
<span>
{formatNumber(profile.xp)} / {formatNumber(profile.maxXp)}
</span>
</div>
<div className="relative h-3 bg-gray-900 border border-pixel-gold/30 rounded overflow-hidden">
<div
className="absolute inset-0 bg-gradient-to-r from-pixel-gold/80 via-pixel-gold/70 to-pixel-gold/80 transition-all duration-1000 ease-out"
style={{ width: `${xpPercentage}%` }}
/>
</div>
</div>
<ProgressBar
value={profile.xp}
max={profile.maxXp}
variant="xp"
showLabel
label="XP"
/>
</div>
{/* Email (read-only) */}
@@ -505,13 +445,9 @@ export default function ProfileForm({
{/* Submit Button */}
<div className="flex justify-end gap-4 pt-4 border-t border-pixel-gold/20">
<button
type="submit"
disabled={isPending}
className="px-6 py-2 border border-pixel-gold/50 bg-black/40 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
<Button type="submit" variant="primary" size="md" disabled={isPending}>
{isPending ? "Enregistrement..." : "Enregistrer les modifications"}
</button>
</Button>
</div>
</form>
@@ -522,65 +458,56 @@ export default function ProfileForm({
Mot de passe
</h3>
{!showPasswordForm && (
<button
<Button
type="button"
variant="primary"
size="md"
onClick={() => setShowPasswordForm(true)}
className="px-4 py-2 border border-pixel-gold/50 bg-black/40 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition"
>
Changer le mot de passe
</button>
</Button>
)}
</div>
{showPasswordForm && (
<form onSubmit={handlePasswordChange} className="space-y-4">
<div>
<label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2">
Mot de passe actuel
</label>
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition"
required
/>
</div>
<Input
type="password"
label="Mot de passe actuel"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
className="bg-black/40"
/>
<div>
<label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2">
Nouveau mot de passe
</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition"
required
minLength={6}
/>
<p className="text-gray-500 text-xs mt-1">
Minimum 6 caractères
</p>
</div>
<Input
type="password"
label="Nouveau mot de passe"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
minLength={6}
className="bg-black/40"
/>
<p className="text-gray-500 text-xs mt-1">
Minimum 6 caractères
</p>
<div>
<label className="block text-pixel-gold text-sm uppercase tracking-widest mb-2">
Confirmer le nouveau mot de passe
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-3 bg-black/40 border border-pixel-gold/30 rounded text-white focus:outline-none focus:border-pixel-gold transition"
required
minLength={6}
/>
</div>
<Input
type="password"
label="Confirmer le nouveau mot de passe"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
minLength={6}
className="bg-black/40"
/>
<div className="flex justify-end gap-4">
<button
<Button
type="button"
variant="secondary"
size="md"
onClick={() => {
setShowPasswordForm(false);
setCurrentPassword("");
@@ -588,25 +515,25 @@ export default function ProfileForm({
setConfirmPassword("");
setError(null);
}}
className="px-4 py-2 border border-gray-600/50 bg-black/40 text-gray-400 uppercase text-xs tracking-widest rounded hover:bg-gray-900/40 hover:border-gray-500 transition"
>
Annuler
</button>
<button
</Button>
<Button
type="submit"
variant="primary"
size="md"
disabled={isChangingPassword}
className="px-4 py-2 border border-pixel-gold/50 bg-black/40 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{isChangingPassword
? "Modification..."
: "Modifier le mot de passe"}
</button>
</Button>
</div>
</form>
)}
</div>
</div>
</Card>
</div>
</section>
</BackgroundSection>
);
}