diff --git a/actions/admin/events.ts b/actions/admin/events.ts new file mode 100644 index 0000000..154eb78 --- /dev/null +++ b/actions/admin/events.ts @@ -0,0 +1,131 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { eventService } from '@/services/events/event.service' +import { Role, EventType } from '@/prisma/generated/prisma/client' +import { ValidationError, NotFoundError } from '@/services/errors' + +function checkAdminAccess() { + return async () => { + const session = await auth() + if (!session?.user || session.user.role !== Role.ADMIN) { + throw new Error('Accès refusé') + } + return session + } +} + +export async function createEvent(data: { + date: string + name: string + description?: string | null + type: string + room?: string | null + time?: string | null + maxPlaces?: number | null +}) { + try { + await checkAdminAccess()() + + const event = await eventService.validateAndCreateEvent({ + date: data.date, + name: data.name, + description: data.description ?? '', + type: data.type as EventType, + room: data.room ?? undefined, + time: data.time ?? undefined, + maxPlaces: data.maxPlaces ?? undefined, + }) + + revalidatePath('/admin') + revalidatePath('/events') + revalidatePath('/') + + return { success: true, data: event } + } catch (error) { + console.error('Error creating event:', error) + + if (error instanceof ValidationError) { + return { success: false, error: error.message } + } + if (error instanceof Error && error.message === 'Accès refusé') { + return { success: false, error: 'Accès refusé' } + } + + return { success: false, error: 'Erreur lors de la création de l\'événement' } + } +} + +export async function updateEvent(eventId: string, data: { + date?: string + name?: string + description?: string | null + type?: string + room?: string | null + time?: string | null + maxPlaces?: number | null +}) { + try { + await checkAdminAccess()() + + const event = await eventService.validateAndUpdateEvent(eventId, { + date: data.date, + name: data.name, + description: data.description ?? undefined, + type: data.type as EventType, + room: data.room ?? undefined, + time: data.time ?? undefined, + maxPlaces: data.maxPlaces ?? undefined, + }) + + revalidatePath('/admin') + revalidatePath('/events') + revalidatePath('/') + + return { success: true, data: event } + } catch (error) { + console.error('Error updating event:', error) + + if (error instanceof ValidationError) { + return { success: false, error: error.message } + } + if (error instanceof NotFoundError) { + return { success: false, error: error.message } + } + if (error instanceof Error && error.message === 'Accès refusé') { + return { success: false, error: 'Accès refusé' } + } + + return { success: false, error: 'Erreur lors de la mise à jour de l\'événement' } + } +} + +export async function deleteEvent(eventId: string) { + try { + await checkAdminAccess()() + + const existingEvent = await eventService.getEventById(eventId) + + if (!existingEvent) { + return { success: false, error: 'Événement non trouvé' } + } + + await eventService.deleteEvent(eventId) + + revalidatePath('/admin') + revalidatePath('/events') + revalidatePath('/') + + return { success: true } + } catch (error) { + console.error('Error deleting event:', error) + + if (error instanceof Error && error.message === 'Accès refusé') { + return { success: false, error: 'Accès refusé' } + } + + return { success: false, error: 'Erreur lors de la suppression de l\'événement' } + } +} + diff --git a/actions/admin/preferences.ts b/actions/admin/preferences.ts new file mode 100644 index 0000000..8a50898 --- /dev/null +++ b/actions/admin/preferences.ts @@ -0,0 +1,48 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { sitePreferencesService } from '@/services/preferences/site-preferences.service' +import { Role } from '@/prisma/generated/prisma/client' + +function checkAdminAccess() { + return async () => { + const session = await auth() + if (!session?.user || session.user.role !== Role.ADMIN) { + throw new Error('Accès refusé') + } + return session + } +} + +export async function updateSitePreferences(data: { + homeBackground?: string | null + eventsBackground?: string | null + leaderboardBackground?: string | null +}) { + try { + await checkAdminAccess()() + + const preferences = await sitePreferencesService.updateSitePreferences({ + homeBackground: data.homeBackground, + eventsBackground: data.eventsBackground, + leaderboardBackground: data.leaderboardBackground, + }) + + revalidatePath('/admin') + revalidatePath('/') + revalidatePath('/events') + revalidatePath('/leaderboard') + + return { success: true, data: preferences } + } catch (error) { + console.error('Error updating admin preferences:', error) + + if (error instanceof Error && error.message === 'Accès refusé') { + return { success: false, error: 'Accès refusé' } + } + + return { success: false, error: 'Erreur lors de la mise à jour des préférences' } + } +} + diff --git a/actions/admin/users.ts b/actions/admin/users.ts new file mode 100644 index 0000000..d475559 --- /dev/null +++ b/actions/admin/users.ts @@ -0,0 +1,116 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { userService } from '@/services/users/user.service' +import { userStatsService } from '@/services/users/user-stats.service' +import { Role } from '@/prisma/generated/prisma/client' +import { + ValidationError, + NotFoundError, + ConflictError, +} from '@/services/errors' + +function checkAdminAccess() { + return async () => { + const session = await auth() + if (!session?.user || session.user.role !== Role.ADMIN) { + throw new Error('Accès refusé') + } + return session + } +} + +export async function updateUser(userId: string, data: { + username?: string + avatar?: string | null + hpDelta?: number + xpDelta?: number + score?: number + level?: number + role?: string +}) { + try { + await checkAdminAccess()() + + // Valider username si fourni + if (data.username !== undefined) { + try { + await userService.validateAndUpdateUserProfile(userId, { username: data.username }) + } catch (error) { + if (error instanceof ValidationError || error instanceof ConflictError) { + return { success: false, error: error.message } + } + throw error + } + } + + // Mettre à jour stats et profil + const updatedUser = await userStatsService.updateUserStatsAndProfile( + userId, + { + username: data.username, + avatar: data.avatar, + hpDelta: data.hpDelta, + xpDelta: data.xpDelta, + score: data.score, + level: data.level, + role: data.role ? (data.role as Role) : undefined, + }, + { + id: true, + username: true, + email: true, + role: true, + score: true, + level: true, + hp: true, + maxHp: true, + xp: true, + maxXp: true, + avatar: true, + } + ) + + revalidatePath('/admin') + revalidatePath('/leaderboard') + + return { success: true, data: updatedUser } + } catch (error) { + console.error('Error updating user:', error) + + if (error instanceof Error && error.message === 'Accès refusé') { + return { success: false, error: 'Accès refusé' } + } + + return { success: false, error: 'Erreur lors de la mise à jour de l\'utilisateur' } + } +} + +export async function deleteUser(userId: string) { + try { + const session = await checkAdminAccess()() + + await userService.validateAndDeleteUser(userId, session.user.id) + + revalidatePath('/admin') + revalidatePath('/leaderboard') + + return { success: true } + } catch (error) { + console.error('Error deleting user:', error) + + if (error instanceof ValidationError) { + return { success: false, error: error.message } + } + if (error instanceof NotFoundError) { + return { success: false, error: error.message } + } + if (error instanceof Error && error.message === 'Accès refusé') { + return { success: false, error: 'Accès refusé' } + } + + return { success: false, error: 'Erreur lors de la suppression de l\'utilisateur' } + } +} + diff --git a/actions/events/feedback.ts b/actions/events/feedback.ts new file mode 100644 index 0000000..b142a13 --- /dev/null +++ b/actions/events/feedback.ts @@ -0,0 +1,45 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { eventFeedbackService } from '@/services/events/event-feedback.service' +import { + ValidationError, + NotFoundError, +} from '@/services/errors' + +export async function createFeedback(eventId: string, data: { + rating: number + comment?: string | null +}) { + try { + const session = await auth() + + if (!session?.user?.id) { + return { success: false, error: 'Non authentifié' } + } + + const feedback = await eventFeedbackService.validateAndCreateFeedback( + session.user.id, + eventId, + { rating: data.rating, comment: data.comment } + ) + + revalidatePath(`/feedback/${eventId}`) + revalidatePath('/events') + + return { success: true, data: feedback } + } catch (error) { + console.error('Error saving feedback:', error) + + if (error instanceof ValidationError) { + return { success: false, error: error.message } + } + if (error instanceof NotFoundError) { + return { success: false, error: error.message } + } + + return { success: false, error: 'Erreur lors de l\'enregistrement du feedback' } + } +} + diff --git a/actions/events/register.ts b/actions/events/register.ts new file mode 100644 index 0000000..ca15476 --- /dev/null +++ b/actions/events/register.ts @@ -0,0 +1,65 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { eventRegistrationService } from '@/services/events/event-registration.service' +import { + ValidationError, + NotFoundError, + ConflictError, +} from '@/services/errors' + +export async function registerForEvent(eventId: string) { + try { + const session = await auth() + + if (!session?.user?.id) { + return { success: false, error: 'Vous devez être connecté pour vous inscrire' } + } + + const registration = await eventRegistrationService.validateAndRegisterUser( + session.user.id, + eventId + ) + + revalidatePath('/events') + revalidatePath('/') + + return { success: true, message: 'Inscription réussie', data: registration } + } catch (error) { + console.error('Registration error:', error) + + if (error instanceof ValidationError || error instanceof ConflictError) { + return { success: false, error: error.message } + } + if (error instanceof NotFoundError) { + return { success: false, error: error.message } + } + + return { success: false, error: 'Une erreur est survenue lors de l\'inscription' } + } +} + +export async function unregisterFromEvent(eventId: string) { + try { + const session = await auth() + + if (!session?.user?.id) { + return { success: false, error: 'Vous devez être connecté' } + } + + await eventRegistrationService.unregisterUserFromEvent( + session.user.id, + eventId + ) + + revalidatePath('/events') + revalidatePath('/') + + return { success: true, message: 'Inscription annulée' } + } catch (error) { + console.error('Unregistration error:', error) + return { success: false, error: 'Une erreur est survenue lors de l\'annulation' } + } +} + diff --git a/actions/profile/update-password.ts b/actions/profile/update-password.ts new file mode 100644 index 0000000..2e437e2 --- /dev/null +++ b/actions/profile/update-password.ts @@ -0,0 +1,46 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { userService } from '@/services/users/user.service' +import { + ValidationError, + NotFoundError, +} from '@/services/errors' + +export async function updatePassword(data: { + currentPassword: string + newPassword: string + confirmPassword: string +}) { + try { + const session = await auth() + + if (!session?.user) { + return { success: false, error: 'Non authentifié' } + } + + await userService.validateAndUpdatePassword( + session.user.id, + data.currentPassword, + data.newPassword, + data.confirmPassword + ) + + revalidatePath('/profile') + + return { success: true, message: 'Mot de passe modifié avec succès' } + } catch (error) { + console.error('Error updating password:', error) + + if (error instanceof ValidationError) { + return { success: false, error: error.message } + } + if (error instanceof NotFoundError) { + return { success: false, error: error.message } + } + + return { success: false, error: 'Erreur lors de la modification du mot de passe' } + } +} + diff --git a/actions/profile/update-profile.ts b/actions/profile/update-profile.ts new file mode 100644 index 0000000..f5e79c1 --- /dev/null +++ b/actions/profile/update-profile.ts @@ -0,0 +1,63 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { auth } from '@/lib/auth' +import { userService } from '@/services/users/user.service' +import { CharacterClass } from '@/prisma/generated/prisma/client' +import { + ValidationError, + ConflictError, +} from '@/services/errors' + +export async function updateProfile(data: { + username?: string + avatar?: string | null + bio?: string | null + characterClass?: string | null +}) { + try { + const session = await auth() + + if (!session?.user) { + return { success: false, error: 'Non authentifié' } + } + + const updatedUser = await userService.validateAndUpdateUserProfile( + session.user.id, + { + username: data.username, + avatar: data.avatar, + bio: data.bio, + characterClass: data.characterClass ? (data.characterClass as CharacterClass) : null, + }, + { + id: true, + email: true, + username: true, + avatar: true, + bio: true, + characterClass: true, + hp: true, + maxHp: true, + xp: true, + maxXp: true, + level: true, + score: true, + } + ) + + revalidatePath('/profile') + revalidatePath('/') + + return { success: true, data: updatedUser } + } catch (error) { + console.error('Error updating profile:', error) + + if (error instanceof ValidationError || error instanceof ConflictError) { + return { success: false, error: error.message } + } + + return { success: false, error: 'Erreur lors de la mise à jour du profil' } + } +} + diff --git a/app/api/admin/events/[id]/route.ts b/app/api/admin/events/[id]/route.ts deleted file mode 100644 index 8eaa088..0000000 --- a/app/api/admin/events/[id]/route.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { NextResponse } from "next/server"; -import { auth } from "@/lib/auth"; -import { eventService } from "@/services/events/event.service"; -import { Role } from "@/prisma/generated/prisma/client"; -import { ValidationError, NotFoundError } from "@/services/errors"; - -export async function PUT( - request: Request, - { params }: { params: Promise<{ id: string }> } -) { - try { - const session = await auth(); - - if (!session?.user || session.user.role !== Role.ADMIN) { - return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); - } - - const { id } = await params; - const body = await request.json(); - const { date, name, description, type, room, time, maxPlaces } = body; - // Le statut est ignoré s'il est fourni, il sera calculé automatiquement - - const event = await eventService.validateAndUpdateEvent(id, { - date, - name, - description, - type, - room, - time, - maxPlaces, - }); - - return NextResponse.json(event); - } catch (error) { - console.error("Error updating event:", error); - - if (error instanceof ValidationError) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - if (error instanceof NotFoundError) { - return NextResponse.json({ error: error.message }, { status: 404 }); - } - - return NextResponse.json( - { error: "Erreur lors de la mise à jour de l'événement" }, - { status: 500 } - ); - } -} - -export async function DELETE( - request: Request, - { params }: { params: Promise<{ id: string }> } -) { - try { - const session = await auth(); - - if (!session?.user || session.user.role !== Role.ADMIN) { - return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); - } - - const { id } = await params; - - // Vérifier que l'événement existe - const existingEvent = await eventService.getEventById(id); - - if (!existingEvent) { - return NextResponse.json( - { error: "Événement non trouvé" }, - { status: 404 } - ); - } - - await eventService.deleteEvent(id); - - return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error deleting event:", error); - return NextResponse.json( - { error: "Erreur lors de la suppression de l'événement" }, - { status: 500 } - ); - } -} diff --git a/app/api/admin/events/route.ts b/app/api/admin/events/route.ts index 9590c44..c231cfe 100644 --- a/app/api/admin/events/route.ts +++ b/app/api/admin/events/route.ts @@ -2,7 +2,6 @@ import { NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { eventService } from "@/services/events/event.service"; import { Role } from "@/prisma/generated/prisma/client"; -import { ValidationError } from "@/services/errors"; export async function GET() { try { @@ -40,38 +39,3 @@ export async function GET() { } } -export async function POST(request: Request) { - try { - const session = await auth(); - - if (!session?.user || session.user.role !== Role.ADMIN) { - return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); - } - - const body = await request.json(); - const { date, name, description, type, room, time, maxPlaces } = body; - - const event = await eventService.validateAndCreateEvent({ - date, - name, - description, - type, - room, - time, - maxPlaces, - }); - - return NextResponse.json(event); - } catch (error) { - console.error("Error creating event:", error); - - if (error instanceof ValidationError) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - - return NextResponse.json( - { error: "Erreur lors de la création de l'événement" }, - { status: 500 } - ); - } -} diff --git a/app/api/admin/preferences/route.ts b/app/api/admin/preferences/route.ts index c7cd9e8..f1d2007 100644 --- a/app/api/admin/preferences/route.ts +++ b/app/api/admin/preferences/route.ts @@ -23,30 +23,3 @@ export async function GET() { ); } } - -export async function PUT(request: Request) { - try { - const session = await auth(); - - if (!session?.user || session.user.role !== Role.ADMIN) { - return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); - } - - const body = await request.json(); - const { homeBackground, eventsBackground, leaderboardBackground } = body; - - const preferences = await sitePreferencesService.updateSitePreferences({ - homeBackground, - eventsBackground, - leaderboardBackground, - }); - - return NextResponse.json(preferences); - } catch (error) { - console.error("Error updating admin preferences:", error); - return NextResponse.json( - { error: "Erreur lors de la mise à jour des préférences" }, - { status: 500 } - ); - } -} diff --git a/app/api/admin/users/[id]/route.ts b/app/api/admin/users/[id]/route.ts deleted file mode 100644 index 905bfa6..0000000 --- a/app/api/admin/users/[id]/route.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { NextResponse } from "next/server"; -import { auth } from "@/lib/auth"; -import { userService } from "@/services/users/user.service"; -import { userStatsService } from "@/services/users/user-stats.service"; -import { Role } from "@/prisma/generated/prisma/client"; -import { - ValidationError, - NotFoundError, - ConflictError, -} from "@/services/errors"; - -export async function PUT( - request: Request, - { params }: { params: Promise<{ id: string }> } -) { - try { - const session = await auth(); - - if (!session?.user || session.user.role !== Role.ADMIN) { - return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); - } - - const { id } = await params; - const body = await request.json(); - const { username, avatar, hpDelta, xpDelta, score, level, role } = body; - - // Valider username si fourni - if (username !== undefined) { - try { - await userService.validateAndUpdateUserProfile(id, { username }); - } catch (error) { - if ( - error instanceof ValidationError || - error instanceof ConflictError - ) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - throw error; - } - } - - // Mettre à jour stats et profil - const updatedUser = await userStatsService.updateUserStatsAndProfile( - id, - { username, avatar, hpDelta, xpDelta, score, level, role }, - { - id: true, - username: true, - email: true, - role: true, - score: true, - level: true, - hp: true, - maxHp: true, - xp: true, - maxXp: true, - avatar: true, - } - ); - - return NextResponse.json(updatedUser); - } catch (error) { - console.error("Error updating user:", error); - return NextResponse.json( - { error: "Erreur lors de la mise à jour de l'utilisateur" }, - { status: 500 } - ); - } -} - -export async function DELETE( - request: Request, - { params }: { params: Promise<{ id: string }> } -) { - try { - const session = await auth(); - - if (!session?.user || session.user.role !== Role.ADMIN) { - return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); - } - - const { id } = await params; - - await userService.validateAndDeleteUser(id, session.user.id); - - return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error deleting user:", error); - - if (error instanceof ValidationError) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - if (error instanceof NotFoundError) { - return NextResponse.json({ error: error.message }, { status: 404 }); - } - - return NextResponse.json( - { error: "Erreur lors de la suppression de l'utilisateur" }, - { status: 500 } - ); - } -} diff --git a/app/api/events/[id]/register/route.ts b/app/api/events/[id]/register/route.ts index 3ef9233..ee37c16 100644 --- a/app/api/events/[id]/register/route.ts +++ b/app/api/events/[id]/register/route.ts @@ -1,88 +1,7 @@ import { NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { eventRegistrationService } from "@/services/events/event-registration.service"; -import { - ValidationError, - NotFoundError, - ConflictError, -} from "@/services/errors"; -export async function POST( - request: Request, - { params }: { params: Promise<{ id: string }> } -) { - try { - const session = await auth(); - - if (!session?.user?.id) { - return NextResponse.json( - { error: "Vous devez être connecté pour vous inscrire" }, - { status: 401 } - ); - } - - const { id: eventId } = await params; - - const registration = await eventRegistrationService.validateAndRegisterUser( - session.user.id, - eventId - ); - - return NextResponse.json( - { message: "Inscription réussie", registration }, - { status: 201 } - ); - } catch (error) { - console.error("Registration error:", error); - - if ( - error instanceof ValidationError || - error instanceof ConflictError - ) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - if (error instanceof NotFoundError) { - return NextResponse.json({ error: error.message }, { status: 404 }); - } - - return NextResponse.json( - { error: "Une erreur est survenue lors de l'inscription" }, - { status: 500 } - ); - } -} - -export async function DELETE( - request: Request, - { params }: { params: Promise<{ id: string }> } -) { - try { - const session = await auth(); - - if (!session?.user?.id) { - return NextResponse.json( - { error: "Vous devez être connecté" }, - { status: 401 } - ); - } - - const { id: eventId } = await params; - - // Supprimer l'inscription - await eventRegistrationService.unregisterUserFromEvent( - session.user.id, - eventId - ); - - return NextResponse.json({ message: "Inscription annulée" }); - } catch (error) { - console.error("Unregistration error:", error); - return NextResponse.json( - { error: "Une erreur est survenue lors de l'annulation" }, - { status: 500 } - ); - } -} export async function GET( request: Request, diff --git a/app/api/feedback/[eventId]/route.ts b/app/api/feedback/[eventId]/route.ts index 3c090ec..afe446e 100644 --- a/app/api/feedback/[eventId]/route.ts +++ b/app/api/feedback/[eventId]/route.ts @@ -1,48 +1,7 @@ import { NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { eventFeedbackService } from "@/services/events/event-feedback.service"; -import { - ValidationError, - NotFoundError, -} from "@/services/errors"; -export async function POST( - request: Request, - { params }: { params: Promise<{ eventId: string }> } -) { - try { - const session = await auth(); - if (!session?.user?.id) { - return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); - } - - const { eventId } = await params; - const body = await request.json(); - const { rating, comment } = body; - - const feedback = await eventFeedbackService.validateAndCreateFeedback( - session.user.id, - eventId, - { rating, comment } - ); - - return NextResponse.json({ success: true, feedback }); - } catch (error) { - console.error("Error saving feedback:", error); - - if (error instanceof ValidationError) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - if (error instanceof NotFoundError) { - return NextResponse.json({ error: error.message }, { status: 404 }); - } - - return NextResponse.json( - { error: "Erreur lors de l'enregistrement du feedback" }, - { status: 500 } - ); - } -} export async function GET( request: Request, diff --git a/app/api/profile/password/route.ts b/app/api/profile/password/route.ts deleted file mode 100644 index b66e6ed..0000000 --- a/app/api/profile/password/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NextResponse } from "next/server"; -import { auth } from "@/lib/auth"; -import { userService } from "@/services/users/user.service"; -import { ValidationError, NotFoundError } from "@/services/errors"; - -export async function PUT(request: Request) { - try { - const session = await auth(); - - if (!session?.user) { - return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); - } - - const body = await request.json(); - const { currentPassword, newPassword, confirmPassword } = body; - - await userService.validateAndUpdatePassword( - session.user.id, - currentPassword, - newPassword, - confirmPassword - ); - - return NextResponse.json({ message: "Mot de passe modifié avec succès" }); - } catch (error) { - console.error("Error updating password:", error); - - if (error instanceof ValidationError) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - if (error instanceof NotFoundError) { - return NextResponse.json({ error: error.message }, { status: 404 }); - } - - return NextResponse.json( - { error: "Erreur lors de la modification du mot de passe" }, - { status: 500 } - ); - } -} diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index af85399..8ac847e 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -1,11 +1,6 @@ import { NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { userService } from "@/services/users/user.service"; -import { - ValidationError, - ConflictError, - NotFoundError, -} from "@/services/errors"; export async function GET() { try { @@ -48,47 +43,3 @@ export async function GET() { } } -export async function PUT(request: Request) { - try { - const session = await auth(); - - if (!session?.user) { - return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); - } - - const body = await request.json(); - const { username, avatar, bio, characterClass } = body; - - const updatedUser = await userService.validateAndUpdateUserProfile( - session.user.id, - { username, avatar, bio, characterClass }, - { - id: true, - email: true, - username: true, - avatar: true, - bio: true, - characterClass: true, - hp: true, - maxHp: true, - xp: true, - maxXp: true, - level: true, - score: true, - } - ); - - return NextResponse.json(updatedUser); - } catch (error) { - console.error("Error updating profile:", error); - - if (error instanceof ValidationError || error instanceof ConflictError) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - - return NextResponse.json( - { error: "Erreur lors de la mise à jour du profil" }, - { status: 500 } - ); - } -} diff --git a/app/feedback/[eventId]/FeedbackPageClient.tsx b/app/feedback/[eventId]/FeedbackPageClient.tsx index f3efd4f..8cb7ed5 100644 --- a/app/feedback/[eventId]/FeedbackPageClient.tsx +++ b/app/feedback/[eventId]/FeedbackPageClient.tsx @@ -1,9 +1,10 @@ "use client"; -import { useState, useEffect, type FormEvent } from "react"; +import { useState, useEffect, useTransition, type FormEvent } from "react"; import { useSession } from "next-auth/react"; import { useRouter, useParams } from "next/navigation"; import Navigation from "@/components/Navigation"; +import { createFeedback } from "@/actions/events/feedback"; interface Event { id: string; @@ -38,6 +39,7 @@ export default function FeedbackPageClient({ 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(""); @@ -95,37 +97,38 @@ export default function FeedbackPageClient({ 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, + }); + } + + // Rediriger après 2 secondes + setTimeout(() => { + router.push("/events"); + }, 2000); + } catch { + setError("Erreur lors de l'enregistrement"); + } finally { + setSubmitting(false); } - - setSuccess(true); - setExistingFeedback(data.feedback); - - // Rediriger après 2 secondes - setTimeout(() => { - router.push("/events"); - }, 2000); - } catch { - setError("Erreur lors de l'enregistrement"); - } finally { - setSubmitting(false); - } + }); }; if (status === "loading" || loading) { @@ -262,4 +265,3 @@ export default function FeedbackPageClient({ ); } - diff --git a/components/BackgroundPreferences.tsx b/components/BackgroundPreferences.tsx index 37a727d..812b4e6 100644 --- a/components/BackgroundPreferences.tsx +++ b/components/BackgroundPreferences.tsx @@ -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"); } }; diff --git a/components/EventManagement.tsx b/components/EventManagement.tsx index dbbf132..b73e132 100644 --- a/components/EventManagement.tsx +++ b/components/EventManagement.tsx @@ -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 = () => { diff --git a/components/EventsPageSection.tsx b/components/EventsPageSection.tsx index ca0c994..288fdcf 100644 --- a/components/EventsPageSection.tsx +++ b/components/EventsPageSection.tsx @@ -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({ ); + 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 ( diff --git a/components/FeedbackModal.tsx b/components/FeedbackModal.tsx index a1176eb..bcbe6a2 100644 --- a/components/FeedbackModal.tsx +++ b/components/FeedbackModal.tsx @@ -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 = () => { diff --git a/components/ProfileForm.tsx b/components/ProfileForm.tsx index c46ae9c..ffdef49 100644 --- a/components/ProfileForm.tsx +++ b/components/ProfileForm.tsx @@ -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(initialProfile); - const [saving, setSaving] = useState(false); + const [isPending, startTransition] = useTransition(); const [error, setError] = useState(null); const [success, setSuccess] = useState(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) => { 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({
@@ -616,10 +594,10 @@ export default function ProfileForm({ diff --git a/components/UserManagement.tsx b/components/UserManagement.tsx index fef1aec..fb707dc 100644 --- a/components/UserManagement.tsx +++ b/components/UserManagement.tsx @@ -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(null); const [saving, setSaving] = useState(false); const [deletingUserId, setDeletingUserId] = useState(null); + const [, startTransition] = useTransition(); const [uploadingAvatar, setUploadingAvatar] = useState(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); diff --git a/services/events/event-registration.service.ts b/services/events/event-registration.service.ts index aa85767..f0c1c93 100644 --- a/services/events/event-registration.service.ts +++ b/services/events/event-registration.service.ts @@ -81,9 +81,6 @@ export class EventRegistrationService { where: { userId, }, - select: { - eventId: true, - }, }); } diff --git a/services/events/event.service.ts b/services/events/event.service.ts index d8a4582..47785bf 100644 --- a/services/events/event.service.ts +++ b/services/events/event.service.ts @@ -1,9 +1,9 @@ import { prisma } from "../database"; import type { Event, - EventType, Prisma, } from "@/prisma/generated/prisma/client"; +import { EventType } from "@/prisma/generated/prisma/client"; import { ValidationError, NotFoundError } from "../errors"; import { calculateEventStatus } from "@/lib/eventStatus"; diff --git a/services/users/user-stats.service.ts b/services/users/user-stats.service.ts index 0e8cc94..2535a15 100644 --- a/services/users/user-stats.service.ts +++ b/services/users/user-stats.service.ts @@ -225,10 +225,11 @@ export class UserStatsService { selectFields ); } else { - updatedUser = await userService.getUserById(id, selectFields); - if (!updatedUser) { + const user = await userService.getUserById(id, selectFields); + if (!user) { throw new NotFoundError("Utilisateur"); } + updatedUser = user; } // Mettre à jour username/avatar si nécessaire diff --git a/services/users/user.service.ts b/services/users/user.service.ts index 07caa98..c34e6cb 100644 --- a/services/users/user.service.ts +++ b/services/users/user.service.ts @@ -3,7 +3,6 @@ import bcrypt from "bcryptjs"; import type { User, CharacterClass, - Role, Prisma, } from "@/prisma/generated/prisma/client"; import { ValidationError, NotFoundError, ConflictError } from "../errors";