Skip to content

[PLAN] Add Superfluid Campaign GoodWidget #129

Description

@goodbounties-nanoclaw-agent

[PLAN] Add Superfluid Campaign GoodWidget

Type: Task
Links back to: #127 (parent [Feature] issue — body unchanged)


One reusable GoodWidget (public-page + GoodWallet V2 embed) that presents the Superfluid Ecosystem Rewards campaign: header/description, two reward-pool sections (GoodDollar actions, Ecosystem funding actions), a public leaderboard, and an FAQ accordion. Wallet connection is optional and only reveals the connected user's own rank/points/activity state. This phase is UI + responsive layout only, against mock data — no points logic or live API integration (tracked as follow-up, out of scope here per #127).

Reference files mapped

GoodDollar/GoodWidget (this repo):

  • packages/ui/src/config.ts:222-246 — Tamagui breakpoint config (media: { sm: maxWidth 480, md: maxWidth 768, gtSm: minWidth 480, gtMd: minWidth 768 }), the mechanism this plan's responsive approach builds on.
  • packages/ai-credits-widget/src/components/manage/CreditsManagementCard.tsx:28-48$gtSm grid-column-count pattern (45% width columns below gtSm, 28% above), direct precedent for the pool action-card grid.
  • packages/governance-widget/src/OptimisticVotingProposalCard.tsx:67, FundingDistributionChart.tsx:114,163 — fixed/contained-width card patterns.
  • packages/streaming-widget/src/ — modularity precedent to follow: thin index.ts contract + adapter.ts/adapter/ (data/state hooks) + components/ (presentational) + element.ts/register.ts (web-component bridge) + widgetRuntimeContract.ts (types). This repo's newest widget and closest structural analog.
  • packages/citizen-claim-widget/src/CitizenClaimWidget.tsx:533activeTab is currently internal useState, hardcoded to 'claim', no initialTab prop exists today. This plan adds one (see Execution plan) rather than working around its absence.
  • packages/citizen-claim-widget/src/widgetRuntimeContract.ts — existing prop/type contract shape to extend.
  • packages/embed/src/createMiniAppElement.tsx — shared web-component bridge (shadow DOM, theme override CSS vars, event bridging) used by every widget's element.ts; the new widget reuses this unchanged.
  • packages/core/src/hooks.ts (useWallet, useHost, useGoodWidget), packages/core/src/provider.tsx (GoodWidgetProvider) — wallet connection state and host detection, reused as-is; no new wallet logic needed.
  • packages/ui/src/index.ts — existing primitives to reuse (Card, Heading, Text, Button, Separator, WidgetTabs, Icon, Drawer/Dialog). No Accordion, ProgressBar, Table, or Avatar component exists yet — these are new (see New components below).
  • AGENTS.md, ARCHITECTURE.md, pnpm-workspace.yaml — package naming (@goodwidget/<name>), workspace registration (packages/*), tsup build convention.

1Hive/gardens-v2 (behavior reference only, per #127 — visual design is the approved mockups, not this repo):

  • apps/web/app/(app)/gardens/campaigns/[campaignId]/client-page.tsx — optional-wallet-connection gating pattern: connected-only "Your Position" block rendered conditionally on useAccount()/points existing, disconnected users see the same table with no personal section.
  • apps/web/components/SuperfluidLeaderboard.tsx — leaderboard-modal structure: a distinct "Your Position" summary block above the table (not an inline highlight), scrollable table body, search-by-address/ENS filtering, no pagination.
  • apps/web/types/index.ts (fetchSuperfluidLeaderboard) — single fetch returns the full ranked list pre-sorted by points; connected user's rank is derived client-side via array index rather than a second API call. Since this phase uses mock data only, the plan adopts the same shape (one mock dataset, rank derived from array position) so swapping in the real API later is a drop-in.
  • Note: gardens-v2 does not highlight the user's row inline within the table — only the separate summary block. The approved mockup does both (summary block and an inline highlighted row). This plan follows the mockup, since [Feature]: Add Superfluid Campaign GoodWidget #127 states gardens is behavior reference only and the mockups are the design source of truth.

Existing @GoodDollar packages imported

  • @goodwidget/coreGoodWidgetProvider, useWallet, useHost, useGoodWidget.
  • @goodwidget/uiCard, Heading, Text, Button, Separator, Icon, WidgetTabs, Stack/XStack/YStack, createGoodWidgetConfig/theme tokens.
  • @goodwidget/embedcreateMiniAppElement for the web-component bridge.
  • @goodwidget/citizen-claim-widget — embedded directly for the Claim and Invite flows (not rebuilt), via a new initialTab prop (see Execution plan).

New components mapped (package placement)

New widget package: packages/superfluid-campaign-widget (@goodwidget/superfluid-campaign-widget), following the streaming-widget decomposition:

packages/superfluid-campaign-widget/src/
  index.ts                       # public exports (component + types)
  element.ts / register.ts       # web-component bridge (mirrors streaming-widget)
  widgetRuntimeContract.ts       # SuperfluidCampaignWidgetProps, mock data shape, activity types
  SuperfluidCampaignWidget.tsx   # thin contract: provider wiring + tab/view routing (content vs leaderboard)
  components/
    CampaignHeader.tsx           # title, description, SUP-allocated/ends-date pills, background art
    RewardPoolSection.tsx        # one of the two pools: heading, participant count, progress bar, action cards
    ActionCard.tsx                # single eligible action row (icon, title, source, copy, points, CTA)
    LeaderboardSummary.tsx       # collapsed "top contributors" + progress bar shown on the content page
    LeaderboardView.tsx          # full leaderboard: search, table, your-position block, pagination controls
    LeaderboardRow.tsx           # one leaderboard row, incl. highlighted-row state for the connected user
    ActivityIcons.tsx            # renders the six done/not-done activity icons from the exact mapping in #127
    FaqAccordion.tsx             # FAQ accordion using the new shared Accordion primitive
    ConnectWalletPrompt.tsx      # disconnected-state CTA

New shared primitives — built in packages/ui (reusable beyond this widget, not campaign-specific):

  • Accordion (packages/ui/src/components/Accordion.tsx) — expandable item list; FAQ is the first consumer but this is generic UI, belongs in packages/ui per the repo's existing "reusable in packages/ui vs widget-specific" split.
  • ProgressBar (packages/ui/src/components/ProgressBar.tsx) — labeled linear progress bar (SUP-allocated and per-pool progress all reuse the same primitive); generic enough for any widget with a completion/allocation metric.

Kept inside the widget package (not packages/ui) because they're specific to this campaign's data shape, not generic UI:

  • LeaderboardRow/LeaderboardView/ActivityIcons — tied to the six-activity, points-based campaign model from [Feature]: Add Superfluid Campaign GoodWidget #127, not a generic table component.
  • ActionCard/RewardPoolSection — tied to the GoodDollar-actions/Ecosystem-funding-actions pool structure specific to this campaign.

citizen-claim-widget change (small, contract-level, not a new component): add initialTab?: 'claim' | 'invite-rewards' to CitizenClaimWidgetProps in widgetRuntimeContract.ts, threaded into the existing activeTab useState initializer in CitizenClaimWidget.tsx:533. This is the only change to an existing package.

Required states, flows, and behaviors

  • Disconnected (public) view: campaign header, both reward-pool sections with mock progress/participant counts, a collapsed leaderboard summary + "View Leaderboard" action, FAQ accordion, and a "Connect wallet" prompt. No personal data anywhere.
  • Connected view: adds, in the leaderboard summary and full leaderboard view, the user's rank + total points, per-activity completion icons (done/not-done, not counts) for the six activity types, and a highlighted row for the user within the full leaderboard table (in addition to the separate "your position" summary block, per the approved mockup).
  • Leaderboard drill-in: "View Leaderboard" opens the full LeaderboardView (search by address/ENS, ranked table, pagination controls per the mockup — gardens-v2 uses scroll instead of pagination; the mockup explicitly shows page-number controls, so this plan follows the mockup).
  • CTA routing:
  • Data: all progress/participant/leaderboard values come from a static mock dataset (shape mirrors gardens-v2's pre-sorted-array approach) so a later real-data swap doesn't change component contracts.
  • Responsive approach (required by [Feature]: Add Superfluid Campaign GoodWidget #127, proposed here — not in the mockups):
    • Breakpoints reuse the existing packages/ui/src/config.ts media config (sm/md/gtSm/gtMd) via $gtSm/$gtMd props — no new responsive utility introduced, per [Feature]: Add Superfluid Campaign GoodWidget #127's constraint.
    • Reward-pool action cards: desktop shows each ActionCard as a single-row card (icon, copy, points pill, CTA button in a horizontal XStack). Below gtSm (i.e. on phones, <480px), each card's internal XStack switches to a stacked YStack (icon+title row, then copy, then points pill + CTA row) — same content and order, just relayout, no columns to collapse since cards are already full-width single-column in the mockup at desktop.
    • Leaderboard table: desktop shows all four columns (Rank, Address/ENS, Points, Activities) inline. Below gtMd (tablet, <768px) the table remains but the six activity icons wrap onto a second line within the Activities cell instead of truncating, since [Feature]: Add Superfluid Campaign GoodWidget #127 requires no content to be dropped. Below gtSm (phone, <480px), each row switches from a table row to a stacked card (YStack) with the same four data groups in the same order (rank+address on top, points below, activities below that) — this preserves hierarchy and avoids introducing horizontal scroll, which the mockup doesn't show and [Feature]: Add Superfluid Campaign GoodWidget #127 doesn't sanction as a mobile pattern.
    • "Your position" summary block: already a compact horizontal strip on desktop (rank, avatar, points, activities); below gtSm it stacks the same four groups vertically in the same order, matching the leaderboard row behavior for consistency.
    • Campaign header: the background wave art is decorative; below gtSm it's de-emphasized (reduced opacity/cropped, not removed) so the title/description/stat-pills — all functional content — keep full width and remain first in reading order, satisfying "no content dropped."
    • Two reward-pool sections: already stacked vertically at desktop per [Feature]: Add Superfluid Campaign GoodWidget #127's explicit requirement ("stacked vertically, not side-by-side") — this is unchanged at every breakpoint, so no responsive behavior is needed here.
    • This breakdown is a proposal for review, not a final locked design.

Execution plan

  1. Scaffold packages/superfluid-campaign-widget (package.json modeled on streaming-widget's, @goodwidget/superfluid-campaign-widget, registered in the existing packages/* workspace glob — no config change needed).
  2. Add widgetRuntimeContract.ts: SuperfluidCampaignWidgetProps, mock leaderboard/pool data types, the six ActivityType enum values with their exact icon mapping from [Feature]: Add Superfluid Campaign GoodWidget #127.
  3. Build static presentational components against the mock dataset: CampaignHeader, RewardPoolSection + ActionCard, LeaderboardSummary, LeaderboardView + LeaderboardRow + ActivityIcons, FaqAccordion, ConnectWalletPrompt — matching the two approved mockups pixel-for-pixel on desktop.
  4. Build the two new shared packages/ui primitives (Accordion, ProgressBar) needed by step 3, each with its own minimal prop contract (not campaign-specific).
  5. Wire SuperfluidCampaignWidget.tsx (thin contract component): consumes useWallet()/useGoodWidget() from @goodwidget/core to branch connected/disconnected props into the components from step 3; routes CTA clicks to citizen-claim-widget (embedded with initialTab) or external links.
  6. Add initialTab prop to citizen-claim-widget's widgetRuntimeContract.ts + CitizenClaimWidget.tsx:533 (small, isolated change; existing internal default behavior unaffected when the prop is omitted).
  7. Implement the responsive layout per the breakpoint plan above across all components, verified against both mockups at desktop width and manually at sm/gtSm/md/gtMd widths.
  8. Add element.ts/register.ts web-component bridge mirroring streaming-widget's, so the widget works both as a public-page React component and a GoodWallet V2 embed from the same implementation.
  9. Add Storybook/example fixture (following the ai-credits-web app pattern) for manual visual QA against the mockups.
  10. Manual QA pass: disconnected view, connected view, all six CTA destinations, FAQ expand/collapse, leaderboard search, all four breakpoints.

Acceptance criteria

  • Widget renders as a public-page component and as a GoodWallet V2 embed from the same reusable implementation (packages/superfluid-campaign-widget).
  • Disconnected state shows full campaign header, both reward-pool sections with mock progress/participant data, leaderboard summary + full leaderboard, and a "Connect wallet" option — no personal data.
  • Connected state additionally shows the user's rank, total points, per-activity completion icons (six types, done/not-done only), and highlights their row in the full leaderboard.
  • Claim UBI and Invite users open citizen-claim-widget on the correct tab via the new initialTab prop; Vote/Fund/Donate CTAs link to the exact URLs from [Feature]: Add Superfluid Campaign GoodWidget #127.
  • FAQ renders as expandable accordion items with the exact copy from [Feature]: Add Superfluid Campaign GoodWidget #127, using the new packages/ui Accordion primitive.
  • Layout, styling, colors, icons, and copy match the two approved mockups on desktop.
  • Tablet/mobile responsive behavior matches the breakpoint plan above: no content dropped or restructured, only relayout, using $gtSm/$gtMd (no new responsive utility).
  • No live points/reward-calculation logic or external API integration is introduced — all data is mocked, matching gardens-v2's pre-sorted-array shape for a drop-in-ready future swap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Prepare AI Task

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions