Register: auto sign-in to /me and speed/cleanup improvements
- Send the verification email after the response via after() so it never blocks sign-up - Invalidate the cached login lookup right after account creation so the automatic sign-in always finds the fresh row - Auto sign in with the submitted credentials and go straight to /me, with a fallback to /login?registered=1 if sign-in is refused (e.g. email verification required) - Cache the register page's online/latest user queries to cut DB load under traffic - Fix terms checkbox label double-toggle cancelling the selection - Add pages.register.redirecting translation to all locales
This commit is contained in:
1 parent
2d09a4a92c
commit
f25a26a93e
25 files changed
+143
-53
No files matched your search
@@ -3,9 +3,11 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Script from "next/script";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useActionState, useState } from "react";
|
||||
import { register } from "@/actions/register";
|
||||
import { useActionState, useEffect, useRef, useState } from "react";
|
||||
import { type RegisterState, register } from "@/actions/register";
|
||||
import { signInWithTransientRetry } from "@/lib/auth/sign-in-retry";
|
||||
|
||||
interface RegisterFormProps {
|
||||
hotelName: string;
|
||||
@@ -40,13 +42,56 @@ export function RegisterForm({
|
||||
}: RegisterFormProps) {
|
||||
const t = useTranslations("pages.register");
|
||||
const showCaptcha = captcha.provider !== "none" && !!captcha.siteKey;
|
||||
const [serverError, formAction, isPending] = useActionState(register, null);
|
||||
const [state, formAction, isPending] = useActionState<
|
||||
RegisterState,
|
||||
FormData
|
||||
>(register, { error: null, ok: false });
|
||||
const [termsAccepted, setTermsAccepted] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const [signingIn, setSigningIn] = useState(false);
|
||||
const credentialsRef = useRef<{ username: string; password: string } | null>(
|
||||
null,
|
||||
);
|
||||
const autoLoginStartedRef = useRef(false);
|
||||
const strength = passwordStrength(password);
|
||||
|
||||
// Successful sign-up: sign in with the submitted credentials and go
|
||||
// straight to /me. If the automatic sign-in is refused (e.g. email
|
||||
// verification required), fall back to the regular login page.
|
||||
useEffect(() => {
|
||||
if (!state.ok || autoLoginStartedRef.current) return;
|
||||
autoLoginStartedRef.current = true;
|
||||
|
||||
const creds = credentialsRef.current;
|
||||
if (!creds) {
|
||||
window.location.href = "/login?registered=1";
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
setSigningIn(true);
|
||||
(async () => {
|
||||
try {
|
||||
const res = await signInWithTransientRetry(() =>
|
||||
signIn("credentials", { ...creds, code: "", redirect: false }),
|
||||
);
|
||||
if (!cancelled && res && !res.error) {
|
||||
window.location.href = "/me";
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
/* fall through to fallback below */
|
||||
}
|
||||
if (!cancelled) window.location.href = "/login?registered=1";
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [state.ok]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[800px]">
|
||||
{showCaptcha && captcha.provider === "turnstile" ? (
|
||||
@@ -112,7 +157,13 @@ export function RegisterForm({
|
||||
|
||||
{/* Form */}
|
||||
<form
|
||||
action={formAction}
|
||||
action={(fd) => {
|
||||
credentialsRef.current = {
|
||||
username: String(fd.get("username") ?? "").trim(),
|
||||
password: String(fd.get("password") ?? ""),
|
||||
};
|
||||
formAction(fd);
|
||||
}}
|
||||
className="p-6 flex flex-col gap-6"
|
||||
style={{
|
||||
backgroundColor:
|
||||
@@ -120,12 +171,12 @@ export function RegisterForm({
|
||||
borderRadius: "0 0 12px 12px",
|
||||
}}
|
||||
>
|
||||
{(error || serverError) && (
|
||||
{(error || state.error) && (
|
||||
<div
|
||||
className="animate-fade-in-up p-3 rounded-lg text-sm font-semibold text-white"
|
||||
style={{ backgroundColor: "var(--color-danger)" }}
|
||||
>
|
||||
{error || serverError}
|
||||
{error || state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -301,14 +352,7 @@ export function RegisterForm({
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="font-medium text-sm text-[var(--color-text-readable)]"
|
||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setTermsAccepted(!termsAccepted);
|
||||
}
|
||||
}}
|
||||
className="font-medium text-sm text-[var(--color-text-readable)] cursor-pointer"
|
||||
>
|
||||
{t("termsAccept", { hotel: hotelName })}
|
||||
</label>
|
||||
@@ -332,7 +376,7 @@ export function RegisterForm({
|
||||
{/* Submit */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
disabled={isPending || signingIn}
|
||||
className="btn-shine w-full rounded-xl font-extrabold text-sm py-3.5 transition-all duration-200 hover:scale-[1.02] active:scale-95 shadow-lg disabled:opacity-60"
|
||||
style={{
|
||||
background: "var(--color-primary)",
|
||||
@@ -341,7 +385,7 @@ export function RegisterForm({
|
||||
"0 4px 24px color-mix(in srgb, var(--color-primary) 35%, transparent)",
|
||||
}}
|
||||
>
|
||||
{isPending ? (
|
||||
{isPending || signingIn ? (
|
||||
<span className="inline-flex items-center justify-center gap-2">
|
||||
<svg
|
||||
className="animate-spin h-4 w-4"
|
||||
@@ -364,7 +408,7 @@ export function RegisterForm({
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
||||
/>
|
||||
</svg>
|
||||
{t("creatingAccount")}
|
||||
{signingIn ? t("redirecting") : t("creatingAccount")}
|
||||
</span>
|
||||
) : (
|
||||
t("createAccount")
|
||||
|
||||
Reference in new issue
Block a user