Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions components/bounties/SealedUntilDeadline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { EyeOff } from 'lucide-react';

import { DueCountdown } from './DueCountdown';

/**
* Sealed-until-deadline card shared by the organizer submissions review and
* payout tabs, so the competition seal looks and behaves the same on both.
*/
export function SealedUntilDeadline({
deadline,
title,
description,
}: {
deadline: string;
title: string;
description: string;
}) {
return (
<div className='rounded-2xl border border-zinc-800 bg-zinc-900/40 py-16 text-center'>
<EyeOff className='mx-auto mb-3 h-6 w-6 text-zinc-500' />
<p className='text-sm font-medium text-zinc-200'>{title}</p>
<p className='mt-1 text-xs text-zinc-500'>{description}</p>
<div className='mt-3 flex justify-center'>
<DueCountdown
deadline={deadline}
className='flex items-center gap-1.5 text-xs font-medium text-zinc-300'
/>
</div>
</div>
);
}
10 changes: 3 additions & 7 deletions components/bounties/detail/submit/BountySubmitForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@ import { uploadService } from '@/lib/api/upload';
import { useWalletContext } from '@/components/providers/wallet-provider';
import {
useSubmitBounty,
ESCROW_PHASE_LABEL,
type BountyPublic,
type EscrowRunPhase,
} from '@/features/bounties';

const PHASE_LABEL: Record<EscrowRunPhase, string> = {
idle: '',
const PHASE_LABEL = {
...ESCROW_PHASE_LABEL,
starting: 'Preparing submission…',
signing: 'Signing…',
submitting: 'Submitting…',
polling: 'Anchoring on-chain…',
completed: 'Confirmed',
failed: 'Failed',
};

type Requirements = BountyPublic['submissionRequirements'];
Expand Down
33 changes: 33 additions & 0 deletions components/bounties/use-deadline-passed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';

import { useEffect, useState } from 'react';

/**
* Whether an ISO deadline has passed, re-checked every 30s (the DueCountdown
* cadence) so deadline gates unlock while the user stays on the page instead
* of only on remount. A null deadline reports false.
*/
export function useDeadlinePassed(deadline: string | null): boolean {
const [passed, setPassed] = useState(() =>
deadline ? new Date(deadline).getTime() <= Date.now() : false
);

useEffect(() => {
if (!deadline) {
setPassed(false);
return;
}
const target = new Date(deadline).getTime();
if (target <= Date.now()) {
setPassed(true);
return;
}
setPassed(false);
const interval = setInterval(() => {
if (Date.now() >= target) setPassed(true);
}, 30_000);
return () => clearInterval(interval);
}, [deadline]);

return passed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@/features/bounties';
import { ordinal } from '@/lib/utils';
import BountySubmissionsPanel from './BountySubmissionsPanel';
import BountyPayoutPanel from './BountyPayoutPanel';

export default function BountyManagementDashboard() {
const params = useParams<{ id: string; bountyId: string }>();
Expand Down Expand Up @@ -171,7 +172,12 @@ export default function BountyManagementDashboard() {
/>
</TabsContent>
<TabsContent value='payout'>
<TabPlaceholder title='Winner selection & payout' issue='#633' />
<BountyPayoutPanel
organizationId={organizationId}
bountyId={bountyId}
overview={overview}
staged={stagedWinners}
/>
</TabsContent>
<TabsContent value='settings'>
<TabPlaceholder title='Settings & cancel / refund' issue='#634' />
Expand Down
Loading
Loading