Repository navigation
fix(issues): funder-only refund, payout-wallet claim gate, dashboard … - #566
Merged
chonilius merged 2 commits intoSep 27, 2026
Conversation
…races, stale network mismatch Four reported frontend bugs on one branch. Verified: eslint clean, `tsc --noEmit` clean, 627 tests / 41 suites green, `next build`, verify:headers, verify:env. 1. "Refund sponsor" was offered to every signed-in user - The funder field existed the whole time: `Bounty.sponsorId` is a plain column on the backend entity (unlike `issue`/`claimedBy`, which need an explicit `relations` option), so it is serialized in every bounty payload — the frontend just dropped it. Added to `Bounty`, `RawBounty` and `adaptBounty`. - `IssueActions` derives `isSponsor` and fails closed: signed-out, or a payload with no `sponsorId`, and the button is not rendered. - Checked the public backend (MergeFi/backend, `BountiesService.refund`): it already throws `ForbiddenException` unless `sponsorId === callerUserId`, and the route is JWT + `@Roles(SPONSOR, MAINTAINER)`. So this was UI noise that 403'd on click, not an authz hole — but it should never have been offered. - Same file's `fund()` carries the *identical* single-sponsor check, so there is no crowdfunding model here to gate: one sponsor, one Escrow. Funding stays status-gated only, with a comment recording why, rather than inventing a rule the backend does not enforce. - Refund/fund derive the caller from the JWT, never the request body, so no `sponsorId` is sent along. 2. Claiming did not require a payout wallet - `ClaimButton` now hard-blocks with an amber `role="status"` prerequisite panel (with a /connect link) when the signed-in user has no `stellarAddress`. That is the durable server-linked field, not the live Freighter session: a reader whose extension is locked but whose profile holds an address is still claimable. - Signed-out readers were claiming as nobody; `handleClaim` routes to /connect first. - The server guard is not a safety net: on MergeFi/backend@9c78fd91e, `BountiesService.claim()` intends `BadRequestException('... has no linked Stellar address')` but interpolates an undefined `contributorId`. Team splits are unchecked too — `splitRelease` builds `recipientAddress: user?.stellarAddress ?? ''` with no empty test. Both recorded in the component as open backend gaps rather than assumed fixed. - New `claimErrorMessage` surfaces non-`ALREADY_CLAIMED` claim failures in a red `role="alert"` panel; the race path keeps its 15s notice. 3. Dashboard useEffect races and a dishonest data badge - Both dashboards keyed their fetch effect on the `user` object, which AuthContext re-creates on every `refresh()`, so an unrelated refresh restarted the fetch. Deps are now `user?.id` (+ `loading`), and a `fetchGeneration` ref drops any response a later run has superseded. - The contributor badge now correlates the two independent fetches: "Live data" only when both reached the backend, "Mixed data" when one fell back to the bundled mock rows, "Demo data" signed out. The old boolean inferred page-wide liveness from the stats request alone while the two bounty sections underneath were mock. Both bounty sections also carry their own SampleDataChip where they are rendered. - The page subtitle is keyed on `user`, not on liveness, so it no longer flips mid-flight. 4. A stale `networkMismatch` blocked every action for the session - `useWalletAction` read the flag once at mount. Fixing the network inside Freighter never cleared it, so every action stayed refused for the rest of the session. It now re-checks live and blocks only if still mismatched. - `WalletContext` exposes `recheckNetworkMismatch()` for that re-read and returns the current verdict. ClaimButton result effect (found by the rewritten claim tests) - It depended on `onClaimSuccess` (an inline parent callback) and `refetch` (rebuilt every render by `useBountyStatus`), so every parent render re-ran the whole branch: duplicate refetch, duplicate `router.refresh()`, and a restarted auto-dismiss countdown that could keep the success panel up indefinitely. Both are now read through a ref; the effect fires exactly once per distinct result. Tests - New `ContributorDashboardClient.test.tsx`: badge honesty (live / mixed / signed-out) and the out-of-order response guard, which fails without the generation check. - `WalletContext.test.tsx`: `recheckNetworkMismatch` clears a resolved mismatch, keeps a real one, and reports none for an unconnected reader. - `IssueActions.test.tsx`, `ClaimButton.test.tsx`, `useWalletAction.test.tsx` and `adapters.test.ts` updated for the above; claim expectations rewritten for the actual ClaimButton-owned path (`Claim Bounty`, `{}` body) instead of the superseded inline one. En route, pre-existing and blocking `next build` at HEAD - `BountyStatusProps` had no `interval`, but `LiveBountyStatus` passed `interval={5000}` (TS2322). Exposed the prop with a 5000 default. - `@testing-library/dom` was absent from devDependencies — a peer of `@testing-library/react`, so the suite did not install clean.
|
@Mikey-222 is attempting to deploy a commit to the chonilius' projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Mikey-222 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
networkMismatchblocked every action for the sessionuseWalletActionread the flag once at mount. Fixing the network inside Freighter never cleared it, so every action stayed refused for the rest of the session. It now re-checks live and blocks only if still mismatched.WalletContextexposesrecheckNetworkMismatch()for that re-read and returns the current verdict.ClaimButton result effect (found by the rewritten claim tests)
onClaimSuccess(an inline parent callback) andrefetch(rebuilt every render byuseBountyStatus), so every parent render re-ran the whole branch: duplicate refetch, duplicaterouter.refresh(), and a restarted auto-dismiss countdown that could keep the success panel up indefinitely. Both are now read through a ref; the effect fires exactly once per distinct result.Tests
ContributorDashboardClient.test.tsx: badge honesty (live / mixed / signed-out) and the out-of-order response guard, which fails without the generation check.WalletContext.test.tsx:recheckNetworkMismatchclears a resolved mismatch, keeps a real one, and reports none for an unconnected reader.IssueActions.test.tsx,ClaimButton.test.tsx,useWalletAction.test.tsxandadapters.test.tsupdated for the above; claim expectations rewritten for the actual ClaimButton-owned path (Claim Bounty,{}body) instead of the superseded inline one.En route, pre-existing and blocking
next buildat HEADBountyStatusPropshad nointerval, butLiveBountyStatuspassedinterval={5000}(TS2322). Exposed the prop with a 5000 default.@testing-library/domwas absent from devDependencies — a peer of@testing-library/react, so the suite did not install clean.What this PR does
Closes
Closes #
What changed
How to test
Checklist
npm run lintpasses locallynpm testpasses locallynpm run buildpasses locally