From 4d7692a102b2cd89be5f072dea822ef54bbe7ffb Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Thu, 21 May 2026 12:58:43 +0100 Subject: [PATCH] fix(rewards): match track winners by submissionId, not participantId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The publish-wizard preview was still showing "3 of 8 winners assigned" even after the track-winner enrichment landed in #576. The enrichment effect was looking up trackWinners by `sub.id`, but the rewards data mapper sets `Submission.id` to the participant ID, not the submission row ID. The Map lookup keyed by `HackathonTrackWinner.submissionId` never matched, so no submissions got `isTrackWinner = true` stamped. For the Boundless × Trustless Work hackathon (3 overall + 5 track winners), the wizard saw only the 3 overall winners — the 5 track winners never made it into the `winners` array. Fix: - Add `submissionId?: string` to the `Submission` type. - Mapper populates it from `submissionData.id || sub.id || sub.submissionId`. The mapper's `id` field stays on the participant ID for compatibility with the existing rank-assignment code that already keys off it. - Track-winner enrichment looks up `byId.get(sub.submissionId)` first, falls back to `byId.get(sub.id)` for older rows where the mapper output predated the new field. After this, the wizard will show "All 8 winners assigned • 1,500 USDC pool" with the three overall placements and five track winners. Co-Authored-By: Claude Opus 4.7 (1M context) --- components/organization/hackathons/rewards/types.ts | 7 +++++++ hooks/use-hackathon-rewards.ts | 9 ++++++++- lib/utils/rewards-data-mapper.ts | 6 ++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/components/organization/hackathons/rewards/types.ts b/components/organization/hackathons/rewards/types.ts index 0633aaaf..788d1adf 100644 --- a/components/organization/hackathons/rewards/types.ts +++ b/components/organization/hackathons/rewards/types.ts @@ -1,5 +1,12 @@ export interface Submission { id: string; + /** + * The actual HackathonSubmission row ID, distinct from `id` which the + * rewards data mapper sets to the participant ID. Required for + * matching submissions against backend payloads (judging results, + * track winners) that key by submissionId. + */ + submissionId?: string; name: string; projectName: string; avatar?: string; diff --git a/hooks/use-hackathon-rewards.ts b/hooks/use-hackathon-rewards.ts index 2b4a8fe4..ec4240cd 100644 --- a/hooks/use-hackathon-rewards.ts +++ b/hooks/use-hackathon-rewards.ts @@ -472,7 +472,14 @@ export const useHackathonRewards = ( const byId = new Map(fetched.map(tw => [tw.submissionId, tw])); setSubmissions(prev => prev.map(sub => { - const tw = byId.get(sub.id); + // Match against the real submission row ID (now threaded + // through by the mapper). Fall back to `sub.id` for any + // mapper output that predates the `submissionId` field — + // older rows would have `id === submissionId` when + // participant data was missing. + const tw = + (sub.submissionId && byId.get(sub.submissionId)) || + byId.get(sub.id); if (!tw) return sub; return { ...sub, diff --git a/lib/utils/rewards-data-mapper.ts b/lib/utils/rewards-data-mapper.ts index a423640e..67137675 100644 --- a/lib/utils/rewards-data-mapper.ts +++ b/lib/utils/rewards-data-mapper.ts @@ -63,6 +63,12 @@ export const mapJudgingSubmissionToRewardSubmission = ( return { id: participant.id || sub.id || '', + // The real submission row ID, used by backend payloads (judging + // results, track winners) that key by submissionId. The mapper's + // `id` field stays on the participant ID for compatibility with + // existing rank-assignment and display code that already keys off + // it. + submissionId: submissionData.id || sub.id || sub.submissionId || '', participantId: participant.id || sub.id || '', name, projectName: submissionData.projectName || '',