"use client"; import { Button } from "@app/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { Checkbox, CheckboxWithLabel } from "@app/components/ui/checkbox"; import { useToast } from "@app/hooks/useToast"; import { zodResolver } from "@hookform/resolvers/zod"; import { useState, useMemo } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Credenza, CredenzaBody, CredenzaClose, CredenzaContent, CredenzaDescription, CredenzaFooter, CredenzaHeader, CredenzaTitle } from "@app/components/Credenza"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useTranslations } from "next-intl"; import { formatAxiosError } from "@app/lib/api"; import { CreateDomainResponse } from "@server/routers/domain/createOrgDomain"; import { StrategySelect } from "@app/components/StrategySelect"; import { AxiosResponse } from "axios"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { InfoIcon, AlertTriangle, Globe } from "lucide-react"; import CopyToClipboard from "@app/components/CopyToClipboard"; import { InfoSection, InfoSectionContent, InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { build } from "@server/build"; import { toASCII, toUnicode } from 'punycode'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { useRouter } from "next/navigation"; // Helper functions for Unicode domain handling function toPunycode(domain: string): string { try { const parts = toASCII(domain); return parts; } catch (error) { return domain.toLowerCase(); } } function fromPunycode(domain: string): string { try { const parts = toUnicode(domain); return parts; } catch (error) { return domain; } } function isValidDomainFormat(domain: string): boolean { const unicodeRegex = /^(?!:\/\/)([^\s.]+\.)*[^\s.]+$/; if (!unicodeRegex.test(domain)) { return false; } const parts = domain.split('.'); for (const part of parts) { if (part.length === 0 || part.startsWith('-') || part.endsWith('-')) { return false; } if (part.length > 63) { return false; } } if (domain.length > 253) { return false; } return true; } const formSchema = z.object({ baseDomain: z .string() .min(1, "Domain is required") .refine((val) => isValidDomainFormat(val), "Invalid domain format") .transform((val) => toPunycode(val)), type: z.enum(["ns", "cname", "wildcard"]), certResolver: z.string().nullable().optional(), preferWildcardCert: z.boolean().optional() }); type FormValues = z.infer; type CreateDomainFormProps = { open: boolean; setOpen: (open: boolean) => void; onCreated?: (domain: CreateDomainResponse) => void; }; // Example cert resolver options (replace with real API/fetch if needed) const certResolverOptions = [ { id: "default", title: "Default" }, { id: "custom", title: "Custom Resolver" } ]; export default function CreateDomainForm({ open, setOpen, onCreated }: CreateDomainFormProps) { const [loading, setLoading] = useState(false); const [createdDomain, setCreatedDomain] = useState(null); const api = createApiClient(useEnvContext()); const t = useTranslations(); const { toast } = useToast(); const { org } = useOrgContext(); const { env } = useEnvContext(); const router = useRouter(); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { baseDomain: "", type: build == "oss" || !env.flags.usePangolinDns ? "wildcard" : "ns", certResolver: null, preferWildcardCert: false } }); const baseDomain = form.watch("baseDomain"); const domainType = form.watch("type"); const punycodePreview = useMemo(() => { if (!baseDomain) return ""; const punycode = toPunycode(baseDomain); return punycode !== baseDomain.toLowerCase() ? punycode : ""; }, [baseDomain]); const reset = () => { form.reset(); setLoading(false); setCreatedDomain(null); }; async function onSubmit(values: FormValues) { setLoading(true); try { const response = await api.put>( `/org/${org.org.orgId}/domain`, values ); const domainData = response.data.data; setCreatedDomain(domainData); toast({ title: t("success"), description: t("domainCreatedDescription") }); onCreated?.(domainData); router.push(`/${org.org.orgId}/settings/domains/${domainData.domainId}`); } catch (e) { toast({ title: t("error"), description: formatAxiosError(e), variant: "destructive" }); } finally { setLoading(false); } }; // Domain type options let domainOptions: any = []; if (build != "oss" && env.flags.usePangolinDns) { domainOptions = [ { id: "ns", title: t("selectDomainTypeNsName"), description: t("selectDomainTypeNsDescription") }, { id: "cname", title: t("selectDomainTypeCnameName"), description: t("selectDomainTypeCnameDescription") } ]; } else { domainOptions = [ { id: "wildcard", title: t("selectDomainTypeWildcardName"), description: t("selectDomainTypeWildcardDescription") } ]; } return ( { setOpen(val); reset(); }} > {t("domainAdd")} {t("domainAddDescription")}
( )} /> ( {t("domain")} {punycodePreview && ( {t("internationaldomaindetected")}

{t("willbestoredas")} {punycodePreview}

)}
)} /> {domainType === "wildcard" && ( <> ( {t("certResolver")} )} /> {form.watch("certResolver") !== null && form.watch("certResolver") !== "default" && ( ( field.onChange(e.target.value)} /> )} /> )} {form.watch("certResolver") !== null && form.watch("certResolver") !== "default" && ( ( {/*
{t("preferWildcardCert")}
*/}
)} /> )} )}
{!createdDomain && ( )}
); }