Skip to content

feat: add social sharing to catalog valuation results#47

Open
sidneyswift wants to merge 1 commit into
mainfrom
feat/valuation-share
Open

feat: add social sharing to catalog valuation results#47
sidneyswift wants to merge 1 commit into
mainfrom
feat/valuation-share

Conversation

@sidneyswift

@sidneyswift sidneyswift commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Adds copy-link, X, and LinkedIn share buttons to valuation result cards. Each share pre-fills with artist name + valuation figure, linking back to /valuation for organic traffic.


Summary by cubic

Adds copy-link, X, and LinkedIn share buttons below the Get full report CTA on valuation result cards to make sharing easy and drive traffic to /valuation. Shares prefill the artist name and formatted central valuation; the copy action confirms with a brief "Copied" state.

Written for commit 69b70c8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added sharing controls on valuation results, including quick links to X/Twitter and LinkedIn.
    • Added a one-click “Copy link” action with temporary confirmation feedback.
    • Valuation results now display a formatted shareable value and artist name in the share section.

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.
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marketing Ready Ready Preview Jul 8, 2026 5:04am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new ShareValuation client component is added, providing copy-link and social share (Twitter/LinkedIn) UI for valuation results. ValuationResult is updated to import and render this component, passing artist name and central valuation value as props.

Changes

Share Valuation Feature

Layer / File(s) Summary
ShareValuation component
components/valuation/ShareValuation.tsx
New client component formats a USD value, builds Twitter/LinkedIn share URLs, and implements a "Copy link" button using clipboard API with a temporary "Copied" state and silent error handling.
ValuationResult integration
components/valuation/ValuationResult.tsx
Imports ShareValuation and renders it in the result card, passing artistName (from artist?.name ?? null) and centralValue (from result.valueBand.central).

Estimated code review effort: 1 (Trivial) | ~5 minutes

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding social sharing to valuation result cards.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/valuation-share

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/valuation/ShareValuation.tsx`:
- Around line 55-63: The copyLink function in ShareValuation should handle
clipboard write failures by updating the UI with a brief error state or “Failed”
label instead of silently swallowing the exception, so users get feedback when
navigator.clipboard.writeText(url) fails. Also store the timeout handle used by
setTimeout(() => setCopied(false), 2000) and clear it during unmount/cleanup to
avoid leaving an active timer behind; keep the logic localized around copyLink
and the copied state handling.
- Around line 47-51: The ShareValuation component is hardcoding brand-specific
values in the tweet text. Update ShareValuation to import the base URL and brand
hashtag from lib/config.ts instead of defining the URL string and `#recoup`
inline, and then build tweetText using those imported values so branding stays
centralized and consistent.
- Around line 70-81: The social share controls in ShareValuation are not
distinguishable to screen readers because both links use the same visible label
and the icon components are exposed to assistive tech. Update the ShareValuation
button/link markup so the Twitter/X and LinkedIn actions each have unique
accessible names (for example via distinct link text or aria-labels) and mark
the icon components used by CopyIcon, CheckIcon, XIcon, and LinkedInIcon as
aria-hidden="true" so they are not announced redundantly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f069dce4-d063-45b2-82bc-c4890de84608

📥 Commits

Reviewing files that changed from the base of the PR and between f9862ee and 69b70c8.

📒 Files selected for processing (2)
  • components/valuation/ShareValuation.tsx
  • components/valuation/ValuationResult.tsx

Comment on lines +47 to +51
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`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Hardcoded brand URL and hashtag violate coding guidelines.

The URL https://recoupable.dev/valuation and the #recoup hashtag are brand values hardcoded directly in the component. As per coding guidelines, brand values should be imported from lib/config.ts.

🔧 Proposed fix
-  const url = "https://recoupable.dev/valuation";
+  const url = `${siteUrl}/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 tweetText = `${name} catalog just got valued at ${value}. What's yours worth?\n\n${url}\n\n#${brandHashtag}`;

Import the base URL and brand hashtag from lib/config.ts at the top of the file:

+import { siteUrl, brandHashtag } from "`@/lib/config`";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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`;
import { siteUrl, brandHashtag } from "`@/lib/config`";
const url = `${siteUrl}/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#${brandHashtag}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/valuation/ShareValuation.tsx` around lines 47 - 51, The
ShareValuation component is hardcoding brand-specific values in the tweet text.
Update ShareValuation to import the base URL and brand hashtag from
lib/config.ts instead of defining the URL string and `#recoup` inline, and then
build tweetText using those imported values so branding stays centralized and
consistent.

Source: Coding guidelines

Comment on lines +55 to +63
async function copyLink() {
try {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Fallback — noop
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Silent clipboard failure and missing timeout cleanup.

Two issues:

  1. If navigator.clipboard.writeText fails (e.g., non-secure context, permissions denied), the user gets no feedback — the button doesn't change. Consider showing an error state or a brief "Failed" label.
  2. The setTimeout is never cleared on unmount. While React 18+ no-ops on unmounted state updates, clearing the timeout is good practice.
🛡️ Proposed fix
+  useEffect(() => {
+    return () => clearTimeout(timerRef.current);
+  }, []);
+
   async function copyLink() {
     try {
       await navigator.clipboard.writeText(url);
       setCopied(true);
-      setTimeout(() => setCopied(false), 2000);
+      timerRef.current = setTimeout(() => setCopied(false), 2000);
     } catch {
-      // Fallback — noop
+      setCopied(false);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/valuation/ShareValuation.tsx` around lines 55 - 63, The copyLink
function in ShareValuation should handle clipboard write failures by updating
the UI with a brief error state or “Failed” label instead of silently swallowing
the exception, so users get feedback when navigator.clipboard.writeText(url)
fails. Also store the timeout handle used by setTimeout(() => setCopied(false),
2000) and clear it during unmount/cleanup to avoid leaving an active timer
behind; keep the logic localized around copyLink and the copied state handling.

Comment on lines +70 to +81
<button type="button" onClick={copyLink} className={btnClass}>
{copied ? <CheckIcon /> : <CopyIcon />}
{copied ? "Copied" : "Copy link"}
</button>
<a href={tweetUrl} target="_blank" rel="noopener noreferrer" className={btnClass}>
<XIcon />
Share
</a>
<a href={linkedInUrl} target="_blank" rel="noopener noreferrer" className={btnClass}>
<LinkedInIcon />
Share
</a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Share buttons are indistinguishable for screen readers.

Both social links render the text "Share" with no distinguishing label. Screen reader users hear "Share" twice with no context. Additionally, the inline SVGs lack aria-hidden="true", so they may be announced redundantly.

♿ Proposed fix
       <button type="button" onClick={copyLink} className={btnClass} aria-label="Copy valuation link">
         {copied ? <CheckIcon /> : <CopyIcon />}
         {copied ? "Copied" : "Copy link"}
       </button>
-      <a href={tweetUrl} target="_blank" rel="noopener noreferrer" className={btnClass}>
+      <a href={tweetUrl} target="_blank" rel="noopener noreferrer" className={btnClass} aria-label="Share on X">
         <XIcon />
         Share
       </a>
-      <a href={linkedInUrl} target="_blank" rel="noopener noreferrer" className={btnClass}>
+      <a href={linkedInUrl} target="_blank" rel="noopener noreferrer" className={btnClass} aria-label="Share on LinkedIn">
         <LinkedInIcon />
         Share
       </a>

Also add aria-hidden="true" to all four icon SVG components, e.g.:

-    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+    <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/valuation/ShareValuation.tsx` around lines 70 - 81, The social
share controls in ShareValuation are not distinguishable to screen readers
because both links use the same visible label and the icon components are
exposed to assistive tech. Update the ShareValuation button/link markup so the
Twitter/X and LinkedIn actions each have unique accessible names (for example
via distinct link text or aria-labels) and mark the icon components used by
CopyIcon, CheckIcon, XIcon, and LinkedInIcon as aria-hidden="true" so they are
not announced redundantly.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/valuation/ShareValuation.tsx">

<violation number="1" location="components/valuation/ShareValuation.tsx:28">
P3: Local `XIcon` component collides with `lucide-react`'s `XIcon` used elsewhere in the codebase. This creates ambiguity when grepping for the close icon. Rename to something specific like `XLogoIcon` to avoid confusion with the dismiss icon.</violation>

<violation number="2" location="components/valuation/ShareValuation.tsx:47">
P2: The share link URL `https://recoupable.dev/valuation` is hardcoded instead of importing from the site config. The project convention (stated in both AGENTS.md and lib/config.ts) is to import `siteConfig` from `@/lib/config` and use `siteConfig.url` as the single source of truth for the domain. Using the config here prevents the share URL from going stale if the domain ever changes.</violation>

<violation number="3" location="components/valuation/ShareValuation.tsx:59">
P2: Two minor stability issues with `copyLink`:
1. If `navigator.clipboard.writeText` rejects (non-HTTPS context, permission denied), the catch block silently swallows the error and provides no user feedback — the button stays unchanged. Consider briefly showing a "Failed" state.
2. The `setTimeout` reference isn't stored or cleared on unmount. While React 18+ won't crash on a stale `setCopied`, clearing it avoids the no-op state update and is straightforward with a ref + cleanup effect.</violation>

<violation number="4" location="components/valuation/ShareValuation.tsx:74">
P2: The X and LinkedIn share buttons both display "Share" with identical visible text and no `aria-label`. This creates an accessibility gap — screen reader users cannot distinguish which platform each button shares to when navigating by control. Consider adding `aria-label="Share on X"` and `aria-label="Share on LinkedIn"` to give each anchor a unique accessible name.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

export function ShareValuation({ artistName, centralValue }: ShareValuationProps) {
const [copied, setCopied] = useState(false);

const url = "https://recoupable.dev/valuation";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The share link URL https://recoupable.dev/valuation is hardcoded instead of importing from the site config. The project convention (stated in both AGENTS.md and lib/config.ts) is to import siteConfig from @/lib/config and use siteConfig.url as the single source of truth for the domain. Using the config here prevents the share URL from going stale if the domain ever changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/valuation/ShareValuation.tsx, line 47:

<comment>The share link URL `https://recoupable.dev/valuation` is hardcoded instead of importing from the site config. The project convention (stated in both AGENTS.md and lib/config.ts) is to import `siteConfig` from `@/lib/config` and use `siteConfig.url` as the single source of truth for the domain. Using the config here prevents the share URL from going stale if the domain ever changes.</comment>

<file context>
@@ -0,0 +1,84 @@
+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";
</file context>

{copied ? <CheckIcon /> : <CopyIcon />}
{copied ? "Copied" : "Copy link"}
</button>
<a href={tweetUrl} target="_blank" rel="noopener noreferrer" className={btnClass}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The X and LinkedIn share buttons both display "Share" with identical visible text and no aria-label. This creates an accessibility gap — screen reader users cannot distinguish which platform each button shares to when navigating by control. Consider adding aria-label="Share on X" and aria-label="Share on LinkedIn" to give each anchor a unique accessible name.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/valuation/ShareValuation.tsx, line 74:

<comment>The X and LinkedIn share buttons both display "Share" with identical visible text and no `aria-label`. This creates an accessibility gap — screen reader users cannot distinguish which platform each button shares to when navigating by control. Consider adding `aria-label="Share on X"` and `aria-label="Share on LinkedIn"` to give each anchor a unique accessible name.</comment>

<file context>
@@ -0,0 +1,84 @@
+        {copied ? <CheckIcon /> : <CopyIcon />}
+        {copied ? "Copied" : "Copy link"}
+      </button>
+      <a href={tweetUrl} target="_blank" rel="noopener noreferrer" className={btnClass}>
+        <XIcon />
+        Share
</file context>

try {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Two minor stability issues with copyLink:

  1. If navigator.clipboard.writeText rejects (non-HTTPS context, permission denied), the catch block silently swallows the error and provides no user feedback — the button stays unchanged. Consider briefly showing a "Failed" state.
  2. The setTimeout reference isn't stored or cleared on unmount. While React 18+ won't crash on a stale setCopied, clearing it avoids the no-op state update and is straightforward with a ref + cleanup effect.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/valuation/ShareValuation.tsx, line 59:

<comment>Two minor stability issues with `copyLink`:
1. If `navigator.clipboard.writeText` rejects (non-HTTPS context, permission denied), the catch block silently swallows the error and provides no user feedback — the button stays unchanged. Consider briefly showing a "Failed" state.
2. The `setTimeout` reference isn't stored or cleared on unmount. While React 18+ won't crash on a stale `setCopied`, clearing it avoids the no-op state update and is straightforward with a ref + cleanup effect.</comment>

<file context>
@@ -0,0 +1,84 @@
+    try {
+      await navigator.clipboard.writeText(url);
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
+    } catch {
+      // Fallback — noop
</file context>

);
}

function XIcon() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Local XIcon component collides with lucide-react's XIcon used elsewhere in the codebase. This creates ambiguity when grepping for the close icon. Rename to something specific like XLogoIcon to avoid confusion with the dismiss icon.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/valuation/ShareValuation.tsx, line 28:

<comment>Local `XIcon` component collides with `lucide-react`'s `XIcon` used elsewhere in the codebase. This creates ambiguity when grepping for the close icon. Rename to something specific like `XLogoIcon` to avoid confusion with the dismiss icon.</comment>

<file context>
@@ -0,0 +1,84 @@
+  );
+}
+
+function XIcon() {
+  return (
+    <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant