diff --git a/apps/marketplace-web/src/lib/api.ts b/apps/marketplace-web/src/lib/api.ts index 26d2abc4..97e69ad7 100644 --- a/apps/marketplace-web/src/lib/api.ts +++ b/apps/marketplace-web/src/lib/api.ts @@ -9,6 +9,13 @@ interface OfferRecord { price: number; sla: string; status: string; + created_at?: string; + gpu_model?: string; + gpu_memory_gb?: number; + gpu_count?: number; + cuda_version?: string; + price_per_hour?: number; + region?: string; } interface OffersResponse { diff --git a/apps/marketplace-web/src/main.ts b/apps/marketplace-web/src/main.ts index 8101677f..0d901ef9 100644 --- a/apps/marketplace-web/src/main.ts +++ b/apps/marketplace-web/src/main.ts @@ -124,38 +124,43 @@ function renderOffers(offers: MarketplaceOffer[]): void { return; } - const rows = offers + const cards = offers .map( (offer) => ` - - ${offer.id} - ${offer.provider} - ${formatNumber(offer.capacity)} units - ${formatNumber(offer.price, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - ${offer.sla} - ${offer.status} - +
+
+
${offer.gpu_model || 'Unknown GPU'}
+ ${offer.status} +
+
${offer.provider}${offer.region ? ` · ${offer.region}` : ''}
+
+
+ VRAM + ${offer.gpu_memory_gb ? `${offer.gpu_memory_gb} GB` : '—'} +
+
+ GPUs + ${offer.gpu_count ?? 1}× +
+
+ CUDA + ${offer.cuda_version || '—'} +
+
+ Capacity + ${formatNumber(offer.capacity)} units +
+
+
+
${formatNumber(offer.price_per_hour ?? offer.price, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} credits/hr
+
${offer.sla}
+
+
`, ) .join(''); - selectors.offersWrapper.innerHTML = ` -
- - - - - - - - - - - - ${rows} -
IDProviderCapacityPriceSLAStatus
-
- `; + selectors.offersWrapper.innerHTML = `
${cards}
`; } function showToast(message: string, duration = 2500): void { diff --git a/apps/marketplace-web/src/style.css b/apps/marketplace-web/src/style.css index 10ade8a7..761a3d5b 100644 --- a/apps/marketplace-web/src/style.css +++ b/apps/marketplace-web/src/style.css @@ -112,6 +112,97 @@ body { overflow-x: auto; } +.offers-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 20px; +} + +.offer-card { + background: #ffffff; + border: 1px solid #e5e9f1; + border-radius: 14px; + padding: 20px; + transition: box-shadow 200ms ease, transform 200ms ease; +} + +.offer-card:hover { + box-shadow: 0 8px 24px rgba(99, 102, 241, 0.12); + transform: translateY(-2px); +} + +.offer-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 4px; +} + +.offer-gpu-name { + font-size: 1.15rem; + font-weight: 700; + color: #1d2736; +} + +.offer-provider { + font-size: 0.85rem; + color: #64748b; + margin-bottom: 16px; +} + +.offer-specs { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + background: #f8fafc; + border-radius: 10px; + padding: 12px; + margin-bottom: 16px; +} + +.spec-item { + text-align: center; +} + +.spec-label { + display: block; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #94a3b8; + margin-bottom: 4px; +} + +.spec-value { + display: block; + font-size: 0.95rem; + font-weight: 600; + color: #1e293b; +} + +.offer-pricing { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.offer-price { + font-size: 1.3rem; + font-weight: 700; + color: #6366f1; +} + +.offer-price small { + font-size: 0.75rem; + font-weight: 500; + color: #94a3b8; +} + +.offer-sla { + font-size: 0.8rem; + color: #64748b; +} + .status-pill { display: inline-flex; align-items: center;