-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor/codebase v2 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
eefebc5
6c0893a
58ac76f
f25651d
cbe15e5
37ca69c
0fbe8a4
6b1632d
227116e
fc28bc4
a2da611
84ad300
d2de97f
3525d0b
58315f8
cea1a09
4b16d3f
ea7aa23
dc5fa21
b77f4ee
8c06fe9
3acb19d
079616e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,20 +4,89 @@ import { | |||||||||||||||||||||||||||||||||||||||||
| 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'; | ||||||||||||||||||||||||||||||||||||||||||
| import Link from 'next/link'; | ||||||||||||||||||||||||||||||||||||||||||
| import { ArrowUpRight } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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: 'text-green-500 font-bold', | ||||||||||||||||||||||||||||||||||||||||||
| cell: (ticker: Ticker) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <Link href={ticker.trade_url} target='_blank' className='exchange-link'> | ||||||||||||||||||||||||||||||||||||||||||
| {ticker.market.name} | ||||||||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| header: 'Pair', | ||||||||||||||||||||||||||||||||||||||||||
| cell: (ticker: Ticker) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <div className='exchange-pair'> | ||||||||||||||||||||||||||||||||||||||||||
| <p className='truncate max-w-[100px] h-full'>{ticker.base}</p>/ | ||||||||||||||||||||||||||||||||||||||||||
| <p className='truncate max-w-[100px] h-full ml-2'>{ticker.target}</p> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| header: 'Price', | ||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'font-medium', | ||||||||||||||||||||||||||||||||||||||||||
| cell: (ticker: Ticker) => formatPrice(ticker.converted_last.usd), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| header: 'Last Traded', | ||||||||||||||||||||||||||||||||||||||||||
| headClassName: 'text-end', | ||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'exchange-timestamp', | ||||||||||||||||||||||||||||||||||||||||||
| cell: (ticker: Ticker) => timeAgo(ticker.timestamp), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||
| <main className='coin-details-main'> | ||||||||||||||||||||||||||||||||||||||||||
| <section className='size-full xl:col-span-2'> | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -27,23 +96,47 @@ const CoinDetails = async ({ params }: { params: Promise<{ id: string }> }) => { | |||||||||||||||||||||||||||||||||||||||||
| coin={coinData} | ||||||||||||||||||||||||||||||||||||||||||
| coinOHLCData={coinOHLCData} | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| {/* Exchange Listings - pass it as a child of a client component so it will be render server side */} | ||||||||||||||||||||||||||||||||||||||||||
| <ExchangeListings coinData={coinData} /> | ||||||||||||||||||||||||||||||||||||||||||
| <div className='w-full mt-8 space-y-4'> | ||||||||||||||||||||||||||||||||||||||||||
| <h4 className='section-title'>Exchange Listings</h4> | ||||||||||||||||||||||||||||||||||||||||||
| <div className='custom-scrollbar mt-5 exchange-container'> | ||||||||||||||||||||||||||||||||||||||||||
| <DataTable | ||||||||||||||||||||||||||||||||||||||||||
| columns={exchangeColumns} | ||||||||||||||||||||||||||||||||||||||||||
| data={coinData.tickers.slice(0, 7)} | ||||||||||||||||||||||||||||||||||||||||||
| rowKey={(_, index) => index} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
| <div className='w-full mt-8 space-y-4'> | |
| <h4 className='section-title'>Exchange Listings</h4> | |
| <div className='custom-scrollbar mt-5 exchange-container'> | |
| <DataTable | |
| columns={exchangeColumns} | |
| data={coinData.tickers.slice(0, 7)} | |
| rowKey={(_, index) => index} | |
| /> | |
| </div> | |
| </div> | |
| <div className='w-full mt-8 space-y-4'> | |
| <h4 className='section-title'>Exchange Listings</h4> | |
| <div className='custom-scrollbar mt-5 exchange-container'> | |
| <DataTable | |
| columns={exchangeColumns} | |
| data={coinData.tickers?.slice(0, 7) ?? []} | |
| rowKey={(_, index) => index} | |
| /> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In app/coins/[id]/page.tsx around lines 99 to 108, coinData.tickers is used with
.slice() without verifying it's defined or an array; change the DataTable data
prop to a safe expression such as Array.isArray(coinData.tickers) ?
coinData.tickers.slice(0, 7) : [] (or use (coinData.tickers ?? []).slice(0,7))
so you never call .slice() on undefined and pass an empty array when tickers is
missing.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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,68 +26,75 @@ const Coins = async ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const estimatedTotalPages = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentPage >= 100 ? Math.ceil(currentPage / 100) * 100 + 100 : 100; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const columns = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header: 'Rank', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'coins-rank', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cell: (coin: CoinMarketData) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #{coin.market_cap_rank} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Link | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href={`/coins/${coin.id}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className='absolute inset-0 z-10' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label='View coin' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header: 'Token', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'coins-token', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cell: (coin: CoinMarketData) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='coins-token-info'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Image src={coin.image} alt={coin.name} width={36} height={36} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className='max-w-full truncate'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {coin.name} ({coin.symbol.toUpperCase()}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header: 'Price', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'coins-price', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cell: (coin: CoinMarketData) => formatPrice(coin.current_price), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header: '24h Change', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'font-medium', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cell: (coin: CoinMarketData) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isTrendingUp = coin.price_change_percentage_24h > 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={cn('coins-change', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'text-green-600': isTrendingUp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'text-red-500': !isTrendingUp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isTrendingUp && '+'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {formatPercentage(coin.price_change_percentage_24h)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
60
to
74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null/undefined check for price change percentage. The comparison on Line 65 assumes 🔎 Proposed fix with null safety cell: (coin: CoinMarketData) => {
- const isTrendingUp = coin.price_change_percentage_24h > 0;
+ const change = coin.price_change_percentage_24h ?? 0;
+ const isTrendingUp = change > 0;
return (
<span
className={cn('coins-change', {
'text-green-600': isTrendingUp,
'text-red-500': !isTrendingUp,
})}
>
{isTrendingUp && '+'}
- {formatPercentage(coin.price_change_percentage_24h)}
+ {formatPercentage(change)}
</span>
);
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header: 'Market Cap', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cellClassName: 'coins-market-cap', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cell: (coin: CoinMarketData) => formatPrice(coin.market_cap), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <main className='coins-main'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='flex flex-col w-full space-y-5'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h4 className='text-2xl'>All Coins</h4> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='custom-scrollbar coins-container'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Table> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableHeader className='coins-header'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableRow className='coins-header-row'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableHead className='coins-header-left'>Rank</TableHead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableHead className='text-purple-100'>Token</TableHead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableHead className='text-purple-100'>Price</TableHead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableHead className='coins-header-right'>24h Change</TableHead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableHead className='coins-header-right'>Market Cap</TableHead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableHeader> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableBody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {coinsData.map((coin: CoinMarketData) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isTrendingUp = coin.price_change_percentage_24h > 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ClickableTableRow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={coin.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href={`/coins/${coin.id}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className='coins-row' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableCell className='coins-rank'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #{coin.market_cap_rank} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableCell className='coins-token'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='coins-token-info'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| src={coin.image} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alt={coin.name} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width={36} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height={36} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className='max-w-[100%] truncate'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {coin.name} ({coin.symbol.toUpperCase()}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableCell className='coins-price'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {formatPrice(coin.current_price)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableCell className='font-medium'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={cn('coins-change', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'text-green-600': isTrendingUp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'text-red-500': !isTrendingUp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isTrendingUp && '+'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {formatPercentage(coin.price_change_percentage_24h)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TableCell className='coins-market-cap'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {formatPrice(coin.market_cap)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ClickableTableRow> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </TableBody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Table> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DataTable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| columns={columns} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data={coinsData} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rowKey={(coin) => coin.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headerClassName='coins-header' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <CoinsPagination | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add null safety for API data access.
The
coinDetailsarray accesses deeply nested properties and array elements fromcoinDatawithout null/undefined checks. If the API response is incomplete or has a different structure, this will cause runtime errors.🔎 Proposed fix using optional chaining and null coalescing
const coinDetails = [ { label: 'Market Cap', - value: formatPrice(coinData.market_data.market_cap.usd), + value: formatPrice(coinData.market_data?.market_cap?.usd), }, { label: 'Market Cap Rank', - value: `# ${coinData.market_cap_rank}`, + value: coinData.market_cap_rank ? `# ${coinData.market_cap_rank}` : '-', }, { label: 'Total Volume', - value: formatPrice(coinData.market_data.total_volume.usd), + value: formatPrice(coinData.market_data?.total_volume?.usd), }, { label: 'Website', value: '-', - link: coinData.links.homepage[0], + link: coinData.links?.homepage?.[0], linkText: 'Website', }, { label: 'Explorer', value: '-', - link: coinData.links.blockchain_site[0], + link: coinData.links?.blockchain_site?.[0], linkText: 'Explorer', }, { label: 'Community Link', value: '-', - link: coinData.links.subreddit_url, + link: coinData.links?.subreddit_url, linkText: 'Community', }, ];📝 Committable suggestion
🤖 Prompt for AI Agents