"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { AlertCircle, CheckCircle2, LoaderCircle, XCircle } from "lucide-react";
import { type HTMLAttributes, useState } from "react";
import useSWR, { preload } from "swr";
import { addDomain, getDomainStatus } from "../actions/add-custom-domain";
import { DNSTable } from "./dns-table";
export type InlineSnippetProps = HTMLAttributes<HTMLSpanElement>;
export const InlineSnippet = ({ className, ...props }: InlineSnippetProps) => (
<span
className={cn(
"rounded-md bg-muted px-1 py-0.2 font-mono text-sm",
className
)}
{...props}
/>
);
export const useDomainStatus = (domain: string) =>
useSWR(`domain-status-${domain}`, () => getDomainStatus(domain), {
refreshInterval: 20_000,
});
export const preloadDomainStatus = (domain: string) =>
preload(`domain-status-${domain}`, () => getDomainStatus(domain));
export type DomainConfigurationProps = HTMLAttributes<HTMLDivElement> & {
domain: string;
};
export const DomainConfiguration = ({
domain,
className,
...props
}: DomainConfigurationProps) => {
const { data, isLoading, mutate, isValidating } = useDomainStatus(domain);
if (isLoading || !data) {
return null;
}
if (data.status === "Valid Configuration") {
return (
<div
className={cn(
"flex w-full justify-start gap-2 text-muted-foreground",
className
)}
{...props}
>
<DomainStatusIcon domain={domain} />
<p>Domain is configured correctly.</p>
</div>
);
}
if (data.status === "Domain is not added") {
return (
<div
className={cn(
"w-full text-left text-muted-foreground text-sm",
className
)}
>
<p>Save the domain to add it to your site.</p>
</div>
);
}
return (
<div className={cn("w-full space-y-4", className)} {...props}>
{data.status === "Pending Verification" ? (
<div className="w-full text-left text-muted-foreground text-sm">
Please set the following TXT record on{" "}
<InlineSnippet>{data.dnsRecordsToSet?.name}</InlineSnippet> to prove
ownership of <InlineSnippet>{domain}</InlineSnippet>:
</div>
) : (
<div
className={cn(
"w-full text-left text-muted-foreground text-sm",
className
)}
>
Set the following DNS records to your domain provider:
</div>
)}
{data.dnsRecordsToSet && <DNSTable records={[data.dnsRecordsToSet]} />}
<div className="flex w-full justify-end">
<Button
disabled={isValidating}
onClick={() => mutate()}
size="sm"
variant="outline"
>
{isValidating ? (
<>
<LoaderCircle className="animate-spin" /> Refresh
</>
) : (
"Refresh"
)}
</Button>
</div>
</div>
);
};
export type DomainStatusIconProps = {
domain: string;
};
export const DomainStatusIcon = ({ domain }: DomainStatusIconProps) => {
const { data, isLoading } = useDomainStatus(domain);
if (isLoading) {
return <LoaderCircle className="animate-spin text-black dark:text-white" />;
}
if (data?.status === "Valid Configuration") {
return (
<CheckCircle2
className="text-white dark:text-white"
fill="#2563EB"
stroke="currentColor"
/>
);
}
if (data?.status === "Pending Verification") {
return (
<AlertCircle
className="text-white dark:text-black"
fill="#FBBF24"
stroke="currentColor"
/>
);
}
if (data?.status === "Domain is not added") {
return (
<XCircle
className="text-white dark:text-black"
fill="#DC2626"
stroke="currentColor"
/>
);
}
if (data?.status === "Invalid Configuration") {
return (
<XCircle
className="text-white dark:text-black"
fill="#DC2626"
stroke="currentColor"
/>
);
}
return null;
};
export type CustomDomainProps = {
defaultDomain?: string;
};
export const CustomDomain = (props: CustomDomainProps) => {
const [domain, setDomain] = useState<string | null>(
props.defaultDomain ?? null
);
const [submitting, setSubmitting] = useState(false);
return (
<form
className="@container w-full max-w-[620px]"
onSubmit={async (event) => {
event.preventDefault();
setSubmitting(true);
const data = new FormData(event.currentTarget);
const customDomain = (data.get("customDomain") as string).toLowerCase();
await addDomain(customDomain);
await preloadDomainStatus(customDomain);
setDomain(customDomain);
setSubmitting(false);
}}
>
<Card className="flex flex-col">
<CardHeader className="flex flex-col text-left">
<CardTitle>Custom Domain</CardTitle>
<CardDescription>The custom domain for your site.</CardDescription>
</CardHeader>
<CardContent className="relative flex w-full @sm:flex-row flex-col items-center justify-start @sm:justify-between gap-2">
<Input
defaultValue={props.defaultDomain}
maxLength={64}
name="customDomain"
onChange={(e) => {
e.target.value = e.target.value.toLowerCase();
}}
placeholder={"example.com"}
type="text"
/>
<Button className="@sm:w-16 w-full" type="submit" variant="outline">
{submitting ? <LoaderCircle className="animate-spin" /> : "Save"}
</Button>
</CardContent>
{domain && (
<CardFooter className="flex flex-col gap-4 border-muted border-t-2 pt-4 text-sm">
<DomainConfiguration domain={domain} />
</CardFooter>
)}
</Card>
</form>
);
};