Skip to content

Commit b39c905

Browse files
0xdevcollinsclaude
andcommitted
fix(bounty): organizers always see submissions regardless of visibility
HIDDEN_UNTIL_DEADLINE only hides peer submissions from other participants until the deadline; the organizer endpoint returns all submissions at all times. Remove the pre-deadline query gating and sealed-card state from the organizer submissions and payout panels, drop the now-dead SealedUntilDeadline and useDeadlinePassed helpers, and reword creation-flow copy that implied submissions were hidden from the organizer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 0e45b09 commit b39c905

9 files changed

Lines changed: 20 additions & 129 deletions

File tree

components/bounties/SealedUntilDeadline.tsx

Lines changed: 0 additions & 31 deletions
This file was deleted.

components/bounties/use-deadline-passed.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

components/organization/bounties/manage/BountyManagementDashboard.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,6 @@ export default function BountyManagementDashboard() {
164164
<BountySubmissionsPanel
165165
organizationId={organizationId}
166166
bountyId={bountyId}
167-
submissionVisibility={overview.submissionVisibility}
168-
submissionDeadline={overview.submissionDeadline ?? null}
169167
rewardCurrency={overview.rewardCurrency}
170168
staged={stagedWinners}
171169
onToggleStage={toggleStagedWinner}

components/organization/bounties/manage/BountyPayoutPanel.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import {
2121
} from '@/components/ui/select';
2222
import { BoundlessButton } from '@/components/buttons';
2323
import EmptyState from '@/components/EmptyState';
24-
import { SealedUntilDeadline } from '@/components/bounties/SealedUntilDeadline';
25-
import { useDeadlinePassed } from '@/components/bounties/use-deadline-passed';
2624
import {
2725
useAllBountySubmissions,
2826
ESCROW_PHASE_LABEL,
@@ -63,20 +61,15 @@ export default function BountyPayoutPanel({
6361
staged: Set<string>;
6462
}) {
6563
const isCompleted = overview.status === 'completed';
66-
const deadlinePassed = useDeadlinePassed(overview.submissionDeadline ?? null);
67-
// UX gate only, mirroring the Submissions tab. Publish validation guarantees
68-
// live competitions have a deadline, so a null deadline does not gate.
69-
const gated =
70-
overview.submissionVisibility === 'HIDDEN_UNTIL_DEADLINE' &&
71-
overview.submissionDeadline != null &&
72-
!deadlinePassed;
7364

7465
// Winner selection must see the COMPLETE pool, never one review page.
66+
// Organizers always see submissions regardless of submissionVisibility;
67+
// HIDDEN_UNTIL_DEADLINE only hides peer work from other participants.
7568
const {
7669
data: allSubmissions,
7770
isLoading,
7871
error,
79-
} = useAllBountySubmissions(organizationId, bountyId, { enabled: !gated });
72+
} = useAllBountySubmissions(organizationId, bountyId);
8073
const submissions = useMemo(() => allSubmissions ?? [], [allSubmissions]);
8174

8275
const payout = useBountyPayout({ organizationId, bountyId });
@@ -168,16 +161,6 @@ export default function BountyPayoutPanel({
168161
);
169162
}
170163

171-
if (gated && overview.submissionDeadline) {
172-
return (
173-
<SealedUntilDeadline
174-
deadline={overview.submissionDeadline}
175-
title='Winner selection opens at the deadline'
176-
description='Competition submissions stay sealed until then.'
177-
/>
178-
);
179-
}
180-
181164
if (isLoading) {
182165
return (
183166
<div className='flex items-center justify-center py-16'>

components/organization/bounties/manage/BountySubmissionsPanel.tsx

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
1919
import { Badge } from '@/components/ui/badge';
2020
import { BoundlessButton } from '@/components/buttons';
2121
import EmptyState from '@/components/EmptyState';
22-
import { SealedUntilDeadline } from '@/components/bounties/SealedUntilDeadline';
2322
import { submissionStatusClass } from '@/components/bounties/statusClass';
24-
import { useDeadlinePassed } from '@/components/bounties/use-deadline-passed';
2523
import {
2624
useBountySubmissions,
27-
type BountyOperateOverview,
2825
type OrganizerBountySubmission,
2926
} from '@/features/bounties';
3027
import { ordinal } from '@/lib/utils';
@@ -51,49 +48,26 @@ function formatTierAmount(amount: string): string {
5148
export default function BountySubmissionsPanel({
5249
organizationId,
5350
bountyId,
54-
submissionVisibility,
55-
submissionDeadline,
5651
rewardCurrency,
5752
staged,
5853
onToggleStage,
5954
}: {
6055
organizationId: string;
6156
bountyId: string;
62-
submissionVisibility: BountyOperateOverview['submissionVisibility'];
63-
submissionDeadline: string | null;
6457
rewardCurrency: string;
6558
staged: Set<string>;
6659
onToggleStage: (id: string) => void;
6760
}) {
6861
const [page, setPage] = useState(1);
69-
const deadlinePassed = useDeadlinePassed(submissionDeadline);
7062

71-
// Keep competition work out of the UI until the deadline. Publish validation
72-
// guarantees a deadline for live competitions; without one there is nothing
73-
// to count down to, so we do not gate. NOTE: this is a UX gate only, the
74-
// organizer endpoint itself returns submissions regardless of visibility.
75-
const gated =
76-
submissionVisibility === 'HIDDEN_UNTIL_DEADLINE' &&
77-
submissionDeadline != null &&
78-
!deadlinePassed;
79-
80-
// Don't fetch sealed competition work until the deadline.
63+
// Organizers always see submissions, regardless of submissionVisibility.
64+
// HIDDEN_UNTIL_DEADLINE only hides peer work from other participants.
8165
const { data, isLoading, error } = useBountySubmissions(
8266
organizationId,
8367
bountyId,
84-
{ params: { page, limit: PAGE_SIZE }, enabled: !gated }
68+
{ params: { page, limit: PAGE_SIZE } }
8569
);
8670

87-
if (gated && submissionDeadline) {
88-
return (
89-
<SealedUntilDeadline
90-
deadline={submissionDeadline}
91-
title='Submissions are hidden until the deadline'
92-
description='This is a competition. Work stays sealed so review stays fair.'
93-
/>
94-
);
95-
}
96-
9771
if (isLoading) {
9872
return (
9973
<div className='flex items-center justify-center py-16'>

components/organization/bounties/new/tabs/SubmissionModelTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ export default function SubmissionModelTab({
408408
</p>
409409
<p className='mt-0.5 text-xs text-zinc-500'>
410410
{isCompetition
411-
? 'Hidden until the deadline so the organizer cannot play favorites.'
411+
? "Participants can't see each other's work until the deadline. You review submissions as they arrive."
412412
: 'Visible to the organizer as submissions arrive.'}
413413
</p>
414414
</div>

components/organization/bounties/new/tabs/schemas/modeSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function computeBountyModeDescription(
7373
if (entryType === 'OPEN') {
7474
return single
7575
? 'Anyone eligible can claim this bounty directly. The first to claim locks it, does the work, and gets paid. One worker, one reward.'
76-
: 'Anyone can join and work in parallel. Submissions stay hidden until the deadline, then the organizer picks the winner(s).';
76+
: 'Anyone can join and work in parallel. Submissions stay hidden from other participants until the deadline, then the organizer picks the winner(s).';
7777
}
7878
const depth =
7979
entryType === 'APPLICATION_LIGHT'

components/organization/bounties/new/tabs/schemas/submissionModelSchema.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,16 @@ export function makeSubmissionModelSchema(
9797
}
9898
}
9999

100-
// Competition modes must hide submissions until the deadline.
100+
// Competition modes must hide peer submissions from other participants
101+
// until the deadline (organizers always see submissions).
101102
if (
102103
claimType === 'COMPETITION' &&
103104
data.submissionVisibility !== 'HIDDEN_UNTIL_DEADLINE'
104105
) {
105106
ctx.addIssue({
106107
code: z.ZodIssueCode.custom,
107-
message: 'Competition submissions are hidden until the deadline',
108+
message:
109+
'Competition submissions are hidden from other participants until the deadline',
108110
path: ['submissionVisibility'],
109111
});
110112
}

features/bounties/api/use-organizer-dashboard.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,14 @@ export function useBountyOverview(
3232

3333
/**
3434
* Submitted work on a bounty, for the reviewing organizer (#337 / #632).
35-
* Pass `enabled: false` to keep the Submissions tab from fetching sealed
36-
* competition work before the deadline. This is a UX gate only: the API
37-
* returns the organizer's submissions regardless of visibility, so the
38-
* actual seal (if required) must be enforced server-side.
35+
* Organizers always see submissions regardless of submissionVisibility —
36+
* HIDDEN_UNTIL_DEADLINE only hides peer work from other participants, and
37+
* the API returns the organizer's submissions at all times.
3938
*/
4039
export function useBountySubmissions(
4140
organizationId: string | undefined,
4241
bountyId: string | undefined,
43-
options: { params?: OrganizerSubmissionsParams; enabled?: boolean } = {}
42+
options: { params?: OrganizerSubmissionsParams } = {}
4443
) {
4544
const params = options.params ?? {};
4645
return useQuery<OrganizerBountySubmissionList>({
@@ -55,19 +54,18 @@ export function useBountySubmissions(
5554
bountyId as string,
5655
params
5756
),
58-
enabled: !!organizationId && !!bountyId && (options.enabled ?? true),
57+
enabled: !!organizationId && !!bountyId,
5958
});
6059
}
6160

6261
/**
6362
* The COMPLETE submission set for a bounty (pages through the capped list
6463
* endpoint). Winner selection reads this so the payout pool is never a
65-
* truncated page. Same UX-only caveat as useBountySubmissions.
64+
* truncated page. Same organizer-visibility rule as useBountySubmissions.
6665
*/
6766
export function useAllBountySubmissions(
6867
organizationId: string | undefined,
69-
bountyId: string | undefined,
70-
options: { enabled?: boolean } = {}
68+
bountyId: string | undefined
7169
) {
7270
return useQuery<OrganizerBountySubmission[]>({
7371
queryKey: bountyKeys.orgSubmissionsAll(
@@ -76,6 +74,6 @@ export function useAllBountySubmissions(
7674
),
7775
queryFn: () =>
7876
listAllBountySubmissions(organizationId as string, bountyId as string),
79-
enabled: !!organizationId && !!bountyId && (options.enabled ?? true),
77+
enabled: !!organizationId && !!bountyId,
8078
});
8179
}

0 commit comments

Comments
 (0)