Skip to content

feat(frontend): Embeddable Campaign Widget component for partner websites - #1389

Merged
joelpeace48-cell merged 2 commits into
FinesseStudioLab:mainfrom
abayomicornelius:feat/1219-embeddable-campaign-widget
Sep 25, 2026
Merged

joelpeace48-cell merged 2 commits into
FinesseStudioLab:mainfrom
abayomicornelius:feat/1219-embeddable-campaign-widget

Conversation

@abayomicornelius

@abayomicornelius abayomicornelius commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Partners can now embed a Trivela campaign widget on their own sites either as a plain <iframe> or as a drop-in React component, using snippets generated from the campaign page.

The backend already serves sandbox-safe widget pages at /embed/v1/:widgetType/:campaignId (card, leaderboard, progress). What was missing was the partner-facing side: the campaign page only offered one hard-coded dark-card iframe, and there was no React component.

Changes

1. frontend/src/lib/embedSnippet.js: snippet builders

  • validateEmbedOptions() applies the same rules the backend enforces in embedWidget.js, so a generated snippet never points at a URL the server rejects:
    • widget ∈ card | leaderboard | progress, theme ∈ dark | light;
    • hex color;
    • partner matching [A-Za-z0-9_-]{1,64};
    • org trimmed to 60 characters;
    • leaderboard limit from 1 to 50, ignored for other widgets.
  • buildEmbedUrl(origin, options) builds the /embed/v1/... URL with encoded query parameters.
  • buildIframeSnippet() produces a copy-paste iframe:
    • sized per widget (card 400×280, leaderboard 400×480, progress 400×200);
    • loading="lazy" and referrerpolicy="strict-origin-when-cross-origin";
    • responsive max-width:100%;
    • HTML-escaped attributes.
  • Sandbox: allow-scripts allow-popups allow-popups-to-escape-sandbox covers what the widgets need (their inline resize/postMessage script, and "Register on Trivela" opening in a new tab). It deliberately omits allow-same-origin, so the widget can't access the host page.
  • buildReactSnippet() produces a <TrivelaCampaignWidget …/> snippet listing only the props that are set.

2. frontend/src/components/TrivelaCampaignWidget.jsx: the React component

<TrivelaCampaignWidget origin campaignId widget theme color partner org limit width height title className style /> renders the widget in a sandboxed, lazy iframe.

  • Error boundary by design: invalid props render nothing and log a warning in development only, so a misconfigured embed can never break the partner's page.

3. frontend/src/components/EmbedSnippetGenerator.jsx in CampaignDetail.jsx

  • The "Embed this campaign" section now has Widget, Theme and Format (HTML iframe / React component) selectors, a live snippet preview and Copy snippet.
  • This replaces the old inline, dark-card-only snippet and its now-unused state.
  • partner attribution (?partner=) is supported by the builders and the component. Partners add their own id to the generated snippet.

Tests (vitest)

  • src/lib/embedSnippet.test.js (7 tests):
    • defaults, backend-matching validation, and leaderboard limit bounds;
    • URL building and encoding; invalid options throw;
    • iframe snippet sandbox, size, lazy loading and attribute escaping, including a check that allow-same-origin is absent;
    • the React snippet lists only the props that are set.
  • src/components/TrivelaCampaignWidget.test.jsx (3 tests):
    • the component renders a sandboxed iframe with the right src and size;
    • invalid props render nothing;
    • the generator switches widget and format and copies to the clipboard.

npx vitest run src/lib/embedSnippet.test.js src/components/TrivelaCampaignWidget.test.jsx: 10/10 passing.
Closes #1219
Closes #1228
Closes #1229
Closes #1220

Backward compatibility

  • No backend changes. The existing /embed/campaign/:id and /embed/v1/* routes are untouched, so iframe snippets already published by partners keep working.

…lders (FinesseStudioLab#1219)

Add lib/embedSnippet.js (validate options with the backend's rules,
build /embed/v1/:widget/:campaignId URLs, and generate sandboxed
<iframe> and React snippets) and a drop-in <TrivelaCampaignWidget>
React component that renders the card/leaderboard/progress widget in a
sandboxed, lazy iframe and renders nothing on invalid props.
…udioLab#1219)

Replace the hard-coded dark-card iframe snippet with EmbedSnippetGenerator:
partners pick widget type, theme and format (HTML iframe or React
component) and copy the generated snippet. Adds component tests.
@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@abayomicornelius Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@joelpeace48-cell
joelpeace48-cell merged commit af1cca4 into FinesseStudioLab:main Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants