All components

Settings Domains

Settings Domains 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

Settings Domains 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.

Settings Domains

registry/new-york/blocks/settings/settings-domains.tsx
"use client";

import { Check, Copy, Globe, Plus, RefreshCw, Trash2, X } from "lucide-react";
import { 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 { Badge } from "@/registry/new-york/ui/badge";
import { Button } from "@/registry/new-york/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/registry/new-york/ui/card";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/registry/new-york/ui/dialog";
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/registry/new-york/ui/field";
import {
  InputGroup,
  InputGroupInput,
} from "@/registry/new-york/ui/input-group";

export interface Domain {
  id: string;
  domain: string;
  status: "verified" | "pending" | "failed";
  sslEnabled: boolean;
  verifiedAt?: Date;
  dnsRecords?: {
    type: string;
    name: string;
    value: string;
  }[];
}

export interface SettingsDomainsProps {
  domains?: Domain[];
  onCreate?: (domain: string) => Promise<Domain>;
  onDelete?: (id: string) => Promise<void>;
  onVerify?: (id: string) => Promise<void>;
  onToggleSSL?: (id: string) => Promise<void>;
  className?: string;
}

function formatDate(date: Date): string {
  return new Intl.DateTimeFormat("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  }).format(date);
}

export default function SettingsDomains({
  domains = [],
  onCreate,
  onDelete,
  onVerify,
  onToggleSSL,
  className,
}: SettingsDomainsProps) {
  const [createDialogOpen, setCreateDialogOpen] = useState(false);
  const [newDomain, setNewDomain] = useState("");
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [copiedDomain, setCopiedDomain] = useState<string | null>(null);

  const handleCreate = async () => {
    setErrors({});

    if (!newDomain.trim()) {
      setErrors({ domain: "Domain is required" });
      return;
    }

    try {
      await onCreate?.(newDomain.trim());
      setNewDomain("");
      setCreateDialogOpen(false);
    } catch (error) {
      setErrors({
        domain: error instanceof Error ? error.message : "Failed to add domain",
      });
    }
  };

  const copyToClipboard = async (text: string) => {
    await navigator.clipboard.writeText(text);
    setCopiedDomain(text);
    setTimeout(() => setCopiedDomain(null), 2000);
  };

  const getStatusBadge = (status: Domain["status"]) => {
    switch (status) {
      case "verified":
        return (
          <Badge className="flex items-center gap-1 text-xs" variant="default">
            <Check className="size-3" />
            <span>Verified</span>
          </Badge>
        );
      case "pending":
        return (
          <Badge className="text-xs" variant="secondary">
            Pending
          </Badge>
        );
      case "failed":
        return (
          <Badge className="text-xs" variant="destructive">
            Failed
          </Badge>
        );
    }
  };

  return (
    <Card className={cn("w-full shadow-xs", className)}>
      <CardHeader>
        <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
          <div className="flex min-w-0 flex-1 flex-col gap-2">
            <CardTitle className="wrap-break-word">Custom Domains</CardTitle>
            <CardDescription className="wrap-break-word">
              Manage your custom domains and SSL certificates
            </CardDescription>
          </div>
          <Dialog onOpenChange={setCreateDialogOpen} open={createDialogOpen}>
            <DialogTrigger asChild>
              <Button className="w-full shrink-0 sm:w-auto" type="button">
                <Plus className="size-4" />
                <span className="whitespace-nowrap">Add Domain</span>
              </Button>
            </DialogTrigger>
            <DialogContent className="sm:max-w-md">
              <DialogHeader>
                <DialogTitle>Add Custom Domain</DialogTitle>
                <DialogDescription>
                  Add a custom domain to your account
                </DialogDescription>
              </DialogHeader>
              <div className="flex flex-col gap-4">
                <Field>
                  <FieldLabel htmlFor="domain">
                    Domain <span className="text-destructive">*</span>
                  </FieldLabel>
                  <FieldContent>
                    <InputGroup>
                      <InputGroupInput
                        id="domain"
                        onChange={(e) => setNewDomain(e.target.value)}
                        placeholder="example.com"
                        type="text"
                        value={newDomain}
                      />
                    </InputGroup>
                    {errors.domain && <FieldError>{errors.domain}</FieldError>}
                    <FieldDescription>
                      Enter your domain name without http:// or https://
                    </FieldDescription>
                  </FieldContent>
                </Field>
              </div>
              <DialogFooter>
                <Button
                  onClick={() => setCreateDialogOpen(false)}
                  type="button"
                  variant="outline"
                >
                  Cancel
                </Button>
                <Button onClick={handleCreate} type="button">
                  Add Domain
                </Button>
              </DialogFooter>
            </DialogContent>
          </Dialog>
        </div>
      </CardHeader>
      <CardContent>
        {domains.length === 0 ? (
          <div className="flex flex-col items-center justify-center gap-4 py-12 text-center">
            <div className="flex size-12 items-center justify-center rounded-full bg-muted">
              <Globe className="size-6 text-muted-foreground" />
            </div>
            <div className="flex flex-col gap-2">
              <p className="font-medium text-sm">No custom domains</p>
              <p className="text-muted-foreground text-sm">
                Add a custom domain to get started
              </p>
            </div>
          </div>
        ) : (
          <div className="flex flex-col gap-4">
            {domains.map((domain) => (
              <div
                className="flex flex-col gap-4 rounded-lg border p-4"
                key={domain.id}
              >
                <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
                  <div className="flex min-w-0 flex-1 flex-col gap-3">
                    <div className="flex flex-wrap items-center gap-2">
                      <div className="flex items-center gap-2">
                        <Globe className="size-5 text-muted-foreground" />
                        <span className="font-medium text-sm">
                          {domain.domain}
                        </span>
                      </div>
                      {getStatusBadge(domain.status)}
                      {domain.sslEnabled && (
                        <Badge className="text-xs" variant="default">
                          SSL Enabled
                        </Badge>
                      )}
                    </div>
                  </div>
                  <div className="flex shrink-0 flex-wrap gap-2">
                    {domain.status === "pending" && (
                      <Button
                        className="w-full sm:w-auto"
                        onClick={() => onVerify?.(domain.id)}
                        type="button"
                        variant="outline"
                      >
                        <RefreshCw className="size-4" />
                        Verify
                      </Button>
                    )}
                    <Button
                      className="w-full sm:w-auto"
                      onClick={() => onToggleSSL?.(domain.id)}
                      type="button"
                      variant="outline"
                    >
                      {domain.sslEnabled ? (
                        <>
                          <X className="size-4" />
                          Disable SSL
                        </>
                      ) : (
                        <>
                          <Check className="size-4" />
                          Enable SSL
                        </>
                      )}
                    </Button>
                    <AlertDialog>
                      <AlertDialogTrigger asChild>
                        <Button
                          className="w-full sm:w-auto"
                          type="button"
                          variant="destructive"
                        >
                          <Trash2 className="size-4" />
                          Remove
                        </Button>
                      </AlertDialogTrigger>
                      <AlertDialogContent>
                        <AlertDialogHeader>
                          <AlertDialogTitle>Remove Domain?</AlertDialogTitle>
                          <AlertDialogDescription>
                            This will remove the domain{" "}
                            <strong>{domain.domain}</strong> from your account.
                            This action cannot be undone.
                          </AlertDialogDescription>
                        </AlertDialogHeader>
                        <AlertDialogFooter>
                          <AlertDialogCancel>Cancel</AlertDialogCancel>
                          <AlertDialogAction
                            onClick={() => onDelete?.(domain.id)}
                          >
                            Remove Domain
                          </AlertDialogAction>
                        </AlertDialogFooter>
                      </AlertDialogContent>
                    </AlertDialog>
                  </div>
                </div>
                {domain.status === "pending" && domain.dnsRecords && (
                  <div className="flex w-full flex-col gap-3 rounded-lg border bg-muted/30 p-4">
                    <FieldLabel className="mb-0">DNS Configuration</FieldLabel>
                    <div className="flex flex-col gap-2">
                      {domain.dnsRecords.map((record, index) => (
                        <div
                          className="flex items-center justify-between rounded-lg border bg-background p-2"
                          key={index}
                        >
                          <div className="flex flex-col gap-1">
                            <span className="font-mono text-xs">
                              {record.type} {record.name}
                            </span>
                            <code className="font-mono text-muted-foreground text-xs">
                              {record.value}
                            </code>
                          </div>
                          <Button
                            aria-label="Copy DNS record"
                            onClick={() =>
                              copyToClipboard(
                                `${record.type} ${record.name} ${record.value}`
                              )
                            }
                            size="icon-sm"
                            type="button"
                            variant="ghost"
                          >
                            {copiedDomain ===
                            `${record.type} ${record.name} ${record.value}` ? (
                              <Check className="size-4 text-green-600" />
                            ) : (
                              <Copy className="size-4" />
                            )}
                          </Button>
                        </div>
                      ))}
                    </div>
                    <FieldDescription>
                      Add these DNS records to your domain provider to verify
                      ownership
                    </FieldDescription>
                  </div>
                )}

                {domain.verifiedAt && (
                  <p className="text-muted-foreground text-xs">
                    Verified on {formatDate(domain.verifiedAt)}
                  </p>
                )}
              </div>
            ))}
          </div>
        )}
      </CardContent>
    </Card>
  );
}

Similar components