Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions contributors/junchao-mao.md
Original file line number Diff line number Diff line change
@@ -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

33 changes: 33 additions & 0 deletions pages/api/coins.tsx
Original file line number Diff line number Diff line change
@@ -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 });

}
}
33 changes: 33 additions & 0 deletions pages/api/supply.tsx
Original file line number Diff line number Diff line change
@@ -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 });
}
}
8 changes: 7 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -185,6 +185,12 @@ export default async function Home() {
<Ethereum />
</div>

<div><App/></div>
<div className="main-content">
<CoinMarket />
</div>


<div>
<Ethereum />
</div>
Expand Down
117 changes: 117 additions & 0 deletions src/components/CoinMarket.css
Original file line number Diff line number Diff line change
@@ -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;
}
172 changes: 172 additions & 0 deletions src/components/CoinMarket.tsx
Original file line number Diff line number Diff line change
@@ -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<CryptoRowProps> = ({ 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 (
<tr className="table-row">
<td>
<img
src={data.iconUrl}
alt={`${data.name} icon`}
style={{ width: '24px', height: '24px' }} // Adjust the size as needed
/>
</td>
<td>{data.name}</td>
<td>{formatNumber(parseFloat(data.marketCap))} USD</td>
<td>{formatNumber(data.price)} USD</td>
<td>{data.rank}</td>
<td>{formatNumber(data.circulatingSupply ?? '')}</td>
<td>{formatNumber(parseFloat(data.maxSupply ?? ''))}</td>
<td style={changeStyle}>
<span style={changeStyle}>{changeSymbol}</span> {data.change}%
</td>
<td>
<button className = "rowbutton">View</button>
<button className = "rowbutton">Explore</button>
</td>
</tr>
);
};



interface CryptoTableProps {
cryptos: CryptoCurrency[];
}

const CryptoTable: React.FC<CryptoTableProps> = ({ cryptos }) => {
return (
<table className="table">
<thead>
<tr className="table-header">
<th>Icon</th>
<th>Name</th>
<th>Market Cap</th>
<th>Price</th>
<th>rank</th>
<th>Circulating Supply</th>
<th>Max Supply</th>
<th>Change 24h</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{cryptos.map((crypto) => (
<CryptoRow key={crypto.uuid} data={crypto} />
))}
</tbody>
</table>
);
};




const App: React.FC = () => {
const [cryptos, setCryptos] = useState<CryptoCurrency[]>([]);
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 (
<div>
<header className="header">Top Market Data</header>
<div className="table-container">
<CryptoTable cryptos={cryptos} />
</div>
<div className="button-container">
<button className = "button" onClick={prevPage} disabled={currentPage === 1}>Prev</button>
<span style={{ color: 'black' }}> Page {currentPage} </span>
<button className = "button" onClick={nextPage}>Next</button>
</div>
</div>
);
};
export default App;
Loading