Enhance user registration and profile management: Update registration API to include bio, character class, and avatar fields. Implement validation for character class and improve error messages. Refactor registration page to support multi-step form with avatar upload and additional profile customization options, enhancing user experience during account creation.
This commit is contained in:
@@ -88,7 +88,7 @@ export async function PUT(request: Request) {
|
||||
}
|
||||
|
||||
// Validation bio
|
||||
if (bio !== undefined) {
|
||||
if (bio !== undefined && bio !== null) {
|
||||
if (typeof bio !== "string") {
|
||||
return NextResponse.json(
|
||||
{ error: "La bio doit être une chaîne de caractères" },
|
||||
|
||||
63
app/api/register/avatar/route.ts
Normal file
63
app/api/register/avatar/route.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { writeFile, mkdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get("file") as File;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json(
|
||||
{ error: "Aucun fichier fourni" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier le type de fichier
|
||||
if (!file.type.startsWith("image/")) {
|
||||
return NextResponse.json(
|
||||
{ error: "Le fichier doit être une image" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Limiter la taille (par exemple 5MB)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
if (file.size > maxSize) {
|
||||
return NextResponse.json(
|
||||
{ error: "L'image est trop grande (max 5MB)" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Créer le dossier uploads s'il n'existe pas
|
||||
const uploadsDir = join(process.cwd(), "public", "uploads");
|
||||
if (!existsSync(uploadsDir)) {
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Générer un nom de fichier unique avec timestamp
|
||||
const timestamp = Date.now();
|
||||
const randomId = Math.random().toString(36).substring(2, 9);
|
||||
const filename = `avatar-register-${timestamp}-${randomId}-${file.name}`;
|
||||
const filepath = join(uploadsDir, filename);
|
||||
|
||||
// Convertir le fichier en buffer et l'écrire
|
||||
const bytes = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(bytes);
|
||||
await writeFile(filepath, buffer);
|
||||
|
||||
// Retourner l'URL de l'image
|
||||
const imageUrl = `/uploads/${filename}`;
|
||||
return NextResponse.json({ url: imageUrl });
|
||||
} catch (error) {
|
||||
console.error("Error uploading avatar:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de l'upload de l'avatar" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
161
app/api/register/complete/route.ts
Normal file
161
app/api/register/complete/route.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { CharacterClass } from "@/prisma/generated/prisma/enums";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { userId, username, avatar, bio, characterClass } = body;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json(
|
||||
{ error: "ID utilisateur requis" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier que l'utilisateur existe et a été créé récemment (dans les 10 dernières minutes)
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: "Utilisateur non trouvé" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier que le compte a été créé récemment (dans les 10 dernières minutes)
|
||||
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
|
||||
if (user.createdAt < tenMinutesAgo) {
|
||||
return NextResponse.json(
|
||||
{ error: "Temps écoulé pour finaliser l'inscription" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validation username
|
||||
if (username !== undefined) {
|
||||
if (typeof username !== "string" || username.trim().length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "Le nom d'utilisateur ne peut pas être vide" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (username.length < 3 || username.length > 20) {
|
||||
return NextResponse.json(
|
||||
{ error: "Le nom d'utilisateur doit contenir entre 3 et 20 caractères" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si le username est déjà pris par un autre utilisateur
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: username.trim(),
|
||||
NOT: { id: userId },
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: "Ce nom d'utilisateur est déjà pris" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validation bio
|
||||
if (bio !== undefined && bio !== null) {
|
||||
if (typeof bio !== "string") {
|
||||
return NextResponse.json(
|
||||
{ error: "La bio doit être une chaîne de caractères" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (bio.length > 500) {
|
||||
return NextResponse.json(
|
||||
{ error: "La bio ne peut pas dépasser 500 caractères" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validation characterClass
|
||||
const validClasses = [
|
||||
"WARRIOR",
|
||||
"MAGE",
|
||||
"ROGUE",
|
||||
"RANGER",
|
||||
"PALADIN",
|
||||
"ENGINEER",
|
||||
"MERCHANT",
|
||||
"SCHOLAR",
|
||||
"BERSERKER",
|
||||
"NECROMANCER",
|
||||
];
|
||||
if (characterClass !== undefined && characterClass !== null) {
|
||||
if (!validClasses.includes(characterClass)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Classe de personnage invalide" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour l'utilisateur
|
||||
const updateData: {
|
||||
username?: string;
|
||||
avatar?: string | null;
|
||||
bio?: string | null;
|
||||
characterClass?: CharacterClass | null;
|
||||
} = {};
|
||||
|
||||
if (username !== undefined) {
|
||||
updateData.username = username.trim();
|
||||
}
|
||||
if (avatar !== undefined) {
|
||||
updateData.avatar = avatar || null;
|
||||
}
|
||||
if (bio !== undefined) {
|
||||
if (bio === null || bio === "") {
|
||||
updateData.bio = null;
|
||||
} else if (typeof bio === "string") {
|
||||
updateData.bio = bio.trim() || null;
|
||||
} else {
|
||||
updateData.bio = null;
|
||||
}
|
||||
}
|
||||
if (characterClass !== undefined) {
|
||||
updateData.characterClass = (characterClass as CharacterClass) || null;
|
||||
}
|
||||
|
||||
// Si aucun champ à mettre à jour, retourner succès quand même
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return NextResponse.json({
|
||||
message: "Profil finalisé avec succès",
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
message: "Profil finalisé avec succès",
|
||||
userId: updatedUser.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error completing registration:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : "Erreur inconnue";
|
||||
return NextResponse.json(
|
||||
{ error: `Erreur lors de la finalisation de l'inscription: ${errorMessage}` },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import bcrypt from "bcryptjs";
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, username, password } = body;
|
||||
const { email, username, password, bio, characterClass, avatar } = body;
|
||||
|
||||
if (!email || !username || !password) {
|
||||
return NextResponse.json(
|
||||
{ error: "Tous les champs sont requis" },
|
||||
{ error: "Email, nom d'utilisateur et mot de passe sont requis" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,26 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
// Valider characterClass si fourni
|
||||
const validCharacterClasses = [
|
||||
"WARRIOR",
|
||||
"MAGE",
|
||||
"ROGUE",
|
||||
"RANGER",
|
||||
"PALADIN",
|
||||
"ENGINEER",
|
||||
"MERCHANT",
|
||||
"SCHOLAR",
|
||||
"BERSERKER",
|
||||
"NECROMANCER",
|
||||
];
|
||||
if (characterClass && !validCharacterClasses.includes(characterClass)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Classe de personnage invalide" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si l'email existe déjà
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
@@ -44,6 +64,9 @@ export async function POST(request: Request) {
|
||||
email,
|
||||
username,
|
||||
password: hashedPassword,
|
||||
bio: bio || null,
|
||||
characterClass: characterClass || null,
|
||||
avatar: avatar || null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user