diff --git a/src/ApiUsage.tsx b/src/ApiUsage.tsx index 820b6e7..f6495c9 100644 --- a/src/ApiUsage.tsx +++ b/src/ApiUsage.tsx @@ -145,6 +145,7 @@ 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 [statusFilter, setStatusFilter] = useState<'all' | 'success' | 'error'>('all'); const filteredCallHistory = statusFilter === 'all' ? callHistory : callHistory.filter(call => call.status === statusFilter); @@ -368,19 +369,36 @@ export default function ApiUsage() { {(apiResponse || isLoading) && ( -
+

Response

{isLoading ? ( -
Loading...
+
+
+ + +
+
+ + + + + +
+
) : (
diff --git a/src/App.tsx b/src/App.tsx index b3decd6..c37e669 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import ApiUsage from './ApiUsage'; import Dashboard from './components/Dashboard'; 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/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 94e48bb..49bdbe9 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); } * { @@ -2668,3 +2710,102 @@ 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; +} + +/* 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); +} + +/* 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/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} 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") {