Files
Puechberty Arthur b7010a1704 feat: add authentication and user management features
- Implemented AuthButton component for Discord sign-in and sign-out functionality.
- Created CopyButton component for copying server IP addresses.
- Developed EventCard and GradeCard components for displaying events and grades.
- Added Footer and Navbar components for site navigation and information.
- Introduced PurchaseButton for handling grade purchases with Stripe integration.
- Created SectionHeader component for consistent section titles.
- Implemented session management with SessionProvider for NextAuth.
- Set up PostgreSQL database with Docker and Prisma for data management.
- Added admin guard functionality to restrict access to certain routes.
- Configured NextAuth with Discord provider for user authentication.
- Defined Prisma schema for user, admin, grade, event, and purchase models.
- Seeded database with initial grades and events data.
- Added SVG hero image for the landing page.
- Extended NextAuth types to include additional user properties.
2026-04-28 21:09:55 +02:00

162 lines
5.7 KiB
TypeScript

import EventCard from "@/components/event-card";
import Footer from "@/components/footer";
import GradeCard from "@/components/grade-card";
import Hero from "@/components/hero";
import Navbar from "@/components/navbar";
import SectionHeader from "@/components/section-header";
import { db } from "@/lib/db";
import { fallbackEvents, fallbackGrades, siteConfig } from "@/lib/site";
export const dynamic = "force-dynamic";
type Grade = {
id: string;
name: string;
price: number;
description: string;
};
type Event = {
id: string;
title: string;
description: string;
eventDate: Date;
};
const getGrades = async (): Promise<Grade[]> => {
try {
const grades = await db.grade.findMany({ orderBy: { price: "asc" } });
return grades.length > 0 ? grades : fallbackGrades;
} catch {
return fallbackGrades;
}
};
const getEvents = async (): Promise<Event[]> => {
try {
const events = await db.event.findMany({ orderBy: { eventDate: "asc" } });
return events.length > 0 ? events : fallbackEvents;
} catch {
return fallbackEvents;
}
};
export default async function Home() {
const [grades, events] = await Promise.all([getGrades(), getEvents()]);
return (
<div className="flex min-h-screen flex-col">
<Navbar />
<main className="flex-1">
<Hero />
<section id="presentation" className="py-20">
<div className="mx-auto w-full max-w-6xl px-6">
<SectionHeader
eyebrow="Presentation"
title="UHC designed for competitive squads"
description="Balance, clarity, and tension. BinouzUHC delivers a premium UHC loop with PvP-first tuning."
/>
<div className="grid gap-6 md:grid-cols-3">
{[
{
title: "Precision combat",
description:
"Hits register fast, clean hitboxes, and optimized knockback for UHC duels.",
},
{
title: "Event experience",
description:
"Weekly tournaments, bracket nights, and events built for squads.",
},
{
title: "Community core",
description:
"Active moderation, high signal Discord, and competitive rankings.",
},
].map((item) => (
<div
key={item.title}
className="rounded-3xl border border-white/10 bg-white/5 p-6 text-sm text-slate-300 backdrop-blur"
>
<p className="text-xs uppercase tracking-[0.3em] text-cyan-200/80">
{item.title}
</p>
<p className="mt-4 text-base text-white">{item.description}</p>
</div>
))}
</div>
</div>
</section>
<section id="grades" className="py-20">
<div className="mx-auto w-full max-w-6xl px-6">
<SectionHeader
eyebrow="Boutique"
title="Grades premium"
description="Unlock cosmetics, priority access, and competitive advantages."
/>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{grades.map((grade) => (
<GradeCard key={grade.id} grade={grade} />
))}
</div>
<p className="mt-6 text-xs uppercase tracking-[0.3em] text-slate-500">
{siteConfig.shopDisclaimer}
</p>
</div>
</section>
<section id="events" className="py-20">
<div className="mx-auto w-full max-w-6xl px-6">
<SectionHeader
eyebrow="Events"
title="Upcoming challenges"
description="Competitive formats tailored for UHC players and squad leaders."
/>
<div className="grid gap-6 md:grid-cols-2">
{events.map((event) => (
<EventCard key={event.id} event={event} />
))}
</div>
</div>
</section>
<section id="discord" className="py-20">
<div className="mx-auto w-full max-w-6xl px-6">
<div className="rounded-3xl border border-white/10 bg-linear-to-r from-violet-600/20 via-indigo-500/20 to-cyan-400/20 p-10 text-center backdrop-blur">
<p className="text-xs uppercase tracking-[0.4em] text-cyan-200/80">
Discord
</p>
<h2 className="mt-4 text-3xl font-semibold text-white">
Join the BinouzUHC squad
</h2>
<p className="mt-3 text-sm text-slate-300">
News, tournaments, and admin contact. Stay synced with the
community.
</p>
<div className="mt-6 flex flex-wrap justify-center gap-4">
<a
href={siteConfig.discordInviteUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-full border border-white/15 bg-white/10 px-6 py-3 text-xs font-semibold uppercase tracking-[0.3em] text-white transition hover:bg-white/20"
>
Connect Discord
</a>
<a
href="#presentation"
className="inline-flex items-center justify-center rounded-full border border-white/15 px-6 py-3 text-xs font-semibold uppercase tracking-[0.3em] text-white/80 transition hover:text-white"
>
Learn more
</a>
</div>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}