| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- "use client";
- import { useState } from "react";
- import { useMutation } from "@tanstack/react-query";
- import { RECOVERY_CODE_LENGTH } from "@/lib/constants";
- export default function RecoverPage() {
- const [code, setCode] = useState("");
- const claimMutation = useMutation({
- mutationFn: async (recoveryCode: string) => {
- const res = await fetch("/api/auth/recovery/claim", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ code: recoveryCode }),
- });
- const data = (await res.json()) as {
- ok?: boolean;
- error?: string;
- };
- if (!res.ok) {
- throw new Error(data.error || "Recovery failed");
- }
- return data as { ok: true };
- },
- onSuccess: () => {
- // Full document load so the new auth cookie set by the server is sent
- // with the next request (router.push() keeps the SPA navigation in the
- // existing fetch context and may race the cookie write).
- window.location.assign("/");
- },
- });
- function handleSubmit(e: React.FormEvent) {
- e.preventDefault();
- const trimmed = code.trim();
- if (trimmed.length === 0) return;
- claimMutation.mutate(trimmed);
- }
- return (
- <>
- <h1 className="mb-6 text-2xl font-bold text-center">Recover Your Account</h1>
- <form onSubmit={handleSubmit} className="space-y-4">
- <div>
- <label htmlFor="recovery-code" className="block text-sm font-medium mb-1">
- Recovery code
- </label>
- <input
- id="recovery-code"
- type="text"
- value={code}
- onChange={(e) => setCode(e.target.value)}
- maxLength={RECOVERY_CODE_LENGTH}
- placeholder="Enter your 24-character code"
- className="w-full rounded-lg border border-foreground/20 bg-background px-3 py-2 font-mono text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-blue-500"
- autoFocus
- autoComplete="off"
- spellCheck={false}
- />
- </div>
- {claimMutation.error && (
- <p className="text-sm text-red-500" role="alert">
- {claimMutation.error instanceof Error
- ? claimMutation.error.message
- : "Something went wrong."}
- </p>
- )}
- <button
- type="submit"
- disabled={claimMutation.isPending || code.trim().length === 0}
- className="w-full rounded-lg bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
- >
- {claimMutation.isPending ? "Recovering..." : "Recover account"}
- </button>
- </form>
- <p className="mt-6 text-center text-sm text-foreground/50">
- Don't have an account?{" "}
- <a href="/login" className="text-blue-500 hover:underline">
- Create one
- </a>
- </p>
- </>
- );
- }
|