diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/rewards/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/rewards/page.tsx
index e97849a2..ae88666f 100644
--- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/rewards/page.tsx
+++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/rewards/page.tsx
@@ -31,6 +31,7 @@ export default function RewardsPage() {
refetchHackathon,
resultsPublished,
hackathon,
+ trackWinners,
} = useHackathonRewards(organizationId, hackathonId);
const { handleRankChange } = useRankAssignment();
@@ -43,9 +44,17 @@ export default function RewardsPage() {
refetch: refetchDistributionStatus,
} = useRewardDistributionStatus(organizationId, hackathonId);
- const maxRank = useMemo(() => prizeTiers.length, [prizeTiers.length]);
+ // `maxRank` is the number of OVERALL prize tier slots; track tiers
+ // are not rank-numbered so they don't contribute to this cap. The
+ // rank-based rendering (podium, rank-keyed lookups) still uses
+ // `maxRank`. Track winners flow through `isTrackWinner` instead.
+ const maxRank = useMemo(
+ () => prizeTiers.filter(t => !t.kind || t.kind === 'OVERALL').length,
+ [prizeTiers]
+ );
const winners = useMemo(
- () => submissions.filter(s => s.rank && s.rank <= maxRank),
+ () =>
+ submissions.filter(s => (s.rank && s.rank <= maxRank) || s.isTrackWinner),
[submissions, maxRank]
);
const hasWinners = winners.length > 0;
@@ -121,6 +130,7 @@ export default function RewardsPage() {
onRefreshDistributionStatus={refetchDistributionStatus}
resultsPublished={resultsPublished}
escrowAddress={hackathon?.escrowAddress || hackathon?.contractId}
+ trackWinners={trackWinners}
/>
)}
diff --git a/components/organization/hackathons/judging/AllocationPreviewCard.tsx b/components/organization/hackathons/judging/AllocationPreviewCard.tsx
index 402eeb88..871ad93c 100644
--- a/components/organization/hackathons/judging/AllocationPreviewCard.tsx
+++ b/components/organization/hackathons/judging/AllocationPreviewCard.tsx
@@ -130,11 +130,17 @@ export default function AllocationPreviewCard({
if (gates.reviewedCount === 0) {
blockers.push('No submissions have been reviewed yet.');
}
+
+ // Unallocated partner contributions are surfaced as a non-blocking
+ // warning. The escrow still holds the funds; organizers can release
+ // or refund them after publish. The backend gate that prevented this
+ // is intentionally relaxed (see judging.service.publishResults).
+ const warnings: string[] = [];
if (gates.unallocatedPartnerContributionAmount > 0.0000001) {
- blockers.push(
+ warnings.push(
`${gates.unallocatedPartnerContributionAmount.toFixed(2)} ${
gates.currency
- } of partner contributions are unallocated.`
+ } of partner contributions are unallocated. Publish will succeed; funds remain in escrow until you release or refund them.`
);
}
@@ -191,6 +197,22 @@ export default function AllocationPreviewCard({
)}
+ {warnings.length > 0 && (
+
+
+
+ {warnings.map((w, i) => (
+ -
+ {w}
+
+ ))}
+
+
+ )}
+
{/* Overall placements */}
{overall.length > 0 && (
diff --git a/components/organization/hackathons/rewards/PreviewStep.tsx b/components/organization/hackathons/rewards/PreviewStep.tsx
index 0cac8ebf..8282a385 100644
--- a/components/organization/hackathons/rewards/PreviewStep.tsx
+++ b/components/organization/hackathons/rewards/PreviewStep.tsx
@@ -11,6 +11,9 @@ interface PreviewStepProps {
rank: number;
prizeAmount: string;
currency: string;
+ place?: string;
+ kind?: 'OVERALL' | 'TRACK';
+ trackId?: string;
}>;
announcement: string;
onEditAnnouncement: () => void;
diff --git a/components/organization/hackathons/rewards/PublishWinnersWizard.tsx b/components/organization/hackathons/rewards/PublishWinnersWizard.tsx
index d364a6d6..cd68ac0d 100644
--- a/components/organization/hackathons/rewards/PublishWinnersWizard.tsx
+++ b/components/organization/hackathons/rewards/PublishWinnersWizard.tsx
@@ -40,12 +40,25 @@ export default function PublishWinnersWizard({
hackathonId,
onSuccess,
}: PublishWinnersWizardProps) {
- const maxRank = prizeTiers.length;
+ // `maxRank` only counts OVERALL slots — track tiers don't have
+ // numeric ranks. The previous code used `prizeTiers.length`, which
+ // over-counted by the number of track tiers and could let a phantom
+ // overall rank slip through.
+ const maxRank = useMemo(
+ () => prizeTiers.filter(t => !t.kind || t.kind === 'OVERALL').length,
+ [prizeTiers]
+ );
+ // Include both overall winners (rank-keyed) AND track winners
+ // (flagged by `isTrackWinner` via useHackathonRewards). The BE
+ // trigger endpoint resolves the actual payout list itself; this
+ // array only drives the preview UI.
const winners = useMemo(
() =>
submissions.filter(
- s => s.rank !== undefined && s.rank !== null && s.rank <= maxRank
+ s =>
+ (s.rank !== undefined && s.rank !== null && s.rank <= maxRank) ||
+ s.isTrackWinner
),
[submissions, maxRank]
);
@@ -92,12 +105,21 @@ export default function PublishWinnersWizard({
rank: tier.rank,
prizeAmount: tier.prizeAmount,
currency: tier.currency,
+ place: tier.place,
+ kind: tier.kind,
+ trackId: tier.trackId,
})),
[prizeTiers]
);
+ // Format the prize for a given rank-based tier slot (overall). Track
+ // winners look up their prize via tier.trackId in WinnersGrid
+ // instead; this helper stays focused on overall placements so the
+ // preview's existing flow doesn't get gnarlier than needed.
const getPrizeForRank = (rank: number) => {
- const tier = mappedPrizeTiers.find(t => t.rank === rank);
+ const tier = mappedPrizeTiers.find(
+ t => (!t.kind || t.kind === 'OVERALL') && t.rank === rank
+ );
if (tier) {
const amount = parseFloat(tier.prizeAmount || '0').toLocaleString(
'en-US'
diff --git a/components/organization/hackathons/rewards/RewardsPageContent.tsx b/components/organization/hackathons/rewards/RewardsPageContent.tsx
index 045feac2..8ceef9a6 100644
--- a/components/organization/hackathons/rewards/RewardsPageContent.tsx
+++ b/components/organization/hackathons/rewards/RewardsPageContent.tsx
@@ -14,6 +14,7 @@ import {
} from 'lucide-react';
import { BoundlessButton } from '@/components/buttons';
import PodiumSection from '@/components/organization/hackathons/rewards/PodiumSection';
+import { TrackWinnersSection } from '@/components/organization/hackathons/rewards/TrackWinnersSection';
import SubmissionsList from '@/components/organization/hackathons/rewards/SubmissionsList';
import EscrowStatusCard from '@/components/organization/hackathons/rewards/EscrowStatusCard';
import { RewardDistributionStatusBanner } from '@/components/organization/hackathons/rewards/RewardDistributionStatusBanner';
@@ -21,6 +22,7 @@ import BoundlessSheet from '@/components/sheet/boundless-sheet';
import { Submission } from '@/components/organization/hackathons/rewards/types';
import type {
HackathonEscrowData,
+ HackathonTrackWinner,
RewardDistributionStatusResponse,
RewardDistributionStatusEnum,
} from '@/lib/api/hackathons';
@@ -41,6 +43,12 @@ interface RewardsPageContentProps {
onRefreshDistributionStatus?: () => void;
resultsPublished?: boolean;
escrowAddress?: string;
+ /**
+ * Per-track winners stamped by publishResults. Rendered in a
+ * dedicated section below the rank-based podium. Empty pre-publish
+ * and on OVERALL_ONLY hackathons.
+ */
+ trackWinners?: HackathonTrackWinner[];
}
export const RewardsPageContent: React.FC = ({
@@ -57,6 +65,7 @@ export const RewardsPageContent: React.FC = ({
onRefreshDistributionStatus,
resultsPublished,
escrowAddress,
+ trackWinners = [],
}) => {
const [isStatusSheetOpen, setIsStatusSheetOpen] = useState(false);
@@ -214,6 +223,7 @@ export const RewardsPageContent: React.FC = ({
+
All Submissions
diff --git a/components/organization/hackathons/rewards/TrackWinnersSection.tsx b/components/organization/hackathons/rewards/TrackWinnersSection.tsx
new file mode 100644
index 00000000..9502a1a5
--- /dev/null
+++ b/components/organization/hackathons/rewards/TrackWinnersSection.tsx
@@ -0,0 +1,114 @@
+'use client';
+
+import React from 'react';
+import { Layers, Trophy } from 'lucide-react';
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
+import { Badge } from '@/components/ui/badge';
+import { cn } from '@/lib/utils';
+import type { HackathonTrackWinner } from '@/lib/api/hackathons';
+
+interface TrackWinnersSectionProps {
+ trackWinners: HackathonTrackWinner[];
+}
+
+/**
+ * Renders track winners on the organizer rewards page.
+ *
+ * Mirrors the pattern used by the public WinnersTab: one card per
+ * track, scoped to its winning submission. Sits below the rank-based
+ * PodiumSection so the page reads as "overall winners on top, track
+ * winners below." Hidden entirely on OVERALL_ONLY hackathons (empty
+ * trackWinners → render null).
+ */
+export const TrackWinnersSection: React.FC = ({
+ trackWinners,
+}) => {
+ if (!trackWinners || trackWinners.length === 0) return null;
+
+ return (
+
+
+
+
Track Winners
+
+ ({trackWinners.length} track
+ {trackWinners.length === 1 ? '' : 's'})
+
+
+
+
+ {trackWinners.map(trackWinner => (
+
+ ))}
+
+
+ );
+};
+
+const formatPrize = (raw: string | null | undefined): string | null => {
+ if (!raw) return null;
+ // Backend emits prize like "100 USDC" or "$100"; normalise to
+ // " USDC" so the chip stays consistent across hackathons.
+ const match = raw.match(/^(?:USDC)?\s*\$?(\d+(?:[.,]\d+)?)\s*(?:USDC)?$/i);
+ return match ? `${match[1]} USDC` : raw;
+};
+
+const TrackWinnerCard: React.FC<{ trackWinner: HackathonTrackWinner }> = ({
+ trackWinner,
+}) => {
+ const prize = formatPrize(trackWinner.prize);
+
+ return (
+
+
+
+ {trackWinner.track.name}
+
+ {prize && (
+
+
+
+ {prize}
+
+
+ )}
+
+
+
+
+ {trackWinner.participants.slice(0, 3).map((p, i) => (
+
+
+
+ {p.username?.charAt(0) || '?'}
+
+
+ ))}
+ {trackWinner.participants.length > 3 && (
+
+ +{trackWinner.participants.length - 3}
+
+ )}
+
+
+
+ {trackWinner.projectName}
+
+ {trackWinner.teamName && (
+
+ {trackWinner.teamName}
+
+ )}
+
+
+
+ );
+};
+
+export default TrackWinnersSection;
diff --git a/components/organization/hackathons/rewards/WinnersGrid.tsx b/components/organization/hackathons/rewards/WinnersGrid.tsx
index 6632d77e..c8fa89de 100644
--- a/components/organization/hackathons/rewards/WinnersGrid.tsx
+++ b/components/organization/hackathons/rewards/WinnersGrid.tsx
@@ -5,12 +5,17 @@ import { cn } from '@/lib/utils';
import { Submission } from './types';
import WinnerCard from './WinnerCard';
+interface WinnersGridTier {
+ rank: number;
+ prizeAmount: string;
+ currency: string;
+ place?: string;
+ kind?: 'OVERALL' | 'TRACK';
+ trackId?: string;
+}
+
interface WinnersGridProps {
- prizeTiers: Array<{
- rank: number;
- prizeAmount: string;
- currency: string;
- }>;
+ prizeTiers: WinnersGridTier[];
winners: Submission[];
getPrizeForRank: (rank: number) => {
amount: string;
@@ -19,6 +24,22 @@ interface WinnersGridProps {
};
}
+const isOverallTier = (t: WinnersGridTier) => !t.kind || t.kind === 'OVERALL';
+const isTrackTier = (t: WinnersGridTier) => t.kind === 'TRACK' && !!t.trackId;
+
+const formatTrackPrize = (
+ amount: string | undefined,
+ currency: string | undefined
+) => {
+ const cleanAmount = parseFloat(amount || '0').toLocaleString('en-US');
+ const cleanCurrency = currency || 'USDC';
+ return {
+ amount: cleanAmount,
+ currency: cleanCurrency,
+ label: `${cleanAmount} ${cleanCurrency}`,
+ };
+};
+
export default function WinnersGrid({
prizeTiers,
winners,
@@ -26,9 +47,40 @@ export default function WinnersGrid({
}: WinnersGridProps) {
const totalTiers = prizeTiers.length;
- // Filter only tiers that have an assigned winner
- const tiersWithWinners = useMemo(() => {
- return prizeTiers.filter(tier => winners.some(w => w.rank === tier.rank));
+ // Build the display list as (tier, winner) pairs. Overall tiers are
+ // matched to winners by `rank`, track tiers by `trackId`. Tiers
+ // without a winner are skipped (preserves the previous "only show
+ // tiers with assigned winners" behaviour).
+ const displayPairs = useMemo(() => {
+ type Pair = { key: string; tier: WinnersGridTier; winner: Submission };
+ const overallPairs: Pair[] = [];
+ const trackPairs: Pair[] = [];
+ for (const tier of prizeTiers) {
+ if (isOverallTier(tier)) {
+ const winner = winners.find(
+ w => w.rank === tier.rank && !w.isTrackWinner
+ );
+ if (winner) {
+ overallPairs.push({
+ key: `overall-${tier.rank}`,
+ tier,
+ winner,
+ });
+ }
+ } else if (isTrackTier(tier)) {
+ const winner = winners.find(
+ w => w.isTrackWinner && w.trackId === tier.trackId
+ );
+ if (winner) {
+ trackPairs.push({
+ key: `track-${tier.trackId}`,
+ tier,
+ winner,
+ });
+ }
+ }
+ }
+ return { overallPairs, trackPairs };
}, [prizeTiers, winners]);
const getGridCols = (count: number) => {
@@ -38,20 +90,20 @@ export default function WinnersGrid({
return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
};
- const getTierOrder = (availableTiers: typeof prizeTiers) => {
- const sortedTiers = [...availableTiers].sort((a, b) => a.rank - b.rank);
-
- if (sortedTiers.length === 3) {
- const secondTier = sortedTiers.find(t => t.rank === 2) || sortedTiers[1];
- const firstTier = sortedTiers.find(t => t.rank === 1) || sortedTiers[0];
- const thirdTier = sortedTiers.find(t => t.rank === 3) || sortedTiers[2];
- return [secondTier, firstTier, thirdTier].filter(Boolean);
+ // 1st/2nd/3rd podium re-ordering (2-1-3) only kicks in when exactly
+ // three overall winners are present, matching the prior look.
+ const orderedOverall = useMemo(() => {
+ const sorted = [...displayPairs.overallPairs].sort(
+ (a, b) => a.tier.rank - b.tier.rank
+ );
+ if (sorted.length === 3) {
+ const first = sorted.find(p => p.tier.rank === 1) ?? sorted[0];
+ const second = sorted.find(p => p.tier.rank === 2) ?? sorted[1];
+ const third = sorted.find(p => p.tier.rank === 3) ?? sorted[2];
+ return [second, first, third].filter(Boolean);
}
-
- return sortedTiers;
- };
-
- const tiersToDisplay = getTierOrder(tiersWithWinners);
+ return sorted;
+ }, [displayPairs.overallPairs]);
return (
@@ -61,32 +113,58 @@ export default function WinnersGrid({
-
- {tiersToDisplay.map(tier => {
- if (!tier) return null;
-
- const rank = tier.rank;
- const winner = winners.find(s => s.rank === rank);
- const prize = getPrizeForRank(rank);
- const amount = prize.amount || '0';
- const currency = prize.currency || 'USDC';
- const label = prize.label;
+ {orderedOverall.length > 0 && (
+
+ {orderedOverall.map(({ key, tier, winner }) => {
+ const prize = getPrizeForRank(tier.rank);
+ return (
+
+ );
+ })}
+
+ )}
- return (
-
- );
- })}
-
+ {displayPairs.trackPairs.length > 0 && (
+
+
+ Track Winners
+
+
+ {displayPairs.trackPairs.map(({ key, tier, winner }) => {
+ const prize = formatTrackPrize(tier.prizeAmount, tier.currency);
+ return (
+
+ );
+ })}
+
+
+ )}
);
}
diff --git a/components/organization/hackathons/rewards/types.ts b/components/organization/hackathons/rewards/types.ts
index 8f040687..0633aaaf 100644
--- a/components/organization/hackathons/rewards/types.ts
+++ b/components/organization/hackathons/rewards/types.ts
@@ -13,4 +13,15 @@ export interface Submission {
judgeCount?: number;
category?: string;
commentCount?: number;
+ // ── Track winner enrichment (post-publish only) ──
+ // Populated by useHackathonRewards from the published winners
+ // payload. `rank` stays null for track winners (their win lives on
+ // SubmissionTrackEntry.wonRank, not submission.rank), so consumers
+ // that need to count winners must OR `rank` with `isTrackWinner`.
+ isTrackWinner?: boolean;
+ trackId?: string;
+ trackName?: string;
+ trackPrize?: string;
+ /** Placement within the track. Currently always 1 in P1. */
+ trackWonRank?: number;
}
diff --git a/hooks/use-hackathon-rewards.ts b/hooks/use-hackathon-rewards.ts
index 06231076..2b4a8fe4 100644
--- a/hooks/use-hackathon-rewards.ts
+++ b/hooks/use-hackathon-rewards.ts
@@ -7,7 +7,9 @@ import {
getHackathonEscrow,
type Hackathon,
type HackathonEscrowData,
+ type HackathonTrackWinner,
} from '@/lib/api/hackathons';
+import { getHackathonWinners } from '@/lib/api/hackathon';
import {
getJudgingResults,
type JudgingResult,
@@ -76,6 +78,12 @@ interface UseHackathonRewardsReturn {
refetchHackathon: () => Promise
;
resultsPublished: boolean;
hackathon: Hackathon | null;
+ /**
+ * Per-track winners stamped by publishResults. Empty until results
+ * are published, and empty for OVERALL_ONLY hackathons. The page
+ * renders these in a separate section below the rank-based podium.
+ */
+ trackWinners: HackathonTrackWinner[];
}
export const useHackathonRewards = (
@@ -91,6 +99,7 @@ export const useHackathonRewards = (
const [isLoadingSubmissions, setIsLoadingSubmissions] = useState(true);
const [error, setError] = useState(null);
const [hackathon, setHackathon] = useState(null);
+ const [trackWinners, setTrackWinners] = useState([]);
const isFetchingEscrowRef = useRef(false);
const lastFetchedContractIdRef = useRef(null);
@@ -141,24 +150,58 @@ export const useHackathonRewards = (
setHackathon(fetchedHackathon);
if (fetchedHackathon.prizeTiers) {
- // Sort tiers by amount descending or use parsed numeric rank from place if available
- const sortedTiers = [...fetchedHackathon.prizeTiers].sort(
- (a: any, b: any) => {
- const rankA = parseInt(a.place?.match(/\d+/)?.[0] || '999');
- const rankB = parseInt(b.place?.match(/\d+/)?.[0] || '999');
- if (rankA !== rankB) return rankA - rankB;
-
- const amountA = parseFloat(a.prizeAmount || '0');
- const amountB = parseFloat(b.prizeAmount || '0');
- return amountB - amountA;
- }
+ // Sort: OVERALL tiers first by parsed numeric place ("1st",
+ // "2nd", etc.) ascending, then by amount descending; TRACK
+ // tiers after, in their original order (which matches the
+ // organizer-defined displayOrder). The previous code fell
+ // back to rank=999 for any non-numeric place string, which
+ // collapsed every track tier to the same slot.
+ const indexedTiers = (fetchedHackathon.prizeTiers as any[]).map(
+ (tier, i) => ({ tier, originalIndex: i })
+ );
+
+ const isTrack = (t: any) => t.kind === 'TRACK';
+
+ const overallEntries = indexedTiers.filter(e => !isTrack(e.tier));
+ const trackEntries = indexedTiers.filter(e => isTrack(e.tier));
+
+ overallEntries.sort((a, b) => {
+ const rankA = parseInt(
+ a.tier.place?.match(/\d+/)?.[0] || '999',
+ 10
+ );
+ const rankB = parseInt(
+ b.tier.place?.match(/\d+/)?.[0] || '999',
+ 10
+ );
+ if (rankA !== rankB) return rankA - rankB;
+ const amountA = parseFloat(a.tier.prizeAmount || '0');
+ const amountB = parseFloat(b.tier.prizeAmount || '0');
+ return amountB - amountA;
+ });
+ // Track entries keep their original ordering — that matches
+ // HackathonTrack.displayOrder on the backend.
+ trackEntries.sort((a, b) => a.originalIndex - b.originalIndex);
+
+ const sortedTiers = [...overallEntries, ...trackEntries].map(
+ e => e.tier
);
const tiers: PrizeTier[] = sortedTiers.map(
(tier: any, index: number) => {
- const parsedRank = parseInt(
- tier.place?.match(/\d+/)?.[0] || String(index + 1)
+ // Overall tiers get a numeric rank parsed from "place".
+ // Track tiers get a synthetic rank slot AFTER the highest
+ // overall rank so existing rank-keyed lookups don't
+ // collide with overall ranks. Components that render
+ // track winners should branch on `kind === 'TRACK'`
+ // instead of relying on this rank value.
+ const numericFromPlace = parseInt(
+ tier.place?.match(/\d+/)?.[0] || '',
+ 10
);
+ const parsedRank = !isNaN(numericFromPlace)
+ ? numericFromPlace
+ : index + 1;
return {
id: tier.id || `tier-${index + 1}`,
place: tier.place || `${getOrdinalSuffix(index + 1)} Place`,
@@ -167,6 +210,8 @@ export const useHackathonRewards = (
passMark: tier.passMark || 0,
description: tier.description,
rank: parsedRank,
+ kind: tier.kind,
+ trackId: tier.trackId,
};
}
);
@@ -396,6 +441,67 @@ export const useHackathonRewards = (
}
}, [organizationId, hackathonId]);
+ // Post-publish track-winner enrichment.
+ //
+ // Track winners are stamped on `SubmissionTrackEntry.wonRank`, not on
+ // `submission.rank`, so the submissions list above has no idea who
+ // won a track. We pull `/judging/winners` (which returns the
+ // overall + trackWinners payload) and merge the track-winner info
+ // into the corresponding submission rows. Runs only after results
+ // are published — pre-publish, trackWinners doesn't exist yet.
+ useEffect(() => {
+ if (!hackathon?.resultsPublished || !hackathonId) {
+ setTrackWinners([]);
+ return;
+ }
+ let cancelled = false;
+ (async () => {
+ try {
+ const winnersRes = await getHackathonWinners(hackathonId);
+ if (cancelled) return;
+ if (!winnersRes.success || !winnersRes.data) {
+ setTrackWinners([]);
+ return;
+ }
+ const data = winnersRes.data as {
+ trackWinners?: HackathonTrackWinner[];
+ };
+ const fetched = data.trackWinners ?? [];
+ setTrackWinners(fetched);
+ if (fetched.length === 0) return;
+ const byId = new Map(fetched.map(tw => [tw.submissionId, tw]));
+ setSubmissions(prev =>
+ prev.map(sub => {
+ const tw = byId.get(sub.id);
+ if (!tw) return sub;
+ return {
+ ...sub,
+ isTrackWinner: true,
+ trackId: tw.track.id,
+ trackName: tw.track.name,
+ trackPrize: tw.prize,
+ trackWonRank: tw.wonRank,
+ };
+ })
+ );
+ } catch (winnersErr) {
+ if (cancelled) return;
+ // Best-effort: an unreachable winners endpoint shouldn't kill
+ // the rewards page. Overall winners (from judging results) still
+ // render; track winners just go missing.
+ reportError(winnersErr, {
+ context: 'rewards-fetchTrackWinners',
+ organizationId,
+ hackathonId,
+ });
+ setTrackWinners([]);
+ }
+ })();
+ return () => {
+ cancelled = true;
+ };
+ }, [hackathon?.resultsPublished, hackathonId, organizationId]);
+
return {
submissions,
setSubmissions,
@@ -410,5 +516,6 @@ export const useHackathonRewards = (
refetchHackathon: fetchHackathon,
resultsPublished: !!hackathon?.resultsPublished,
hackathon,
+ trackWinners,
};
};