├── .gitignore ├── .nvmrc ├── components.json ├── components ├── alert.tsx ├── api-endpoint.tsx ├── log-viewer.tsx ├── method-label.tsx ├── pages │ ├── changelog-page.tsx │ └── home-page.tsx ├── ui │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── data-table.tsx │ ├── input.tsx │ ├── label.tsx │ ├── select.tsx │ ├── skeleton.tsx │ ├── table.tsx │ ├── tabs.tsx │ └── textarea.tsx └── websocket-playground.tsx ├── constants.ts ├── global.css ├── lib └── utils.ts ├── next-env.d.ts ├── next-sitemap.config.cjs ├── next.config.ts ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── _meta.js ├── changelog.mdx ├── faq.mdx ├── glossary.mdx ├── guides │ ├── _meta.js │ ├── client-order-id.mdx │ └── fireblocks.mdx ├── index.mdx ├── platform │ ├── _meta.js │ ├── colocation.mdx │ ├── oauth │ │ ├── _meta.js │ │ ├── overview.mdx │ │ └── usage.mdx │ ├── overview.mdx │ ├── self-trade-prevention.mdx │ └── webhook.mdx ├── private │ ├── _meta.js │ ├── http-auth.mdx │ ├── http-main-v4.mdx │ ├── http-trade-v1.mdx │ ├── http-trade-v4.mdx │ └── websocket.mdx ├── public │ ├── _meta.js │ ├── http-v1.mdx │ ├── http-v2.mdx │ ├── http-v4.mdx │ └── websocket.mdx └── sdks.mdx ├── postcss.config.mjs ├── public ├── .nojekyll ├── _redirects ├── data │ └── changelog.json ├── fonts │ ├── Inter-Bold.ttf │ └── Inter-Regular.ttf ├── img │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── fireblocks │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── logo.svg │ ├── og-image.png │ └── pattern.png ├── manifest.json ├── og-images │ ├── changelog.png │ ├── faq.png │ ├── glossary.png │ ├── guides │ │ ├── client-order-id.png │ │ └── fireblocks.png │ ├── home.png │ ├── platform │ │ ├── colocation.png │ │ ├── oauth.png │ │ ├── oauth │ │ │ └── usage.png │ │ ├── overview.png │ │ ├── self-trade-prevention.png │ │ └── webhook.png │ ├── private │ │ ├── http-auth.png │ │ ├── http-main-v4.png │ │ ├── http-trade-v1.png │ │ ├── http-trade-v4.png │ │ └── websocket.png │ ├── public │ │ ├── http-v1.png │ │ ├── http-v2.png │ │ ├── http-v4.png │ │ └── websocket.png │ └── sdks.png ├── robots.txt ├── sitemap-0.xml └── sitemap.xml ├── scripts └── generate-og-images.tsx ├── seo.config.ts ├── tailwind.config.ts ├── theme.config.tsx ├── tsconfig.json └── types └── logs.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Production 2 | /build 3 | .idea 4 | 5 | # Generated files 6 | .docusaurus 7 | .cache-loader 8 | 9 | # Misc 10 | .DS_Store 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | /.next/ 20 | /out/ 21 | node_modules 22 | .cursorrules 23 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "global.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /components/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | export interface AlertProps { 5 | /** 6 | * Optional: Header/title for the alert 7 | */ 8 | header?: React.ReactNode; 9 | /** 10 | * The message or content to display inside the alert. 11 | */ 12 | message: React.ReactNode; 13 | /** 14 | * Optional: Alert type for styling (info, success, warning, error) 15 | */ 16 | type?: "info" | "success" | "warning" | "error"; 17 | /** 18 | * Optional: Custom className for the alert container 19 | */ 20 | className?: string; 21 | /** 22 | * Optional: Show alert only after this ISO date/time (in user's local time) 23 | */ 24 | showAfter?: string; // ISO string, e.g. "2025-06-02T12:00:00Z" 25 | } 26 | 27 | const typeStyles: Record = { 28 | info: "bg-blue-50 text-blue-900 border-blue-200", 29 | success: "bg-green-50 text-green-900 border-green-200", 30 | warning: "bg-yellow-50 text-yellow-900 border-yellow-200", 31 | error: "bg-red-50 text-red-900 border-red-200", 32 | }; 33 | 34 | const typeEmojis: Record = { 35 | info: "ℹ️", 36 | success: "✅", 37 | warning: "⚠️", 38 | error: "❌", 39 | }; 40 | 41 | export const Alert: React.FC = ({ 42 | header, 43 | message, 44 | type = "info", 45 | className, 46 | showAfter, 47 | }) => { 48 | // If showAfter is set, only show the alert after this time (in UTC) 49 | if (showAfter) { 50 | const now = new Date(); 51 | const showAt = new Date(showAfter); 52 | // Compare using UTC time 53 | if (now.getTime() < showAt.getTime()) return null; 54 | } 55 | return ( 56 |
64 | 70 |
71 | {header &&
{header}
} 72 |
{message}
73 |
74 |
75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /components/api-endpoint.tsx: -------------------------------------------------------------------------------- 1 | import { MethodLabel } from "@/components/method-label"; 2 | import { CopyToClipboard } from "nextra/components"; 3 | import { cn } from "@/lib/utils"; 4 | 5 | interface ApiEndpointProps { 6 | method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; 7 | path: string; 8 | } 9 | 10 | const methodBackgrounds: Record = { 11 | GET: "bg-green-50/50 dark:bg-green-900/20", 12 | POST: "bg-blue-50/50 dark:bg-blue-900/20", 13 | PUT: "bg-yellow-50/50 dark:bg-yellow-900/20", 14 | DELETE: "bg-red-50/50 dark:bg-red-900/20", 15 | PATCH: "bg-purple-50/50 dark:bg-purple-900/20", 16 | }; 17 | 18 | export function ApiEndpoint({ method, path }: ApiEndpointProps) { 19 | // Split the path into base path and query parameters 20 | const [basePath, queryString] = path.split('?'); 21 | 22 | // Parse dynamic path parameters 23 | const renderPath = (pathPart: string) => { 24 | const methodTextColors: Record = { 25 | GET: "text-green-700 dark:text-green-400", 26 | POST: "text-blue-700 dark:text-blue-400", 27 | PUT: "text-yellow-700 dark:text-yellow-400", 28 | DELETE: "text-red-700 dark:text-red-400", 29 | PATCH: "text-purple-700 dark:text-purple-400", 30 | }; 31 | 32 | return pathPart.split(/({[^}]+})/).map((part, index) => { 33 | if (part.match(/^{[^}]+}$/)) { 34 | return ( 35 | 36 | {part} 37 | 38 | ); 39 | } 40 | return {part}; 41 | }); 42 | }; 43 | 44 | // Parse query parameters with formatting 45 | const renderQueryParams = (queryStr: string) => { 46 | if (!queryStr) return null; 47 | 48 | // Split by & but keep the & symbol for display 49 | return queryStr.split('&').map((param, index) => { 50 | const [key, value] = param.split('=').map((part) => part.trim()); 51 | 52 | return ( 53 | 54 | {index > 0 && &} 55 | 56 | {key} 57 | {value && ( 58 | <> 59 | {'='}{value} 60 | 61 | )} 62 | 63 | 64 | ); 65 | }); 66 | }; 67 | 68 | return ( 69 |
73 |
74 | 75 | 76 | {renderPath(basePath)} 77 | {queryString && ( 78 | <> 79 | ? 80 | {renderQueryParams(queryString)} 81 | 82 | )} 83 | 84 |
85 | path} 87 | className="absolute sm:relative right-3 top-3 sm:right-auto sm:top-auto sm:self-start" 88 | /> 89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /components/log-viewer.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { ApiLog, TradePayload } from "@/types/logs"; 3 | import { ColumnDef } from "@tanstack/react-table"; 4 | import { DataTable } from "@/components/ui/data-table"; 5 | import { useQuery } from "@tanstack/react-query"; 6 | import { Loader2 } from "lucide-react"; 7 | 8 | const ENDPOINTS = [ 9 | "/api/v4/order/collateral/limit", 10 | "/api/v4/order/collateral/market", 11 | "/api/v4/order/collateral/stop-limit", 12 | "/api/v4/order/collateral/trigger-market", 13 | "/api/v4/order/modify", 14 | "/api/v4/order/new", 15 | "/api/v4/order/market", 16 | "/api/v4/order/stop_limit", 17 | "/api/v4/order/stop_market", 18 | "/api/v4/order/cancel", 19 | ]; 20 | 21 | interface TickerData { 22 | [key: string]: { 23 | quote_volume: string; 24 | [key: string]: any; 25 | }; 26 | } 27 | 28 | async function fetchTopMarkets(): Promise< 29 | { market: string; last_price?: string }[] 30 | > { 31 | try { 32 | const response = await fetch("https://whitebit.com/api/v4/public/ticker"); 33 | const data: TickerData = await response.json(); 34 | // Sort and slice after collecting all markets 35 | return Object.entries(data) 36 | .reduce((acc, [market, marketData]) => { 37 | if (market.endsWith("_USDT") || market.endsWith("_PERP")) { 38 | acc.push({ 39 | market, 40 | volume: parseFloat(marketData.quote_volume), 41 | last_price: marketData.last_price, 42 | }); 43 | } 44 | 45 | return acc; 46 | }, [] as { market: string; last_price?: string; volume: number }[]) 47 | .sort((a, b) => b.volume - a.volume) 48 | .slice(0, 20) 49 | .map((item) => ({ market: item.market, last_price: item.last_price })); 50 | } catch (error) { 51 | console.error("Failed to fetch markets:", error); 52 | return [ 53 | { market: "BTC_USDT" }, 54 | { market: "BTC_PERP" }, 55 | { market: "WBT_USDT" }, 56 | { market: "ETH_USDT" }, 57 | { market: "ETH_PERP" }, 58 | ]; // Updated fallback to only include USDT and PERP pairs 59 | } 60 | } 61 | 62 | function generatePayload( 63 | endpoint: string, 64 | markets: { market: string; last_price?: string }[] 65 | ): TradePayload { 66 | const marketData = markets[Math.floor(Math.random() * markets.length)]; 67 | const side = Math.random() > 0.5 ? "buy" : "sell"; 68 | const basePrice = parseFloat(marketData.last_price || "30000"); 69 | const priceVariation = basePrice * 0.01; // 1% variation 70 | const randomPrice = basePrice + (Math.random() * 2 - 1) * priceVariation; 71 | 72 | const basePayload = { 73 | market: marketData.market, 74 | side, 75 | } as const; 76 | 77 | // Dynamic amount generation based on market and order type 78 | const getAmount = () => { 79 | const baseValue = Math.random() * 100000 + 5000; // Between 5k and 105k USDT 80 | const isLargeOrder = Math.random() < 0.1; // 10% chance of large order 81 | const multiplier = isLargeOrder ? Math.random() * 10 + 5 : 1; // 5x-15x for large orders 82 | return ((baseValue * multiplier) / basePrice).toFixed(6); 83 | }; 84 | 85 | switch (endpoint) { 86 | case "/api/v4/order/collateral/limit": 87 | case "/api/v4/order/new": 88 | return { 89 | ...basePayload, 90 | amount: getAmount(), 91 | price: randomPrice.toFixed(2), 92 | timeInForce: Math.random() > 0.3 ? "GTC" : "IOC", // Mix of GTC and IOC orders 93 | }; 94 | case "/api/v4/order/collateral/market": 95 | case "/api/v4/order/market": 96 | return { 97 | ...basePayload, 98 | amount: getAmount(), 99 | timeInForce: "IOC", 100 | }; 101 | case "/api/v4/order/collateral/stop-limit": 102 | case "/api/v4/order/stop_limit": 103 | return { 104 | ...basePayload, 105 | amount: getAmount(), 106 | price: randomPrice.toFixed(2), 107 | stopPrice: ( 108 | randomPrice * 109 | (1 + (side === "buy" ? 0.002 : -0.002)) 110 | ).toFixed(2), // 0.2% stop distance 111 | type: "stop_limit", 112 | }; 113 | case "/api/v4/order/collateral/trigger-market": 114 | case "/api/v4/order/stop_market": 115 | return { 116 | ...basePayload, 117 | amount: getAmount(), 118 | activation_price: ( 119 | randomPrice * 120 | (1 + (side === "buy" ? 0.002 : -0.002)) 121 | ).toFixed(2), 122 | type: "stop", 123 | }; 124 | case "/api/v4/order/modify": 125 | return { 126 | ...basePayload, 127 | amount: getAmount(), 128 | price: randomPrice.toFixed(2), 129 | timeInForce: "GTC", 130 | }; 131 | case "/api/v4/order/cancel": 132 | return { 133 | market: marketData.market, 134 | orderId: Math.floor(Math.random() * 1000000000).toString(), 135 | side, 136 | }; 137 | default: 138 | return basePayload; 139 | } 140 | } 141 | 142 | function formatTime(date: string): string { 143 | const dateObj = new Date(date); 144 | const hours = dateObj.getHours().toString().padStart(2, "0"); 145 | const minutes = dateObj.getMinutes().toString().padStart(2, "0"); 146 | const seconds = dateObj.getSeconds().toString().padStart(2, "0"); 147 | const ms = dateObj.getMilliseconds().toString().padStart(3, "0"); 148 | 149 | return `${hours}:${minutes}:${seconds}.${ms}`; 150 | } 151 | 152 | function generateBatch( 153 | size: number, 154 | markets: { market: string; last_price?: string }[] 155 | ): ApiLog[] { 156 | const baseTime = new Date(); 157 | 158 | return Array.from({ length: size }, () => { 159 | const endpoint = ENDPOINTS[Math.floor(Math.random() * ENDPOINTS.length)]; 160 | const randomOffset = Math.random() * 100; 161 | const timestamp = new Date(baseTime.getTime() - randomOffset); 162 | 163 | // Add weighted random status code generation 164 | const random = Math.random(); 165 | const statusCode = 166 | random < 0.97 167 | ? 200 // 97% success 168 | : random < 0.99 169 | ? 400 // 2% bad request 170 | : random < 0.995 171 | ? 429 // 0.5% rate limit 172 | : 401; // 0.5% unauthorized 173 | 174 | return { 175 | id: timestamp.toString(), 176 | timestamp: timestamp.toISOString(), 177 | path: endpoint, 178 | payload: generatePayload(endpoint, markets), 179 | statusCode, 180 | }; 181 | }).sort( 182 | (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() 183 | ); 184 | } 185 | 186 | export default function LogViewer({ className }: { className?: string }) { 187 | const [logs, setLogs] = useState([]); 188 | 189 | const { data: markets = [], isLoading } = useQuery({ 190 | queryKey: ["markets"], 191 | queryFn: fetchTopMarkets, 192 | }); 193 | 194 | useEffect(() => { 195 | if (markets.length === 0) return; 196 | 197 | const interval = setInterval(() => { 198 | const batchSize = Math.floor(Math.random() * 8) + 6; // 6-14 orders per batch 199 | 200 | setLogs((prevLogs) => { 201 | const newBatch = generateBatch(batchSize, markets); 202 | return [...newBatch, ...prevLogs].slice(0, 1000); 203 | }); 204 | }, 128); 205 | 206 | return () => { 207 | clearInterval(interval); 208 | }; 209 | }, [markets, logs]); 210 | 211 | if (isLoading) { 212 | return ( 213 |
214 | 215 |
216 | ); 217 | } 218 | 219 | const columns: ColumnDef[] = [ 220 | { 221 | accessorKey: "timestamp", 222 | header: "Time", 223 | cell: ({ row }) => {formatTime(row.getValue("timestamp"))}, 224 | minSize: 100, 225 | size: 100, 226 | maxSize: 100, 227 | }, 228 | { 229 | accessorKey: "statusCode", 230 | header: "Code", 231 | cell: ({ row }) => { 232 | const status = row.getValue("statusCode") as number; 233 | return ( 234 | 245 | {status} 246 | 247 | ); 248 | }, 249 | minSize: 30, 250 | size: 30, 251 | maxSize: 30, 252 | }, 253 | { 254 | accessorKey: "path", 255 | header: "Endpoint", 256 | cell: ({ row }) => ( 257 |
258 | 259 | {row.original.path} 260 | 261 |
262 | ), 263 | minSize: 360, 264 | size: 360, 265 | maxSize: 360, 266 | }, 267 | { 268 | accessorKey: "payload", 269 | header: "Payload", 270 | cell: ({ row }) => { 271 | const payload = row.original.payload; 272 | return ( 273 |
274 | 281 | {payload.side.toUpperCase()} 282 | 283 | {JSON.stringify(payload)} 284 |
285 | ); 286 | }, 287 | minSize: 520, 288 | size: 520, 289 | maxSize: 520, 290 | }, 291 | ]; 292 | 293 | return ( 294 | 300 | ); 301 | } 302 | -------------------------------------------------------------------------------- /components/method-label.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; 4 | 5 | interface MethodLabelProps { 6 | method: Method; 7 | className?: string; 8 | } 9 | 10 | const methodColors: Record = { 11 | GET: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", 12 | POST: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", 13 | PUT: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400", 14 | DELETE: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", 15 | PATCH: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400", 16 | }; 17 | 18 | export function MethodLabel({ method, className }: MethodLabelProps) { 19 | return ( 20 | 27 | {method} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /components/pages/changelog-page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link from "next/link"; 4 | import { toast } from "sonner"; 5 | import { useQuery } from "@tanstack/react-query"; 6 | import { 7 | AlertCircle, 8 | CheckCircle2, 9 | Clock, 10 | Info, 11 | AlertTriangle, 12 | ExternalLink, 13 | Link as LinkIcon, 14 | CreditCard, 15 | } from "lucide-react"; 16 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 17 | import { Badge } from "@/components/ui/badge"; 18 | import { Skeleton } from "@/components/ui/skeleton"; 19 | 20 | interface ChangelogLink { 21 | title: string; 22 | url: string; 23 | } 24 | 25 | interface ChangelogChange { 26 | title: string; 27 | description: string; 28 | type: string; 29 | links?: ChangelogLink[]; 30 | } 31 | 32 | interface ChangelogItem { 33 | title: string; 34 | timeframe: string; 35 | changes: ChangelogChange[]; 36 | } 37 | 38 | interface RawChangelogData { 39 | changes: ChangelogItem[]; 40 | } 41 | 42 | interface ProcessedChangelogData { 43 | upcomingChanges: ChangelogItem[]; 44 | previousChanges: ChangelogItem[]; 45 | } 46 | 47 | const renderChangeTypeIcon = (type: string) => { 48 | switch (type) { 49 | case "feature": 50 | return ; 51 | case "improvement": 52 | return ; 53 | case "bugfix": 54 | return ; 55 | case "deprecation": 56 | return ; 57 | case "security": 58 | return ; 59 | case "planned": 60 | return ; 61 | case "roadmap": 62 | return ; 63 | case "fiat": 64 | return ; 65 | default: 66 | return ; 67 | } 68 | }; 69 | 70 | const renderChangeLinks = (links?: ChangelogLink[]) => { 71 | if (!links?.length) return null; 72 | 73 | return ( 74 |
75 | {links.map((link, index) => ( 76 | 81 | 82 | {link.title} 83 | 84 | ))} 85 |
86 | ); 87 | }; 88 | 89 | const createSlug = (text: string) => { 90 | return text 91 | .toLowerCase() 92 | .replace(/[^a-z0-9]+/g, "-") 93 | .replace(/(^-|-$)/g, ""); 94 | }; 95 | 96 | const copyLinkToClipboard = (slug: string) => { 97 | const url = `${window.location.origin}${window.location.pathname}#${slug}`; 98 | navigator.clipboard.writeText(url); 99 | toast.success("Link copied to clipboard"); 100 | }; 101 | 102 | async function fetchChangelogData(): Promise { 103 | const response = await fetch("/data/changelog.json"); 104 | if (!response.ok) { 105 | throw new Error(`HTTP error! status: ${response.status}`); 106 | } 107 | return response.json(); 108 | } 109 | 110 | function ChangelogCardSkeleton() { 111 | return ( 112 | 113 | 114 |
115 |
116 |
117 | 118 | 119 |
120 | 121 |
122 |
123 |
124 |
125 | ); 126 | } 127 | 128 | function ChangelogLoadingSkeleton() { 129 | return ( 130 | <> 131 |
132 |
133 | 134 | 135 |
136 | 137 | 138 |
139 |
140 |
141 | 142 | 143 |
144 | 145 | 146 |
147 | 148 | ); 149 | } 150 | 151 | export default function ChangelogPage() { 152 | const { data, isLoading, isError, error } = useQuery({ 153 | queryKey: ['changelogData'], 154 | queryFn: fetchChangelogData, 155 | select: (rawData) => { 156 | const now = new Date(); 157 | now.setHours(0, 0, 0, 0); 158 | 159 | const upcoming: ChangelogItem[] = []; 160 | const previous: ChangelogItem[] = []; 161 | 162 | rawData.changes.forEach((item) => { 163 | try { 164 | const itemDate = new Date(item.timeframe); 165 | itemDate.setHours(0, 0, 0, 0); 166 | 167 | if (itemDate >= now) { 168 | upcoming.push(item); 169 | } else { 170 | previous.push(item); 171 | } 172 | } catch (e) { 173 | console.error(`Invalid date format for item: ${item.title}`, e); 174 | } 175 | }); 176 | 177 | upcoming.sort( 178 | (a, b) => new Date(a.timeframe).getTime() - new Date(b.timeframe).getTime() 179 | ); 180 | 181 | previous.sort( 182 | (a, b) => new Date(b.timeframe).getTime() - new Date(a.timeframe).getTime() 183 | ); 184 | 185 | return { 186 | upcomingChanges: upcoming, 187 | previousChanges: previous 188 | } 189 | }, 190 | staleTime: 1000 * 60 * 5, 191 | gcTime: 1000 * 60 * 15, 192 | }); 193 | 194 | function renderContent() { 195 | if (isLoading) { 196 | return ; 197 | } 198 | 199 | if (isError) { 200 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; 201 | return ( 202 |
203 | Error loading changelog: {errorMessage} 204 |
205 | ); 206 | } 207 | 208 | if (data) { 209 | const { upcomingChanges, previousChanges } = data; 210 | const hasUpcoming = upcomingChanges.length > 0; 211 | const hasPrevious = previousChanges.length > 0; 212 | 213 | if (!hasUpcoming && !hasPrevious) { 214 | return ( 215 |
216 | No changelog entries found. 217 |
218 | ); 219 | } 220 | 221 | return ( 222 | <> 223 | {hasUpcoming && ( 224 |
225 |
226 |

227 | Upcoming Changes 228 |

229 |
230 |
231 | {upcomingChanges.map((item, index) => { 232 | const slug = createSlug(item.title); 233 | return ( 234 | 235 | 236 |
237 |
238 |
239 | 240 | {item.title} 241 | 242 |

243 | {new Date(item.timeframe).toLocaleDateString( 244 | undefined, 245 | { year: "numeric", month: "long", day: "numeric" } 246 | )} 247 |

248 |
249 | 256 |
257 |
258 |
259 | 260 |
261 | {item.changes.map((change, changeIndex) => ( 262 |
263 |
264 | {renderChangeTypeIcon(change.type)} 265 |
266 |
267 |

268 | {change.title} 269 |

270 |

271 | {change.description} 272 |

273 | {renderChangeLinks(change.links)} 274 |
275 |
276 | ))} 277 |
278 |
279 |
280 | ); 281 | })} 282 |
283 | )} 284 | 285 | {hasPrevious && ( 286 |
287 |
288 |

Previous Changes

289 |
290 |
291 | {previousChanges.map((item, index) => { 292 | const slug = createSlug(item.title); 293 | return ( 294 | 295 | 296 |
297 |
298 |
299 | 300 | {item.title} 301 | 302 |

303 | {new Date(item.timeframe).toLocaleDateString( 304 | undefined, 305 | { year: "numeric", month: "long", day: "numeric" } 306 | )} 307 |

308 |
309 | 316 |
317 |
318 |
319 | 320 |
321 | {item.changes.map((change, changeIndex) => ( 322 |
323 |
324 | {renderChangeTypeIcon(change.type)} 325 |
326 |
327 |
328 | {change.title} 329 |
330 |

331 | {change.description} 332 |

333 | {renderChangeLinks(change.links)} 334 |
335 |
336 | ))} 337 |
338 |
339 |
340 | ); 341 | })} 342 |
343 | )} 344 | 345 | ); 346 | } 347 | 348 | return ( 349 |
350 | No changelog data available. 351 |
352 | ); 353 | } 354 | 355 | return ( 356 |
357 |
358 |

359 | WhiteBIT API Changelog 360 |

361 |

362 | Track all updates, improvements, and fixes to the WhiteBIT API 363 | Platform. This page documents both upcoming and previous changes to 364 | help you stay informed about our platform's evolution. 365 |

366 |
367 |
368 | {renderContent()} 369 |
370 |
371 | ); 372 | } 373 | -------------------------------------------------------------------------------- /components/pages/home-page.tsx: -------------------------------------------------------------------------------- 1 | import LogViewer from "@/components/log-viewer"; 2 | import { 3 | ArrowRight, 4 | Zap, 5 | Shield, 6 | Globe, 7 | BarChart3, 8 | Code, 9 | Layers, 10 | Sparkles, 11 | Clock, 12 | Headphones, 13 | } from "lucide-react"; 14 | import { Button } from "@/components/ui/button"; 15 | import { motion } from "framer-motion"; 16 | import Link from "next/link"; 17 | 18 | export default function HomePage() { 19 | return ( 20 |
21 | {/* Hero Section */} 22 |
23 |
24 |
25 |
26 | 32 |

33 | WhiteBIT API Platform 34 |

35 |

36 | Access the global cryptocurrency market with WhiteBIT's 37 | comprehensive trading APIs. Build powerful trading applications 38 | with our developer-friendly tools. 39 |

40 |
41 |
42 | 43 | Low Latency 44 |
45 |
46 | 47 | Global Access 48 |
49 |
50 | 51 | 24/7 Support 52 |
53 |
54 |
55 | 56 | 60 | 61 |
62 |
63 | 64 | 70 |
71 |
72 |
73 |
74 |

75 | API Activity 76 |

77 |
78 |
79 | Live Demo 80 |
81 |
82 |

83 | Watch our high-performance API handle thousands of trading 84 | operations in real-time. From market orders to advanced 85 | trading strategies, see the full spectrum of what's 86 | possible. 87 |

88 |
89 |
90 |
91 |
92 | 93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | {/* Features Grid */} 101 |
102 |
103 | 110 |

Everything You Need

111 |

112 | Build powerful trading applications with our comprehensive suite 113 | of APIs and tools 114 |

115 |
116 |
117 | {[ 118 | { 119 | icon: Zap, 120 | title: "Advanced Trading", 121 | description: 122 | "Full access to spot trading, margin trading, and futures markets with high-performance execution.", 123 | }, 124 | { 125 | icon: BarChart3, 126 | title: "Real-time Data", 127 | description: 128 | "Live market data, order book updates, and trade execution via WebSocket with millisecond latency.", 129 | }, 130 | { 131 | icon: Shield, 132 | title: "Enterprise Grade", 133 | description: 134 | "High-performance infrastructure with exceptional uptime and industry-leading security measures.", 135 | }, 136 | { 137 | icon: Globe, 138 | title: "Global Compliance", 139 | description: 140 | "Built-in compliance tools and security features for safe trading across jurisdictions.", 141 | }, 142 | { 143 | icon: Code, 144 | title: "Developer Tools", 145 | description: 146 | "Comprehensive SDKs, detailed documentation, and code examples to accelerate your development process.", 147 | }, 148 | { 149 | icon: Layers, 150 | title: "Market Coverage", 151 | description: 152 | "Access to a wide range of spot and derivative markets with support for multiple trading pairs and instruments.", 153 | }, 154 | ].map((feature, index) => ( 155 | 163 |
164 | 165 |
166 |

{feature.title}

167 |

{feature.description}

168 |
169 | ))} 170 |
171 |
172 |
173 | 174 | {/* CTA Section */} 175 |
176 |
177 | 184 |

Ready to Get Started?

185 |

186 | Join thousands of developers building with WhiteBIT's API platform 187 |

188 |
189 | 194 | 198 | 199 | 200 | 203 | 204 |
205 |
206 |
207 |
208 |
209 | ); 210 | } 211 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground", 13 | secondary: 14 | "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", 15 | destructive: 16 | "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /components/ui/data-table.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | ColumnDef, 5 | Row, 6 | flexRender, 7 | getCoreRowModel, 8 | useReactTable, 9 | } from "@tanstack/react-table"; 10 | 11 | import { TableCell, TableHead, TableRow } from "@/components/ui/table"; 12 | import { HTMLAttributes, forwardRef, useState } from "react"; 13 | import { TableVirtuoso } from "react-virtuoso"; 14 | import { cn } from "@/lib/utils"; 15 | 16 | // Original Table is wrapped with a
(see https://ui.shadcn.com/docs/components/table#radix-:r24:-content-manual), 17 | // but here we don't want it, so let's use a new component with only tag 18 | const TableComponent = forwardRef< 19 | HTMLTableElement, 20 | React.HTMLAttributes 21 | >(({ className, ...props }, ref) => ( 22 |
27 | )); 28 | TableComponent.displayName = "TableComponent"; 29 | 30 | const TableRowComponent = (rows: Row[]) => 31 | function getTableRow(props: HTMLAttributes) { 32 | const index = props["data-index"]; 33 | const row = rows[index]; 34 | 35 | if (!row) return null; 36 | 37 | return ( 38 | 43 | {row.getVisibleCells().map((cell) => ( 44 | 45 | {flexRender(cell.column.columnDef.cell, cell.getContext())} 46 | 47 | ))} 48 | 49 | ); 50 | }; 51 | 52 | interface DataTableProps { 53 | columns: ColumnDef[]; 54 | data: TData[]; 55 | height: string; 56 | className?: string; 57 | } 58 | 59 | export function DataTable({ 60 | columns, 61 | data, 62 | height, 63 | className, 64 | }: DataTableProps) { 65 | const table = useReactTable({ 66 | data, 67 | columns, 68 | getCoreRowModel: getCoreRowModel(), 69 | }); 70 | 71 | const { rows } = table.getRowModel(); 72 | 73 | return ( 74 |
75 | 83 | table.getHeaderGroups().map((headerGroup) => ( 84 | // Change header background color to non-transparent 85 | 86 | {headerGroup.headers.map((header) => { 87 | return ( 88 | 95 | {header.isPlaceholder ? null : ( 96 |
108 | {flexRender( 109 | header.column.columnDef.header, 110 | header.getContext() 111 | )} 112 |
113 | )} 114 |
115 | ); 116 | })} 117 |
118 | )) 119 | } 120 | /> 121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectContent = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, children, position = "popper", ...props }, ref) => ( 39 | 40 | 51 | 58 | {children} 59 | 60 | 61 | 62 | )) 63 | SelectContent.displayName = SelectPrimitive.Content.displayName 64 | 65 | const SelectLabel = React.forwardRef< 66 | React.ElementRef, 67 | React.ComponentPropsWithoutRef 68 | >(({ className, ...props }, ref) => ( 69 | 74 | )) 75 | SelectLabel.displayName = SelectPrimitive.Label.displayName 76 | 77 | const SelectItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef 80 | >(({ className, children, ...props }, ref) => ( 81 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | {children} 96 | 97 | )) 98 | SelectItem.displayName = SelectPrimitive.Item.displayName 99 | 100 | const SelectSeparator = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 109 | )) 110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 111 | 112 | export { 113 | Select, 114 | SelectGroup, 115 | SelectValue, 116 | SelectTrigger, 117 | SelectContent, 118 | SelectLabel, 119 | SelectItem, 120 | SelectSeparator, 121 | } 122 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 |
15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 67 | )) 68 | TableRow.displayName = "TableRow" 69 | 70 | const TableHead = React.forwardRef< 71 | HTMLTableCellElement, 72 | React.ThHTMLAttributes 73 | >(({ className, ...props }, ref) => ( 74 |
[role=checkbox]]:translate-y-[2px]", 78 | className 79 | )} 80 | {...props} 81 | /> 82 | )) 83 | TableHead.displayName = "TableHead" 84 | 85 | const TableCell = React.forwardRef< 86 | HTMLTableCellElement, 87 | React.TdHTMLAttributes 88 | >(({ className, ...props }, ref) => ( 89 | [role=checkbox]]:translate-y-[2px]", 93 | className 94 | )} 95 | {...props} 96 | /> 97 | )) 98 | TableCell.displayName = "TableCell" 99 | 100 | const TableCaption = React.forwardRef< 101 | HTMLTableCaptionElement, 102 | React.HTMLAttributes 103 | >(({ className, ...props }, ref) => ( 104 |
109 | )) 110 | TableCaption.displayName = "TableCaption" 111 | 112 | export { 113 | Table, 114 | TableHeader, 115 | TableBody, 116 | TableFooter, 117 | TableHead, 118 | TableRow, 119 | TableCell, 120 | TableCaption, 121 | } 122 | -------------------------------------------------------------------------------- /components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as TabsPrimitive from "@radix-ui/react-tabs" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Tabs = TabsPrimitive.Root 7 | 8 | const TabsList = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | TabsList.displayName = TabsPrimitive.List.displayName 22 | 23 | const TabsTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 35 | )) 36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName 37 | 38 | const TabsContent = React.forwardRef< 39 | React.ElementRef, 40 | React.ComponentPropsWithoutRef 41 | >(({ className, ...props }, ref) => ( 42 | 50 | )) 51 | TabsContent.displayName = TabsPrimitive.Content.displayName 52 | 53 | export { Tabs, TabsList, TabsTrigger, TabsContent } 54 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |