All components

Auth Account Delete

Auth Account Delete is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

HextaUI MIT other

What it is

Auth Account Delete is a React and Tailwind CSS component from HextaUI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

No packages beyond the project baseline

Licence

This component comes from HextaUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Auth Account Delete

registry/new-york/blocks/auth/auth-account-delete.tsx
"use client";

import { AlertTriangle, Loader2, Trash2 } from "lucide-react";
import { useCallback, useState } from "react";
import { cn } from "@/lib/utils";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/registry/new-york/ui/alert-dialog";
import { Button } from "@/registry/new-york/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/registry/new-york/ui/card";
import { Checkbox } from "@/registry/new-york/ui/checkbox";
import {
  Field,
  FieldContent,
  FieldError,
  FieldLabel,
} from "@/registry/new-york/ui/field";
import {
  InputGroup,
  InputGroupInput,
} from "@/registry/new-york/ui/input-group";
import { Label } from "../../ui/label";

export interface AuthAccountDeleteProps {
  onDelete?: (data: { confirmText: string }) => void;
  className?: string;
  isLoading?: boolean;
  errors?: {
    confirmText?: string;
    general?: string;
  };
  confirmText?: string;
  dataSummary?: {
    projects?: number;
    files?: number;
    storage?: string;
  };
}

interface WarningBannerProps {
  className?: string;
}

function WarningBanner({ className }: WarningBannerProps) {
  return (
    <div
      className={cn(
        "flex items-start gap-3 rounded-lg border border-destructive/50 bg-destructive/5 p-4",
        className
      )}
    >
      <AlertTriangle
        aria-hidden="true"
        className="size-5 shrink-0 text-destructive"
      />
      <div className="flex flex-1 flex-col gap-2">
        <p className="font-medium text-destructive text-sm">
          This action cannot be undone
        </p>
        <p className="text-muted-foreground text-xs">
          This will permanently delete your account and remove all of your data
          from our servers. This action is irreversible.
        </p>
      </div>
    </div>
  );
}

interface DataSummaryProps {
  dataSummary: {
    projects?: number;
    files?: number;
    storage?: string;
  };
}

function DataSummary({ dataSummary }: DataSummaryProps) {
  return (
    <div className="flex flex-col gap-2 rounded-lg border bg-muted/50 p-4">
      <h3 className="font-medium text-sm">Data that will be deleted:</h3>
      <ul className="flex flex-col gap-1 text-muted-foreground text-xs">
        {dataSummary.projects !== undefined && (
          <li>
            •{" "}
            <span className="tabular-nums">
              {dataSummary.projects} project
              {dataSummary.projects !== 1 ? "s" : ""}
            </span>
          </li>
        )}
        {dataSummary.files !== undefined && (
          <li>
            •{" "}
            <span className="tabular-nums">
              {dataSummary.files} file{dataSummary.files !== 1 ? "s" : ""}
            </span>
          </li>
        )}
        {dataSummary.storage && <li>• {dataSummary.storage} of storage</li>}
        <li>• All account settings and preferences</li>
        <li>• All personal information</li>
      </ul>
    </div>
  );
}

interface ConfirmTextFieldProps {
  id: string;
  value: string;
  error?: string;
  confirmText: string;
  onChange: (value: string) => void;
}

function ConfirmTextField({
  id,
  value,
  error,
  confirmText,
  onChange,
}: ConfirmTextFieldProps) {
  return (
    <Field data-invalid={!!error}>
      <FieldLabel htmlFor={id}>
        Type <span className="font-mono">{confirmText}</span> to confirm
        <span aria-label="required" className="text-destructive">
          {" "}
          *
        </span>
      </FieldLabel>
      <FieldContent>
        <InputGroup aria-invalid={!!error}>
          <InputGroupInput
            aria-describedby={error ? `${id}-error` : undefined}
            aria-invalid={!!error}
            id={id}
            name="confirmText"
            onChange={(e) => onChange(e.target.value)}
            placeholder={`${confirmText}…`}
            required
            style={{ fontSize: "16px" }}
            type="text"
            value={value}
          />
        </InputGroup>
        {error && (
          <FieldError id={`${id}-error`} role="alert">
            {error}
          </FieldError>
        )}
      </FieldContent>
    </Field>
  );
}

interface AcknowledgmentCheckboxProps {
  id: string;
  checked: boolean;
  label: string;
  onCheckedChange: (checked: boolean) => void;
}

function AcknowledgmentCheckbox({
  id,
  checked,
  label,
  onCheckedChange,
}: AcknowledgmentCheckboxProps) {
  return (
    <div className="flex items-center gap-2">
      <Checkbox
        checked={checked}
        id={id}
        onCheckedChange={(checked) => onCheckedChange(checked === true)}
      />
      <Label
        className="cursor-pointer text-sm leading-relaxed peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
        htmlFor={id}
      >
        {label}
      </Label>
    </div>
  );
}

interface DeleteConfirmationDialogProps {
  onOpenChange: (open: boolean) => void;
  typedConfirm: string;
  acknowledgeDataLoss: boolean;
  acknowledgeIrreversible: boolean;
  confirmText: string;
  isLoading: boolean;
  isFormValid: boolean;
  confirmTextError?: string;
  onTypedConfirmChange: (value: string) => void;
  onAcknowledgeDataLossChange: (checked: boolean) => void;
  onAcknowledgeIrreversibleChange: (checked: boolean) => void;
  onDelete: () => void;
  onReset: () => void;
}

function DeleteConfirmationDialog({
  onOpenChange,
  typedConfirm,
  acknowledgeDataLoss,
  acknowledgeIrreversible,
  confirmText,
  isLoading,
  isFormValid,
  confirmTextError,
  onTypedConfirmChange,
  onAcknowledgeDataLossChange,
  onAcknowledgeIrreversibleChange,
  onDelete,
  onReset,
}: DeleteConfirmationDialogProps) {
  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (e.key === "Enter" && isFormValid && !isLoading) {
        e.preventDefault();
        onDelete();
      }
      if (e.key === "Escape" && !isLoading) {
        onOpenChange(false);
        onReset();
      }
    },
    [isFormValid, isLoading, onDelete, onOpenChange, onReset]
  );

  const completedSteps = [
    typedConfirm === confirmText,
    acknowledgeDataLoss,
    acknowledgeIrreversible,
  ].filter(Boolean).length;

  return (
    <AlertDialogContent
      className="max-w-md"
      onKeyDown={handleKeyDown}
      style={{ overscrollBehavior: "contain" }}
    >
      <AlertDialogHeader>
        <AlertDialogTitle className="flex items-center gap-2">
          <AlertTriangle
            aria-hidden="true"
            className="size-5 text-destructive"
          />
          Confirm account deletion
        </AlertDialogTitle>
        <AlertDialogDescription>
          This action is permanent and cannot be undone. Please confirm by
          typing <span className="font-mono font-semibold">{confirmText}</span>{" "}
          below.
        </AlertDialogDescription>
      </AlertDialogHeader>

      <div className="flex flex-col gap-4 py-4">
        <ConfirmTextField
          confirmText={confirmText}
          error={confirmTextError}
          id="delete-account-confirm"
          onChange={onTypedConfirmChange}
          value={typedConfirm}
        />

        <div className="flex flex-col gap-3">
          <AcknowledgmentCheckbox
            checked={acknowledgeDataLoss}
            id="acknowledge-data-loss"
            label="I understand that all my data will be permanently deleted and cannot be recovered"
            onCheckedChange={onAcknowledgeDataLossChange}
          />

          <AcknowledgmentCheckbox
            checked={acknowledgeIrreversible}
            id="acknowledge-irreversible"
            label="I understand this action is irreversible"
            onCheckedChange={onAcknowledgeIrreversibleChange}
          />
        </div>
      </div>

      <AlertDialogFooter>
        <AlertDialogCancel
          className="min-h-[32px] min-w-[32px] touch-manipulation"
          onClick={onReset}
        >
          Cancel
        </AlertDialogCancel>
        <AlertDialogAction
          aria-busy={isLoading}
          className="min-h-[32px] min-w-[32px] touch-manipulation bg-destructive text-destructive-foreground hover:bg-destructive/90"
          data-loading={isLoading}
          disabled={!isFormValid || isLoading}
          onClick={onDelete}
        >
          {isLoading ? (
            <>
              <Loader2 aria-hidden="true" className="size-4 animate-spin" />
              Deleting…
            </>
          ) : (
            <>
              <Trash2 aria-hidden="true" className="size-4" />
              Delete account permanently
            </>
          )}
        </AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  );
}

export default function AuthAccountDelete({
  onDelete,
  className,
  isLoading = false,
  errors,
  confirmText = "DELETE",
  dataSummary,
}: AuthAccountDeleteProps) {
  const [typedConfirm, setTypedConfirm] = useState("");
  const [acknowledgeDataLoss, setAcknowledgeDataLoss] = useState(false);
  const [acknowledgeIrreversible, setAcknowledgeIrreversible] = useState(false);
  const [isDialogOpen, setIsDialogOpen] = useState(false);

  const isFormValid =
    typedConfirm === confirmText &&
    acknowledgeDataLoss &&
    acknowledgeIrreversible;

  const handleDelete = useCallback(() => {
    if (!isFormValid) {
      return;
    }
    onDelete?.({
      confirmText: typedConfirm,
    });
  }, [isFormValid, typedConfirm, onDelete]);

  const handleReset = useCallback(() => {
    setTypedConfirm("");
    setAcknowledgeDataLoss(false);
    setAcknowledgeIrreversible(false);
  }, []);

  const handleDialogOpenChange = useCallback(
    (open: boolean) => {
      setIsDialogOpen(open);
      if (!open) {
        handleReset();
      }
    },
    [handleReset]
  );

  const confirmTextError = errors?.confirmText;
  const generalError = errors?.general;

  return (
    <Card
      className={cn(
        "w-full max-w-sm border-destructive/50 shadow-xs",
        className
      )}
    >
      <CardHeader>
        <CardTitle className="flex items-center gap-2 text-destructive">
          Delete account
        </CardTitle>
        <CardDescription>
          Permanently delete your account and all associated data
        </CardDescription>
      </CardHeader>
      <CardContent>
        <div className="flex flex-col gap-6">
          {generalError && (
            <div
              aria-live="polite"
              className="rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm"
              role="alert"
            >
              {generalError}
            </div>
          )}

          <WarningBanner />

          {dataSummary && <DataSummary dataSummary={dataSummary} />}

          <AlertDialog
            onOpenChange={handleDialogOpenChange}
            open={isDialogOpen}
          >
            <AlertDialogTrigger asChild>
              <Button
                className="min-h-[32px] w-full touch-manipulation"
                type="button"
                variant="destructive"
              >
                <Trash2 aria-hidden="true" className="size-4" />
                Delete my account
              </Button>
            </AlertDialogTrigger>
            <DeleteConfirmationDialog
              acknowledgeDataLoss={acknowledgeDataLoss}
              acknowledgeIrreversible={acknowledgeIrreversible}
              confirmText={confirmText}
              confirmTextError={confirmTextError}
              isFormValid={isFormValid}
              isLoading={isLoading}
              onAcknowledgeDataLossChange={setAcknowledgeDataLoss}
              onAcknowledgeIrreversibleChange={setAcknowledgeIrreversible}
              onDelete={handleDelete}
              onOpenChange={handleDialogOpenChange}
              onReset={handleReset}
              onTypedConfirmChange={setTypedConfirm}
              typedConfirm={typedConfirm}
            />
          </AlertDialog>
        </div>
      </CardContent>
    </Card>
  );
}

Similar components