Add database and Prisma configurations, enhance event and leaderboard components with API integration, and update navigation for session management
This commit is contained in:
253
app/admin/page.tsx
Normal file
253
app/admin/page.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Navigation from "@/components/Navigation";
|
||||
|
||||
interface UserPreferences {
|
||||
id: string;
|
||||
userId: string;
|
||||
homeBackground: string | null;
|
||||
eventsBackground: string | null;
|
||||
leaderboardBackground: string | null;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function AdminPage() {
|
||||
const { data: session, status } = useSession();
|
||||
const router = useRouter();
|
||||
const [preferences, setPreferences] = useState<UserPreferences[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editingUserId, setEditingUserId] = useState<string | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
homeBackground: "",
|
||||
eventsBackground: "",
|
||||
leaderboardBackground: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "unauthenticated") {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "authenticated" && session?.user?.role !== "ADMIN") {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "authenticated" && session?.user?.role === "ADMIN") {
|
||||
fetchPreferences();
|
||||
}
|
||||
}, [status, session, router]);
|
||||
|
||||
const fetchPreferences = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/admin/preferences");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setPreferences(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching preferences:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (pref: UserPreferences) => {
|
||||
setEditingUserId(pref.userId);
|
||||
setFormData({
|
||||
homeBackground: pref.homeBackground || "",
|
||||
eventsBackground: pref.eventsBackground || "",
|
||||
leaderboardBackground: pref.leaderboardBackground || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!editingUserId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/preferences", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: editingUserId,
|
||||
...formData,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await fetchPreferences();
|
||||
setEditingUserId(null);
|
||||
setFormData({
|
||||
homeBackground: "",
|
||||
eventsBackground: "",
|
||||
leaderboardBackground: "",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating preferences:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setEditingUserId(null);
|
||||
setFormData({
|
||||
homeBackground: "",
|
||||
eventsBackground: "",
|
||||
leaderboardBackground: "",
|
||||
});
|
||||
};
|
||||
|
||||
if (status === "loading" || loading) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<div className="flex items-center justify-center min-h-screen text-pixel-gold">
|
||||
Chargement...
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
|
||||
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
|
||||
<h1 className="text-4xl font-gaming font-black mb-8 text-center">
|
||||
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
|
||||
ADMIN - GESTION DES PRÉFÉRENCES UI
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-6 backdrop-blur-sm">
|
||||
<div className="space-y-4">
|
||||
{preferences.map((pref) => (
|
||||
<div
|
||||
key={pref.id}
|
||||
className="bg-black/60 border border-pixel-gold/20 rounded p-4"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="text-pixel-gold font-bold text-lg">
|
||||
{pref.user.username}
|
||||
</h3>
|
||||
<p className="text-gray-400 text-sm">{pref.user.email}</p>
|
||||
</div>
|
||||
{editingUserId !== pref.userId && (
|
||||
<button
|
||||
onClick={() => handleEdit(pref)}
|
||||
className="px-4 py-2 border border-pixel-gold/50 bg-black/60 text-white uppercase text-xs tracking-widest rounded hover:bg-pixel-gold/10 transition"
|
||||
>
|
||||
Modifier
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editingUserId === pref.userId ? (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-1">
|
||||
Background Home
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.homeBackground}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
homeBackground: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="/got-2.jpg"
|
||||
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-1">
|
||||
Background Events
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.eventsBackground}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
eventsBackground: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="/got-2.jpg"
|
||||
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-1">
|
||||
Background Leaderboard
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.leaderboardBackground}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
leaderboardBackground: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="/leaderboard-bg.jpg"
|
||||
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-xs tracking-widest rounded hover:bg-green-900/30 transition"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="px-4 py-2 border border-gray-600/50 bg-gray-900/20 text-gray-400 uppercase text-xs tracking-widest rounded hover:bg-gray-900/30 transition"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2 text-sm text-gray-400">
|
||||
<div>
|
||||
Home: {pref.homeBackground || "Par défaut"}
|
||||
</div>
|
||||
<div>
|
||||
Events: {pref.eventsBackground || "Par défaut"}
|
||||
</div>
|
||||
<div>
|
||||
Leaderboard:{" "}
|
||||
{pref.leaderboardBackground || "Par défaut"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{preferences.length === 0 && (
|
||||
<div className="text-center text-gray-400 py-8">
|
||||
Aucune préférence trouvée
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
76
app/api/admin/preferences/route.ts
Normal file
76
app/api/admin/preferences/route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Role } from "@/prisma/generated/prisma/client";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user || session.user.role !== Role.ADMIN) {
|
||||
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
|
||||
}
|
||||
|
||||
// Récupérer toutes les préférences utilisateur
|
||||
const preferences = await prisma.userPreferences.findMany({
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(preferences);
|
||||
} catch (error) {
|
||||
console.error("Error fetching admin preferences:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération des préférences" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 { userId, homeBackground, eventsBackground, leaderboardBackground } =
|
||||
body;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: "userId requis" }, { status: 400 });
|
||||
}
|
||||
|
||||
const preferences = await prisma.userPreferences.upsert({
|
||||
where: { userId },
|
||||
update: {
|
||||
homeBackground: homeBackground ?? undefined,
|
||||
eventsBackground: eventsBackground ?? undefined,
|
||||
leaderboardBackground: leaderboardBackground ?? undefined,
|
||||
},
|
||||
create: {
|
||||
userId,
|
||||
homeBackground: homeBackground ?? null,
|
||||
eventsBackground: eventsBackground ?? null,
|
||||
leaderboardBackground: leaderboardBackground ?? null,
|
||||
},
|
||||
});
|
||||
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
4
app/api/auth/[...nextauth]/route.ts
Normal file
4
app/api/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { handlers } from "@/lib/auth";
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
|
||||
21
app/api/events/route.ts
Normal file
21
app/api/events/route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const events = await prisma.event.findMany({
|
||||
orderBy: {
|
||||
date: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(events);
|
||||
} catch (error) {
|
||||
console.error("Error fetching events:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération des événements" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
37
app/api/leaderboard/route.ts
Normal file
37
app/api/leaderboard/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: {
|
||||
score: "desc",
|
||||
},
|
||||
take: 10,
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
score: true,
|
||||
level: true,
|
||||
avatar: true,
|
||||
},
|
||||
});
|
||||
|
||||
const leaderboard = users.map((user: { id: string; username: string; score: number; level: number; avatar: string | null }, index: number) => ({
|
||||
rank: index + 1,
|
||||
username: user.username,
|
||||
score: user.score,
|
||||
level: user.level,
|
||||
avatar: user.avatar,
|
||||
}));
|
||||
|
||||
return NextResponse.json(leaderboard);
|
||||
} catch (error) {
|
||||
console.error("Error fetching leaderboard:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération du leaderboard" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
65
app/api/preferences/route.ts
Normal file
65
app/api/preferences/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { Role } from "@/prisma/generated/prisma/client";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
const preferences = await prisma.userPreferences.findUnique({
|
||||
where: { userId: session.user.id },
|
||||
});
|
||||
|
||||
return NextResponse.json(preferences || {});
|
||||
} catch (error) {
|
||||
console.error("Error fetching preferences:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération des préférences" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 { homeBackground, eventsBackground, leaderboardBackground, theme } =
|
||||
body;
|
||||
|
||||
const preferences = await prisma.userPreferences.upsert({
|
||||
where: { userId: session.user.id },
|
||||
update: {
|
||||
homeBackground: homeBackground ?? undefined,
|
||||
eventsBackground: eventsBackground ?? undefined,
|
||||
leaderboardBackground: leaderboardBackground ?? undefined,
|
||||
theme: theme ?? undefined,
|
||||
},
|
||||
create: {
|
||||
userId: session.user.id,
|
||||
homeBackground: homeBackground ?? null,
|
||||
eventsBackground: eventsBackground ?? null,
|
||||
leaderboardBackground: leaderboardBackground ?? null,
|
||||
theme: theme ?? "default",
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(preferences);
|
||||
} catch (error) {
|
||||
console.error("Error updating preferences:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la mise à jour des préférences" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
62
app/api/register/route.ts
Normal file
62
app/api/register/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, username, password } = body;
|
||||
|
||||
if (!email || !username || !password) {
|
||||
return NextResponse.json(
|
||||
{ error: "Tous les champs sont requis" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return NextResponse.json(
|
||||
{ error: "Le mot de passe doit contenir au moins 6 caractères" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si l'email existe déjà
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ email }, { username }],
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: "Cet email ou nom d'utilisateur est déjà utilisé" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Hasher le mot de passe
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Créer l'utilisateur
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
username,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ message: "Compte créé avec succès", userId: user.id },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Registration error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Une erreur est survenue lors de l'inscription" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
50
app/api/users/[id]/route.ts
Normal file
50
app/api/users/[id]/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth();
|
||||
const { id } = await params;
|
||||
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Vérifier que l'utilisateur demande ses propres données ou est admin
|
||||
if (session.user.id !== id && session.user.role !== "ADMIN") {
|
||||
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
avatar: true,
|
||||
hp: true,
|
||||
maxHp: true,
|
||||
xp: true,
|
||||
maxXp: true,
|
||||
level: true,
|
||||
score: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "Utilisateur non trouvé" }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(user);
|
||||
} catch (error) {
|
||||
console.error("Error fetching user:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur lors de la récupération de l'utilisateur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Orbitron, Rajdhani } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import SessionProvider from "@/components/SessionProvider";
|
||||
|
||||
const orbitron = Orbitron({
|
||||
subsets: ["latin"],
|
||||
@@ -26,8 +27,9 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="fr" className={`${orbitron.variable} ${rajdhani.variable}`}>
|
||||
<body className="antialiased">{children}</body>
|
||||
<body className="antialiased">
|
||||
<SessionProvider>{children}</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
141
app/login/page.tsx
Normal file
141
app/login/page.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Navigation from "@/components/Navigation";
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const result = await signIn("credentials", {
|
||||
email,
|
||||
password,
|
||||
redirect: false,
|
||||
callbackUrl: "/",
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
setError("Email ou mot de passe incorrect");
|
||||
setLoading(false);
|
||||
} else if (result?.ok) {
|
||||
router.push("/");
|
||||
router.refresh();
|
||||
} else {
|
||||
setError("Une erreur est survenue lors de la connexion");
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Login error:", err);
|
||||
setError("Une erreur est survenue");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center justify-center overflow-hidden pt-24">
|
||||
{/* Background Image */}
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: `url('/got-2.jpg')`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
|
||||
</div>
|
||||
|
||||
{/* Login Form */}
|
||||
<div className="relative z-10 w-full max-w-md mx-auto px-8">
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-8 backdrop-blur-sm">
|
||||
<h1 className="text-4xl font-gaming font-black mb-2 text-center">
|
||||
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
|
||||
CONNEXION
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-gray-400 text-sm text-center mb-8">
|
||||
Connectez-vous à votre compte
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{error && (
|
||||
<div className="bg-red-900/50 border border-red-500/50 text-red-400 px-4 py-3 rounded text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition"
|
||||
placeholder="votre@email.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
|
||||
>
|
||||
Mot de passe
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-6 py-3 border border-pixel-gold/50 bg-black/60 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Connexion..." : "Se connecter"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-gray-400 text-sm">
|
||||
Pas encore de compte ?{" "}
|
||||
<Link
|
||||
href="/register"
|
||||
className="text-pixel-gold hover:text-orange-400 transition"
|
||||
>
|
||||
S'inscrire
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
204
app/register/page.tsx
Normal file
204
app/register/page.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Navigation from "@/components/Navigation";
|
||||
|
||||
export default function RegisterPage() {
|
||||
const router = useRouter();
|
||||
const [formData, setFormData] = useState({
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError("Les mots de passe ne correspondent pas");
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.password.length < 6) {
|
||||
setError("Le mot de passe doit contenir au moins 6 caractères");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: formData.email,
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setError(data.error || "Une erreur est survenue");
|
||||
return;
|
||||
}
|
||||
|
||||
router.push("/login?registered=true");
|
||||
} catch (err) {
|
||||
setError("Une erreur est survenue");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black relative">
|
||||
<Navigation />
|
||||
<section className="relative w-full min-h-screen flex flex-col items-center justify-center overflow-hidden pt-24">
|
||||
{/* Background Image */}
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
style={{
|
||||
backgroundImage: `url('/got-2.jpg')`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
|
||||
</div>
|
||||
|
||||
{/* Register Form */}
|
||||
<div className="relative z-10 w-full max-w-md mx-auto px-8">
|
||||
<div className="bg-black/80 border border-pixel-gold/30 rounded-lg p-8 backdrop-blur-sm">
|
||||
<h1 className="text-4xl font-gaming font-black mb-2 text-center">
|
||||
<span className="bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent">
|
||||
INSCRIPTION
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-gray-400 text-sm text-center mb-8">
|
||||
Créez votre compte pour commencer
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{error && (
|
||||
<div className="bg-red-900/50 border border-red-500/50 text-red-400 px-4 py-3 rounded text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition"
|
||||
placeholder="votre@email.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="username"
|
||||
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
|
||||
>
|
||||
Nom d'utilisateur
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition"
|
||||
placeholder="VotrePseudo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
|
||||
>
|
||||
Mot de passe
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="confirmPassword"
|
||||
className="block text-sm font-semibold text-gray-300 mb-2 uppercase tracking-wider"
|
||||
>
|
||||
Confirmer le mot de passe
|
||||
</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-6 py-3 border border-pixel-gold/50 bg-black/60 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/10 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Inscription..." : "S'inscrire"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-gray-400 text-sm">
|
||||
Déjà un compte ?{" "}
|
||||
<Link
|
||||
href="/login"
|
||||
className="text-pixel-gold hover:text-orange-400 transition"
|
||||
>
|
||||
Se connecter
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user