"use client" import { useState, useEffect } from "react" import { useMutation, useQueryClient } from "@tanstack/react-query" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { z } from "zod" import { useToast } from "@/components/ui/use-toast" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Switch } from "@/components/ui/switch" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { createDomain, updateDomain, lookupDomain, cleanDomain, type Domain, type CreateDomainRequest, type UpdateDomainRequest, type DomainLookupResult, } from "@/lib/domains" import { Loader2, Search, AlertTriangle, Calendar } from "lucide-react" const formSchema = z.object({ domain_name: z.string().min(1, "Domain name is required"), tags: z.string().optional(), notes: z.string().optional(), purchase_price: z.coerce.number().min(0).optional(), current_value: z.coerce.number().min(0).optional(), renewal_cost: z.coerce.number().min(0).optional(), auto_renew: z.boolean(), monitor_type: z.enum(["expiry", "watchlist", "portfolio"]), // Expiry alerts alert_days_before: z.coerce.number().min(1).max(365), ssl_alert_enabled: z.boolean(), ssl_alert_days: z.coerce.number().min(1).max(90), // Notification settings notify_on_expiry: z.boolean(), notify_on_ssl_expiry: z.boolean(), notify_on_dns_change: z.boolean(), notify_on_registrar_change: z.boolean(), notify_on_value_change: z.boolean(), value_change_threshold: z.coerce.number().min(1).optional(), // Quiet hours quiet_hours_enabled: z.boolean(), quiet_hours_start: z.string(), quiet_hours_end: z.string(), }) type FormData = z.infer interface DomainDialogProps { open: boolean onOpenChange: (open: boolean) => void domain?: Domain | null isEdit?: boolean } export function DomainDialog({ open, onOpenChange, domain, isEdit = false }: DomainDialogProps) { const { toast } = useToast() const queryClient = useQueryClient() const [activeTab, setActiveTab] = useState("basic") const [lookupData, setLookupData] = useState(null) const [isLookingUp, setIsLookingUp] = useState(false) // Manual expiry inputs when WHOIS fails const [manualRegDate, setManualRegDate] = useState(() => { const today = new Date() return today.toISOString().split("T")[0] }) const [manualRegPeriod, setManualRegPeriod] = useState(1) const [manualPurchasePrice, setManualPurchasePrice] = useState(0) const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { domain_name: "", tags: "", notes: "", purchase_price: 0, current_value: 0, renewal_cost: 0, auto_renew: false, monitor_type: "expiry", // Expiry alerts alert_days_before: 30, ssl_alert_enabled: true, ssl_alert_days: 14, // Notification settings notify_on_expiry: true, notify_on_ssl_expiry: true, notify_on_dns_change: false, notify_on_registrar_change: false, notify_on_value_change: false, value_change_threshold: 10, // Quiet hours quiet_hours_enabled: false, quiet_hours_start: "22:00", quiet_hours_end: "08:00", }, }) useEffect(() => { if (open && isEdit && domain) { form.reset({ domain_name: domain.domain_name, tags: domain.tags?.join(", ") || "", notes: domain.notes || "", purchase_price: domain.purchase_price || 0, current_value: domain.current_value || 0, renewal_cost: domain.renewal_cost || 0, auto_renew: domain.auto_renew || false, monitor_type: domain.monitor_type || "expiry", // Expiry alerts alert_days_before: domain.alert_days_before || 30, ssl_alert_enabled: domain.ssl_alert_enabled || true, ssl_alert_days: domain.ssl_alert_days || 14, // Notification settings notify_on_expiry: domain.notify_on_expiry !== false, notify_on_ssl_expiry: domain.notify_on_ssl_expiry !== false, notify_on_dns_change: domain.notify_on_dns_change || false, notify_on_registrar_change: domain.notify_on_registrar_change || false, notify_on_value_change: domain.notify_on_value_change || false, value_change_threshold: domain.value_change_threshold || 10, // Quiet hours quiet_hours_enabled: domain.quiet_hours_enabled || false, quiet_hours_start: domain.quiet_hours_start || "22:00", quiet_hours_end: domain.quiet_hours_end || "08:00", }) } else if (open && !isEdit) { form.reset({ domain_name: "", tags: "", notes: "", purchase_price: 0, current_value: 0, renewal_cost: 0, auto_renew: false, monitor_type: "expiry", // Expiry alerts alert_days_before: 30, ssl_alert_enabled: true, ssl_alert_days: 14, // Notification settings notify_on_expiry: true, notify_on_ssl_expiry: true, notify_on_dns_change: false, notify_on_registrar_change: false, notify_on_value_change: false, value_change_threshold: 10, // Quiet hours quiet_hours_enabled: false, quiet_hours_start: "22:00", quiet_hours_end: "08:00", }) setLookupData(null) const today = new Date().toISOString().split("T")[0] setManualRegDate(today) setManualRegPeriod(1) setManualPurchasePrice(0) } }, [open, isEdit, domain, form]) const createMutation = useMutation({ mutationFn: createDomain, onSuccess: () => { toast({ title: "Domain added successfully" }) queryClient.invalidateQueries({ queryKey: ["domains"] }) onOpenChange(false) }, onError: (error: Error) => { toast({ title: "Failed to add domain", description: error.message, variant: "destructive", }) }, }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateDomainRequest }) => updateDomain(id, data), onSuccess: () => { toast({ title: "Domain updated successfully" }) queryClient.invalidateQueries({ queryKey: ["domains"] }) onOpenChange(false) }, onError: (error: Error) => { toast({ title: "Failed to update domain", description: error.message, variant: "destructive", }) }, }) const handleLookup = async () => { const domainName = form.getValues("domain_name") if (!domainName) return setIsLookingUp(true) try { const data = await lookupDomain(domainName) setLookupData(data) // Reset manual inputs on new lookup const today = new Date().toISOString().split("T")[0] setManualRegDate(today) setManualRegPeriod(1) setManualPurchasePrice(0) toast({ title: "Domain info retrieved successfully" }) } catch (error) { toast({ title: "Failed to lookup domain", description: error instanceof Error ? error.message : "Unknown error", variant: "destructive", }) } finally { setIsLookingUp(false) } } // Calculate expiry date from registration date + period in years const calculateExpiryDate = (regDateStr: string, years: number): string | null => { const regDate = new Date(regDateStr) if (isNaN(regDate.getTime())) return null const expiry = new Date(regDate) expiry.setFullYear(expiry.getFullYear() + years) // Subtract 1 day (expiry is typically the day before the anniversary) expiry.setDate(expiry.getDate() - 1) return expiry.toISOString().split("T")[0] } const onSubmit = (data: FormData) => { const payload: CreateDomainRequest = { domain_name: cleanDomain(data.domain_name), auto_lookup: !isEdit && lookupData !== null, tags: data.tags?.split(",").map((t) => t.trim()).filter(Boolean), notes: data.notes, purchase_price: data.purchase_price, current_value: data.current_value, renewal_cost: data.renewal_cost, auto_renew: data.auto_renew, monitor_type: data.monitor_type, // Expiry alerts alert_days_before: data.alert_days_before, ssl_alert_enabled: data.ssl_alert_enabled, ssl_alert_days: data.ssl_alert_days, // Notification settings notify_on_expiry: data.notify_on_expiry, notify_on_ssl_expiry: data.notify_on_ssl_expiry, notify_on_dns_change: data.notify_on_dns_change, notify_on_registrar_change: data.notify_on_registrar_change, notify_on_value_change: data.notify_on_value_change, value_change_threshold: data.notify_on_value_change ? data.value_change_threshold : undefined, // Quiet hours quiet_hours_enabled: data.quiet_hours_enabled, quiet_hours_start: data.quiet_hours_enabled ? data.quiet_hours_start : undefined, quiet_hours_end: data.quiet_hours_enabled ? data.quiet_hours_end : undefined, } // If lookup returned no expiry, attach manual dates if (!isEdit && lookupData && !lookupData.expiry_date) { const calculatedExpiry = calculateExpiryDate(manualRegDate, manualRegPeriod) if (calculatedExpiry) { payload.expiry_date = calculatedExpiry payload.creation_date = manualRegDate } // Use the manual purchase price if set (overrides form value when WHOIS fails) if (manualPurchasePrice > 0) { payload.purchase_price = manualPurchasePrice } } if (isEdit && domain) { updateMutation.mutate({ id: domain.id, data: { tags: payload.tags, notes: payload.notes, purchase_price: payload.purchase_price, current_value: payload.current_value, renewal_cost: payload.renewal_cost, auto_renew: payload.auto_renew, monitor_type: payload.monitor_type, // Expiry alerts alert_days_before: payload.alert_days_before, ssl_alert_enabled: payload.ssl_alert_enabled, ssl_alert_days: payload.ssl_alert_days, // Notification settings notify_on_expiry: payload.notify_on_expiry, notify_on_ssl_expiry: payload.notify_on_ssl_expiry, notify_on_dns_change: payload.notify_on_dns_change, notify_on_registrar_change: payload.notify_on_registrar_change, notify_on_value_change: payload.notify_on_value_change, value_change_threshold: payload.value_change_threshold, // Quiet hours quiet_hours_enabled: payload.quiet_hours_enabled, quiet_hours_start: payload.quiet_hours_start, quiet_hours_end: payload.quiet_hours_end, }, }) } else { createMutation.mutate(payload) } } const isPending = createMutation.isPending || updateMutation.isPending return ( {isEdit ? "Edit Domain" : "Add Domain"} {isEdit ? "Update domain tracking settings." : "Add a domain to track its expiry, SSL, and DNS information."}
Basic Valuation Alerts {!isEdit && (
( Domain Name )} />
)} {isEdit && ( ( Domain Name )} /> )} ( Tags (comma separated) )} /> ( Notes )} /> {lookupData && !isEdit && (
{/* Lookup Results */}

Lookup Results

{lookupData.registrar_name && (

Registrar: {lookupData.registrar_name}

)} {lookupData.expiry_date && (

Expires: {lookupData.expiry_date}

)} {lookupData.ssl_valid_to && (

SSL Expires: {lookupData.ssl_valid_to}

)} {lookupData.host_country && (

Location: {lookupData.host_country}

)} {lookupData.dns_provider && (

DNS: {lookupData.dns_provider}

)} {lookupData.hosting_provider && (

Hosting: {lookupData.hosting_provider}

)} {lookupData.email_provider && (

Email: {lookupData.email_provider}

)} {lookupData.ca_provider && (

CA: {lookupData.ca_provider}

)}
{/* Manual expiry fallback when WHOIS doesn't return expiry */} {!lookupData.expiry_date && (

Expiry date not found in WHOIS

Enter your registration details below and we'll calculate the expiry date.

setManualRegDate(e.target.value)} className="h-8 text-sm" />
setManualPurchasePrice(Number(e.target.value))} className="h-8 text-sm" /> {manualPurchasePrice > 0 && manualRegPeriod > 1 && (

~{(manualPurchasePrice / manualRegPeriod).toFixed(2)} per year

)}
{manualRegDate && manualRegPeriod > 0 && (

Calculated Expiry:

{calculateExpiryDate(manualRegDate, manualRegPeriod)}

)}
)}
)}
( Purchase Price )} /> ( Current Value )} /> ( Renewal Cost )} /> (
Auto Renew
)} />
{/* Monitor Type */} ( Monitoring Purpose
)} /> {/* Domain Expiry Alerts */}

Domain Expiry Alerts

(
Notify before expiry

Alert when domain is about to expire

)} /> {form.watch("notify_on_expiry") && ( ( Days before expiry )} /> )}
{/* SSL Alerts */}

SSL Certificate Alerts

(
Notify on SSL expiry

Alert when SSL certificate expires

)} /> {form.watch("notify_on_ssl_expiry") && ( ( Days before SSL expiry )} /> )}
{/* Change Detection Alerts */}

Change Detection

(
DNS changes

Alert when DNS records change

)} /> (
Registrar changes

Alert when registrar information changes

)} /> (
Value changes

Alert when estimated value changes significantly

)} /> {form.watch("notify_on_value_change") && ( ( Change threshold (%) )} /> )}
{/* Quiet Hours */}

Quiet Hours

(
Enable quiet hours

Suppress notifications during specific hours

)} /> {form.watch("quiet_hours_enabled") && (
( Start time )} /> ( End time )} />
)}
) }