import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks"; import { useMemo, useState } from "react"; import { Link, useNavigate } from "react-router"; import { toast } from "sonner"; import { PageHeader } from "@/components/crm/Surface"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; interface WorkspaceConnection { id: string; provider: "hubspot" | "salesforce"; label: string; accountLabel?: string | null; status: string; } export default function SetupRoute() { const navigate = useNavigate(); const connectionsQuery = useActionQuery( "list-workspace-connections" as never, { includeDisabled: false } as never, ); const connections = useMemo( () => crmConnections(connectionsQuery.data), [connectionsQuery.data], ); const [workspaceConnectionId, setWorkspaceConnectionId] = useState(""); const [pipelineIds, setPipelineIds] = useState(""); const [historyDays, setHistoryDays] = useState("90"); const configure = useActionMutation< { id: string }, { workspaceConnectionId: string; provider: "hubspot" | "salesforce"; selectedPipelineIds: string[]; selectedObjectTypes: string[]; } >("configure-crm-connection" as never); const configureNative = useActionMutation< { id: string }, Record >("configure-native-crm" as never); const sync = useActionMutation< unknown, { connectionId: string; objectType: string; scope: { updatedAfter: string; pipelineIds?: string[] }; maxPages: number; } >("sync-crm" as never); const selected = connections.find( (connection) => connection.id === workspaceConnectionId, ); async function syncRecentRecords() { if (!selected) return; const selectedPipelines = pipelineIds .split(",") .map((value) => value.trim()) .filter(Boolean) .slice(0, 50); const days = Math.max( 1, Math.min(365, Number.parseInt(historyDays, 10) || 90), ); try { const connection = await configure.mutateAsync({ workspaceConnectionId, provider: selected.provider, selectedPipelineIds: selected.provider === "hubspot" ? selectedPipelines : [], selectedObjectTypes: objectTypesForProvider(selected.provider), }); const updatedAfter = new Date( Date.now() - days * 24 * 60 * 60 * 1_000, ).toISOString(); for (const objectType of objectTypesForProvider(selected.provider)) { await sync.mutateAsync({ connectionId: connection.id, objectType, scope: { updatedAfter, ...(selected.provider === "hubspot" && objectType === "deals" && selectedPipelines.length ? { pipelineIds: selectedPipelines } : {}), }, maxPages: 2, }); } toast.success( `Recent ${providerLabel(selected.provider)} records are ready.`, ); navigate("/", { replace: true }); } catch (error) { toast.error(error instanceof Error ? error.message : "CRM sync failed."); } } async function startNativeCrm() { try { await configureNative.mutateAsync({}); toast.success("Your native CRM is ready."); navigate("/", { replace: true }); } catch (error) { toast.error( error instanceof Error ? error.message : "Could not start native CRM.", ); } } return ( <>

Start with Native SQL

Run accounts, people, opportunities, saved views, tasks, and cadence without connecting another CRM. Your CRM is local-authoritative and portable across SQLite, Postgres, and D1.

Or connect an existing CRM

Only workspace Connections granted to this app can be used. CRM never stores provider tokens.

setHistoryDays(event.target.value)} />

Days, capped at 365.

{selected?.provider === "hubspot" ? (
setPipelineIds(event.target.value)} />

Leave blank for recently updated deals.

) : selected?.provider === "salesforce" ? (

Salesforce objects

CRM mirrors recently updated Accounts, Contacts, and Opportunities in this initial cohort.

) : (

Cohort objects

Choose a connection to see its initial object cohort.

)}
{connections.length ? ( ) : (

No connected HubSpot or Salesforce account is available.

You can start with Native SQL above, or authorize a provider and grant it to CRM from shared settings.

Using a Salesforce sandbox?{" "} Authorize the sandbox directly .

)}
); } function crmConnections(data: unknown): WorkspaceConnection[] { if (!data || typeof data !== "object") return []; const rows = (data as { connections?: unknown }).connections; if (!Array.isArray(rows)) return []; return rows.flatMap((row) => { if (!row || typeof row !== "object") return []; const item = row as Record; if ( (item.provider !== "hubspot" && item.provider !== "salesforce") || typeof item.id !== "string" || typeof item.label !== "string" || typeof item.status !== "string" ) { return []; } return [ { id: item.id, provider: item.provider, label: item.label, accountLabel: typeof item.accountLabel === "string" ? item.accountLabel : null, status: item.status, }, ]; }); } function objectTypesForProvider(provider: WorkspaceConnection["provider"]) { return provider === "hubspot" ? ["companies", "contacts", "deals"] : ["Account", "Contact", "Opportunity"]; } function providerLabel(provider: WorkspaceConnection["provider"]) { return provider === "hubspot" ? "HubSpot" : "Salesforce"; }