diff --git a/contributors/junchao-mao.md b/contributors/junchao-mao.md new file mode 100644 index 0000000..74c440b --- /dev/null +++ b/contributors/junchao-mao.md @@ -0,0 +1,7 @@ +Name: Junchao Mao Username: @OHMFOHS + +Contributions: +*Create CSS to mimic https://blockexplorer.one/markets UI +*Add CoinMarket component to display real time market performance of Bitcoin and other currencies +*Use RapidAPI-Coinranking to fetch coin market information and supply + diff --git a/pages/api/coins.tsx b/pages/api/coins.tsx new file mode 100644 index 0000000..7ee6955 --- /dev/null +++ b/pages/api/coins.tsx @@ -0,0 +1,33 @@ +// pages/api/crypto.ts +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { limit, offset } = req.query; + + const API_KEY = process.env.API_KEY!; + const API_HOST = process.env.API_HOST!; + + const url = `https://${API_HOST}/coins?limit=${limit}&offset=${offset}`; + + try { + const response = await fetch(url, { + headers: { + 'X-RapidAPI-Key': API_KEY, + 'X-RapidAPI-Host': API_HOST, + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch data'); + } + + const data = await response.json(); + res.status(200).json(data); + } catch (error) { + res.status(500).json({ message: (error as Error).message }); + + } +} \ No newline at end of file diff --git a/pages/api/supply.tsx b/pages/api/supply.tsx new file mode 100644 index 0000000..3a3e121 --- /dev/null +++ b/pages/api/supply.tsx @@ -0,0 +1,33 @@ + +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { coinUuid } = req.query; + + const API_KEY = process.env.API_KEY!; + const API_HOST = process.env.API_HOST!; + + + const url = `https://${API_HOST}/coin/${coinUuid}/supply`; + + try { + const response = await fetch(url, { + headers: { + 'X-RapidAPI-Key': API_KEY, + 'X-RapidAPI-Host': API_HOST, + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch data'); + } + + const data = await response.json(); + res.status(200).json(data); + } catch (error) { + res.status(500).json({ message: (error as Error).message }); + } +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 0d8c0bd..a5fbe9c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -18,7 +18,7 @@ import CryptoMarketData from "../components/CryptoMarketData"; import App from '../components/transactions'; import BitcoinBlocks from '@/pages/LatestBlocks/BitcoinBlocks' import Link from "next/link"; - +import CoinMarket from "../components/CoinMarket" //Mainent Imports import MarketData from "../components/MarketData"; import TransactionDetails from "../components/TransactionDetails"; @@ -185,6 +185,12 @@ export default async function Home() { +
+
+ +
+ +
diff --git a/src/components/CoinMarket.css b/src/components/CoinMarket.css new file mode 100644 index 0000000..304b323 --- /dev/null +++ b/src/components/CoinMarket.css @@ -0,0 +1,117 @@ +.header { + color: #040504; + padding: 10px; + font-size: 24px; + text-align: left; + font-family: 'Roboto', sans-serif; + + font-weight: 700; + } + + + .table-container { + margin: 20px; + background-color: #ffffff; + box-shadow: 0 2px 4px 0 rgba(0,0,0,0.1); + border-radius: 8px; + overflow: hidden; + } + + .table { + width: 100%; + border-collapse: collapse; + } + + .table-header { + background-color: #f0f0f0; + padding: 25px; + } + + .table-header th { + background-color: #f0f0f0; + color: #040504; + padding: + 15px; + } + .table-row { + border-bottom: 1px solid #e0e0e0; + } + + + .table-row:last-child { + border-bottom: none; + } + + .table-row:hover { + background-color: #c1c1c1; + } + + .table-cell { + padding: 100px 20px; + text-align: left; + } + .button-container { + display: flex; + justify-content: center; + align-items: center; + height: 5vh; + } + + .button { + background-color: #d6d6d6; + color: rgb(0, 0, 0); + border: 20px; + height: 40px; + width: 40px; + border-radius: 50%; + cursor: pointer; + font-size: 16px; + display: flex; + justify-content: center; + align-items: center; + margin: 0 20px; + } + + + .button:hover { + background-color: #818281; + } + .rowbutton{ + background-color: #040504; + color: rgb(255, 255, 255); + border: none; + padding: 5px 15px; + text-align: center; + text-decoration: none; + display: inline-block; + margin: 4px 2px; + cursor: pointer; + border-radius: 20px; + font-size: 14px; + font-weight: bold; + } + + .pagination { + padding: 20px; + text-align: center; + } + + .disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .table-row td { + color: rgb(52, 51, 51); + text-align: center; + vertical-align: middle; + padding: 20px; + font-size: 14px; + font-weight: bold; + } + + + .table-row img { + display: block; + margin: auto; + } \ No newline at end of file diff --git a/src/components/CoinMarket.tsx b/src/components/CoinMarket.tsx new file mode 100644 index 0000000..86e3078 --- /dev/null +++ b/src/components/CoinMarket.tsx @@ -0,0 +1,172 @@ +'use client' +import React, { useState, useEffect } from 'react'; +import { GraphQLClient, gql } from 'graphql-request'; +import { CryptoCurrency } from '../types'; +import '../components/CoinMarket.css'; + + + +interface CryptoRowProps { + data: CryptoCurrency; +} + +const CryptoRow: React.FC = ({ data }) => { + const changeSymbol = Number(data.change) >= 0 ? '▲' : '▼'; + + const changeStyle = { + color: Number(data.change) >= 0 ? 'green' : 'red', + }; + + const formatNumber = (number: number | string) => { + return new Intl.NumberFormat('en-US', { + style: 'decimal', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(Number(number)); + }; + + + + return ( + + + {`${data.name} + + {data.name} + {formatNumber(parseFloat(data.marketCap))} USD + {formatNumber(data.price)} USD + {data.rank} + {formatNumber(data.circulatingSupply ?? '')} + {formatNumber(parseFloat(data.maxSupply ?? ''))} + + {changeSymbol} {data.change}% + + + + + + + ); +}; + + + +interface CryptoTableProps { + cryptos: CryptoCurrency[]; +} + +const CryptoTable: React.FC = ({ cryptos }) => { + return ( + + + + + + + + + + + + + + + + {cryptos.map((crypto) => ( + + ))} + +
IconNameMarket CapPricerankCirculating SupplyMax SupplyChange 24hDetails
+ ); +}; + + + + +const App: React.FC = () => { + const [cryptos, setCryptos] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(10); + + + const goToPage = (number: number) => { + setCurrentPage(number); + }; + + + useEffect(() => { + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +const fetchCryptos = async () => { + try { + const offset = (currentPage - 1) * itemsPerPage; + // const response = await fetch(`https://${API_HOST}/coins?limit=${itemsPerPage}&offset=${offset}`, { + // headers: { + // 'X-RapidAPI-Key': API_KEY, + // 'X-RapidAPI-Host': API_HOST, + // }, + // }); + const response = await fetch(`/api/coins?limit=${itemsPerPage}&offset=${offset}`); + const data = await response.json(); + if (data && data.data && Array.isArray(data.data.coins)) { + const coins: CryptoCurrency[] = data.data.coins; + + const coinsWithSupply = []; + for (const coin of coins) { + try { + // const supplyResponse = await fetch(`https://${API_HOST}/coin/${coin.uuid}/supply`, { + // headers: { + // 'X-RapidAPI-Key': API_KEY, + // 'X-RapidAPI-Host': API_HOST, + // }, + // }); + //const supplyResponse = await fetch(`https://${API_HOST}/coin/${coin.uuid}/supply`); + const supplyResponse = await fetch(`/api/supply?coinUuid=${coin.uuid}`); + + const supplyData = await supplyResponse.json(); + if (supplyData && supplyData.status === 'success') { + coinsWithSupply.push({ + ...coin, + circulatingSupply: supplyData.data.supply.circulatingAmount, + maxSupply: supplyData.data.supply.maxAmount, + }); + } else { + coinsWithSupply.push(coin); + } + await delay(50); + } catch (error) { + console.error('Fetching supply data failed', error); + coinsWithSupply.push(coin); + } + } + setCryptos(coinsWithSupply); + } + } catch (error) { + console.error('Fetching cryptos failed', error); + } +}; +fetchCryptos(); +}, [currentPage, itemsPerPage]); + +const nextPage = () => setCurrentPage((prev) => prev + 1); +const prevPage = () => setCurrentPage((prev) => Math.max(1, prev - 1)); + +return ( +
+
Top Market Data
+
+ +
+
+ + Page {currentPage} + +
+
+); +}; +export default App; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 8603304..7cbe94e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,3 +60,17 @@ export interface ApiResponse { cryptoapis: CryptoapisResponse; }; } + + +export interface CryptoCurrency { + id: number; + name: string; + iconUrl: string; + marketCap: string; + price: string; + rank: string; + uuid: string; + circulatingSupply?: string; + maxSupply?: string; + change: string; +} \ No newline at end of file