diff --git a/frontend/src/BountyDetailPage.test.tsx b/frontend/src/BountyDetailPage.test.tsx index 030f69d3..1477f1af 100644 --- a/frontend/src/BountyDetailPage.test.tsx +++ b/frontend/src/BountyDetailPage.test.tsx @@ -154,6 +154,30 @@ describe("BountyDetailPage copy actions", () => { expect(print).toHaveBeenCalledOnce(); }); + it("renders social share links with prefilled bounty details", () => { + renderDetail(); + + const twitterLink = screen.getByRole("link", { name: /share bounty on x/i }); + const linkedInLink = screen.getByRole("link", { name: /share bounty on linkedin/i }); + + expect(twitterLink).toHaveAttribute("target", "_blank"); + expect(twitterLink).toHaveAttribute("rel", "noopener noreferrer"); + expect(twitterLink.getAttribute("href")).toContain("https://twitter.com/intent/tweet?"); + expect(twitterLink.getAttribute("href")).toContain( + encodeURIComponent("Copy button test bounty — 150 XLM bounty"), + ); + expect(twitterLink.getAttribute("href")).toContain( + encodeURIComponent("http://localhost:3000/bounties/BNTY-42"), + ); + + expect(linkedInLink).toHaveAttribute("target", "_blank"); + expect(linkedInLink).toHaveAttribute("rel", "noopener noreferrer"); + expect(linkedInLink).toHaveAttribute( + "href", + "https://www.linkedin.com/sharing/share-offsite/?url=http%3A%2F%2Flocalhost%3A3000%2Fbounties%2FBNTY-42", + ); + }); + it("announces status changes for assistive technology", () => { const { rerender } = renderDetail(); const reservedBounty: Bounty = { diff --git a/frontend/src/BountyDetailPage.tsx b/frontend/src/BountyDetailPage.tsx index 23aef262..be212b58 100644 --- a/frontend/src/BountyDetailPage.tsx +++ b/frontend/src/BountyDetailPage.tsx @@ -7,6 +7,12 @@ import { updateSocialMetaTags } from "./metaTags"; import CopyIcon from "./CopyIcons"; import { extendDeadline } from "./api"; import { findSimilarBounties, type BountyRecommendation } from "./recommendations"; +import { + buildBountyPermalink, + buildBountyShareText, + buildLinkedInShareUrl, + buildTwitterShareUrl, +} from "./shareLinks"; type BountyAction = "reserve" | "submit" | "release" | "refund"; @@ -143,16 +149,18 @@ export default function BountyDetailPage({ window.print(); } + const sharePermalink = bounty ? buildBountyPermalink(bounty.id) : ""; + const shareText = bounty ? buildBountyShareText(bounty) : ""; + function handleShare() { if (!bounty) return; - const permalink = `${window.location.origin}/bounties/${encodeURIComponent(bounty.id)}`; - navigator.clipboard.writeText(permalink).then(() => { + navigator.clipboard.writeText(sharePermalink).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }).catch((err) => { console.error("Failed to copy URL:", err); // Fallback: show the URL in a prompt so the user can manually copy it - window.prompt("Copy the bounty URL manually:", permalink); + window.prompt("Copy the bounty URL manually:", sharePermalink); }); } @@ -171,6 +179,44 @@ export default function BountyDetailPage({

{bounty ? bounty.title : "Bounty"}

+
+ { + if (loading || !bounty) { + event.preventDefault(); + } + }} + > + + X + + { + if (loading || !bounty) { + event.preventDefault(); + } + }} + > + + LinkedIn + +