diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..581af7c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.addMissingImports": "explicit" + }, + "prettier.tabWidth": 2, + "prettier.useTabs": false, + "prettier.semi": true, + "prettier.singleQuote": true, + "prettier.jsxSingleQuote": true, + "prettier.trailingComma": "es5", + "prettier.arrowParens": "always", + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/app/coins/[id]/page.tsx b/app/coins/[id]/page.tsx index 1954775..95c3ad4 100644 --- a/app/coins/[id]/page.tsx +++ b/app/coins/[id]/page.tsx @@ -1,49 +1,152 @@ +import Link from 'next/link'; +import { ArrowUpRight } from 'lucide-react'; + import { getCoinDetails, getCoinOHLC, fetchPools, fetchTopPool, } from '@/lib/coingecko.actions'; -import { Converter } from '@/components/Converter'; +import { Converter } from '@/components/coin-details/Converter'; import LiveDataWrapper from '@/components/LiveDataWrapper'; -import { ExchangeListings } from '@/components/ExchangeListings'; -import { CoinDetailsSection } from '@/components/CoinDetailsSection'; -import { TopGainersLosers } from '@/components/TopGainersLosers'; +import { TopGainersLosers } from '@/components/coin-details/TopGainersLosers'; +import { DataTable } from '@/components/DataTable'; +import { formatPrice, timeAgo } from '@/lib/utils'; const CoinDetails = async ({ params }: { params: Promise<{ id: string }> }) => { const { id } = await params; const coinData = await getCoinDetails(id); + const pool = coinData.asset_platform_id ? await fetchTopPool(coinData.asset_platform_id, coinData.contract_address) : await fetchPools(id); + const coinOHLCData = await getCoinOHLC(id, 1, 'usd', 'hourly', 'full'); + const coinDetails = [ + { + label: 'Market Cap', + value: formatPrice(coinData.market_data.market_cap.usd), + }, + { + label: 'Market Cap Rank', + value: `# ${coinData.market_cap_rank}`, + }, + { + label: 'Total Volume', + value: formatPrice(coinData.market_data.total_volume.usd), + }, + { + label: 'Website', + value: '-', + link: coinData.links.homepage[0], + linkText: 'Website', + }, + { + label: 'Explorer', + value: '-', + link: coinData.links.blockchain_site[0], + linkText: 'Explorer', + }, + { + label: 'Community Link', + value: '-', + link: coinData.links.subreddit_url, + linkText: 'Community', + }, + ]; + + const exchangeColumns = [ + { + header: 'Exchange', + cellClassName: 'exchange-name', + cell: (ticker: Ticker) => ( + <> + {ticker.market.name} + + + + ), + }, + { + header: 'Pair', + cell: (ticker: Ticker) => ( +
+

{ticker.base}

+

{ticker.target}

+
+ ), + }, + { + header: 'Price', + cellClassName: 'price-cell', + cell: (ticker: Ticker) => formatPrice(ticker.converted_last.usd), + }, + { + header: 'Last Traded', + headClassName: 'text-end', + cellClassName: 'time-cell', + cell: (ticker: Ticker) => timeAgo(ticker.timestamp), + }, + ]; + return ( -
-
+
+
- {/* Exchange Listings - pass it as a child of a client component so it will be render server side */} - +
+

Exchange Listings

+ + index} + bodyCellClassName='py-2!' + /> +
-
- {/* Converter */} +
- {/* Coin Details */} - +
+

Coin Details

+ +
    + {coinDetails.map(({ label, value, link, linkText }, index) => ( +
  • +

    {label}

    + + {link ? ( +
    + + {linkText || label} + + +
    + ) : ( +

    {value}

    + )} +
  • + ))} +
+
- {/* Top Gainers / Losers */}
diff --git a/app/coins/page.tsx b/app/coins/page.tsx index 6bb1f9f..0a26806 100644 --- a/app/coins/page.tsx +++ b/app/coins/page.tsx @@ -1,16 +1,10 @@ import { getCoinList } from '@/lib/coingecko.actions'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; +import { DataTable } from '@/components/DataTable'; import Image from 'next/image'; -import { cn, formatPercentage, formatPrice } from '@/lib/utils'; +import Link from 'next/link'; + import CoinsPagination from '@/components/CoinsPagination'; -import { ClickableTableRow } from '@/components/ClickableTableRow'; +import { cn, formatPercentage, formatPrice } from '@/lib/utils'; const Coins = async ({ searchParams, @@ -32,69 +26,71 @@ const Coins = async ({ const estimatedTotalPages = currentPage >= 100 ? Math.ceil(currentPage / 100) * 100 + 100 : 100; - return ( -
-
-

All Coins

-
- - - - Rank - Token - Price - 24h Change - Market Cap - - - - {coinsData.map((coin: CoinMarketData) => { - const isTrendingUp = coin.price_change_percentage_24h > 0; - return ( - - - #{coin.market_cap_rank} - - -
- -

- {coin.name} ({coin.symbol.toUpperCase()}) -

-
-
- - {formatPrice(coin.current_price)} - - - - {isTrendingUp && '+'} - {formatPercentage(coin.price_change_percentage_24h)} - - - - {formatPrice(coin.market_cap)} - -
- ); - })} -
-
+ const columns = [ + { + header: 'Rank', + cellClassName: 'rank-cell', + cell: (coin: CoinMarketData) => ( + <> + #{coin.market_cap_rank} + + + ), + }, + { + header: 'Token', + cellClassName: 'token-cell', + cell: (coin: CoinMarketData) => ( +
+ {coin.name} +

+ {coin.name} ({coin.symbol.toUpperCase()}) +

+ ), + }, + { + header: 'Price', + cellClassName: 'price-cell', + cell: (coin: CoinMarketData) => formatPrice(coin.current_price), + }, + { + header: '24h Change', + cellClassName: 'change-cell', + cell: (coin: CoinMarketData) => { + const isTrendingUp = coin.price_change_percentage_24h > 0; + + return ( + + {isTrendingUp && '+'} + {formatPercentage(coin.price_change_percentage_24h)} + + ); + }, + }, + { + header: 'Market Cap', + cellClassName: 'market-cap-cell', + cell: (coin: CoinMarketData) => formatPrice(coin.market_cap), + }, + ]; + + return ( +
+
+

All Coins

+ + coin.id} + />