Problem
Every campaign detail route ignores its own params.id and renders one hardcoded mock campaign, regardless of which campaign was actually clicked.
app/marketplace/[id]/page.tsx:
export default function CampaignDetailPage({ params }: { params: { id: string } }) {
const campaign = campaignDetailMock; // params.id is never read
app/dashboard/business/campaigns/[id]/page.tsx:
const campaign = campaignDetail; // always mock for now — params.id ignored
Meanwhile the marketplace grid correctly generates distinct links for each of the 13 campaigns in components/marketplace/marketplace-data.ts (nextgen-wallet, lumen-node, cross-border, ...), via Link href={/marketplace/${campaign.id}} in marketplace-grid.tsx. Every one of those routes currently renders the identical campaignDetailMock object (id: "quantum-wearables"), no matter which card was clicked.
Compounding bug
ApplicationForm's "already applied" check is keyed by campaign-application:${campaignId} in sessionStorage, and campaignId defaults to campaignDetailMock.id (components/marketplace/application-form.tsx:17-21) since the page never passes the real id down. So once a creator applies to any campaign, every other campaign's detail page falsely shows "APPLICATION SUBMITTED!" as if they'd already applied to that one too.
Concrete failure scenario
- Creator browses the marketplace, clicks into "NextGen Wallet Launch" — sees the "Quantum Wearables" brief/budget/requirements instead.
- Clicks into "Lumen Node Validator" — sees the exact same "Quantum Wearables" content again.
- Applies to one campaign. Every other campaign in the marketplace now silently shows as already-applied, blocking legitimate applications to genuinely different campaigns.
Expected behaviour
Look up the campaign by params.id from the same dataset used to build the marketplace/dashboard lists, and 404/redirect on an unknown id. Scope the sessionStorage "already applied" key to the real per-campaign id, not a shared default.
Note: app/dashboard/business/campaigns/[id]/page.tsx also still types params synchronously ({ id: string }) rather than Promise<{ id: string }> — unlike the already-correct app/dashboard/creator/campaigns/[id]/page.tsx, which does async function ... { params: Promise<PageParams> } and await params. The app is on Next.js 16, where dynamic route params are async; align the business page to the same pattern while wiring up the real id lookup, since it's currently only harmless because the page never reads params.id at all.
Files
app/marketplace/[id]/page.tsx
app/dashboard/business/campaigns/[id]/page.tsx
components/marketplace/application-form.tsx — sessionStorage key scoping
Acceptance criteria
Problem
Every campaign detail route ignores its own
params.idand renders one hardcoded mock campaign, regardless of which campaign was actually clicked.app/marketplace/[id]/page.tsx:app/dashboard/business/campaigns/[id]/page.tsx:Meanwhile the marketplace grid correctly generates distinct links for each of the 13 campaigns in
components/marketplace/marketplace-data.ts(nextgen-wallet,lumen-node,cross-border, ...), viaLink href={/marketplace/${campaign.id}}inmarketplace-grid.tsx. Every one of those routes currently renders the identicalcampaignDetailMockobject (id: "quantum-wearables"), no matter which card was clicked.Compounding bug
ApplicationForm's "already applied" check is keyed bycampaign-application:${campaignId}insessionStorage, andcampaignIddefaults tocampaignDetailMock.id(components/marketplace/application-form.tsx:17-21) since the page never passes the real id down. So once a creator applies to any campaign, every other campaign's detail page falsely shows "APPLICATION SUBMITTED!" as if they'd already applied to that one too.Concrete failure scenario
Expected behaviour
Look up the campaign by
params.idfrom the same dataset used to build the marketplace/dashboard lists, and 404/redirect on an unknown id. Scope the sessionStorage "already applied" key to the real per-campaign id, not a shared default.Note:
app/dashboard/business/campaigns/[id]/page.tsxalso still typesparamssynchronously ({ id: string }) rather thanPromise<{ id: string }>— unlike the already-correctapp/dashboard/creator/campaigns/[id]/page.tsx, which doesasync function ... { params: Promise<PageParams> }andawait params. The app is on Next.js 16, where dynamic route params are async; align the business page to the same pattern while wiring up the real id lookup, since it's currently only harmless because the page never readsparams.idat all.Files
app/marketplace/[id]/page.tsxapp/dashboard/business/campaigns/[id]/page.tsxcomponents/marketplace/application-form.tsx— sessionStorage key scopingAcceptance criteria
app/marketplace/[id]/page.tsxrenders the campaign matchingparams.id, not a hardcoded mockapp/dashboard/business/campaigns/[id]/page.tsxdoes the same, and adopts the asyncparams: Promise<{id: string}>pattern already used on the creator sideidshows a proper not-found state instead of falling back to an arbitrary campaign