Integrate InviteSDK rewards flow into citizen claim widget#91
Conversation
Co-authored-by: L03TJ3 <6606028+L03TJ3@users.noreply.github.com>
|
Please update the direct dependency to Then wire only these two SDK capabilities. No broader Invite Rewards redesign, copy pass, or documentation work is needed in this follow-up.
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 laterUpdate the widget so a user who already has user.inviteCode !== zeroHash &&
user.invitedBy === zeroAddress &&
!user.bountyPaidUse the existing 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.
Use the new SDK helper in 1.0.3: const collectableInvitees = await sdk.getCollectableInvitees(address)Keep This replaces the current Please add focused tests for:
|
Co-authored-by: L03TJ3 <6606028+L03TJ3@users.noreply.github.com>
|
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
There was a problem hiding this comment.
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.
| 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 |
| sdk: '@goodsdks/citizen-sdk', | ||
| inviteSdk: '@goodsdks/invite-sdk@1.0.3', | ||
| capabilitySource: 'citizenSdkCapabilities', |
Adds
@goodsdks/invite-sdk@1.0.1to 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
Invite code and joining
InviteSDK.resolveCode.Rewards experience