7.3 KiB
7.3 KiB
marketplace-web
Web app for listing compute offers, placing bids, and viewing market stats in the AITBC stack.
Stage-aware: works now against a mock API, later switches to coordinator/pool-hub/blockchain endpoints without touching the UI.
Goals
- Browse offers (GPU/CPU, price per token, location, queue, latency).
- Place/manage bids (instant or scheduled).
- Watch market stats (price trends, filled volume, miner capacity).
- Wallet view (balance, recent tx; read-only first).
- Internationalization (EU langs later), dark theme, 960px layout.
Tech/Structure (Windsurf-friendly)
- Vanilla TypeScript + Vite (no React), separate JS/CSS files.
- File layout (desktop 960px grid, mobile-first CSS, dark theme):
marketplace-web/
├─ public/
│ ├─ icons/ # favicons, app icons
│ └─ i18n/ # JSON dictionaries (en/… later)
├─ src/
│ ├─ app.ts # app bootstrap/router
│ ├─ router.ts # hash-router (/, /offer/:id, /bids, /stats, /wallet)
│ ├─ api/
│ │ ├─ http.ts # fetch wrapper + baseURL swap (mock → real)
│ │ └─ marketplace.ts # typed API calls
│ ├─ store/
│ │ ├─ state.ts # global app state (signals or tiny pubsub)
│ │ └─ types.ts # shared types/interfaces
│ ├─ views/
│ │ ├─ HomeView.ts
│ │ ├─ OfferDetailView.ts
│ │ ├─ BidsView.ts
│ │ ├─ StatsView.ts
│ │ └─ WalletView.ts
│ ├─ components/
│ │ ├─ OfferCard.ts
│ │ ├─ BidForm.ts
│ │ ├─ Table.ts
│ │ ├─ Sparkline.ts # minimal chart (no external lib)
│ │ └─ Toast.ts
│ ├─ styles/
│ │ ├─ base.css # reset, variables, dark theme
│ │ ├─ layout.css # 960px grid, sections, header/footer
│ │ └─ components.css
│ └─ util/
│ ├─ format.ts # fmt token, price, time
│ ├─ validate.ts # input validation
│ └─ i18n.ts # simple t() loader
├─ index.html
├─ vite.config.ts
└─ README.md
Routes (Hash router)
/— Offer list + filters/offer/:id— Offer details + BidForm/bids— User bids (open, filled, cancelled)/stats— Price/volume/capacity charts/wallet— Balance + last 10 tx (read-only)
UI/UX Spec
- Dark theme, accent = ice-blue/white outlines (fits OIB style).
- 960px max width desktop, mobile-first, Nothing Phone 2a as reference.
- Toast bottom-center for actions.
- Forms: no animations, clear validation, disable buttons during submit.
Data Types (minimal)
type TokenAmount = `${number}`; // keep as string to avoid FP errors
type PricePerToken = `${number}`;
interface Offer {
id: string;
provider: string; // miner or pool label
hw: { gpu: string; vramGB?: number; cpu?: string };
region: string; // e.g., eu-central
queue: number; // jobs waiting
latencyMs: number;
price: PricePerToken; // AIToken per 1k tokens processed
minTokens: number;
maxTokens: number;
updatedAt: string;
}
interface BidInput {
offerId: string;
tokens: number; // requested tokens to process
maxPrice: PricePerToken; // cap
}
interface Bid extends BidInput {
id: string;
status: "open" | "filled" | "cancelled" | "expired";
createdAt: string;
filledTokens?: number;
avgFillPrice?: PricePerToken;
}
interface MarketStats {
ts: string[];
medianPrice: number[]; // per interval
filledVolume: number[]; // tokens
capacity: number[]; // available tokens
}
interface Wallet {
address: string;
balance: TokenAmount;
recent: Array<{ id: string; kind: "mint"|"spend"|"refund"; amount: TokenAmount; at: string }>;
}
Mock API (Stage 0)
Base URL: /.mock (served via Vite dev middleware or static JSON)
GET /.mock/offers.json→Offer[]GET /.mock/offers/:id.json→OfferPOST /.mock/bids(bodyBidInput) →BidGET /.mock/bids.json→Bid[]GET /.mock/stats.json→MarketStatsGET /.mock/wallet.json→Wallet
Switch to real endpoints by changing BASE_URL in api/http.ts.
Real API (Stage 2/3/4 wiring)
When coordinator/pool-hub/blockchain are ready:
GET /api/market/offers→Offer[]GET /api/market/offers/:id→OfferPOST /api/market/bids→ create bid, returnsBidGET /api/market/bids?owner=<wallet>→Bid[]GET /api/market/stats?range=24h|7d|30d→MarketStatsGET /api/wallet/summary?addr=<wallet>→Wallet
Auth header (later): Authorization: Bearer <session-or-wallet-token>.
State & Caching
- In-memory store (
store/state.ts) with tiny pub/sub. - Offer list cached 30s; stats cached 60s; bust on route change if stale.
- Optimistic UI for Bid create; reconcile on server response.
Filters (Home)
- Region (multi)
- HW capability (GPU model, min VRAM, CPU present)
- Price range (slider)
- Latency max (ms)
- Queue max
All filters are client-side over fetched offers (server-side later).
Validation Rules
BidInput.tokens∈[offer.minTokens, offer.maxTokens].maxPrice >= offer.pricefor instant fill hint; otherwise place as limit.- Warn if
queue > thresholdorlatencyMs > threshold.
Security Notes (Web)
- Input sanitize; never eval.
- CSRF not needed for read-only; for POST use standard token once auth exists.
- Rate-limit POST (server).
- Display wallet read-only unless signing is integrated (later via wallet-daemon).
i18n
public/i18n/en.jsonas root.util/i18n.tsprovidest(key, params?).- Keys only (no concatenated sentences). EU languages can be added later via your i18n tool.
Accessibility
- Semantic HTML, label every input, focus states visible.
- Keyboard: Tab order, Enter submits forms, Esc closes dialogs.
- Color contrast AA in dark theme.
Minimal Styling Rules
styles/base.css: CSS variables for colors, spacing, radius.styles/layout.css: header, main container (max-width: 960px), grid for cards.styles/components.css: OfferCard, Table, Buttons, Toast.
Testing
- Manual first:
- Offers list loads, filters act.
- Place bid with edge values.
- Stats sparkline renders with missing points.
- Later: Vitest for
util/+api/modules.
Env/Config
VITE_API_BASE→ mock or real.VITE_DEFAULT_REGION→ optional default filter.VITE_FEATURE_WALLET=readonly|disabled.
Build/Run
# dev
npm i
npm run dev
# build
npm run build
npm run preview
Migration Checklist (Mock → Real)
- Replace
VITE_API_BASEwith coordinator gateway URL. - Enable auth header injection when session is present.
- Wire
/walletto wallet-daemon read endpoint. - Swap stats source to real telemetry.
- Keep the same types; server must honor them.
Open Tasks
- Create file skeletons per structure above.
- Add mock JSON under
public/.mock/. - Implement OfferCard + filters.
- Implement BidForm with validation + optimistic UI.
- Implement StatsView with
Sparkline(no external chart lib). - Wire
VITE_API_BASEswitch. - Basic a11y pass + dark theme polish.
- Wallet view (read-only).