215 lines
8.6 KiB
TypeScript
215 lines
8.6 KiB
TypeScript
"use client";
|
|
|
|
import { Eye, EyeOff, Lock, Mail, User } from "lucide-react";
|
|
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;
|
|
}
|
|
|
|
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 [showPassword, setShowPassword] = useState(false);
|
|
|
|
return (
|
|
<div className="animate-fade-in">
|
|
<div
|
|
className="relative overflow-hidden rounded-3xl border border-white/[0.06] bg-[#09090b] shadow-2xl p-8 md:p-10"
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-[#5AB4C6]/5 via-transparent to-transparent" />
|
|
<div className="absolute -top-40 -right-40 w-[500px] h-[500px] bg-[#5AB4C6]/5 rounded-full blur-[120px]" />
|
|
|
|
<div className="relative">
|
|
{/* Header */}
|
|
<div className="text-center mb-8">
|
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-[#5AB4C6]/10 border border-[#5AB4C6]/20">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#5AB4C6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
|
<polyline points="10 17 15 12 10 7" />
|
|
<line x1="15" x2="3" y1="12" y2="12" />
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-2xl font-black text-white tracking-tight m-0">
|
|
{t("title")}
|
|
</h1>
|
|
<p className="mt-1.5 text-sm text-white/40">
|
|
{t("subtitle", { hotel: hotelName })}
|
|
</p>
|
|
</div>
|
|
|
|
<form action={formAction} className="space-y-5 max-w-md mx-auto">
|
|
{(error || serverError) && (
|
|
<div className="rounded-xl bg-red-500/10 border border-red-500/20 px-4 py-3">
|
|
<p className="text-xs font-bold text-red-400 text-center m-0">
|
|
{error || serverError}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
|
Username
|
|
</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
|
<User size={15} />
|
|
</div>
|
|
<input
|
|
id="username"
|
|
name="username"
|
|
type="text"
|
|
placeholder={t("usernamePlaceholder")}
|
|
autoComplete="username"
|
|
className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
|
Email
|
|
</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
|
<Mail size={15} />
|
|
</div>
|
|
<input
|
|
id="mail"
|
|
name="mail"
|
|
type="email"
|
|
placeholder={t("emailPlaceholder")}
|
|
autoComplete="email"
|
|
className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
|
Password
|
|
</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
|
<Lock size={15} />
|
|
</div>
|
|
<input
|
|
id="password"
|
|
name="password"
|
|
type={showPassword ? "text" : "password"}
|
|
placeholder={t("passwordPlaceholder")}
|
|
autoComplete="new-password"
|
|
className="w-full pl-10 pr-10 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white transition-colors"
|
|
>
|
|
{showPassword ? <EyeOff size={15} /> : <Eye size={15} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
|
Confirm
|
|
</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
|
<Lock size={15} />
|
|
</div>
|
|
<input
|
|
id="password_confirmation"
|
|
name="password_confirmation"
|
|
type="password"
|
|
placeholder={t("confirmPasswordPlaceholder")}
|
|
autoComplete="new-password"
|
|
className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Terms */}
|
|
<div className="flex items-start gap-3 rounded-xl border border-white/10 bg-white/[0.03] p-4">
|
|
<button
|
|
type="button"
|
|
role="checkbox"
|
|
aria-checked={termsAccepted}
|
|
onClick={() => setTermsAccepted(!termsAccepted)}
|
|
className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-md border-2 transition-all"
|
|
style={{
|
|
borderColor: termsAccepted ? "#5AB4C6" : "rgba(255,255,255,0.15)",
|
|
backgroundColor: termsAccepted ? "#5AB4C6" : "transparent",
|
|
}}
|
|
>
|
|
{termsAccepted && (
|
|
<svg className="h-3 w-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" : ""} />
|
|
<label
|
|
className="text-sm cursor-pointer select-none text-white/60"
|
|
onClick={() => setTermsAccepted(!termsAccepted)}
|
|
>
|
|
{t("termsAccept", { hotel: hotelName })}
|
|
</label>
|
|
</div>
|
|
|
|
{/* Captcha */}
|
|
{showCaptcha && captcha.provider === "turnstile" && (
|
|
<div className="flex justify-center">
|
|
<div className="cf-turnstile" data-sitekey={captcha.siteKey} />
|
|
</div>
|
|
)}
|
|
{showCaptcha && captcha.provider === "recaptcha" && (
|
|
<div className="flex justify-center">
|
|
<div className="g-recaptcha" data-sitekey={captcha.siteKey} />
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isPending}
|
|
className="w-full flex items-center justify-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3.5 rounded-lg transition-all disabled:opacity-50 active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20"
|
|
>
|
|
{isPending ? (
|
|
<span className="flex items-center justify-center gap-2">
|
|
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
</svg>
|
|
{t("creatingAccount")}
|
|
</span>
|
|
) : t("createAccount")}
|
|
</button>
|
|
|
|
<div className="text-center pt-2">
|
|
<p className="text-sm text-white/40">
|
|
{t("alreadyHaveAccount")}{" "}
|
|
<Link href="/login" className="font-bold text-[#5AB4C6] hover:underline">
|
|
{t("signIn")}
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|