Skip to content

fix(hackathons): always open submissions in a new tab#568

Merged
Benjtalkshow merged 4 commits into
boundlessfi:mainfrom
Benjtalkshow:fix/submissions-open-in-new-tab
May 18, 2026
Merged

fix(hackathons): always open submissions in a new tab#568
Benjtalkshow merged 4 commits into
boundlessfi:mainfrom
Benjtalkshow:fix/submissions-open-in-new-tab

Conversation

@Benjtalkshow
Copy link
Copy Markdown
Collaborator

@Benjtalkshow Benjtalkshow commented May 18, 2026

Summary

  • Submission clicks now consistently open the project page in a new tab across the public hackathon, organizer, judge, and profile surfaces, so reviewers and participants do not lose their list/queue context.
  • Switched the remaining anchor tags pointing at submissions to next/link <Link> components.

Changes

  • Judge submissions list (app/judge/[hackathonId]/submissions): row link uses target="_blank"; Enter keyboard shortcut uses window.open.
  • Organizer Submissions page (grid + table): card/row click opens the submission in a new tab.
  • Organizer Judging page (JudgingParticipant): "View Details" link and the external-link icon button both open in a new tab.
  • Hackathon detail sidebar (MySubmissionPanel): "View" button opens the submission in a new tab.
  • Hackathon Winners tab (TrackWinnerCard): track winner card link opens in a new tab, matching the existing podium/general winner cards.
  • Anchors converted to next/link: public hackathon SubmissionCard, GeneralWinnerCard, PodiumWinnerCard, TopWinnerCard, and the /me/hackathons/submissions sheet/table external-link affordances.

Test plan

  • /judge/<hackathonId>/submissions: click a row and press Enter on a focused row, confirm both open in a new tab.
  • /organizations/<id>/hackathons/<hackathonId>/submissions: in grid view, click a card; in table view, click a row. Both should open in a new tab without losing filters/selection.
  • /organizations/<id>/hackathons/<hackathonId>/judging: click "View Details" and the external-link icon on a participant card. Both open in a new tab.
  • Hackathon detail page sidebar "Your Submission": click "View", opens in a new tab.
  • Hackathon Winners tab: click a track winner card, opens in a new tab. Confirm the podium/general winner cards still behave the same.
  • Public hackathon Submissions tab: click "View Project", opens in a new tab.
  • Profile → projects tab + /me/hackathons/submissions: confirm submission links still open in a new tab.

Summary by CodeRabbit

  • Improvements
    • Submission details and project links now open in new browser tabs instead of navigating away, allowing you to maintain your current page while reviewing details.

Review Change Stack

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.
Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.
… fix/teams-tab-single-column-pagination

Merge into main
Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

@Benjtalkshow is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

This PR standardizes external link behavior across the platform by converting submission and project links to open in new browser tabs with security isolation. Changes affect landing page cards, submission management pages, organization views, and user dashboards.

Changes

External link navigation and tab isolation

Layer / File(s) Summary
Landing page card components Link migration
app/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx, app/(landing)/hackathons/[slug]/components/tabs/contents/winners/GeneralWinnerCard.tsx, app/(landing)/hackathons/[slug]/components/tabs/contents/winners/PodiumWinnerCard.tsx, app/(landing)/hackathons/[slug]/components/tabs/contents/winners/TopWinnerCard.tsx
Replaces plain <a> tags with Next.js Link components in winner and submission cards on landing pages, preserving target="_blank" and rel="noopener noreferrer" for external navigation.
Submission page navigation to window.open
app/(landing)/hackathons/[slug]/components/sidebar/MySubmissionPanel.tsx, app/judge/[hackathonId]/submissions/page.tsx, components/organization/hackathons/submissions/SubmissionsList.tsx
Converts router-based navigation to window.open with security flags for opening submission details in new tabs; removes useRouter hooks and adds external link attributes to submission row links.
Dashboard submission links Link migration
app/me/hackathons/submissions/submission-components.tsx
Replaces plain <a> tags with Next.js Link components in user submissions dashboard while preserving accessibility attributes, interaction handlers, and external link attributes.
Winner and organization card external link attributes
components/hackathons/winners/WinnersTab.tsx, components/organization/cards/JudgingParticipant.tsx
Adds target="_blank" and rel="noopener noreferrer" attributes to existing Link components in winner and organization participant cards to enable new-tab navigation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • boundlessfi/boundless#438: Modifies the Winners tab project link rendering in components/hackathons/winners/WinnersTab.tsx with external link behavior changes.
  • boundlessfi/boundless#562: Modifies app/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx to replace link/navigation behavior with next/link and external-tab handling.
  • boundlessfi/boundless#430: Modifies the submissions UI in app/me/hackathons/submissions/submission-components.tsx for link behavior and opening details in a new tab.

Suggested reviewers

  • 0xdevcollins

Poem

🐰 Links now leap to new tabs with care,
target="_blank" floats in the air,
No more router walks the old track,
window.open brings the views right back,
Security blessed with noopener flair! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: converting submission click behavior to open in a new tab across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/judge/[hackathonId]/submissions/page.tsx (1)

72-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent double-open on Enter for focused interactive elements.

The global Enter handler can open an extra tab when focus is already on a row link (target="_blank"), because native link activation and window.open both fire. Skip Enter handling for interactive targets (e.g., anchors/buttons) or call preventDefault when you intentionally handle it.

Suggested fix
   const onKey = (e: KeyboardEvent) => {
     const target = e.target as HTMLElement | null;
     if (
       target &&
       (target.tagName === 'INPUT' ||
         target.tagName === 'TEXTAREA' ||
-        target.isContentEditable)
+        target.isContentEditable ||
+        !!target.closest('a,button,[role="button"]'))
     )
       return;
@@
     } else if (e.key === 'Enter') {
+      e.preventDefault();
       const s = visible[focusIdx];
       if (s) {
         window.open(
           `/judge/${hackathonId}/submissions/${s.submission.id}`,
           '_blank',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/judge/`[hackathonId]/submissions/page.tsx around lines 72 - 96, The onKey
handler opens a new tab on Enter even when the focused element is already an
interactive element (anchor/button), causing double-open; update onKey (the
onKey function handling Enter, using visible, focusIdx, hackathonId,
setFocusIdx) to first detect interactive targets (e.g., tagName 'A' or 'BUTTON',
or target has role/button or an onclick, or target.closest('a')) and return
early, or if you intend to handle activation here call e.preventDefault() before
invoking window.open so the native activation doesn't also fire; ensure the
check occurs before reading visible[focusIdx] so only non-interactive contexts
trigger your window.open logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/judge/`[hackathonId]/submissions/page.tsx:
- Around line 72-96: The onKey handler opens a new tab on Enter even when the
focused element is already an interactive element (anchor/button), causing
double-open; update onKey (the onKey function handling Enter, using visible,
focusIdx, hackathonId, setFocusIdx) to first detect interactive targets (e.g.,
tagName 'A' or 'BUTTON', or target has role/button or an onclick, or
target.closest('a')) and return early, or if you intend to handle activation
here call e.preventDefault() before invoking window.open so the native
activation doesn't also fire; ensure the check occurs before reading
visible[focusIdx] so only non-interactive contexts trigger your window.open
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6de01555-1ed8-452d-bacb-26f73e8a1a6e

📥 Commits

Reviewing files that changed from the base of the PR and between 1269fdf and 217b544.

📒 Files selected for processing (10)
  • app/(landing)/hackathons/[slug]/components/sidebar/MySubmissionPanel.tsx
  • app/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx
  • app/(landing)/hackathons/[slug]/components/tabs/contents/winners/GeneralWinnerCard.tsx
  • app/(landing)/hackathons/[slug]/components/tabs/contents/winners/PodiumWinnerCard.tsx
  • app/(landing)/hackathons/[slug]/components/tabs/contents/winners/TopWinnerCard.tsx
  • app/judge/[hackathonId]/submissions/page.tsx
  • app/me/hackathons/submissions/submission-components.tsx
  • components/hackathons/winners/WinnersTab.tsx
  • components/organization/cards/JudgingParticipant.tsx
  • components/organization/hackathons/submissions/SubmissionsList.tsx

@Benjtalkshow Benjtalkshow merged commit 80c7f2f into boundlessfi:main May 18, 2026
6 of 8 checks passed
Benjtalkshow added a commit that referenced this pull request May 18, 2026
Resolves conflict in components/hackathons/winners/WinnersTab.tsx by
keeping main's new-tab Link (target='_blank'), introduced by PR #568,
over production's older single-tab Link.
Benjtalkshow added a commit that referenced this pull request May 19, 2026
* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

---------

Co-authored-by: Collins Ikechukwu <collinschristroa@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0xdevcollins added a commit that referenced this pull request May 20, 2026
* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Benjtalkshow <chinedubenj@gmail.com>
0xdevcollins added a commit that referenced this pull request May 21, 2026
…es (#575)

* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Benjtalkshow <chinedubenj@gmail.com>
0xdevcollins added a commit that referenced this pull request May 21, 2026
…es (#578)

* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): show track winners on the organizer rewards page (#576)

The rewards page was filtering winners by `submission.rank` only, so
on the track-based prize structure it silently dropped every track
winner. For the Boundless × Trustless Work hackathon (3 overall + 5
track tiers), the page rendered 3 of 8 winners and the publish wizard
preview showed 3 of 8 prize tiers paired.

Same root cause as boundlessfi/boundless-nestjs#132: track winners
live on `SubmissionTrackEntry.wonRank`, not on `submission.rank`, and
the rewards UI was rank-keyed end to end.

Changes:

- Extend `Submission` with optional track-winner fields (`isTrackWinner`,
  `trackId`, `trackName`, `trackPrize`, `trackWonRank`).
- `useHackathonRewards` now fetches `getHackathonWinners` in a
  dedicated effect that runs once results are published. The
  `trackWinners` payload is stamped onto matching submissions AND
  returned raw so the page can render a per-track section.
- `useHackathonRewards` also preserves `tier.kind` and `tier.trackId`
  on the mapped `prizeTiers` and re-sorts so track tiers no longer
  collapse to the 999 fallback rank.
- `rewards/page.tsx` winners filter now ORs `s.isTrackWinner` with the
  rank-based check, and `maxRank` counts only OVERALL tiers so the
  rank-keyed lookups don't get polluted with synthetic track ranks.
- New `TrackWinnersSection` component renders below the existing
  rank-based `PodiumSection`. Mirrors the public WinnersTab pattern:
  one card per track with prize chip, project name, team, avatars.
- `PublishWinnersWizard` includes track winners in its preview list
  and threads `kind`/`trackId` through to `WinnersGrid`.
- `WinnersGrid` now renders overall + track tiers as separate
  sections. Overall keeps the 2-1-3 podium re-order; track tiers
  render in display order. Each tier is matched to its winner via
  rank OR trackId.

Pairs with boundless-nestjs#132 (BE trigger endpoint already
respects track winners after that merged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Benjtalkshow <chinedubenj@gmail.com>
0xdevcollins added a commit that referenced this pull request May 21, 2026
* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): show track winners on the organizer rewards page (#576)

The rewards page was filtering winners by `submission.rank` only, so
on the track-based prize structure it silently dropped every track
winner. For the Boundless × Trustless Work hackathon (3 overall + 5
track tiers), the page rendered 3 of 8 winners and the publish wizard
preview showed 3 of 8 prize tiers paired.

Same root cause as boundlessfi/boundless-nestjs#132: track winners
live on `SubmissionTrackEntry.wonRank`, not on `submission.rank`, and
the rewards UI was rank-keyed end to end.

Changes:

- Extend `Submission` with optional track-winner fields (`isTrackWinner`,
  `trackId`, `trackName`, `trackPrize`, `trackWonRank`).
- `useHackathonRewards` now fetches `getHackathonWinners` in a
  dedicated effect that runs once results are published. The
  `trackWinners` payload is stamped onto matching submissions AND
  returned raw so the page can render a per-track section.
- `useHackathonRewards` also preserves `tier.kind` and `tier.trackId`
  on the mapped `prizeTiers` and re-sorts so track tiers no longer
  collapse to the 999 fallback rank.
- `rewards/page.tsx` winners filter now ORs `s.isTrackWinner` with the
  rank-based check, and `maxRank` counts only OVERALL tiers so the
  rank-keyed lookups don't get polluted with synthetic track ranks.
- New `TrackWinnersSection` component renders below the existing
  rank-based `PodiumSection`. Mirrors the public WinnersTab pattern:
  one card per track with prize chip, project name, team, avatars.
- `PublishWinnersWizard` includes track winners in its preview list
  and threads `kind`/`trackId` through to `WinnersGrid`.
- `WinnersGrid` now renders overall + track tiers as separate
  sections. Overall keeps the 2-1-3 podium re-order; track tiers
  render in display order. Each tier is matched to its winner via
  rank OR trackId.

Pairs with boundless-nestjs#132 (BE trigger endpoint already
respects track winners after that merged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): polish publish-wizard preview to industry-standard layout (#580)

The wizard preview's "3/8 Winners Assigned" line plus the existing
WinnerCard layout was confusing — "$300 USDC" double-signed the
currency, "0 Comments" was meaningless in a payout preview, and track
winners got a "4th Position" ribbon they didn't earn.

Redesigned to match the patterns Devpost / Devfolio / ETHGlobal use
in their publish flows:

WinnersGrid header:
- Replace the cryptic "X/Y Winners Assigned" with a callout chip:
  green check + "All N winners assigned" when complete, amber
  warning + "X of Y assigned (Z unassigned)" when not.
- Show the total prize pool ("1,500 USDC pool") as a sibling chip so
  the organizer sees the dollar figure they're committing.
- Render "Overall Placements" as a sub-header only when track
  winners also exist (avoids redundant heading on OVERALL_ONLY).

WinnerCard:
- Drop the dollar sign for non-dollar currencies — `"$300 USDC"` is
  now `"300 USDC"`, a cleaner industry-standard read.
- Track winners get a track-name chip ("Best UI/UX") instead of a
  synthetic-rank ribbon ("4th Position").
- Drop the "0 Comments" noise; not a meaningful signal at payout time.
- Drop the placeholder bitmed.png; surface project name + participant
  name + category as the primary content.
- Cards now have consistent dimensions (no podium scaling for tracks),
  uniform border + hover treatment, prize chip aligned to the right.

Only the publish-wizard preview is touched; the public Winners tab
and the rewards podium are unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): add judging dataset to export dropdown (#577)

Surface a new "Judging" entry alongside the existing export options
(Winners, Submissions, etc.) so organizers can download judging
criteria, judges, aggregated results, per-judge scores, and judge
comments. Requires the matching backend dataset to be deployed.

---------

Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Benjtalkshow <chinedubenj@gmail.com>
0xdevcollins added a commit that referenced this pull request May 21, 2026
…sibility (#583)

* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): show track winners on the organizer rewards page (#576)

The rewards page was filtering winners by `submission.rank` only, so
on the track-based prize structure it silently dropped every track
winner. For the Boundless × Trustless Work hackathon (3 overall + 5
track tiers), the page rendered 3 of 8 winners and the publish wizard
preview showed 3 of 8 prize tiers paired.

Same root cause as boundlessfi/boundless-nestjs#132: track winners
live on `SubmissionTrackEntry.wonRank`, not on `submission.rank`, and
the rewards UI was rank-keyed end to end.

Changes:

- Extend `Submission` with optional track-winner fields (`isTrackWinner`,
  `trackId`, `trackName`, `trackPrize`, `trackWonRank`).
- `useHackathonRewards` now fetches `getHackathonWinners` in a
  dedicated effect that runs once results are published. The
  `trackWinners` payload is stamped onto matching submissions AND
  returned raw so the page can render a per-track section.
- `useHackathonRewards` also preserves `tier.kind` and `tier.trackId`
  on the mapped `prizeTiers` and re-sorts so track tiers no longer
  collapse to the 999 fallback rank.
- `rewards/page.tsx` winners filter now ORs `s.isTrackWinner` with the
  rank-based check, and `maxRank` counts only OVERALL tiers so the
  rank-keyed lookups don't get polluted with synthetic track ranks.
- New `TrackWinnersSection` component renders below the existing
  rank-based `PodiumSection`. Mirrors the public WinnersTab pattern:
  one card per track with prize chip, project name, team, avatars.
- `PublishWinnersWizard` includes track winners in its preview list
  and threads `kind`/`trackId` through to `WinnersGrid`.
- `WinnersGrid` now renders overall + track tiers as separate
  sections. Overall keeps the 2-1-3 podium re-order; track tiers
  render in display order. Each tier is matched to its winner via
  rank OR trackId.

Pairs with boundless-nestjs#132 (BE trigger endpoint already
respects track winners after that merged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): polish publish-wizard preview to industry-standard layout (#580)

The wizard preview's "3/8 Winners Assigned" line plus the existing
WinnerCard layout was confusing — "$300 USDC" double-signed the
currency, "0 Comments" was meaningless in a payout preview, and track
winners got a "4th Position" ribbon they didn't earn.

Redesigned to match the patterns Devpost / Devfolio / ETHGlobal use
in their publish flows:

WinnersGrid header:
- Replace the cryptic "X/Y Winners Assigned" with a callout chip:
  green check + "All N winners assigned" when complete, amber
  warning + "X of Y assigned (Z unassigned)" when not.
- Show the total prize pool ("1,500 USDC pool") as a sibling chip so
  the organizer sees the dollar figure they're committing.
- Render "Overall Placements" as a sub-header only when track
  winners also exist (avoids redundant heading on OVERALL_ONLY).

WinnerCard:
- Drop the dollar sign for non-dollar currencies — `"$300 USDC"` is
  now `"300 USDC"`, a cleaner industry-standard read.
- Track winners get a track-name chip ("Best UI/UX") instead of a
  synthetic-rank ribbon ("4th Position").
- Drop the "0 Comments" noise; not a meaningful signal at payout time.
- Drop the placeholder bitmed.png; surface project name + participant
  name + category as the primary content.
- Cards now have consistent dimensions (no podium scaling for tracks),
  uniform border + hover treatment, prize chip aligned to the right.

Only the publish-wizard preview is touched; the public Winners tab
and the rewards podium are unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): add judging dataset to export dropdown (#577)

Surface a new "Judging" entry alongside the existing export options
(Winners, Submissions, etc.) so organizers can download judging
criteria, judges, aggregated results, per-judge scores, and judge
comments. Requires the matching backend dataset to be deployed.

* fix(rewards): match track winners by submissionId, not participantId (#582)

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) <noreply@anthropic.com>

---------

Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Benjtalkshow <chinedubenj@gmail.com>
0xdevcollins added a commit that referenced this pull request May 21, 2026
…, and UI fixes (#585)

* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): show track winners on the organizer rewards page (#576)

The rewards page was filtering winners by `submission.rank` only, so
on the track-based prize structure it silently dropped every track
winner. For the Boundless × Trustless Work hackathon (3 overall + 5
track tiers), the page rendered 3 of 8 winners and the publish wizard
preview showed 3 of 8 prize tiers paired.

Same root cause as boundlessfi/boundless-nestjs#132: track winners
live on `SubmissionTrackEntry.wonRank`, not on `submission.rank`, and
the rewards UI was rank-keyed end to end.

Changes:

- Extend `Submission` with optional track-winner fields (`isTrackWinner`,
  `trackId`, `trackName`, `trackPrize`, `trackWonRank`).
- `useHackathonRewards` now fetches `getHackathonWinners` in a
  dedicated effect that runs once results are published. The
  `trackWinners` payload is stamped onto matching submissions AND
  returned raw so the page can render a per-track section.
- `useHackathonRewards` also preserves `tier.kind` and `tier.trackId`
  on the mapped `prizeTiers` and re-sorts so track tiers no longer
  collapse to the 999 fallback rank.
- `rewards/page.tsx` winners filter now ORs `s.isTrackWinner` with the
  rank-based check, and `maxRank` counts only OVERALL tiers so the
  rank-keyed lookups don't get polluted with synthetic track ranks.
- New `TrackWinnersSection` component renders below the existing
  rank-based `PodiumSection`. Mirrors the public WinnersTab pattern:
  one card per track with prize chip, project name, team, avatars.
- `PublishWinnersWizard` includes track winners in its preview list
  and threads `kind`/`trackId` through to `WinnersGrid`.
- `WinnersGrid` now renders overall + track tiers as separate
  sections. Overall keeps the 2-1-3 podium re-order; track tiers
  render in display order. Each tier is matched to its winner via
  rank OR trackId.

Pairs with boundless-nestjs#132 (BE trigger endpoint already
respects track winners after that merged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): polish publish-wizard preview to industry-standard layout (#580)

The wizard preview's "3/8 Winners Assigned" line plus the existing
WinnerCard layout was confusing — "$300 USDC" double-signed the
currency, "0 Comments" was meaningless in a payout preview, and track
winners got a "4th Position" ribbon they didn't earn.

Redesigned to match the patterns Devpost / Devfolio / ETHGlobal use
in their publish flows:

WinnersGrid header:
- Replace the cryptic "X/Y Winners Assigned" with a callout chip:
  green check + "All N winners assigned" when complete, amber
  warning + "X of Y assigned (Z unassigned)" when not.
- Show the total prize pool ("1,500 USDC pool") as a sibling chip so
  the organizer sees the dollar figure they're committing.
- Render "Overall Placements" as a sub-header only when track
  winners also exist (avoids redundant heading on OVERALL_ONLY).

WinnerCard:
- Drop the dollar sign for non-dollar currencies — `"$300 USDC"` is
  now `"300 USDC"`, a cleaner industry-standard read.
- Track winners get a track-name chip ("Best UI/UX") instead of a
  synthetic-rank ribbon ("4th Position").
- Drop the "0 Comments" noise; not a meaningful signal at payout time.
- Drop the placeholder bitmed.png; surface project name + participant
  name + category as the primary content.
- Cards now have consistent dimensions (no podium scaling for tracks),
  uniform border + hover treatment, prize chip aligned to the right.

Only the publish-wizard preview is touched; the public Winners tab
and the rewards podium are unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): add judging dataset to export dropdown (#577)

Surface a new "Judging" entry alongside the existing export options
(Winners, Submissions, etc.) so organizers can download judging
criteria, judges, aggregated results, per-judge scores, and judge
comments. Requires the matching backend dataset to be deployed.

* fix(rewards): match track winners by submissionId, not participantId (#582)

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) <noreply@anthropic.com>

* fix(auth): send absolute callbackURL on Google sign-up (#584)

The Google sign-up flow passed `callbackURL: process.env.NEXT_PUBLIC_APP_URL || '/'`
to Better Auth. Better Auth treats a relative path as relative to the
API host that processed the OAuth callback, not the frontend host.
When `NEXT_PUBLIC_APP_URL` wasn't set (or was missing in the runtime
env even though available at build time on Next.js), the fallback `'/'`
sent users to the API host's root after OAuth completed. The session
cookie WAS set on the shared `.boundlessfi.xyz` domain during the
callback, but the user landed on a blank API page and thought sign-up
had failed. Clearing browser cache (cookies survive — different
section in Chrome) didn't drop the cookie, so the next visit to the
frontend silently restored their session and they appeared
"automatically logged in."

Fix: always build an absolute `callbackURL` pointing at the frontend
host. Same pattern LoginWrapper already uses — falls back to
window.location.origin at runtime, then to the env var at build/SSR,
then to the production canonical URL. All three are in the BE's
`trustedOrigins` list so Better Auth won't reject the URL.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Benjtalkshow <chinedubenj@gmail.com>
Benjtalkshow added a commit that referenced this pull request May 22, 2026
* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): show track winners on the organizer rewards page (#576)

The rewards page was filtering winners by `submission.rank` only, so
on the track-based prize structure it silently dropped every track
winner. For the Boundless × Trustless Work hackathon (3 overall + 5
track tiers), the page rendered 3 of 8 winners and the publish wizard
preview showed 3 of 8 prize tiers paired.

Same root cause as boundlessfi/boundless-nestjs#132: track winners
live on `SubmissionTrackEntry.wonRank`, not on `submission.rank`, and
the rewards UI was rank-keyed end to end.

Changes:

- Extend `Submission` with optional track-winner fields (`isTrackWinner`,
  `trackId`, `trackName`, `trackPrize`, `trackWonRank`).
- `useHackathonRewards` now fetches `getHackathonWinners` in a
  dedicated effect that runs once results are published. The
  `trackWinners` payload is stamped onto matching submissions AND
  returned raw so the page can render a per-track section.
- `useHackathonRewards` also preserves `tier.kind` and `tier.trackId`
  on the mapped `prizeTiers` and re-sorts so track tiers no longer
  collapse to the 999 fallback rank.
- `rewards/page.tsx` winners filter now ORs `s.isTrackWinner` with the
  rank-based check, and `maxRank` counts only OVERALL tiers so the
  rank-keyed lookups don't get polluted with synthetic track ranks.
- New `TrackWinnersSection` component renders below the existing
  rank-based `PodiumSection`. Mirrors the public WinnersTab pattern:
  one card per track with prize chip, project name, team, avatars.
- `PublishWinnersWizard` includes track winners in its preview list
  and threads `kind`/`trackId` through to `WinnersGrid`.
- `WinnersGrid` now renders overall + track tiers as separate
  sections. Overall keeps the 2-1-3 podium re-order; track tiers
  render in display order. Each tier is matched to its winner via
  rank OR trackId.

Pairs with boundless-nestjs#132 (BE trigger endpoint already
respects track winners after that merged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): polish publish-wizard preview to industry-standard layout (#580)

The wizard preview's "3/8 Winners Assigned" line plus the existing
WinnerCard layout was confusing — "$300 USDC" double-signed the
currency, "0 Comments" was meaningless in a payout preview, and track
winners got a "4th Position" ribbon they didn't earn.

Redesigned to match the patterns Devpost / Devfolio / ETHGlobal use
in their publish flows:

WinnersGrid header:
- Replace the cryptic "X/Y Winners Assigned" with a callout chip:
  green check + "All N winners assigned" when complete, amber
  warning + "X of Y assigned (Z unassigned)" when not.
- Show the total prize pool ("1,500 USDC pool") as a sibling chip so
  the organizer sees the dollar figure they're committing.
- Render "Overall Placements" as a sub-header only when track
  winners also exist (avoids redundant heading on OVERALL_ONLY).

WinnerCard:
- Drop the dollar sign for non-dollar currencies — `"$300 USDC"` is
  now `"300 USDC"`, a cleaner industry-standard read.
- Track winners get a track-name chip ("Best UI/UX") instead of a
  synthetic-rank ribbon ("4th Position").
- Drop the "0 Comments" noise; not a meaningful signal at payout time.
- Drop the placeholder bitmed.png; surface project name + participant
  name + category as the primary content.
- Cards now have consistent dimensions (no podium scaling for tracks),
  uniform border + hover treatment, prize chip aligned to the right.

Only the publish-wizard preview is touched; the public Winners tab
and the rewards podium are unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): add judging dataset to export dropdown (#577)

Surface a new "Judging" entry alongside the existing export options
(Winners, Submissions, etc.) so organizers can download judging
criteria, judges, aggregated results, per-judge scores, and judge
comments. Requires the matching backend dataset to be deployed.

* fix(rewards): match track winners by submissionId, not participantId (#582)

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) <noreply@anthropic.com>

* fix(auth): send absolute callbackURL on Google sign-up (#584)

The Google sign-up flow passed `callbackURL: process.env.NEXT_PUBLIC_APP_URL || '/'`
to Better Auth. Better Auth treats a relative path as relative to the
API host that processed the OAuth callback, not the frontend host.
When `NEXT_PUBLIC_APP_URL` wasn't set (or was missing in the runtime
env even though available at build time on Next.js), the fallback `'/'`
sent users to the API host's root after OAuth completed. The session
cookie WAS set on the shared `.boundlessfi.xyz` domain during the
callback, but the user landed on a blank API page and thought sign-up
had failed. Clearing browser cache (cookies survive — different
section in Chrome) didn't drop the cookie, so the next visit to the
frontend silently restored their session and they appeared
"automatically logged in."

Fix: always build an absolute `callbackURL` pointing at the frontend
host. Same pattern LoginWrapper already uses — falls back to
window.location.origin at runtime, then to the env var at build/SSR,
then to the production canonical URL. All three are in the BE's
`trustedOrigins` list so Better Auth won't reject the URL.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): unbreak submission detail page + publish hackathon recap (#586)

* fix(submissions): wire submission entity type through votes, comments, and rank chip

The submission detail page (/projects/[slug]?type=submission) was
treating submissions as crowdfunding projects, which broke every
interactive surface for hackathon entries:

- Sidebar getVoteCounts and the voters tab's getProjectVotes were
  hardcoded to CROWDFUNDING_CAMPAIGN, so the lookup failed with
  "Failed to load voting data" and disabled the buttons.
- createVote on the voters tab was also hardcoded, so even a click
  through hit Prisma's "Invalid reference to related record" (P2025)
  for non-existent campaign rows.
- ProjectComments used CommentEntityType.PROJECT, so comments and
  replies posted against a non-existent project record.
- The Follow button hardcoded entityType='PROJECT' with the
  submission's id, leading to "Failed to update follow status". The
  follow EntityType enum has no HACKATHON_SUBMISSION value, so the
  button is hidden on submission pages.
- The status badge displayed raw enum values like "SHORTLISTED" and
  there was no signal that a submission was a winner.

All vote/comment surfaces now derive the entity type from the URL,
matching the backend's HACKATHON_SUBMISSION enum on both votes and
comments. The page mapper surfaces submissionRank and a friendly
submissionStatus on the project, and the sidebar header renders a
"Winner" / "Rank #N" chip next to the existing status badge.

* fix(blog): use object-cover so card and detail covers fill the frame

The earlier switch back to object-contain left letterbox bands around
banners that aren't an exact 2:1 ratio on both the BlogCard grid and
the post-details hero. With properly-sized cover banners, object-cover
fills the frame without visible cropping. Authors should size cover
images for a 2:1 ratio on the card and the responsive heights on the
details page.

* feat(blog): publish Boundless x Trustless Work hackathon winners recap

Adds the post-hackathon recap announcing the three main winners
(Conductor, Crypt, GoPadi), the five honorable mentions, and the
Showcase recognitions from the May 16 finale. Featured on the blog
index.

* chore(deps): npm audit fix to clear js-cookie high-severity advisory

GHSA-qjx8-664m-686j (js-cookie per-instance prototype hijack in
assign()) was newly flagged as high severity. The pre-push hook runs
`npm audit --audit-level=high` and refused to push any branch until
this was patched. Lockfile-only update, no package.json changes,
semver-compatible. Bundled here because the gate was blocking the
parent PR; reviewers can treat this as an unrelated chore commit.

---------

Co-authored-by: Collins Ikechukwu <collinschristroa@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Benjtalkshow added a commit that referenced this pull request May 22, 2026
* fix(hackathons): single-column teams tab and primary-colored pager (#562)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* feat(hackathons): track-based prize structure + submission polish + tracks UI (#564)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Feat/submission visibility hidden until results (#566)

* feat(hackathons): add "hidden until results" submission visibility mode

Surfaces the new HIDDEN_UNTIL_RESULTS option (added in the nestjs PR) in
the organizer settings tab. Reorders the three visibility options so the
recommended "Shortlisted only" leads, the new "Hidden until results are
announced" sits in the middle, and "All submissions" comes last. Rewrites
the copy on the "All submissions" choice that incorrectly claimed
disqualified projects would be shown -- they never were on the backend,
and Phase 2 makes that an explicit guarantee. Aligns the form's default
and API-fallback value with the backend default (ACCEPTED_SHORTLISTED,
not ALL) so organizers don't see a misleading initial selection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): track-based prize structure, submission polish, tracks UI

Wires the frontend for the new track-based prize flow:

- New TracksSettingsTab with full CRUD: name/slug/description/eligibility/
  prompt/customQuestions/requiredArtifacts; per-row bulk-opt-in action with
  confirmation dialog for retrofitting existing submissions
- RewardsTab gains a 3-card prize structure picker, per-tier kind toggle and
  track dropdown, amber "tracks unbound" banner, and an inline Manage Tracks
  dialog embedding the settings table
- SubmissionForm: track picker + per-track answers (prompt / custom
  questions / required artifacts), tagline, builtWith chips, screenshots,
  license, code attestation, with soft compliance gate for already-submitted
  submissions. trackIds hydrate from trackEntries on edit so bulk-opted-in
  submitters don't strip themselves out
- SubmissionDetailModal renders tagline, screenshots, built-with, license
  badge, and per-track answers
- Public hackathon page: Overview splits prizes into Overall/Track sections;
  sidebar tier list shows TRACK prefix and looks up track names; Winners tab
  gets a Track Winners section with per-track cards
- API client: lib/api/hackathons/tracks.ts with listTracks /
  listOrganizerTracks / createTrack / updateTrack / deleteTrack /
  bulkOptInAllSubmissions, plus types for HackathonTrack,
  TrackCustomQuestion, TrackRequiredArtifact, TrackAnswer,
  SubmissionTrackEntry, BulkOptInResult
- Hackathon provider/hooks expose trackWinners and per-track entries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): tighten Zod schema + surface backend debug info

- Add max constraints that previously only existed on the backend DTO so
  validation fires inline (projectName 100, description 5000, URL 500).
- ApiErrorField gains an optional `debug` field that the backend Prisma
  filter populates outside production.
- useSubmission's error formatter prefers `debug` over the generic field
  message when present, so toasts show the real Prisma reason behind
  "Data validation failed" instead of a blank "validation: …" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submit-page): hydrate Phase A fields + stop wiping user input on re-render

The submit page mapped `initialData` inline on every render, which (a)
recreated the object reference on every render so the form's reset
effect fired continuously and wiped values the user was typing, and (b)
dropped the Phase A fields entirely (tagline, builtWith, screenshots,
license, codeAttestedAt) plus trackEntries. The combined effect was the
documented symptom — only logo and videoUrl survived the save because
those round-tripped through the type-narrowed object literal, while
tagline / builtWith / license kept appearing to "switch to empty".

- Memoize `initialData` against `mySubmission` so the reference only
  changes when the underlying submission actually changes.
- Pass through Phase A fields and trackEntries so the form can hydrate
  the saved values, and so a follow-up save doesn't write back empties.
- Widen SubmissionFormContent's `initialData` prop to accept the raw
  server-side extras (trackEntries, codeAttestedAt) that the form
  already consumes via cast — keeps the parent's hydration explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hackathons): always open submissions in a new tab (#568)

* fix(hackathons): single-column teams tab and primary-colored pager

Revert the teams tab grid to a single column and rework the shared
Pagination component to match the icon-chevron layout used by the
organizer submissions and participants pages, styled with the primary
color.

* feat(submissions): link submission card avatars to profile pages

Wrap the individual avatar on SubmissionCard in a profile link and
forward team-member usernames to GroupAvatar so each clustered avatar
opens that user's profile in a new tab.

* fix(hackathons): always open submissions in a new tab

Across the hackathon, organizer, judge, and profile surfaces, clicking
a submission now opens the project page in a new tab so reviewers and
participants do not lose their list/queue context. Switched the
remaining anchor tags to next/link Link components.

* feat(judging): organizer dashboard — coverage, preview, per-track results (#570) (#572)

Pairs with boundless-nestjs feat(judging) organizer dashboard upgrades.
Adds three new components to the organizer judging page; no changes to
existing components, judges, or scoring flows.

- `CoverageMatrix` — heatmap on the Overview tab. Rows are
  submissions, columns are judges. Surfaces idle judges (mostly-empty
  columns) and orphan submissions (rows with 0-1 scores) at a glance —
  both block a defensible publish.

- `AllocationPreviewCard` — sits above the Publish button on the
  Results tab. Read-only allocator dry-run showing overall placements
  + per-track winners exactly as publish-results would commit. Calls
  out EXCLUSIVE stacking effects (track leader losing to overall),
  plus surfaces publish gates (deadline, completeness, partner
  allocation) so the organizer sees blockers without attempting to
  publish.

- `TrackResultsSection` — per-track collapsible standings on the
  Results tab. Each section is scoped to a track's opt-ins, sorted by
  averageScore, with the bound prize tier shown as a chip. The leader
  is highlighted as the current pick — soft preview only; the
  Allocation Preview above shows the authoritative EXCLUSIVE-stacked
  outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(judging): unallocated partner funds become a warning, not a blocker (#574)

Mirrors the backend relaxation. The allocator preview's
"Ready to publish" badge previously turned amber whenever any partner
contribution had unallocated balance — but the backend gate that
backed that signal has been relaxed (the funds stay in escrow
post-publish; they're not lost).

Split the existing publish-readiness messaging into two lists:

- Blockers — the hard gates (deadline, completeness, no reviews).
  Same red treatment, same ready-to-publish badge logic.
- Warnings — informational, never blocks. Renders in blue, calls out
  the unallocated amount with a note that it remains in escrow.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): show track winners on the organizer rewards page (#576)

The rewards page was filtering winners by `submission.rank` only, so
on the track-based prize structure it silently dropped every track
winner. For the Boundless × Trustless Work hackathon (3 overall + 5
track tiers), the page rendered 3 of 8 winners and the publish wizard
preview showed 3 of 8 prize tiers paired.

Same root cause as boundlessfi/boundless-nestjs#132: track winners
live on `SubmissionTrackEntry.wonRank`, not on `submission.rank`, and
the rewards UI was rank-keyed end to end.

Changes:

- Extend `Submission` with optional track-winner fields (`isTrackWinner`,
  `trackId`, `trackName`, `trackPrize`, `trackWonRank`).
- `useHackathonRewards` now fetches `getHackathonWinners` in a
  dedicated effect that runs once results are published. The
  `trackWinners` payload is stamped onto matching submissions AND
  returned raw so the page can render a per-track section.
- `useHackathonRewards` also preserves `tier.kind` and `tier.trackId`
  on the mapped `prizeTiers` and re-sorts so track tiers no longer
  collapse to the 999 fallback rank.
- `rewards/page.tsx` winners filter now ORs `s.isTrackWinner` with the
  rank-based check, and `maxRank` counts only OVERALL tiers so the
  rank-keyed lookups don't get polluted with synthetic track ranks.
- New `TrackWinnersSection` component renders below the existing
  rank-based `PodiumSection`. Mirrors the public WinnersTab pattern:
  one card per track with prize chip, project name, team, avatars.
- `PublishWinnersWizard` includes track winners in its preview list
  and threads `kind`/`trackId` through to `WinnersGrid`.
- `WinnersGrid` now renders overall + track tiers as separate
  sections. Overall keeps the 2-1-3 podium re-order; track tiers
  render in display order. Each tier is matched to its winner via
  rank OR trackId.

Pairs with boundless-nestjs#132 (BE trigger endpoint already
respects track winners after that merged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(rewards): polish publish-wizard preview to industry-standard layout (#580)

The wizard preview's "3/8 Winners Assigned" line plus the existing
WinnerCard layout was confusing — "$300 USDC" double-signed the
currency, "0 Comments" was meaningless in a payout preview, and track
winners got a "4th Position" ribbon they didn't earn.

Redesigned to match the patterns Devpost / Devfolio / ETHGlobal use
in their publish flows:

WinnersGrid header:
- Replace the cryptic "X/Y Winners Assigned" with a callout chip:
  green check + "All N winners assigned" when complete, amber
  warning + "X of Y assigned (Z unassigned)" when not.
- Show the total prize pool ("1,500 USDC pool") as a sibling chip so
  the organizer sees the dollar figure they're committing.
- Render "Overall Placements" as a sub-header only when track
  winners also exist (avoids redundant heading on OVERALL_ONLY).

WinnerCard:
- Drop the dollar sign for non-dollar currencies — `"$300 USDC"` is
  now `"300 USDC"`, a cleaner industry-standard read.
- Track winners get a track-name chip ("Best UI/UX") instead of a
  synthetic-rank ribbon ("4th Position").
- Drop the "0 Comments" noise; not a meaningful signal at payout time.
- Drop the placeholder bitmed.png; surface project name + participant
  name + category as the primary content.
- Cards now have consistent dimensions (no podium scaling for tracks),
  uniform border + hover treatment, prize chip aligned to the right.

Only the publish-wizard preview is touched; the public Winners tab
and the rewards podium are unchanged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hackathons): add judging dataset to export dropdown (#577)

Surface a new "Judging" entry alongside the existing export options
(Winners, Submissions, etc.) so organizers can download judging
criteria, judges, aggregated results, per-judge scores, and judge
comments. Requires the matching backend dataset to be deployed.

* fix(rewards): match track winners by submissionId, not participantId (#582)

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) <noreply@anthropic.com>

* fix(auth): send absolute callbackURL on Google sign-up (#584)

The Google sign-up flow passed `callbackURL: process.env.NEXT_PUBLIC_APP_URL || '/'`
to Better Auth. Better Auth treats a relative path as relative to the
API host that processed the OAuth callback, not the frontend host.
When `NEXT_PUBLIC_APP_URL` wasn't set (or was missing in the runtime
env even though available at build time on Next.js), the fallback `'/'`
sent users to the API host's root after OAuth completed. The session
cookie WAS set on the shared `.boundlessfi.xyz` domain during the
callback, but the user landed on a blank API page and thought sign-up
had failed. Clearing browser cache (cookies survive — different
section in Chrome) didn't drop the cookie, so the next visit to the
frontend silently restored their session and they appeared
"automatically logged in."

Fix: always build an absolute `callbackURL` pointing at the frontend
host. Same pattern LoginWrapper already uses — falls back to
window.location.origin at runtime, then to the env var at build/SSR,
then to the production canonical URL. All three are in the BE's
`trustedOrigins` list so Better Auth won't reject the URL.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(submissions): unbreak submission detail page + publish hackathon recap (#586)

* fix(submissions): wire submission entity type through votes, comments, and rank chip

The submission detail page (/projects/[slug]?type=submission) was
treating submissions as crowdfunding projects, which broke every
interactive surface for hackathon entries:

- Sidebar getVoteCounts and the voters tab's getProjectVotes were
  hardcoded to CROWDFUNDING_CAMPAIGN, so the lookup failed with
  "Failed to load voting data" and disabled the buttons.
- createVote on the voters tab was also hardcoded, so even a click
  through hit Prisma's "Invalid reference to related record" (P2025)
  for non-existent campaign rows.
- ProjectComments used CommentEntityType.PROJECT, so comments and
  replies posted against a non-existent project record.
- The Follow button hardcoded entityType='PROJECT' with the
  submission's id, leading to "Failed to update follow status". The
  follow EntityType enum has no HACKATHON_SUBMISSION value, so the
  button is hidden on submission pages.
- The status badge displayed raw enum values like "SHORTLISTED" and
  there was no signal that a submission was a winner.

All vote/comment surfaces now derive the entity type from the URL,
matching the backend's HACKATHON_SUBMISSION enum on both votes and
comments. The page mapper surfaces submissionRank and a friendly
submissionStatus on the project, and the sidebar header renders a
"Winner" / "Rank #N" chip next to the existing status badge.

* fix(blog): use object-cover so card and detail covers fill the frame

The earlier switch back to object-contain left letterbox bands around
banners that aren't an exact 2:1 ratio on both the BlogCard grid and
the post-details hero. With properly-sized cover banners, object-cover
fills the frame without visible cropping. Authors should size cover
images for a 2:1 ratio on the card and the responsive heights on the
details page.

* feat(blog): publish Boundless x Trustless Work hackathon winners recap

Adds the post-hackathon recap announcing the three main winners
(Conductor, Crypt, GoPadi), the five honorable mentions, and the
Showcase recognitions from the May 16 finale. Featured on the blog
index.

* chore(deps): npm audit fix to clear js-cookie high-severity advisory

GHSA-qjx8-664m-686j (js-cookie per-instance prototype hijack in
assign()) was newly flagged as high severity. The pre-push hook runs
`npm audit --audit-level=high` and refused to push any branch until
this was patched. Lockfile-only update, no package.json changes,
semver-compatible. Bundled here because the gate was blocking the
parent PR; reviewers can treat this as an unrelated chore commit.

* fix(blog): drop duplicate h1 from hackathon winners post (#588)

BlogPostDetails already renders post.title from frontmatter as the
page heading above the cover banner, so the matching `#` heading at
the top of the MDX body rendered a second time directly under the
banner. Removing the body-level h1 keeps the page to a single title.

---------

Co-authored-by: Collins Ikechukwu <collinschristroa@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant