"use client"; import { useState } from "react"; import { ExternalLinkIcon, PlusIcon, XIcon } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { PlatformIcon } from "@/components/crm/platform-icon"; import { BRAND_LABEL, NAMED_PLATFORMS, PLATFORMS, PLATFORM_LABEL, brandFor, detectPlatform, linkHref, linkLabel, normaliseLink, type PlatformKey, } from "@/lib/social"; export type ContactLinkValues = { linkedin: string; twitter: string; instagram: string; github: string; website: string; otherLinks: string[]; }; /** * Every way to reach a person, as one list rather than six labelled boxes. * * The old form had a single "LinkedIn" field, which is the right guess for a * recruiter and wrong for everyone else. Six stacked inputs would have been * the obvious fix and a worse one — most are empty for most people, and an * empty box still costs a row of the sidebar. * * So: what is set is listed, and one row adds more. Paste a URL and the * platform is worked out from its host; type a bare "@handle" and the picker * is how you say which platform it belongs to, because nothing in "@will" * says X rather than Instagram. */ export function ContactLinks({ values, onChange, }: { values: ContactLinkValues; /** Called with the patch to persist. Only changed fields are included. */ onChange: (patch: Partial) => void; }) { const [draft, setDraft] = useState(""); const [platform, setPlatform] = useState("auto"); const rows: { key: string; platform: PlatformKey; value: string; remove: () => void }[] = [ ...NAMED_PLATFORMS.filter((key) => values[key].trim()).map((key) => ({ key, platform: key as PlatformKey, value: values[key], remove: () => onChange({ [key]: "" } as Partial), })), ...values.otherLinks.map((value, index) => ({ key: `other-${index}-${value}`, platform: "other" as PlatformKey, value, remove: () => onChange({ otherLinks: values.otherLinks.filter((_, i) => i !== index) }), })), ]; const add = () => { const raw = draft.trim(); if (!raw) return; const resolved = platform === "auto" ? detectPlatform(raw) : platform; if (!resolved) { toast.error("That isn't a link I can place. Pick a platform, or paste the full URL."); return; } const value = normaliseLink(raw, resolved); // "@will" means something under X, where the handle expands to a profile. // Under Website or Other it expands to nothing, and storing it would put a // row on the card that can never be opened. if (!linkHref(value)) { toast.error(`Paste the full URL for a ${PLATFORM_LABEL[resolved].toLowerCase()} link.`); return; } // A named slot that is already taken is not overwritten — the second // LinkedIn URL is kept as another link rather than silently replacing the // first, because losing an address you pasted is worse than an untidy list. if (resolved !== "other" && !values[resolved].trim()) { onChange({ [resolved]: value } as Partial); } else if ( values.otherLinks.some((existing) => existing.toLowerCase() === value.toLowerCase()) || NAMED_PLATFORMS.some((key) => values[key].toLowerCase() === value.toLowerCase()) ) { toast.error("You already have that one."); return; } else { onChange({ otherLinks: [...values.otherLinks, value] }); } setDraft(""); setPlatform("auto"); }; return (
{rows.length > 0 && (
    {rows.map((row) => { const href = linkHref(row.value); // What it IS, not what column it sits in: "Open their Other" was // the label on every link that wasn't one of the five. const what = BRAND_LABEL[brandFor(row.value, row.platform)]; return (
  • {linkLabel(row.value)} {href && ( )}
  • ); })}
)}
setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); add(); } }} placeholder={ platform === "auto" ? "Paste a link" : PLATFORMS.find((option) => option.key === platform)?.placeholder } className="h-9 text-[13px] md:h-8" aria-label="Link to add" />
{rows.length === 0 && (

Wherever they actually answer — LinkedIn, X, Instagram, their own site.

)}
); }