From 9bf9d09f185d154f53c19f208008c4a80d38243e Mon Sep 17 00:00:00 2001 From: AugistineCreates Date: Tue, 2 Jun 2026 11:47:29 +0100 Subject: [PATCH 1/4] feat: skeleton and aria-live loading state for test call --- src/ApiUsage.tsx | 25 ++++++++++++++++++++++--- src/index.css | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/ApiUsage.tsx b/src/ApiUsage.tsx index adafb20..ea9733c 100644 --- a/src/ApiUsage.tsx +++ b/src/ApiUsage.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; import EmptyState from '../components/EmptyState'; +import Skeleton from './components/Skeleton'; + type ApiEndpoint = { id: string; @@ -368,19 +370,36 @@ export default function ApiUsage() { {(apiResponse || isLoading) && ( -
+

Response

{isLoading ? ( -
Loading...
+
+
+ + +
+
+ + + + + +
+
) : (
diff --git a/src/index.css b/src/index.css index e1cae39..c613f47 100644 --- a/src/index.css +++ b/src/index.css @@ -2561,3 +2561,37 @@ code, color: var(--text); cursor: pointer; } + +/* Button Spinner & Loading States */ +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.button-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: #ffffff; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-right: 8px; + flex-shrink: 0; +} + +.primary-button.button-loading { + cursor: not-allowed; +} + +/* Response JSON Skeleton Container */ +.response-json-skeleton { + background: var(--surface); + padding: 16px; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 12px; +} + From d4c1f22af415f80c87c955e77c094bbf10c645e2 Mon Sep 17 00:00:00 2001 From: AugistineCreates Date: Tue, 2 Jun 2026 11:50:13 +0100 Subject: [PATCH 2/4] fix: resolve out-of-order state variable declarations, correct import paths, and restore missing Dashboard import --- src/ApiUsage.tsx | 4 ++-- src/App.tsx | 1 + src/components/Dashboard.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ApiUsage.tsx b/src/ApiUsage.tsx index ea9733c..8944082 100644 --- a/src/ApiUsage.tsx +++ b/src/ApiUsage.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import EmptyState from '../components/EmptyState'; +import EmptyState from './components/EmptyState'; import Skeleton from './components/Skeleton'; @@ -147,11 +147,11 @@ export default function ApiUsage() { const [apiResponse, setApiResponse] = useState(null); const [responseTime, setResponseTime] = useState(null); const [callCost, setCallCost] = useState(null); + const [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'error'>('all'); const [callHistory, setCallHistory] = useState(MOCK_CALL_HISTORY); const filteredCallHistory = statusFilter === 'all' ? callHistory : callHistory.filter(call => call.status === statusFilter); const [selectedLanguage, setSelectedLanguage] = useState<'javascript' | 'python' | 'curl'>('javascript'); const [expandedCall, setExpandedCall] = useState(null); - const [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'error'>('all'); const [usageStats, setUsageStats] = useState({ callsToday: 47, diff --git a/src/App.tsx b/src/App.tsx index 4ee2443..2e9d1d4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { ThemeToggle } from './ThemeToggle'; import ApiUsage from './ApiUsage'; import ServerError from './components/ServerError'; import NotFound from './components/NotFound'; +import Dashboard from './components/Dashboard'; type DepositStage = 'input' | 'approving' | 'pending' | 'confirmed' | 'failed'; type DemoOutcome = 'confirmed' | 'failed'; diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index ef3a7be..2aa766c 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import './Dashboard.css'; import { useNavigate } from 'react-router-dom'; import Skeleton from '../components/Skeleton'; From c820be1f48c3c4ace0902c0dc599e2ccf0a61b02 Mon Sep 17 00:00:00 2001 From: AugistineCreates Date: Tue, 2 Jun 2026 11:57:37 +0100 Subject: [PATCH 3/4] task: color-blind-safe HTTP method badges --- src/index.css | 87 +++++++++++++++++++++++++++++++++++++ src/pages/ApiDetailPage.tsx | 10 +---- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/index.css b/src/index.css index c613f47..35e738f 100644 --- a/src/index.css +++ b/src/index.css @@ -31,6 +31,27 @@ rgba(20, 27, 50, 0.98), rgba(12, 18, 34, 0.98) ); + + /* HTTP Method Badge Tokens - Dark Theme (High Contrast / Accessible) */ + --method-get-bg: rgba(59, 130, 246, 0.16); + --method-get-color: #60a5fa; + --method-get-border: rgba(59, 130, 246, 0.35); + + --method-post-bg: rgba(16, 185, 129, 0.16); + --method-post-color: #34d399; + --method-post-border: rgba(16, 185, 129, 0.35); + + --method-put-bg: rgba(245, 158, 11, 0.16); + --method-put-color: #fbbf24; + --method-put-border: rgba(245, 158, 11, 0.35); + + --method-delete-bg: rgba(239, 68, 68, 0.16); + --method-delete-color: #f87171; + --method-delete-border: rgba(239, 68, 68, 0.35); + + --method-patch-bg: rgba(139, 92, 246, 0.16); + --method-patch-color: #a78bfa; + --method-patch-border: rgba(139, 92, 246, 0.35); } [data-theme="light"] { @@ -55,6 +76,27 @@ rgba(255, 255, 255, 0.98), rgba(248, 250, 255, 0.98) ); + + /* HTTP Method Badge Tokens - Light Theme (High Contrast / Accessible) */ + --method-get-bg: rgba(37, 99, 235, 0.1); + --method-get-color: #1e40af; + --method-get-border: rgba(37, 99, 235, 0.25); + + --method-post-bg: rgba(5, 150, 105, 0.1); + --method-post-color: #065f46; + --method-post-border: rgba(5, 150, 105, 0.25); + + --method-put-bg: rgba(217, 119, 6, 0.1); + --method-put-color: #92400e; + --method-put-border: rgba(217, 119, 6, 0.25); + + --method-delete-bg: rgba(220, 38, 38, 0.1); + --method-delete-color: #991b1b; + --method-delete-border: rgba(220, 38, 38, 0.25); + + --method-patch-bg: rgba(109, 40, 217, 0.1); + --method-patch-color: #5b21b6; + --method-patch-border: rgba(109, 40, 217, 0.25); } * { @@ -2595,3 +2637,48 @@ code, gap: 12px; } +/* Accessible HTTP Method Badges */ +.method-badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 4px 8px; + border-radius: 6px; + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + border: 1px solid transparent; +} + +.method-badge--get { + background: var(--method-get-bg); + color: var(--method-get-color); + border-color: var(--method-get-border); +} + +.method-badge--post { + background: var(--method-post-bg); + color: var(--method-post-color); + border-color: var(--method-post-border); +} + +.method-badge--put { + background: var(--method-put-bg); + color: var(--method-put-color); + border-color: var(--method-put-border); +} + +.method-badge--patch { + background: var(--method-patch-bg); + color: var(--method-patch-color); + border-color: var(--method-patch-border); +} + +.method-badge--delete { + background: var(--method-delete-bg); + color: var(--method-delete-color); + border-color: var(--method-delete-border); +} + + diff --git a/src/pages/ApiDetailPage.tsx b/src/pages/ApiDetailPage.tsx index 415fcb4..e76e34c 100644 --- a/src/pages/ApiDetailPage.tsx +++ b/src/pages/ApiDetailPage.tsx @@ -549,15 +549,7 @@ print(data)`;
{ep.method} From beea92764442ae3dcaef2096b8b84b1f4792aad7 Mon Sep 17 00:00:00 2001 From: AugistineCreates Date: Tue, 2 Jun 2026 12:03:55 +0100 Subject: [PATCH 4/4] feat: validate marketplace price range inputs --- src/components/FiltersSidebar.tsx | 42 +++++++++++++++++++++++++------ src/index.css | 20 +++++++++++++++ src/pages/MarketplacePage.tsx | 11 +++++--- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/components/FiltersSidebar.tsx b/src/components/FiltersSidebar.tsx index 2c57e78..a4f98f0 100644 --- a/src/components/FiltersSidebar.tsx +++ b/src/components/FiltersSidebar.tsx @@ -57,26 +57,52 @@ export default function FiltersSidebar({ Price range
maxPrice ? 'filter-input--invalid' : ''}`} placeholder="min" value={minPrice ?? ""} - onChange={(e) => - setMinPrice(e.target.value === "" ? null : Number(e.target.value)) - } + onChange={(e) => { + const val = e.target.value === "" ? null : Math.max(0, Number(e.target.value)); + setMinPrice(val); + }} + aria-invalid={minPrice !== null && maxPrice !== null && minPrice > maxPrice} + aria-describedby={minPrice !== null && maxPrice !== null && minPrice > maxPrice ? "price-range-error" : undefined} style={{ width: "100%" }} /> maxPrice ? 'filter-input--invalid' : ''}`} placeholder="max" value={maxPrice ?? ""} - onChange={(e) => - setMaxPrice(e.target.value === "" ? null : Number(e.target.value)) - } + onChange={(e) => { + const val = e.target.value === "" ? null : Math.max(0, Number(e.target.value)); + setMaxPrice(val); + }} + aria-invalid={minPrice !== null && maxPrice !== null && minPrice > maxPrice} + aria-describedby={minPrice !== null && maxPrice !== null && minPrice > maxPrice ? "price-range-error" : undefined} style={{ width: "100%" }} />
+ {minPrice !== null && maxPrice !== null && minPrice > maxPrice && ( + + )}
diff --git a/src/index.css b/src/index.css index 35e738f..03c8b77 100644 --- a/src/index.css +++ b/src/index.css @@ -2681,4 +2681,24 @@ code, border-color: var(--method-delete-border); } +/* Price Filter Input Validation Styles */ +.filter-input { + padding: 8px 12px; + background: var(--page-bg); + border: 1px solid var(--line); + border-radius: 6px; + color: var(--text); + transition: border-color 0.2s, box-shadow 0.2s; +} + +.filter-input--invalid { + border-color: var(--danger) !important; +} + +.filter-input--invalid:focus-visible { + outline: 2px solid var(--danger) !important; + box-shadow: 0 0 0 3px rgba(255, 125, 141, 0.4) !important; +} + + diff --git a/src/pages/MarketplacePage.tsx b/src/pages/MarketplacePage.tsx index fdca792..75122cc 100644 --- a/src/pages/MarketplacePage.tsx +++ b/src/pages/MarketplacePage.tsx @@ -63,10 +63,13 @@ export default function MarketplacePage(): JSX.Element { items = items.filter((a) => selectedCategories.has(a.category ?? "")); } - if (minPrice !== null) - items = items.filter((a) => a.pricePerRequest >= minPrice); - if (maxPrice !== null) - items = items.filter((a) => a.pricePerRequest <= maxPrice); + const hasInvertedPrice = minPrice !== null && maxPrice !== null && minPrice > maxPrice; + if (!hasInvertedPrice) { + if (minPrice !== null) + items = items.filter((a) => a.pricePerRequest >= minPrice); + if (maxPrice !== null) + items = items.filter((a) => a.pricePerRequest <= maxPrice); + } // popularity-based ordering if (popularity === "mostUsed") {