diff --git a/app/(landing)/organizations/[id]/bounties/[bountyId]/page.tsx b/app/(landing)/organizations/[id]/bounties/[bountyId]/page.tsx index 12387ac4..b2b98946 100644 --- a/app/(landing)/organizations/[id]/bounties/[bountyId]/page.tsx +++ b/app/(landing)/organizations/[id]/bounties/[bountyId]/page.tsx @@ -1,3 +1,4 @@ +import { Suspense } from 'react'; import { Metadata } from 'next'; import { AuthGuard } from '@/components/auth'; @@ -11,7 +12,10 @@ export default function BountyManagePageRoute() { return ( }>
- + {/* Dashboard reads ?tab= via useSearchParams; needs a suspense boundary. */} + }> + +
); diff --git a/app/(landing)/organizations/[id]/bounties/page.tsx b/app/(landing)/organizations/[id]/bounties/page.tsx index 170b4fa3..02332495 100644 --- a/app/(landing)/organizations/[id]/bounties/page.tsx +++ b/app/(landing)/organizations/[id]/bounties/page.tsx @@ -3,7 +3,15 @@ import { useMemo, useState } from 'react'; import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; -import { Calendar, FileText, Plus, Search, Target, Trash2 } from 'lucide-react'; +import { + ArrowRight, + Calendar, + FileText, + Plus, + Search, + Target, + Trash2, +} from 'lucide-react'; import { BoundlessButton } from '@/components/buttons'; import { Badge } from '@/components/ui/badge'; @@ -68,6 +76,26 @@ function statusDisplay(status: string) { ); } +/** + * Status-aware manage CTA: deep-links into the dashboard tab that matches where + * the organizer's attention is needed next. + */ +function manageCta(status: string): { label: string; tab: string } { + switch (status) { + case 'submitted': + case 'under_review': + return { label: 'Review submissions', tab: 'submissions' }; + case 'in_progress': + return { label: 'Review & pay', tab: 'payout' }; + case 'completed': + return { label: 'View results', tab: 'results' }; + case 'cancelled': + return { label: 'View bounty', tab: 'overview' }; + default: + return { label: 'Manage', tab: 'overview' }; + } +} + const draftPrizePool = (draft: BountyDraft): number => (draft.data?.reward?.prizeTiers ?? []).reduce( (sum, tier) => sum + (Number(tier.amount) || 0), @@ -279,6 +307,25 @@ export default function OrganizationBountiesPage() { {bounty._count?.submissions ?? 0} + {(() => { + const cta = manageCta(bounty.status); + return ( + { + e.stopPropagation(); + router.push( + `/organizations/${organizationId}/bounties/${bounty.id}?tab=${cta.tab}` + ); + }} + > + {cta.label} + + + ); + })()} ); })} diff --git a/components/organization/bounties/manage/BountyManagementDashboard.tsx b/components/organization/bounties/manage/BountyManagementDashboard.tsx index 38ca2944..bf563bc7 100644 --- a/components/organization/bounties/manage/BountyManagementDashboard.tsx +++ b/components/organization/bounties/manage/BountyManagementDashboard.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import { useParams } from 'next/navigation'; +import { useParams, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { ArrowLeft, Info, Loader2 } from 'lucide-react'; @@ -30,10 +30,22 @@ import BountyApplicationsPanel from './BountyApplicationsPanel'; import BountySettingsPanel from './BountySettingsPanel'; import BountyResultsPanel from './BountyResultsPanel'; +/** Tabs the org bounty list can deep-link into via `?tab=`. */ +const LINKABLE_TABS = new Set([ + 'overview', + 'applications', + 'submissions', + 'payout', + 'results', + 'settings', +]); + export default function BountyManagementDashboard() { const params = useParams<{ id: string; bountyId: string }>(); + const searchParams = useSearchParams(); const organizationId = params?.id ?? ''; const bountyId = params?.bountyId ?? ''; + const requestedTab = searchParams?.get('tab') ?? ''; // Winner staging lives here (above the tab boundary) so it survives tab // switches and is reachable by the Payout tab (#633). @@ -87,6 +99,14 @@ export default function BountyManagementDashboard() { overview.entryType === 'APPLICATION_LIGHT' || overview.entryType === 'APPLICATION_FULL'; const isCompleted = overview.status === 'completed'; + + // Honor a deep-linked ?tab= only when that tab is actually available for this + // bounty (applications need an application mode; results need completion). + const tabAvailable = + LINKABLE_TABS.has(requestedTab) && + (requestedTab !== 'applications' || isApplication) && + (requestedTab !== 'results' || isCompleted); + const initialTab = tabAvailable ? requestedTab : 'overview'; const modeLabel = overview.entryType && overview.claimType ? computeBountyModeLabel(overview.entryType, overview.claimType) @@ -145,7 +165,7 @@ export default function BountyManagementDashboard() { - + Overview {isApplication && (