Skip to content

Integrate InviteSDK rewards flow into citizen claim widget - #91

Draft
L03TJ3 with Copilot wants to merge 6 commits into
mainfrom
copilot/integrate-invite-sdk-into-widget
Draft

Integrate InviteSDK rewards flow into citizen claim widget#91
L03TJ3 with Copilot wants to merge 6 commits into
mainfrom
copilot/integrate-invite-sdk-into-widget

Conversation

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Adds @goodsdks/invite-sdk@1.0.1 to the citizen claim widget and replaces the Invite Rewards placeholder with a provider-first, viem-backed invite lifecycle. Claim and Invite Rewards now share invite validation, joining, and refresh state without changing claim behavior.

  • Shared invite runtime

    • Initializes InviteSDK only for Celo/XDC and delegates reads, simulations, writes, error mapping, and collection to the SDK.
    • Loads invite user, level, invitees, pending invitees, and eligibility diagnostics.
    • Supports retryable loading, unsupported-network, disconnected, join, collection, and mutation-error states.
  • Invite code and joining

    • Implements GoodWallet’s Base58 shortest-unused-prefix algorithm using InviteSDK.resolveCode.
    • Validates unknown, self, and already-joined invite codes before joining.
    • Exposes the same pending-safe join action from both Claim and Invite Rewards.
  • Rewards experience

    • Adds reward education, verified-code creation, current-page sharing, pending/approved invitee status, diagnostics, and batch collection.
    • Shares the exact current page URL without host callback, route, or destination configuration.
const message =
  `Claim GoodDollar with me. Open this page and use my invite code: ${code}\n` +
  window.location.href
  • Coverage and documentation
    • Adds Invite Rewards tab smoke coverage and updated screenshot evidence.
    • Documents supported networks, environment behavior, sharing contract, code generation, and collection lifecycle.

Copilot AI linked an issue Jul 16, 2026 that may be closed by this pull request
12 tasks
Co-authored-by: L03TJ3 <6606028+L03TJ3@users.noreply.github.com>
Copilot AI changed the title [WIP] Integrate invite SDK into citizen-claim-widget Integrate InviteSDK rewards flow into citizen claim widget Jul 16, 2026
Copilot AI requested a review from L03TJ3 July 16, 2026 05:08
@L03TJ3

L03TJ3 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

@copilot

Please update the direct dependency to @goodsdks/invite-sdk@1.0.3 and refresh pnpm-lock.yaml.

Then wire only these two SDK capabilities. No broader Invite Rewards redesign, copy pass, or documentation work is needed in this follow-up.

  1. Deferred inviter attachment

The contract and InviteSDK 1.0.3 support this valid flow:

await sdk.join(myCode, zeroHash)     // personal code was registered earlier
await sdk.join(myCode, inviterCode)  // attach one valid inviter later

Update the widget so a user who already has user.inviteCode may still enter an inviter code when:

user.inviteCode !== zeroHash &&
user.invitedBy === zeroAddress &&
!user.bountyPaid

Use the existing user.inviteCode as myCode in sdk.join; only run generateInviteCode for a user with no code yet. Remove the widget-level joinedAt > 0n rejection and change the join-card visibility predicate to the rule above. Keep the SDK typed errors as the final guard.

Important protocol boundary: an ordinary UBI claim does not prevent this. A paid invite bounty or an already attached inviter does; show the normal SDK error in those cases.

  1. Pending invitees that can be collected

Use the new SDK helper in 1.0.3:

const collectableInvitees = await sdk.getCollectableInvitees(address)

Keep getPendingInvitees(address) for showing the full pending-address list. Store collectableInvitees in the invite runtime and enable "Collect eligible rewards" only when that array is non-empty. actions.collectAll can continue to call sdk.collectAllBounties(); the contract already safely skips pending invitees that are not eligible.

This replaces the current details.isActive check, which is not an eligibility check. Do not add per-invitee diagnostic logic beyond what is already present.

Please add focused tests for:

  • a code-only registered user attaching a valid inviter later, using the original code;
  • a paid/already-attached user not seeing or completing that flow;
  • collect enabled only when getCollectableInvitees is non-empty.

Co-authored-by: L03TJ3 <6606028+L03TJ3@users.noreply.github.com>

Copilot AI commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

Please update the direct dependency to @goodsdks/invite-sdk@1.0.3 and refresh pnpm-lock.yaml....

Addressed in 573f1c1: upgraded to InviteSDK 1.0.3, reuses registered codes for deferred inviter attachment, gates collection through getCollectableInvitees, and adds focused coverage.

@AntonsBB

Copy link
Copy Markdown

Contributor handoff for #109 is ready in #111. That branch preserves all three commits from this PR, resolves the current main-branch conflicts, and adds the scoped Invite Rewards fixes plus deterministic desktop/mobile evidence. I could not push to this upstream head directly; #111 is currently open and mergeable.

…idence (#112)

* Finalize Invite Rewards UI, state feedback, and test evidence

Fix the scoped gaps left in the InviteSDK Invite Rewards integration:
correct approved/pending/collectable/total-earned labels to use the
protocol-provided values instead of the raw invitee count, show a clear
ready-to-collect vs waiting status per invitee, and make join/collection
success and error feedback persist in the view after the join card
disappears or a background refresh runs. Extracts the join/collect
orchestration into pure, testable functions and adds deterministic
adapter unit tests plus Storybook/Playwright coverage for the deferred
attach and collection-ready flows without needing a live wallet.

* Align Invite Rewards layout with GoodWallet's actual InviteView

Fetched the live reference (InviteView.tsx, ShareLinkBox.tsx,
TotalEarnedBox.tsx, etc.) from GoodDollar/GoodWallet to check the
widget against the real flow instead of only the issue's text
description. Two structural gaps stood out:

- "How it works" was inline, always-visible text. GoodWallet opens it
  from a ghost button into a Drawer (bottom sheet) — switched to the
  same pattern using the existing Drawer primitive.
- Total earned was buried inside the invitee-status card. GoodWallet
  gives it its own card (TotalEarnedBox); split it out to match.

Card order now mirrors GoodWallet's: reward headline + How-it-works
trigger, share/create, code entry, total rewards, invitee list.

* Align Invite Rewards copy and How-it-works style with the live flow

Three copy/style gaps remained after the structural Drawer/TotalEarnedCard
fix, found by comparing directly against the live Invite Rewards tab:

- The invitee also gets shown a reward figure ("Your invitee will
  also receive X G$") — GoodWallet computes this client-side as
  bounty / 2 and displays it live, so this mirrors that existing
  display convention rather than introducing a new payout rule.
- "How it works" is a plain inline info-icon link in production, not
  a filled button — switched to the existing `text` Button variant
  plus the existing `info` Icon.
- Copy tightened to match: "Get X G$ every time a friend joins!" and
  "Total rewards earned".

* Hide the invitee-list card when there are no invitees yet

Matches GoodWallet's InviteesListBox, which is also omitted entirely
until there's at least one invitee to report on, instead of showing
an empty "0 approved / 0 pending" breakdown. The full approved/
pending/collectable status still shows in full once there is at
least one invitee, so this doesn't affect the acceptance criteria
around accurate invitee-status distinctions — it only removes noise
on a fresh account with nothing to report.

* Offer deferred join before the caller has a code or is whitelisted

canAttachInviter() required user.inviteCode !== zeroHash before showing
the join-with-code card. That precondition isn't part of the deferred-
inviter rule the issue protects (invitedBy empty + bounty unpaid); it's
an extra restriction added by the original implementation. It also
doesn't reflect the actual InvitesV2.join() contract call, which creates
the caller's own code and attaches the inviter in the same transaction,
and it doesn't match GoodWallet's InvCodeBox, which offers deferred
attachment regardless of whether the caller has a code or is whitelisted.

Confirmed both via a mocked fixture and against a real wallet connected
locally, in the same state as a live goodwallet.xyz session: our widget
was showing 2 of the 3 cards GoodWallet shows for that exact state.

- Dropped the inviteCode check from canAttachInviter(); invitedBy-empty
  and bounty-unpaid remain untouched.
- Share card: when not whitelisted, shows only the explanatory error
  text (no disabled button), matching GoodWallet's ShareLinkBox, and
  keeps one stable card title across states instead of switching
  between "Create..." and "Share...".
- Join card renamed "Use invite code" with matching placeholder copy,
  visible now in this state as confirmed live.
- Updated the one existing test that encoded the old, over-restrictive
  behavior, and added coverage for the corrected behavior and for this
  exact not-yet-whitelisted state.

* fixed the Ui/ux flow

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR integrates the InviteSDK-backed “Invite Rewards” flow into the citizen-claim-widget, introducing a shared invite runtime (provider-first, viem-backed) that powers both the Claim tab’s inviter-join entry point and the dedicated Invite Rewards tab, plus Storybook QA fixtures and Playwright smoke coverage.

Changes:

  • Adds a shared InviteSDK runtime (useInviteAdapter + provider/context) with snapshot loading, join, collect, and error mapping.
  • Introduces the Invite Rewards UI (share, join-with-code, invitee status/collection) and wires it into the widget tabs alongside Claim.
  • Adds deterministic Storybook QA fixtures/stories and Playwright coverage (including new unit-style Playwright tests for invite rules/adapter helpers), plus updates docs/dependencies.

Reviewed changes

Copilot reviewed 15 out of 33 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/widgets/citizen-claim-widget/states.spec.ts Adds a smoke check that the Invite Rewards tab entry point opens and is screenshot-captured.
tests/widgets/citizen-claim-widget/inviteRules.spec.ts Adds Playwright-run unit tests for pure invite rule helpers.
tests/widgets/citizen-claim-widget/inviteRewards.spec.ts Adds Playwright coverage for Invite Rewards UI states and key flows using deterministic fixtures.
tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts Adds Playwright-run unit tests for invite adapter helpers (snapshot/join/collect/code encode).
pnpm-lock.yaml Locks new dependencies (@goodsdks/invite-sdk, bs58).
packages/citizen-claim-widget/src/inviteRules.ts Introduces pure invite rules reused across adapter/UI/tests.
packages/citizen-claim-widget/src/InviteRewards.tsx Adds Invite Rewards UI + Claim-tab join card powered by the shared invite runtime.
packages/citizen-claim-widget/src/inviteAdapter.ts Implements the shared InviteSDK runtime, snapshot loading, join/collect orchestration, and code helpers.
packages/citizen-claim-widget/src/integration.ts Declares InviteSDK integration metadata (capabilities/states/uses).
packages/citizen-claim-widget/src/index.ts Exposes the new invite runtime, rules, and UI components from the package entrypoint.
packages/citizen-claim-widget/src/CitizenClaimWidget.tsx Wires InviteRuntimeProvider into the widget and replaces the placeholder tab with InviteRewards.
packages/citizen-claim-widget/src/adapter.ts Extracts a shared createCitizenWidgetClients factory used by both claim + invite flows.
packages/citizen-claim-widget/README.md Documents Invite Rewards behavior, sharing contract, and protocol-derived counters.
packages/citizen-claim-widget/package.json Adds @goodsdks/invite-sdk and bs58 dependencies.
examples/storybook/src/stories/helpers/inviteRewardsStories.tsx Adds deterministic Invite Rewards fixtures + a stateful mock invite runtime provider for QA stories/tests.
examples/storybook/src/stories/citizen-claim-widget/InviteRewardsQA.stories.tsx Adds QA stories (static + interactive) for Invite Rewards.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +385 to +388
const collectAll = useCallback(async () => {
const sdk = await getSdk()
if (!sdk || !address) return

): Promise<string> {
const encodedAddress = bs58.encode(hexToBytes(address))

for (let length = 10; length <= 30; length += 1) {
* pnpm storybook (in one terminal)
* pnpm test:demo (in another terminal)
*
* Screenshot evidence: tests/widgets/citizen-claim-widget/test-results/ccw-06 .. ccw-13
Comment on lines 3 to 5
sdk: '@goodsdks/citizen-sdk',
inviteSdk: '@goodsdks/invite-sdk@1.0.3',
capabilitySource: 'citizenSdkCapabilities',
@L03TJ3
L03TJ3 deployed to internal August 17, 2026 08:48 — with GitHub Actions Active
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.

[PLAN] Integrate invite SDK into citizen-claim-widget

5 participants