Refactor API routes and component logic: Remove unused event and user management routes, streamline feedback handling in components, and enhance state management with transitions for improved user experience. Update service layer methods for better organization and maintainability across the application.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m38s

This commit is contained in:
Julien Froidefond
2025-12-12 16:28:07 +01:00
parent 494ac3f503
commit db01c25de7
26 changed files with 747 additions and 743 deletions

View File

@@ -2,6 +2,7 @@
import { useState, useEffect, useMemo } from "react";
import ImageSelector from "@/components/ImageSelector";
import { updateSitePreferences } from "@/actions/admin/preferences";
interface SitePreferences {
id: string;
@@ -90,40 +91,33 @@ export default function BackgroundPreferences({
),
};
const response = await fetch("/api/admin/preferences", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(apiData),
});
const result = await updateSitePreferences(apiData);
if (response.ok) {
const data = await response.json();
setPreferences(data);
if (result.success && result.data) {
setPreferences(result.data);
// Réinitialiser formData avec les nouvelles valeurs (ou images par défaut)
setFormData({
homeBackground: getFormValue(
data.homeBackground,
result.data.homeBackground,
DEFAULT_IMAGES.home
),
eventsBackground: getFormValue(
data.eventsBackground,
result.data.eventsBackground,
DEFAULT_IMAGES.events
),
leaderboardBackground: getFormValue(
data.leaderboardBackground,
result.data.leaderboardBackground,
DEFAULT_IMAGES.leaderboard
),
});
setIsEditing(false);
} else {
const errorData = await response.json();
console.error("Error updating preferences:", errorData);
alert(errorData.error || "Erreur lors de la mise à jour");
console.error("Error updating preferences:", result.error);
alert(result.error || "Erreur lors de la mise à jour");
}
} catch (error) {
console.error("Error updating preferences:", error);
alert("Erreur lors de la mise à jour");
}
};

View File

@@ -1,7 +1,8 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useTransition } from "react";
import { calculateEventStatus } from "@/lib/eventStatus";
import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events";
interface Event {
id: string;
@@ -124,51 +125,42 @@ export default function EventManagement() {
});
};
const [, startTransition] = useTransition();
const handleSave = async () => {
setSaving(true);
try {
let response;
if (isCreating) {
response = await fetch("/api/admin/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
} else if (editingEvent) {
response = await fetch(`/api/admin/events/${editingEvent.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
}
startTransition(async () => {
try {
let result;
if (isCreating) {
result = await createEvent(formData);
} else if (editingEvent) {
result = await updateEvent(editingEvent.id, formData);
}
if (response?.ok) {
await fetchEvents();
setEditingEvent(null);
setIsCreating(false);
setFormData({
date: "",
name: "",
description: "",
type: "ATELIER",
room: "",
time: "",
maxPlaces: undefined,
});
} else {
const error = await response?.json();
alert(error.error || "Erreur lors de la sauvegarde");
if (result?.success) {
await fetchEvents();
setEditingEvent(null);
setIsCreating(false);
setFormData({
date: "",
name: "",
description: "",
type: "ATELIER",
room: "",
time: "",
maxPlaces: undefined,
});
} else {
alert(result?.error || "Erreur lors de la sauvegarde");
}
} catch (error) {
console.error("Error saving event:", error);
alert("Erreur lors de la sauvegarde");
} finally {
setSaving(false);
}
} catch (error) {
console.error("Error saving event:", error);
alert("Erreur lors de la sauvegarde");
} finally {
setSaving(false);
}
});
};
const handleDelete = async (eventId: string) => {
@@ -176,21 +168,20 @@ export default function EventManagement() {
return;
}
try {
const response = await fetch(`/api/admin/events/${eventId}`, {
method: "DELETE",
});
startTransition(async () => {
try {
const result = await deleteEvent(eventId);
if (response.ok) {
await fetchEvents();
} else {
const error = await response.json();
alert(error.error || "Erreur lors de la suppression");
if (result.success) {
await fetchEvents();
} else {
alert(result.error || "Erreur lors de la suppression");
}
} catch (error) {
console.error("Error deleting event:", error);
alert("Erreur lors de la suppression");
}
} catch (error) {
console.error("Error deleting event:", error);
alert("Erreur lors de la suppression");
}
});
};
const handleCancel = () => {

View File

@@ -1,10 +1,14 @@
"use client";
import { useState, useEffect, useMemo, useRef } from "react";
import { useState, useEffect, useMemo, useRef, useTransition } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { calculateEventStatus } from "@/lib/eventStatus";
import FeedbackModal from "@/components/FeedbackModal";
import {
registerForEvent,
unregisterFromEvent,
} from "@/actions/events/register";
interface Event {
id: string;
@@ -526,6 +530,8 @@ export default function EventsPageSection({
</div>
);
const [, startTransition] = useTransition();
const handleRegister = async (eventId: string) => {
if (!session?.user?.id) {
router.push("/login");
@@ -535,53 +541,38 @@ export default function EventsPageSection({
setLoading((prev) => ({ ...prev, [eventId]: true }));
setError("");
try {
const response = await fetch(`/api/events/${eventId}/register`, {
method: "POST",
});
startTransition(async () => {
const result = await registerForEvent(eventId);
const data = await response.json();
if (!response.ok) {
setError(data.error || "Une erreur est survenue");
return;
if (result.success) {
setRegistrations((prev) => ({
...prev,
[eventId]: true,
}));
} else {
setError(result.error || "Une erreur est survenue");
}
setRegistrations((prev) => ({
...prev,
[eventId]: true,
}));
} catch {
setError("Une erreur est survenue");
} finally {
setLoading((prev) => ({ ...prev, [eventId]: false }));
}
});
};
const handleUnregister = async (eventId: string) => {
setLoading((prev) => ({ ...prev, [eventId]: true }));
setError("");
try {
const response = await fetch(`/api/events/${eventId}/register`, {
method: "DELETE",
});
startTransition(async () => {
const result = await unregisterFromEvent(eventId);
if (!response.ok) {
const data = await response.json();
setError(data.error || "Une erreur est survenue");
return;
if (result.success) {
setRegistrations((prev) => ({
...prev,
[eventId]: false,
}));
} else {
setError(result.error || "Une erreur est survenue");
}
setRegistrations((prev) => ({
...prev,
[eventId]: false,
}));
} catch {
setError("Une erreur est survenue");
} finally {
setLoading((prev) => ({ ...prev, [eventId]: false }));
}
});
};
return (

View File

@@ -1,7 +1,8 @@
"use client";
import { useState, useEffect, type FormEvent } from "react";
import { useState, useEffect, useTransition, type FormEvent } from "react";
import { useSession } from "next-auth/react";
import { createFeedback } from "@/actions/events/feedback";
interface Event {
id: string;
@@ -36,6 +37,7 @@ export default function FeedbackModal({
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const [, startTransition] = useTransition();
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
@@ -118,37 +120,38 @@ export default function FeedbackModal({
setSubmitting(true);
try {
const response = await fetch(`/api/feedback/${eventId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
startTransition(async () => {
try {
const result = await createFeedback(eventId, {
rating,
comment: comment.trim() || null,
}),
});
});
const data = await response.json();
if (!result.success) {
setError(result.error || "Erreur lors de l'enregistrement");
setSubmitting(false);
return;
}
if (!response.ok) {
setError(data.error || "Erreur lors de l'enregistrement");
return;
setSuccess(true);
if (result.data) {
setExistingFeedback({
id: result.data.id,
rating: result.data.rating,
comment: result.data.comment,
});
}
// Fermer la modale après 1.5 secondes
setTimeout(() => {
onClose();
}, 1500);
} catch {
setError("Erreur lors de l'enregistrement");
} finally {
setSubmitting(false);
}
setSuccess(true);
setExistingFeedback(data.feedback);
// Fermer la modale après 1.5 secondes
setTimeout(() => {
onClose();
}, 1500);
} catch {
setError("Erreur lors de l'enregistrement");
} finally {
setSubmitting(false);
}
});
};
const handleClose = () => {

View File

@@ -1,7 +1,9 @@
"use client";
import { useState, useRef, type ChangeEvent } from "react";
import { useState, useRef, useTransition, type ChangeEvent } from "react";
import Avatar from "./Avatar";
import { updateProfile } from "@/actions/profile/update-profile";
import { updatePassword } from "@/actions/profile/update-password";
type CharacterClass =
| "WARRIOR"
@@ -46,7 +48,7 @@ export default function ProfileForm({
backgroundImage,
}: ProfileFormProps) {
const [profile, setProfile] = useState<UserProfile>(initialProfile);
const [saving, setSaving] = useState(false);
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
@@ -64,7 +66,7 @@ export default function ProfileForm({
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [changingPassword, setChangingPassword] = useState(false);
const [isChangingPassword, startPasswordTransition] = useTransition();
const handleAvatarUpload = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
@@ -104,81 +106,57 @@ export default function ProfileForm({
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
setError(null);
setSuccess(null);
try {
const response = await fetch("/api/profile", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
avatar,
bio,
characterClass,
}),
startTransition(async () => {
const result = await updateProfile({
username,
avatar,
bio,
characterClass,
});
if (response.ok) {
const data = await response.json();
setProfile(data);
setBio(data.bio || null);
setCharacterClass(data.characterClass || null);
if (result.success && result.data) {
setProfile({
...result.data,
createdAt: result.data.createdAt instanceof Date
? result.data.createdAt.toISOString()
: result.data.createdAt,
} as UserProfile);
setBio(result.data.bio || null);
setCharacterClass(result.data.characterClass as CharacterClass || null);
setSuccess("Profil mis à jour avec succès");
setTimeout(() => setSuccess(null), 3000);
} else {
const errorData = await response.json();
setError(errorData.error || "Erreur lors de la mise à jour");
setError(result.error || "Erreur lors de la mise à jour");
}
} catch (err) {
console.error("Error updating profile:", err);
setError("Erreur lors de la mise à jour du profil");
} finally {
setSaving(false);
}
});
};
const handlePasswordChange = async (e: React.FormEvent) => {
e.preventDefault();
setChangingPassword(true);
setError(null);
setSuccess(null);
try {
const response = await fetch("/api/profile/password", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
currentPassword,
newPassword,
confirmPassword,
}),
startPasswordTransition(async () => {
const result = await updatePassword({
currentPassword,
newPassword,
confirmPassword,
});
if (response.ok) {
setSuccess("Mot de passe modifié avec succès");
if (result.success) {
setSuccess(result.message || "Mot de passe modifié avec succès");
setCurrentPassword("");
setNewPassword("");
setConfirmPassword("");
setShowPasswordForm(false);
setTimeout(() => setSuccess(null), 3000);
} else {
const errorData = await response.json();
setError(
errorData.error || "Erreur lors de la modification du mot de passe"
);
setError(result.error || "Erreur lors de la modification du mot de passe");
}
} catch (err) {
console.error("Error changing password:", err);
setError("Erreur lors de la modification du mot de passe");
} finally {
setChangingPassword(false);
}
});
};
const hpPercentage = (profile.hp / profile.maxHp) * 100;
@@ -529,10 +507,10 @@ export default function ProfileForm({
<div className="flex justify-end gap-4 pt-4 border-t border-pixel-gold/20">
<button
type="submit"
disabled={saving}
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"
>
{saving ? "Enregistrement..." : "Enregistrer les modifications"}
{isPending ? "Enregistrement..." : "Enregistrer les modifications"}
</button>
</div>
</form>
@@ -616,10 +594,10 @@ export default function ProfileForm({
</button>
<button
type="submit"
disabled={changingPassword}
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"
>
{changingPassword
{isChangingPassword
? "Modification..."
: "Modifier le mot de passe"}
</button>

View File

@@ -1,7 +1,8 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useTransition } from "react";
import Avatar from "./Avatar";
import { updateUser, deleteUser } from "@/actions/admin/users";
interface User {
id: string;
@@ -35,6 +36,7 @@ export default function UserManagement() {
const [editingUser, setEditingUser] = useState<EditingUser | null>(null);
const [saving, setSaving] = useState(false);
const [deletingUserId, setDeletingUserId] = useState<string | null>(null);
const [, startTransition] = useTransition();
const [uploadingAvatar, setUploadingAvatar] = useState<string | null>(null);
useEffect(() => {
@@ -72,60 +74,55 @@ export default function UserManagement() {
if (!editingUser) return;
setSaving(true);
try {
const body: {
username?: string;
avatar?: string | null;
hpDelta?: number;
xpDelta?: number;
score?: number;
level?: number;
role?: string;
} = {};
startTransition(async () => {
try {
const body: {
username?: string;
avatar?: string | null;
hpDelta?: number;
xpDelta?: number;
score?: number;
level?: number;
role?: string;
} = {};
if (editingUser.username !== null) {
body.username = editingUser.username;
}
if (editingUser.avatar !== undefined) {
body.avatar = editingUser.avatar;
}
if (editingUser.hpDelta !== 0) {
body.hpDelta = editingUser.hpDelta;
}
if (editingUser.xpDelta !== 0) {
body.xpDelta = editingUser.xpDelta;
}
if (editingUser.score !== null) {
body.score = editingUser.score;
}
if (editingUser.level !== null) {
body.level = editingUser.level;
}
if (editingUser.role !== null) {
body.role = editingUser.role;
}
if (editingUser.username !== null) {
body.username = editingUser.username;
}
if (editingUser.avatar !== undefined) {
body.avatar = editingUser.avatar;
}
if (editingUser.hpDelta !== 0) {
body.hpDelta = editingUser.hpDelta;
}
if (editingUser.xpDelta !== 0) {
body.xpDelta = editingUser.xpDelta;
}
if (editingUser.score !== null) {
body.score = editingUser.score;
}
if (editingUser.level !== null) {
body.level = editingUser.level;
}
if (editingUser.role !== null) {
body.role = editingUser.role;
}
const response = await fetch(`/api/admin/users/${editingUser.userId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const result = await updateUser(editingUser.userId, body);
if (response.ok) {
await fetchUsers();
setEditingUser(null);
} else {
const error = await response.json();
alert(error.error || "Erreur lors de la mise à jour");
if (result.success) {
await fetchUsers();
setEditingUser(null);
} else {
alert(result.error || "Erreur lors de la mise à jour");
}
} catch (error) {
console.error("Error updating user:", error);
alert("Erreur lors de la mise à jour");
} finally {
setSaving(false);
}
} catch (error) {
console.error("Error updating user:", error);
alert("Erreur lors de la mise à jour");
} finally {
setSaving(false);
}
});
};
const handleCancel = () => {
@@ -143,15 +140,12 @@ export default function UserManagement() {
setDeletingUserId(userId);
try {
const response = await fetch(`/api/admin/users/${userId}`, {
method: "DELETE",
});
const result = await deleteUser(userId);
if (response.ok) {
if (result.success) {
await fetchUsers();
} else {
const error = await response.json();
alert(error.error || "Erreur lors de la suppression");
alert(result.error || "Erreur lors de la suppression");
}
} catch (error) {
console.error("Error deleting user:", error);