Finalize citizen claim Invite Rewards UI, state feedback, and test evidence#112
Conversation
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.
|
Hi @L03TJ3 Please can you take a look at this, thank you |
|
I think you missed the instruction step of testing out the actual flow (ux/ui) as we have it in the live wallet: https://goodwallet.xyz the design system as-is should support most of the design and flow, including usage of drawer
|
|
Once that step is followed up on and resolved. |
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.
|
Thanks for the catch, @L03TJ3 Two structural gaps stood out and are now fixed:
Card order now mirrors GoodWallet's |
There was a problem hiding this comment.
Hi @L03TJ3
I have fixed the requested changes, i don't have the access to request review
|
What do you mean with: |
|
and if you did manually/humanly test the flow on goodwallet.xyz as requested |
|
Apologies! I was connected on vpn that was why, i am able to access it now Thank you |
|
Thanks for the screenshot, @L03TJ3 that made this much easier to verify precisely. I have fixed the requested changes can you pls review this |
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".
e6f35c0 to
503f401
Compare
|
Are you only looking at the design reference screenshot, or have you actually tested the flow yourself? |
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.
|
Yes i did, i will attach a screenshot of that now |
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.
|
Hi @L03TJ3 I just tested it now on the widget and i have attached the updated screenshot of the fixes |

Contributor: @robertocarlous
Continuation of #91, addressing the scoped gaps described in #109. References #84 and #85 for background/design intent.
Summary
Finishes the Invite Rewards presentation and state handling introduced in #91, without touching
@goodsdks/invite-sdk, GoodProtocol, or InvitesV2.Fixed
getInvitees().length(everyone registered, paid or not). Now uses the protocol'stotalApprovedInvites, with registered / approved / pending / collectable shown as distinct values.totalEarnedis now shown, in its own card.getCollectableInvitees().Alertbanner, independent of which card triggered them. Also fixed a real bug ininviteAdapter.tswhere a failed post-action snapshot reload could flip the view to a hard, full-page error screen, silently hiding a join/collection that had already succeeded on-chain.UX parity with the live goodwallet.xyz flow
Checked three ways: against the GoodDollar/GoodWallet reference source, against a live screenshot @L03TJ3 shared on this PR, and against a real wallet connected locally to this widget's own Storybook (same wallet, same state, side-by-side with goodwallet.xyz).
InviteView— instead of inline text in a filled button. Uses the existingDrawer,textButton variant, andinfoIcon — no new component surface.TotalEarnedBox.bounty / 2and displays it live; this mirrors that existing display convention, not a new payout rule.InviteesListBoxuntil there's at least one invitee. The full approved/pending/collectable breakdown still shows in full once there's at least one invitee.canAttachInviter()requiredinviteCode !== zeroHash) that isn't part of the deferred-inviter rule [GoodBounty]: Finalize citizen claim Invite Rewards PR #109 protects (that rule is only:invitedByempty + bounty unpaid) and isn't required by theInvitesV2.join()contract call itself (it creates the caller's code and attaches the inviter in one transaction —performJoin/getMyInviteCodealready handle this). Dropped that extra precondition; the two protected conditions are untouched.Card order now mirrors GoodWallet's
InviteView: reward headline + How-it-works trigger → share/create → code entry → total rewards → invitee list (when non-empty).Acceptance criteria (from #109)
Definition of Done (from #109)
packages/citizen-claim-widgetand the invite-sdk integration, its stories/tests, curated screenshots, and necessary documentation.Badge,Alert,Card,Drawer,Icon, etc. from@goodwidget/ui— no new theme targets, no redesign) and now match GoodWallet's actual InviteView hierarchy.getCollectableInvitees()is non-empty; the UI does not treat campaign activity as eligibility.invitedByis empty and the bounty is unpaid, matching the protected rule precisely —canAttachInviterno longer requires a precondition (having a personal code) that isn't part of that rule (see above).pnpm install --frozen-lockfile, scoped build/lint, and the relevant Playwright tests pass (see below).Verified
performJoinunit test.performJoinunit test.inviteRules.spec.ts+ Playwright + confirmed against a real connected wallet locally.performCollectAllunit test + Playwright flow.performJoin/performCollectAllunit tests.Evidence
Unit tests (deterministic, no browser):
tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts,inviteRules.spec.ts— 13/13 passing.Playwright:
tests/widgets/citizen-claim-widget/inviteRewards.spec.ts— 15/15 passing (31/31 across the whole widget suite).Storybook:
examples/storybook/src/stories/citizen-claim-widget/InviteRewardsQA.stories.tsx(QA/CitizenClaimWidget/Invite Rewards Fixtures) — 122/122 passing repo-wide, no regressions.How this was tested
pnpm install --frozen-lockfile
pnpm --filter @goodwidget/citizen-claim-widget build # pass
pnpm --filter @goodwidget/citizen-claim-widget lint # pass
pnpm storybook &
pnpm test:demo tests/widgets/citizen-claim-widget # 31/31 pass
pnpm test:storybook # 122/122 pass