This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
@@ -4,75 +4,91 @@ import { useState } from "react";
|
||||
import { linkDiscordId } from "@/actions/link-discord";
|
||||
|
||||
export function DiscordVerifyForm() {
|
||||
const [discordId, setDiscordId] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [discordId, setDiscordId] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
setIsPending(true);
|
||||
try {
|
||||
const result = await linkDiscordId(discordId);
|
||||
if (result === null) {
|
||||
setSuccess(true);
|
||||
} else {
|
||||
setError(result);
|
||||
}
|
||||
} catch {
|
||||
setError("Er is een fout opgetreden");
|
||||
} finally {
|
||||
setIsPending(false);
|
||||
}
|
||||
}
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
setIsPending(true);
|
||||
try {
|
||||
const result = await linkDiscordId(discordId);
|
||||
if (result === null) {
|
||||
setSuccess(true);
|
||||
} else {
|
||||
setError(result);
|
||||
}
|
||||
} catch {
|
||||
setError("Er is een fout opgetreden");
|
||||
} finally {
|
||||
setIsPending(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="rounded-md p-4 text-center" style={{ backgroundColor: "#14532d", color: "white" }}>
|
||||
<p className="text-lg font-bold mb-1">Geverifieerd!</p>
|
||||
<p className="text-sm opacity-90">Je Discord account is gekoppeld en je account is geverifieerd.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (success) {
|
||||
return (
|
||||
<div
|
||||
className="rounded-md p-4 text-center"
|
||||
style={{ backgroundColor: "#14532d", color: "white" }}
|
||||
>
|
||||
<p className="text-lg font-bold mb-1">Geverifieerd!</p>
|
||||
<p className="text-sm opacity-90">
|
||||
Je Discord account is gekoppeld en je account is geverifieerd.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
|
||||
{error && (
|
||||
<div className="p-3 rounded-md text-sm" style={{ backgroundColor: "#ef4444", color: "white" }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
|
||||
{error && (
|
||||
<div
|
||||
className="p-3 rounded-md text-sm"
|
||||
style={{ backgroundColor: "#ef4444", color: "white" }}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-sm" style={{ color: "var(--color-text-muted-readable, var(--color-text-muted))" }}>
|
||||
Open Discord, ga naar <strong>Instellingen → Geavanceerd → Ontwikkelaarsmodus</strong> (aan). Klik met
|
||||
rechts op je eigen naam en kies <strong>ID kopiëren</strong>. Plak dat hier:
|
||||
</p>
|
||||
<p
|
||||
className="text-sm"
|
||||
style={{
|
||||
color: "var(--color-text-muted-readable, var(--color-text-muted))",
|
||||
}}
|
||||
>
|
||||
Open Discord, ga naar{" "}
|
||||
<strong>Instellingen → Geavanceerd → Ontwikkelaarsmodus</strong> (aan).
|
||||
Klik met rechts op je eigen naam en kies <strong>ID kopiëren</strong>.
|
||||
Plak dat hier:
|
||||
</p>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Plak je Discord ID hier"
|
||||
value={discordId}
|
||||
onChange={(e) => setDiscordId(e.target.value)}
|
||||
className="flex-1 focus:ring-0 border-4 rounded text-sm px-3 py-2"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="rounded px-4 py-2 text-white font-semibold whitespace-nowrap"
|
||||
style={{ backgroundColor: "#5865F2", border: "2px solid #7983F5" }}
|
||||
>
|
||||
{isPending ? "Bezig..." : "Verifiëren"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Plak je Discord ID hier"
|
||||
value={discordId}
|
||||
onChange={(e) => setDiscordId(e.target.value)}
|
||||
className="flex-1 focus:ring-0 border-4 rounded text-sm px-3 py-2"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="rounded px-4 py-2 text-white font-semibold whitespace-nowrap"
|
||||
style={{ backgroundColor: "#5865F2", border: "2px solid #7983F5" }}
|
||||
>
|
||||
{isPending ? "Bezig..." : "Verifiëren"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -5,103 +5,114 @@ import { type FormEvent, useState } from "react";
|
||||
import { precheckLogin } from "@/actions/auth-precheck";
|
||||
|
||||
export function HomeLoginForm() {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [needs2fa, setNeeds2fa] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pending, setPending] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [needs2fa, setNeeds2fa] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
async function onSubmit(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setPending(true);
|
||||
try {
|
||||
if (!needs2fa) {
|
||||
const pre = await precheckLogin(username, password);
|
||||
if (pre === "invalid") {
|
||||
setError("Invalid username or password");
|
||||
return;
|
||||
}
|
||||
if (pre === "twofactor") {
|
||||
setNeeds2fa(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const res = await signIn("credentials", {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
redirect: false,
|
||||
});
|
||||
if (!res || res.error) {
|
||||
setError(needs2fa ? "Invalid 2FA code" : "Invalid username or password");
|
||||
return;
|
||||
}
|
||||
window.location.href = "/";
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
}
|
||||
async function onSubmit(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setPending(true);
|
||||
try {
|
||||
if (!needs2fa) {
|
||||
const pre = await precheckLogin(username, password);
|
||||
if (pre === "invalid") {
|
||||
setError("Invalid username or password");
|
||||
return;
|
||||
}
|
||||
if (pre === "twofactor") {
|
||||
setNeeds2fa(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const res = await signIn("credentials", {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
redirect: false,
|
||||
});
|
||||
if (!res || res.error) {
|
||||
setError(
|
||||
needs2fa ? "Invalid 2FA code" : "Invalid username or password",
|
||||
);
|
||||
return;
|
||||
}
|
||||
window.location.href = "/";
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className="relative flex flex-col gap-6 rounded-lg bg-gray-100 p-3 dark:bg-gray-800"
|
||||
>
|
||||
<fieldset className="flex w-full flex-col gap-1">
|
||||
<label className="block font-semibold text-gray-700 dark:text-gray-200">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder=""
|
||||
className="focus:ring-0 relative z-10 w-full rounded border-4 border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
style={{ borderColor: "#e5e7eb", borderWidth: "4px" }}
|
||||
required
|
||||
disabled={needs2fa}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className="flex w-full flex-col gap-1">
|
||||
<label className="block font-semibold text-gray-700 dark:text-gray-200">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder=""
|
||||
className="focus:ring-0 relative z-10 w-full rounded border-4 border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
style={{ borderColor: "#e5e7eb", borderWidth: "4px" }}
|
||||
required
|
||||
disabled={needs2fa}
|
||||
/>
|
||||
</fieldset>
|
||||
{needs2fa ? (
|
||||
<fieldset className="flex w-full flex-col gap-1">
|
||||
<label className="block font-semibold text-gray-700 dark:text-gray-200">2FA Code</label>
|
||||
<input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="Enter your 2FA code"
|
||||
inputMode="numeric"
|
||||
className="focus:ring-0 relative z-10 w-full rounded border-4 border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
style={{ borderColor: "#e5e7eb", borderWidth: "4px" }}
|
||||
autoFocus
|
||||
/>
|
||||
</fieldset>
|
||||
) : null}
|
||||
{error ? <p className="m-0 text-center text-sm text-red-600 dark:text-red-400">{error}</p> : null}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="w-full cursor-pointer rounded-md border-2 p-2 text-base font-semibold text-white transition-colors disabled:opacity-50"
|
||||
style={{
|
||||
backgroundColor: "#27a44d",
|
||||
borderColor: "#34d399",
|
||||
}}
|
||||
>
|
||||
{pending ? "Please wait..." : needs2fa ? "Verify" : "Login"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
return (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className="relative flex flex-col gap-6 rounded-lg bg-gray-100 p-3 dark:bg-gray-800"
|
||||
>
|
||||
<fieldset className="flex w-full flex-col gap-1">
|
||||
<label className="block font-semibold text-gray-700 dark:text-gray-200">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder=""
|
||||
className="focus:ring-0 relative z-10 w-full rounded border-4 border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
style={{ borderColor: "#e5e7eb", borderWidth: "4px" }}
|
||||
required
|
||||
disabled={needs2fa}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className="flex w-full flex-col gap-1">
|
||||
<label className="block font-semibold text-gray-700 dark:text-gray-200">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder=""
|
||||
className="focus:ring-0 relative z-10 w-full rounded border-4 border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
style={{ borderColor: "#e5e7eb", borderWidth: "4px" }}
|
||||
required
|
||||
disabled={needs2fa}
|
||||
/>
|
||||
</fieldset>
|
||||
{needs2fa ? (
|
||||
<fieldset className="flex w-full flex-col gap-1">
|
||||
<label className="block font-semibold text-gray-700 dark:text-gray-200">
|
||||
2FA Code
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="Enter your 2FA code"
|
||||
inputMode="numeric"
|
||||
className="focus:ring-0 relative z-10 w-full rounded border-4 border-gray-200 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"
|
||||
style={{ borderColor: "#e5e7eb", borderWidth: "4px" }}
|
||||
/>
|
||||
</fieldset>
|
||||
) : null}
|
||||
{error ? (
|
||||
<p className="m-0 text-center text-sm text-red-600 dark:text-red-400">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="w-full cursor-pointer rounded-md border-2 p-2 text-base font-semibold text-white transition-colors disabled:opacity-50"
|
||||
style={{
|
||||
backgroundColor: "#27a44d",
|
||||
borderColor: "#34d399",
|
||||
}}
|
||||
>
|
||||
{pending ? "Please wait..." : needs2fa ? "Verify" : "Login"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,256 +1,308 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useActionState, useState } from "react";
|
||||
import { register } from "@/actions/register";
|
||||
|
||||
interface RegisterFormProps {
|
||||
hotelName: string;
|
||||
captcha: { provider: string; siteKey?: string };
|
||||
error?: string;
|
||||
hotelName: string;
|
||||
captcha: { provider: string; siteKey?: string };
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) {
|
||||
const t = useTranslations("pages.register");
|
||||
const showCaptcha = captcha.provider !== "none" && !!captcha.siteKey;
|
||||
const [serverError, formAction, isPending] = useActionState(register, null);
|
||||
const [termsAccepted, setTermsAccepted] = useState(false);
|
||||
const t = useTranslations("pages.register");
|
||||
const showCaptcha = captcha.provider !== "none" && !!captcha.siteKey;
|
||||
const [serverError, formAction, isPending] = useActionState(register, null);
|
||||
const [termsAccepted, setTermsAccepted] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[800px]">
|
||||
{/* Header Banner */}
|
||||
<div
|
||||
className="relative overflow-hidden bg-center bg-cover rounded-t-lg"
|
||||
style={{
|
||||
backgroundImage: "url(/assets/images/mybobba_banner.png)",
|
||||
height: "180px",
|
||||
maxHeight: "150px",
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 z-10 bg-black/30" />
|
||||
<div className="relative flex items-center justify-center w-full h-full z-20">
|
||||
<div className="relative" style={{ width: "140px", height: "140px" }}>
|
||||
<div className="absolute inset-0 rounded-full overflow-hidden" style={{ zIndex: -1 }}>
|
||||
<div
|
||||
className="w-full h-full"
|
||||
style={{ background: "black", filter: "blur(8px)", transform: "scale(1.2)", opacity: 0.6 }}
|
||||
/>
|
||||
</div>
|
||||
<Image
|
||||
src="/assets/images/FrankwithBag.gif"
|
||||
alt=""
|
||||
fill
|
||||
className="!static"
|
||||
style={{ objectFit: "contain", objectPosition: "center", width: "100%", height: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[800px]">
|
||||
{/* Header Banner */}
|
||||
<div
|
||||
className="relative overflow-hidden bg-center bg-cover rounded-t-lg"
|
||||
style={{
|
||||
backgroundImage: "url(/assets/images/mybobba_banner.png)",
|
||||
height: "180px",
|
||||
maxHeight: "150px",
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 z-10 bg-black/30" />
|
||||
<div className="relative flex items-center justify-center w-full h-full z-20">
|
||||
<div className="relative" style={{ width: "140px", height: "140px" }}>
|
||||
<div
|
||||
className="absolute inset-0 rounded-full overflow-hidden"
|
||||
style={{ zIndex: -1 }}
|
||||
>
|
||||
<div
|
||||
className="w-full h-full"
|
||||
style={{
|
||||
background: "black",
|
||||
filter: "blur(8px)",
|
||||
transform: "scale(1.2)",
|
||||
opacity: 0.6,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Image
|
||||
src="/assets/images/FrankwithBag.gif"
|
||||
alt=""
|
||||
fill
|
||||
className="!static"
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
objectPosition: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form
|
||||
action={formAction}
|
||||
className="p-4 flex flex-col gap-6"
|
||||
style={{
|
||||
backgroundColor: "color-mix(in srgb, var(--color-background) 50%, var(--color-surface))",
|
||||
borderRadius: "0 0 8px 8px",
|
||||
}}
|
||||
>
|
||||
{error && (
|
||||
<div className="p-3 rounded-md text-sm" style={{ backgroundColor: "#ef4444", color: "white" }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{serverError && (
|
||||
<div className="p-3 rounded-md text-sm" style={{ backgroundColor: "#ef4444", color: "white" }}>
|
||||
{serverError}
|
||||
</div>
|
||||
)}
|
||||
{/* Form */}
|
||||
<form
|
||||
action={formAction}
|
||||
className="p-4 flex flex-col gap-6"
|
||||
style={{
|
||||
backgroundColor:
|
||||
"color-mix(in srgb, var(--color-background) 50%, var(--color-surface))",
|
||||
borderRadius: "0 0 8px 8px",
|
||||
}}
|
||||
>
|
||||
{error && (
|
||||
<div
|
||||
className="p-3 rounded-md text-sm"
|
||||
style={{ backgroundColor: "#ef4444", color: "white" }}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{serverError && (
|
||||
<div
|
||||
className="p-3 rounded-md text-sm"
|
||||
style={{ backgroundColor: "#ef4444", color: "white" }}
|
||||
>
|
||||
{serverError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Username & Email */}
|
||||
<div className="w-full flex flex-col md:flex-row gap-4">
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="username"
|
||||
className="block font-semibold text-sm"
|
||||
style={{ color: "var(--color-text-readable, var(--color-text))" }}
|
||||
>
|
||||
{t("username")}
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder={t("usernamePlaceholder")}
|
||||
autoComplete="username"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="mail"
|
||||
className="block font-semibold text-sm"
|
||||
style={{ color: "var(--color-text-readable, var(--color-text))" }}
|
||||
>
|
||||
{t("email")}
|
||||
</label>
|
||||
<input
|
||||
id="mail"
|
||||
name="mail"
|
||||
type="email"
|
||||
placeholder={t("emailPlaceholder")}
|
||||
autoComplete="email"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p
|
||||
className="text-xs mt-1"
|
||||
style={{ color: "var(--color-text-muted-readable, var(--color-text-muted))" }}
|
||||
>
|
||||
Geen e-mail? Je kunt later via Discord verifiëren.
|
||||
</p>
|
||||
</div>
|
||||
{/* Username & Email */}
|
||||
<div className="w-full flex flex-col md:flex-row gap-4">
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="username"
|
||||
className="block font-semibold text-sm"
|
||||
style={{
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
}}
|
||||
>
|
||||
{t("username")}
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder={t("usernamePlaceholder")}
|
||||
autoComplete="username"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="mail"
|
||||
className="block font-semibold text-sm"
|
||||
style={{
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
}}
|
||||
>
|
||||
{t("email")}
|
||||
</label>
|
||||
<input
|
||||
id="mail"
|
||||
name="mail"
|
||||
type="email"
|
||||
placeholder={t("emailPlaceholder")}
|
||||
autoComplete="email"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p
|
||||
className="text-xs mt-1"
|
||||
style={{
|
||||
color:
|
||||
"var(--color-text-muted-readable, var(--color-text-muted))",
|
||||
}}
|
||||
>
|
||||
Geen e-mail? Je kunt later via Discord verifiëren.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Password & Confirm */}
|
||||
<div className="w-full flex flex-col md:flex-row gap-4">
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block font-semibold text-sm"
|
||||
style={{ color: "var(--color-text-readable, var(--color-text))" }}
|
||||
>
|
||||
{t("password")}
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={t("passwordPlaceholder")}
|
||||
autoComplete="new-password"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="password_confirmation"
|
||||
className="block font-semibold text-sm"
|
||||
style={{ color: "var(--color-text-readable, var(--color-text))" }}
|
||||
>
|
||||
{t("confirmPassword")}
|
||||
</label>
|
||||
<input
|
||||
id="password_confirmation"
|
||||
name="password_confirmation"
|
||||
type="password"
|
||||
placeholder={t("confirmPasswordPlaceholder")}
|
||||
autoComplete="new-password"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
{/* Password & Confirm */}
|
||||
<div className="w-full flex flex-col md:flex-row gap-4">
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block font-semibold text-sm"
|
||||
style={{
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
}}
|
||||
>
|
||||
{t("password")}
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={t("passwordPlaceholder")}
|
||||
autoComplete="new-password"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<fieldset className="flex flex-col w-full gap-1">
|
||||
<label
|
||||
htmlFor="password_confirmation"
|
||||
className="block font-semibold text-sm"
|
||||
style={{
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
}}
|
||||
>
|
||||
{t("confirmPassword")}
|
||||
</label>
|
||||
<input
|
||||
id="password_confirmation"
|
||||
name="password_confirmation"
|
||||
type="password"
|
||||
placeholder={t("confirmPasswordPlaceholder")}
|
||||
autoComplete="new-password"
|
||||
className="focus:ring-0 border-4 rounded w-full text-sm px-3 py-2 mt-1"
|
||||
style={{
|
||||
backgroundColor: "var(--color-background)",
|
||||
color: "var(--color-text-readable, var(--color-text))",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Terms */}
|
||||
<div
|
||||
className="rounded-md p-3 flex flex-col gap-3"
|
||||
style={{
|
||||
backgroundColor: "color-mix(in srgb, var(--color-background) 50%, var(--color-surface))",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<button
|
||||
type="button"
|
||||
role="checkbox"
|
||||
aria-checked={termsAccepted}
|
||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||
className="w-5 h-5 rounded border-2 flex items-center justify-center shrink-0 transition-colors"
|
||||
style={{
|
||||
borderColor: termsAccepted
|
||||
? "#27a44d"
|
||||
: "color-mix(in srgb, var(--color-text-muted) 30%, transparent)",
|
||||
backgroundColor: termsAccepted ? "#27a44d" : "transparent",
|
||||
}}
|
||||
>
|
||||
{termsAccepted && (
|
||||
<svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<input type="hidden" name="terms" value={termsAccepted ? "1" : ""} />
|
||||
<span
|
||||
className="font-semibold cursor-pointer"
|
||||
style={{ color: "var(--color-text-readable, var(--color-text))" }}
|
||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||
>
|
||||
{t("termsAccept", { hotel: hotelName })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Terms */}
|
||||
<div
|
||||
className="rounded-md p-3 flex flex-col gap-3"
|
||||
style={{
|
||||
backgroundColor:
|
||||
"color-mix(in srgb, var(--color-background) 50%, var(--color-surface))",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<button
|
||||
type="button"
|
||||
role="checkbox"
|
||||
aria-checked={termsAccepted}
|
||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||
className="w-5 h-5 rounded border-2 flex items-center justify-center shrink-0 transition-colors"
|
||||
style={{
|
||||
borderColor: termsAccepted
|
||||
? "#27a44d"
|
||||
: "color-mix(in srgb, var(--color-text-muted) 30%, transparent)",
|
||||
backgroundColor: termsAccepted ? "#27a44d" : "transparent",
|
||||
}}
|
||||
>
|
||||
{termsAccepted && (
|
||||
<svg
|
||||
className="w-3 h-3 text-white"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<input
|
||||
type="hidden"
|
||||
name="terms"
|
||||
value={termsAccepted ? "1" : ""}
|
||||
/>
|
||||
<span
|
||||
className="font-semibold cursor-pointer"
|
||||
style={{ color: "var(--color-text-readable, var(--color-text))" }}
|
||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||
>
|
||||
{t("termsAccept", { hotel: hotelName })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Captcha */}
|
||||
{showCaptcha && captcha.provider === "turnstile" && (
|
||||
<div className="cf-turnstile" data-sitekey={captcha.siteKey} />
|
||||
)}
|
||||
{showCaptcha && captcha.provider === "recaptcha" && (
|
||||
<div className="g-recaptcha" data-sitekey={captcha.siteKey} />
|
||||
)}
|
||||
{/* Captcha */}
|
||||
{showCaptcha && captcha.provider === "turnstile" && (
|
||||
<div className="cf-turnstile" data-sitekey={captcha.siteKey} />
|
||||
)}
|
||||
{showCaptcha && captcha.provider === "recaptcha" && (
|
||||
<div className="g-recaptcha" data-sitekey={captcha.siteKey} />
|
||||
)}
|
||||
|
||||
{/* Submit */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded p-2 text-white font-semibold transition-all duration-200 text-base"
|
||||
style={{
|
||||
backgroundColor: "#27a44d",
|
||||
border: "2px solid #34c661",
|
||||
}}
|
||||
>
|
||||
{isPending ? t("creatingAccount") : t("createAccount")}
|
||||
</button>
|
||||
{/* Submit */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded p-2 text-white font-semibold transition-all duration-200 text-base"
|
||||
style={{
|
||||
backgroundColor: "#27a44d",
|
||||
border: "2px solid #34c661",
|
||||
}}
|
||||
>
|
||||
{isPending ? t("creatingAccount") : t("createAccount")}
|
||||
</button>
|
||||
|
||||
<div className="text-center">
|
||||
<Link
|
||||
href="/login"
|
||||
className="text-sm font-semibold hover:underline"
|
||||
style={{ color: "var(--color-primary-readable, var(--color-primary))" }}
|
||||
>
|
||||
{t("alreadyHaveAccount")}
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
<div className="text-center">
|
||||
<Link
|
||||
href="/login"
|
||||
className="text-sm font-semibold hover:underline"
|
||||
style={{
|
||||
color: "var(--color-primary-readable, var(--color-primary))",
|
||||
}}
|
||||
>
|
||||
{t("alreadyHaveAccount")}
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user