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} |
+ {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 (
+
+
+
+ | Icon |
+ Name |
+ Market Cap |
+ Price |
+ rank |
+ Circulating Supply |
+ Max Supply |
+ Change 24h |
+ Details |
+
+
+
+ {cryptos.map((crypto) => (
+
+ ))}
+
+
+ );
+};
+
+
+
+
+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 (
+
+
+
+
+
+
+
+ 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