What I owned
Owning the full stack: Next.js 16 App Router with strict TypeScript, NextAuth 5 with role-based access (Business, Broker, Agent, Provider, Admin), Prisma 6 over Postgres with a domain model covering tenders, bids, contracts, escrow states (Locked, Partial Release, Released, Refunded, Disputed), sectors, and audit trails. Server Actions handle mutations; RSC streams the marketplace and tender browsing.
Constraints
- Five user roles with distinct workflows and permissions
- Escrow lifecycle must be auditable end-to-end
- Tender, bid, contract, and dispute states must be reflected accurately in real time
- Strict TypeScript across the data layer and UI
Process
- 01
Domain modeling
Five-role permissions matrix, tender/bid/contract/escrow state machines, sector-scoped capabilities — all written before code.
- 02
Schema in Prisma
Postgres schema with explicit enums for Role, Sector, TenderStatus, BidStatus, ContractStatus, EscrowStatus.
- 03
App Router build
Server Components for marketplace + tender pages, Server Actions for create/update/dispute, RSC streaming with skeleton states.
- 04
Escrow workflow
State machine for funds movement with dispute hooks, partial release support, and full audit log.
- 05
Admin tooling
Admin panel for moderation, dispute resolution, and platform-level reporting.
Approach
Marketplaces fail when role boundaries blur. The hardest design decision was making the Role enum first-class in the data model and propagating it through every mutation rather than treating it as a session-scoped detail. Escrow followed the same principle — every state transition is named, recorded, and reversible.
Deliverables
- Next.js 16 App Router marketplace
- NextAuth 5 with role-based authorization
- Prisma 6 schema covering tenders, bids, contracts, escrow
- Tender creation, bidding, and award workflow
- Contract management with escrow lifecycle
- Dispute and refund workflow
- Admin panel with audit trail and moderation
Outcomes
- Domain model covering 4 sectors and 5 roles in production-ready form
- Escrow state machine documented and enforced at the data layer
- Strict TypeScript across the data, API, and UI surfaces
- Admin tooling built into the same codebase as the public marketplace