Files
binouz/components/purchase-button.tsx
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

129 lines
4.0 KiB
TypeScript

"use client";
import { useState } from "react";
type PurchaseButtonProps = {
gradeId: string;
};
type PurchaseState = "idle" | "loading" | "error";
const minecraftNameRegex = /^[A-Za-z0-9_]{3,16}$/;
export default function PurchaseButton({ gradeId }: PurchaseButtonProps) {
const [isOpen, setIsOpen] = useState(false);
const [username, setUsername] = useState("");
const [state, setState] = useState<PurchaseState>("idle");
const [error, setError] = useState<string | null>(null);
const openModal = () => {
setIsOpen(true);
setError(null);
};
const closeModal = () => {
setIsOpen(false);
setState("idle");
setError(null);
};
const handleCheckout = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const trimmed = username.trim();
if (!minecraftNameRegex.test(trimmed)) {
setError("Enter a valid Minecraft username (3-16, letters, numbers, _).");
return;
}
setState("loading");
setError(null);
try {
const res = await fetch("/api/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ gradeId, minecraftUsername: trimmed }),
});
const data = (await res.json().catch(() => ({}))) as {
url?: string;
error?: string;
};
if (!res.ok || !data.url) {
setState("error");
setError(data.error ?? "Checkout failed.");
return;
}
window.location.href = data.url;
} catch {
setState("error");
setError("Checkout failed.");
}
};
return (
<>
<button
type="button"
onClick={openModal}
className="inline-flex items-center justify-center gap-2 rounded-full bg-linear-to-r from-violet-600 via-indigo-500 to-cyan-400 px-4 py-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-950 transition hover:brightness-110"
aria-label="Open checkout"
>
<span>Acheter</span>
</button>
{isOpen ? (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/80 px-6"
onClick={closeModal}
>
<div
className="w-full max-w-md rounded-3xl border border-white/10 bg-slate-950/90 p-6 text-white shadow-[0_30px_80px_rgba(2,6,23,0.6)]"
onClick={(event) => event.stopPropagation()}
>
<p className="text-xs uppercase tracking-[0.4em] text-cyan-200/80">
Checkout
</p>
<h3 className="mt-3 text-2xl font-semibold">
Enter Minecraft username
</h3>
<p className="mt-2 text-sm text-slate-300">
No account needed. We will send you to Stripe.
</p>
<form onSubmit={handleCheckout} className="mt-5 space-y-4">
<input
value={username}
onChange={(event) => setUsername(event.target.value)}
placeholder="Minecraft username"
className="w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-sm text-white"
/>
{error ? (
<p className="text-xs text-rose-300">{error}</p>
) : null}
<div className="flex flex-wrap gap-3">
<button
type="submit"
className="flex-1 rounded-2xl bg-linear-to-r from-violet-600 via-indigo-500 to-cyan-400 px-4 py-3 text-xs font-semibold uppercase tracking-[0.2em] text-slate-950"
disabled={state === "loading"}
>
{state === "loading" ? "Redirecting" : "Go to Stripe"}
</button>
<button
type="button"
onClick={closeModal}
className="rounded-2xl border border-white/10 px-4 py-3 text-xs font-semibold uppercase tracking-[0.2em] text-white/80"
>
Cancel
</button>
</div>
</form>
</div>
</div>
) : null}
</>
);
}