import { format, formatDistanceToNow } from "date-fns"; export function formatDateTime(iso: string | null | undefined): string { if (!iso) return "—"; return format(new Date(iso), "MMM d, yyyy h:mm a"); } export function formatRelative(iso: string | null | undefined): string { if (!iso) return "—"; return formatDistanceToNow(new Date(iso), { addSuffix: true }); } /** * NinjaPear's funding fields (`total_raised`, a round's `amount`) come back * as raw numeric strings (e.g. "500000000"), with no currency formatting. * Turns that into "$500,000,000"; leaves already-formatted or non-numeric * values (e.g. "$1M", "Undisclosed") untouched. */ export function formatMoney(value: string): string { const trimmed = value.trim(); if (!trimmed || !/^\d+(\.\d+)?$/.test(trimmed)) return trimmed; const amount = Number(trimmed); if (!Number.isFinite(amount)) return trimmed; return `$${amount.toLocaleString("en-US")}`; } /** * NinjaPear's competitor/customer `name` fields sometimes come back as a * bare URL (e.g. "https://squareup.com") rather than a company name. Turns * that into something readable; leaves already-clean names untouched. */ export function companyNameFromUrl(value: string): string { const trimmed = value.trim(); const withoutProtocol = trimmed.replace(/^https?:\/\//i, "").replace(/^www\./i, ""); if (!withoutProtocol.includes(".")) return trimmed; const label = withoutProtocol.split("/")[0]?.split(".")[0]; if (!label) return trimmed; return label.charAt(0).toUpperCase() + label.slice(1).toLowerCase(); } const ENRICHMENT_REASON_LABELS: Record = { product_overlap: "Similar products", organic_keyword_overlap: "Overlapping search keywords", }; /** Turns a NinjaPear competitor-match slug (e.g. "product_overlap") into a * human-readable label; unrecognized slugs still read as words. */ export function formatEnrichmentReason(reason: string): string { const known = ENRICHMENT_REASON_LABELS[reason]; if (known) return known; return reason .split("_") .filter(Boolean) .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); } /** Light heuristics to turn a raw exception string from an enrichment * section failure into a short, non-technical summary. */ export function summarizeEnrichmentError(raw: string): string { const firstLine = raw.split("\n")[0]?.trim() ?? raw; if (/^\d+ validation errors?/i.test(firstLine)) { return "The data returned didn't match the expected format."; } if (/404 not found/i.test(raw)) { return "The requested data wasn't found."; } if (/timed? ?out/i.test(raw)) { return "The request timed out."; } if (/\b5\d\d\b/.test(raw) && /error/i.test(raw)) { return "The data provider returned a server error."; } return firstLine.length > 100 ? `${firstLine.slice(0, 100)}…` : firstLine; }