import { useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Link } from "@tanstack/react-router"; import { toast } from "sonner"; import { LOCATIONS } from "@/client/features/keywords/locations"; import { AlertTriangle, Archive, Globe, Plus, ChevronRight, Search, } from "lucide-react"; import { getRankTrackingConfigSummaries, updateRankTrackingConfig, } from "@/serverFunctions/rank-tracking"; import { devicesLabel, scheduleLabel } from "@/shared/rank-tracking"; import { formatLocationLabel } from "@/shared/keyword-locations"; import { Modal } from "@/client/components/Modal"; import { applyDomainListFilters, countActiveDomainListFilters, DomainListFilterBar, EMPTY_DOMAIN_LIST_FILTERS, getDomainListFilterOptions, type DomainListFilters, } from "./RankTrackingFilters"; type ConfigSummary = Awaited< ReturnType >[number]; // Below this many domains the list is short enough to scan by eye, so the // filter controls are more chrome than help. Still shown if filters are active // (e.g. archiving dropped the count) so they never get orphaned. const FILTER_BAR_MIN_DOMAINS = 6; export function RankTrackingDomainList({ projectId, onAddDomain, }: { projectId: string; onAddDomain: () => void; }) { const queryClient = useQueryClient(); const [archiveTarget, setArchiveTarget] = useState( null, ); const [filters, setFilters] = useState( EMPTY_DOMAIN_LIST_FILTERS, ); const { data: summaries } = useQuery({ queryKey: ["rankTrackingConfigSummaries", projectId], queryFn: () => getRankTrackingConfigSummaries({ data: { projectId } }), }); const allSummaries = useMemo(() => summaries ?? [], [summaries]); const filteredSummaries = useMemo( () => applyDomainListFilters(allSummaries, filters), [allSummaries, filters], ); const filterOptions = useMemo( () => getDomainListFilterOptions(allSummaries), [allSummaries], ); const activeFilterCount = countActiveDomainListFilters(filters); const archiveMutation = useMutation({ mutationFn: (configId: string) => updateRankTrackingConfig({ data: { projectId, configId, isActive: false }, }), onSuccess: () => { setArchiveTarget(null); void queryClient.invalidateQueries({ queryKey: ["rankTrackingConfigSummaries", projectId], }); void queryClient.invalidateQueries({ queryKey: ["rankTrackingConfigs", projectId], }); toast.success("Domain archived"); }, }); return (

Tracked Domains

{(allSummaries.length >= FILTER_BAR_MIN_DOMAINS || activeFilterCount > 0) && ( setFilters(EMPTY_DOMAIN_LIST_FILTERS)} /> )}
{allSummaries.length === 0 ? (

No tracked domains yet

Add a domain to start monitoring keyword rankings over time.

) : filteredSummaries.length === 0 ? (

No matching tracked domains

Try clearing search or adjusting filters.

) : ( filteredSummaries.map((summary) => ( setArchiveTarget(summary)} /> )) )}
{archiveTarget && ( setArchiveTarget(null)} labelledBy="archive-domain-title" >

Archive {archiveTarget.domain}?

Scheduled checks will stop and this domain will be hidden from the list. Ranking history is preserved.

)}
); } function DomainRow({ projectId, summary, onArchive, }: { projectId: string; summary: ConfigSummary; onArchive: () => void; }) { return (

{summary.domain}

{summary.locationName ? formatLocationLabel(summary.locationName, 2) : (LOCATIONS[summary.locationCode] ?? "US")}{" "} · {devicesLabel(summary.devices)} ·{" "} {scheduleLabel(summary.scheduleInterval)} {summary.lastRunCompletedAt && ( <> {" "} · Last:{" "} {new Date(summary.lastRunCompletedAt).toLocaleDateString()} )}

{summary.lastSkipReason === "insufficient_credits" && (

Scheduled check skipped — insufficient credits

)}
{summary.keywordCount > 0 && (

Keywords

{summary.keywordCount}

)}
); }