Implements requirements 7.4, 7.5 (public marketplace frontend) and 4.1, 4.4 (investor funding endpoint).
frontend/src/lib/api.ts
- Typed interfaces:
TradeDeal,Document,Milestone getOpenDeals()— fetches all open deals fromGET /trade-dealsgetDealById(id)— fetches a single deal, returnsnullon 404
frontend/src/components/FundingProgressBar.tsx
- Visual progress bar showing amount raised, percentage, and remaining funding
- Uses
role="progressbar"witharia-valuenow/min/maxfor accessibility
frontend/src/components/StatusBadge.tsx
- Colour-coded pill badge: open=green, funded=blue, delivered=orange, completed=grey, failed=red
frontend/src/app/marketplace/page.tsx
- Public page — no auth required
- Fetches open deals only (client-side filter as belt-and-suspenders)
- Responsive card grid (1→2→3 columns) with commodity, quantity, total value, delivery date, funding progress bar
- Empty state when no open deals exist
- Each card links to
/marketplace/[id]
frontend/src/app/marketplace/[id]/page.tsx
- Public page — no auth required (read-only)
- Displays commodity, quantity, quantity unit, total value, delivery date, token symbol, status badge
- Funding progress bar (total_invested / total_value)
- Document list: type, IPFS link, upload date
- Milestone timeline: ordered farm → warehouse → port → importer with timestamp and notes
- "Fund this Deal" button rendered only when
status === 'open'(links to/invest/[id]) - Calls
notFound()for missing deals — renders Next.js 404 page
backend/src/investments/dto/create-investment.dto.ts
trade_deal_id: UUID (required)token_amount: positive integer (required)
backend/src/investments/entities/investment.entity.ts
- Re-exports
Investmentfrom canonical location (users/entities) for local module imports
backend/src/investments/investments.service.ts
- Role check: rejects non-investors with 403
- Runs inside a
pessimistic_writeDB transaction to prevent race conditions on concurrent funding - Locks the deal row, checks
status === 'open'(422 otherwise) - Sums
confirmedinvestment token amounts to compute availability - Rejects with 400 if
token_amountexceeds available tokens - Stores
amount_usd = token_amount * 100 - Creates investment with
status = 'pending'
backend/src/investments/investments.controller.ts
POST /investments— guarded byAuthGuard('jwt')+KycGuard- Returns 201 with the created investment object
backend/src/investments/investments.module.ts
- Registers
InvestmentandTradeDealrepositories - Exports
InvestmentsServicefor use by other modules
| Req | Description | Status |
|---|---|---|
| 7.5 | Marketplace shows only status = 'open' deals |
done |
| 7.4 | Deal detail shows commodity, quantity, value, funding progress, documents, milestones | done |
| 4.1 | amount_usd calculated as token_amount * 100 |
done |
| 4.4 | Reject if token_amount exceeds available tokens |
done |
-
GET /trade-dealsfetched without auth on marketplace page - Card grid with commodity, quantity, total value, funding progress bar, remaining funding, delivery date
- Each card links to deal detail page
- Empty state when no open deals
- Deal detail shows all required fields + status badge
- Document list with type, IPFS URL, upload date
- Milestone timeline ordered with timestamp and notes
- "Fund this Deal" button visible only when
status = 'open' - 404 page if deal does not exist
-
POST /investmentsrequires JWT +role = 'investor'(403 otherwise) -
POST /investmentsrequireskyc_status = 'verified'(403KYC_REQUIREDotherwise) - Request body validated:
trade_deal_id(UUID) andtoken_amount(positive integer) - 422 if deal status is not
open - 400 if
token_amountexceeds available tokens - Available token check is atomic (pessimistic row lock in DB transaction)
- Investment created with
status = 'pending', returns 201 - No Stellar transaction submitted
# Marketplace
open http://localhost:3000/marketplace
# Deal detail
open http://localhost:3000/marketplace/<deal-id>
# Fund a deal (investor JWT required)
curl -X POST http://localhost:3001/investments \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"trade_deal_id": "<uuid>", "token_amount": 5}'