From 69b70c81dc975bf463266dd7278cf44f2891f5d3 Mon Sep 17 00:00:00 2001 From: Recoupable Date: Wed, 8 Jul 2026 01:03:22 -0400 Subject: [PATCH] feat: add social sharing to catalog valuation results Adds copy-link, X, and LinkedIn share buttons below the 'Get full report' CTA on valuation result cards. Each share pre-fills with the artist name and valuation figure, linking back to /valuation to drive organic traffic. Design matches existing system: font-pixel labels, shadow-as-border, foreground opacity modifiers, muted hover states. --- components/valuation/ShareValuation.tsx | 84 ++++++++++++++++++++++++ components/valuation/ValuationResult.tsx | 2 + 2 files changed, 86 insertions(+) create mode 100644 components/valuation/ShareValuation.tsx diff --git a/components/valuation/ShareValuation.tsx b/components/valuation/ShareValuation.tsx new file mode 100644 index 0000000..0e3842e --- /dev/null +++ b/components/valuation/ShareValuation.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { useState } from "react"; +import { formatUsd } from "@/lib/valuation/formatUsd"; + +type ShareValuationProps = { + artistName: string | null; + centralValue: number; +}; + +function CopyIcon() { + return ( + + + + + ); +} + +function CheckIcon() { + return ( + + + + ); +} + +function XIcon() { + return ( + + + + ); +} + +function LinkedInIcon() { + return ( + + + + ); +} + +export function ShareValuation({ artistName, centralValue }: ShareValuationProps) { + const [copied, setCopied] = useState(false); + + const url = "https://recoupable.dev/valuation"; + const value = formatUsd(centralValue); + const name = artistName ?? "My artist"; + + const tweetText = `${name} catalog just got valued at ${value}. What's yours worth?\n\n${url}\n\n#recoup`; + const tweetUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`; + const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`; + + async function copyLink() { + try { + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Fallback — noop + } + } + + const btnClass = + "inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-[12px] font-pixel uppercase tracking-[0.12em] text-(--foreground)/45 transition-all duration-200 hover:text-(--foreground)/70 hover:bg-(--foreground)/[0.04]"; + + return ( +
+ + + + Share + + + + Share + +
+ ); +} diff --git a/components/valuation/ValuationResult.tsx b/components/valuation/ValuationResult.tsx index 5fe6598..71e7c29 100644 --- a/components/valuation/ValuationResult.tsx +++ b/components/valuation/ValuationResult.tsx @@ -3,6 +3,7 @@ import { ArtistHeader } from "@/components/valuation/ArtistHeader"; import { ValuationStats } from "@/components/valuation/ValuationStats"; import { MeasuredCatalog } from "@/components/valuation/MeasuredCatalog"; import { GetFullReportCta } from "@/components/valuation/GetFullReportCta"; +import { ShareValuation } from "@/components/valuation/ShareValuation"; import { formatUsd } from "@/lib/valuation/formatUsd"; type ValuationResultProps = { @@ -49,6 +50,7 @@ export function ValuationResult({ artist, result, catalogAlbums }: ValuationResu totalStreams={result.totalStreams} /> + ); }