Ctrl K
ComunidadGuías…

src/components/auth/restore-account-card.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { CalendarClock } from "lucide-react";

import { useAuth } from "@/contexts/AuthContext";
import { Button } from "@/components/ui/button";
import { ButtonSpinner } from "@/components/ui/button-spinner";
import { StatusMessage } from "@/components/ui/status-message";
import {
  StandaloneCard,
  StandaloneCardHeader,
} from "@/components/layout/standalone-card";
import { getApiErrorMessage } from "@/lib/api/error";
import { DEFAULT_POST_AUTH_REDIRECT } from "@/lib/auth-redirect";
import { formatPurgeDay, parsePurgeDate } from "@/lib/purge-date";

const RESTORE_FAILED = "Couldn't restore your account. Please try again.";

/**
 * The offer at the end of a sign-in into an account that is waiting to be purged,
 * and the click that undoes the deletion.
 *
 * Nothing has happened yet by the time this renders: the identity was verified, the
 * API answered `deletion_pending`, and no session was issued and no row was touched.
 * Signing in must not quietly cancel a deletion somebody deliberately asked for, so
 * the restore is its own decision and this screen is where it is made.
 */
export function RestoreAccountCard() {
  const { restore, restoreAccount } = useAuth();
  const router = useRouter();
  const [isRestoring, setIsRestoring] = useState(false);
  const [error, setError] = useState<string | null>(null);

  if (!restore) {
    return null;
  }

  const purgeOn = parsePurgeDate(restore.purgeAfter);

  const handleRestore = async () => {
    setIsRestoring(true);
    setError(null);
    try {
      await restoreAccount(restore.restoreToken);
      router.push(DEFAULT_POST_AUTH_REDIRECT);
      // Deliberately still `isRestoring`: the navigation is under way, and the
      // token has been spent either way, so re-arming the button would only offer
      // a second restore that cannot succeed.
    } catch (err) {
      // Worth showing verbatim. The API distinguishes a spent or expired token
      // from an account whose grace period ran out while this was on screen, and
      // the second is the one thing the diver most needs to be told plainly.
      setError(getApiErrorMessage(err, RESTORE_FAILED));
      setIsRestoring(false);
    }
  };

  return (
    <StandaloneCard className="text-center">
      <StandaloneCardHeader
        icon={CalendarClock}
        iconClassName="text-muted-foreground"
        title="Your account is scheduled for deletion"
      />
      <div className="space-y-4">
        <p className="text-sm text-muted-foreground">
          <span className="font-medium text-foreground">{restore.email}</span>{" "}
          was deleted, and nothing has been erased yet.{" "}
          {purgeOn ? (
            <>
              Your dives, dive drafts, dive sites, trips, courses,
              certifications, gear, contacts, people and tags are erased for
              good on{" "}
              <span className="font-medium text-foreground">
                {formatPurgeDay(purgeOn)}
              </span>
              .
            </>
          ) : (
            "Your dives, dive drafts, dive sites, trips, courses, certifications, gear, contacts, people and tags are erased for good once this instance's grace period runs out."
          )}
        </p>
        <p className="text-sm text-muted-foreground">
          Restoring brings all of it back and signs you in. Leave it, and the
          date above decides.
        </p>

        <StatusMessage variant="error">{error}</StatusMessage>

        <Button
          className="w-full"
          onClick={handleRestore}
          disabled={isRestoring}
        >
          {isRestoring ? (
            <span className="flex items-center space-x-2">
              <ButtonSpinner />
              <span>Restoring...</span>
            </span>
          ) : (
            "Restore my account"
          )}
        </Button>

        {/* The one-shot warning, and it is not boilerplate: the six-digit code
            claims its request row before the account is even resolved, so a code
            spent on reaching this screen is spent. Whichever way in was used, this
            offer lives in memory and a reload has nothing to come back to. */}
        <p className="text-xs text-muted-foreground">
          This offer belongs to the sign-in you just completed - if you leave or
          reload this page, sign in again to get back here.
        </p>

        <p className="text-sm text-muted-foreground">
          <Link href="/" className="underline hover:text-foreground">
            No thanks, leave my account deleted
          </Link>
        </p>
      </div>
    </StandaloneCard>
  );
}