Skip to content

Commit bd974bf

Browse files
authored
Merge pull request #1 from JavaScript-Mastery-Pro/refactor/codebase-v2
Refactor/codebase v2
2 parents 10cc61d + 079616e commit bd974bf

31 files changed

+1784
-1502
lines changed

.vscode/settings.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"editor.formatOnSave": true,
4+
"editor.codeActionsOnSave": {
5+
"source.fixAll.eslint": "explicit",
6+
"source.addMissingImports": "explicit"
7+
},
8+
"prettier.tabWidth": 2,
9+
"prettier.useTabs": false,
10+
"prettier.semi": true,
11+
"prettier.singleQuote": true,
12+
"prettier.jsxSingleQuote": true,
13+
"prettier.trailingComma": "es5",
14+
"prettier.arrowParens": "always",
15+
"[typescriptreact]": {
16+
"editor.defaultFormatter": "esbenp.prettier-vscode"
17+
}
18+
}

app/coins/[id]/page.tsx

Lines changed: 116 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,152 @@
1+
import Link from 'next/link';
2+
import { ArrowUpRight } from 'lucide-react';
3+
14
import {
25
getCoinDetails,
36
getCoinOHLC,
47
fetchPools,
58
fetchTopPool,
69
} from '@/lib/coingecko.actions';
7-
import { Converter } from '@/components/Converter';
10+
import { Converter } from '@/components/coin-details/Converter';
811
import LiveDataWrapper from '@/components/LiveDataWrapper';
9-
import { ExchangeListings } from '@/components/ExchangeListings';
10-
import { CoinDetailsSection } from '@/components/CoinDetailsSection';
11-
import { TopGainersLosers } from '@/components/TopGainersLosers';
12+
import { TopGainersLosers } from '@/components/coin-details/TopGainersLosers';
13+
import { DataTable } from '@/components/DataTable';
14+
import { formatPrice, timeAgo } from '@/lib/utils';
1215

1316
const CoinDetails = async ({ params }: { params: Promise<{ id: string }> }) => {
1417
const { id } = await params;
1518
const coinData = await getCoinDetails(id);
19+
1620
const pool = coinData.asset_platform_id
1721
? await fetchTopPool(coinData.asset_platform_id, coinData.contract_address)
1822
: await fetchPools(id);
23+
1924
const coinOHLCData = await getCoinOHLC(id, 1, 'usd', 'hourly', 'full');
2025

26+
const coinDetails = [
27+
{
28+
label: 'Market Cap',
29+
value: formatPrice(coinData.market_data.market_cap.usd),
30+
},
31+
{
32+
label: 'Market Cap Rank',
33+
value: `# ${coinData.market_cap_rank}`,
34+
},
35+
{
36+
label: 'Total Volume',
37+
value: formatPrice(coinData.market_data.total_volume.usd),
38+
},
39+
{
40+
label: 'Website',
41+
value: '-',
42+
link: coinData.links.homepage[0],
43+
linkText: 'Website',
44+
},
45+
{
46+
label: 'Explorer',
47+
value: '-',
48+
link: coinData.links.blockchain_site[0],
49+
linkText: 'Explorer',
50+
},
51+
{
52+
label: 'Community Link',
53+
value: '-',
54+
link: coinData.links.subreddit_url,
55+
linkText: 'Community',
56+
},
57+
];
58+
59+
const exchangeColumns = [
60+
{
61+
header: 'Exchange',
62+
cellClassName: 'exchange-name',
63+
cell: (ticker: Ticker) => (
64+
<>
65+
{ticker.market.name}
66+
67+
<Link
68+
href={ticker.trade_url}
69+
target='_blank'
70+
aria-label='View coin'
71+
/>
72+
</>
73+
),
74+
},
75+
{
76+
header: 'Pair',
77+
cell: (ticker: Ticker) => (
78+
<div className='pair'>
79+
<p>{ticker.base}</p>
80+
<p>{ticker.target}</p>
81+
</div>
82+
),
83+
},
84+
{
85+
header: 'Price',
86+
cellClassName: 'price-cell',
87+
cell: (ticker: Ticker) => formatPrice(ticker.converted_last.usd),
88+
},
89+
{
90+
header: 'Last Traded',
91+
headClassName: 'text-end',
92+
cellClassName: 'time-cell',
93+
cell: (ticker: Ticker) => timeAgo(ticker.timestamp),
94+
},
95+
];
96+
2197
return (
22-
<main className='coin-details-main'>
23-
<section className='size-full xl:col-span-2'>
98+
<main id='coin-details-page'>
99+
<section className='primary'>
24100
<LiveDataWrapper
25101
coinId={id}
26102
poolId={pool.id}
27103
coin={coinData}
28104
coinOHLCData={coinOHLCData}
29105
>
30-
{/* Exchange Listings - pass it as a child of a client component so it will be render server side */}
31-
<ExchangeListings coinData={coinData} />
106+
<div className='exchange-section'>
107+
<h4>Exchange Listings</h4>
108+
109+
<DataTable
110+
tableClassName='exchange-table'
111+
columns={exchangeColumns}
112+
data={coinData.tickers.slice(0, 7)}
113+
rowKey={(_, index) => index}
114+
bodyCellClassName='py-2!'
115+
/>
116+
</div>
32117
</LiveDataWrapper>
33118
</section>
34119

35-
<section className='size-full max-lg:mt-8 lg:col-span-1'>
36-
{/* Converter */}
120+
<section className='secondary'>
37121
<Converter
38122
symbol={coinData.symbol}
39123
icon={coinData.image.small}
40124
priceList={coinData.market_data.current_price}
41125
/>
42126

43-
{/* Coin Details */}
44-
<CoinDetailsSection coinData={coinData} />
127+
<div className='details'>
128+
<h4>Coin Details</h4>
129+
130+
<ul className='details-grid'>
131+
{coinDetails.map(({ label, value, link, linkText }, index) => (
132+
<li key={index}>
133+
<p className='label'>{label}</p>
134+
135+
{link ? (
136+
<div className='link'>
137+
<Link href={link} target='_blank'>
138+
{linkText || label}
139+
</Link>
140+
<ArrowUpRight size={16} />
141+
</div>
142+
) : (
143+
<p className='text-base font-medium'>{value}</p>
144+
)}
145+
</li>
146+
))}
147+
</ul>
148+
</div>
45149

46-
{/* Top Gainers / Losers */}
47150
<TopGainersLosers />
48151
</section>
49152
</main>

app/coins/page.tsx

Lines changed: 68 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import { getCoinList } from '@/lib/coingecko.actions';
2-
import {
3-
Table,
4-
TableBody,
5-
TableCell,
6-
TableHead,
7-
TableHeader,
8-
TableRow,
9-
} from '@/components/ui/table';
2+
import { DataTable } from '@/components/DataTable';
103
import Image from 'next/image';
11-
import { cn, formatPercentage, formatPrice } from '@/lib/utils';
4+
import Link from 'next/link';
5+
126
import CoinsPagination from '@/components/CoinsPagination';
13-
import { ClickableTableRow } from '@/components/ClickableTableRow';
7+
import { cn, formatPercentage, formatPrice } from '@/lib/utils';
148

159
const Coins = async ({
1610
searchParams,
@@ -32,69 +26,71 @@ const Coins = async ({
3226
const estimatedTotalPages =
3327
currentPage >= 100 ? Math.ceil(currentPage / 100) * 100 + 100 : 100;
3428

35-
return (
36-
<main className='coins-main'>
37-
<div className='flex flex-col w-full space-y-5'>
38-
<h4 className='text-2xl'>All Coins</h4>
39-
<div className='custom-scrollbar coins-container'>
40-
<Table>
41-
<TableHeader className='coins-header'>
42-
<TableRow className='coins-header-row'>
43-
<TableHead className='coins-header-left'>Rank</TableHead>
44-
<TableHead className='text-purple-100'>Token</TableHead>
45-
<TableHead className='text-purple-100'>Price</TableHead>
46-
<TableHead className='coins-header-right'>24h Change</TableHead>
47-
<TableHead className='coins-header-right'>Market Cap</TableHead>
48-
</TableRow>
49-
</TableHeader>
50-
<TableBody>
51-
{coinsData.map((coin: CoinMarketData) => {
52-
const isTrendingUp = coin.price_change_percentage_24h > 0;
53-
return (
54-
<ClickableTableRow
55-
key={coin.id}
56-
href={`/coins/${coin.id}`}
57-
className='coins-row'
58-
>
59-
<TableCell className='coins-rank'>
60-
#{coin.market_cap_rank}
61-
</TableCell>
62-
<TableCell className='coins-token'>
63-
<div className='coins-token-info'>
64-
<Image
65-
src={coin.image}
66-
alt={coin.name}
67-
width={36}
68-
height={36}
69-
/>
70-
<p className='max-w-[100%] truncate'>
71-
{coin.name} ({coin.symbol.toUpperCase()})
72-
</p>
73-
</div>
74-
</TableCell>
75-
<TableCell className='coins-price'>
76-
{formatPrice(coin.current_price)}
77-
</TableCell>
78-
<TableCell className='font-medium'>
79-
<span
80-
className={cn('coins-change', {
81-
'text-green-600': isTrendingUp,
82-
'text-red-500': !isTrendingUp,
83-
})}
84-
>
85-
{isTrendingUp && '+'}
86-
{formatPercentage(coin.price_change_percentage_24h)}
87-
</span>
88-
</TableCell>
89-
<TableCell className='coins-market-cap'>
90-
{formatPrice(coin.market_cap)}
91-
</TableCell>
92-
</ClickableTableRow>
93-
);
94-
})}
95-
</TableBody>
96-
</Table>
29+
const columns = [
30+
{
31+
header: 'Rank',
32+
cellClassName: 'rank-cell',
33+
cell: (coin: CoinMarketData) => (
34+
<>
35+
#{coin.market_cap_rank}
36+
<Link href={`/coins/${coin.id}`} aria-label='View coin' />
37+
</>
38+
),
39+
},
40+
{
41+
header: 'Token',
42+
cellClassName: 'token-cell',
43+
cell: (coin: CoinMarketData) => (
44+
<div className='token-info'>
45+
<Image src={coin.image} alt={coin.name} width={36} height={36} />
46+
<p>
47+
{coin.name} ({coin.symbol.toUpperCase()})
48+
</p>
9749
</div>
50+
),
51+
},
52+
{
53+
header: 'Price',
54+
cellClassName: 'price-cell',
55+
cell: (coin: CoinMarketData) => formatPrice(coin.current_price),
56+
},
57+
{
58+
header: '24h Change',
59+
cellClassName: 'change-cell',
60+
cell: (coin: CoinMarketData) => {
61+
const isTrendingUp = coin.price_change_percentage_24h > 0;
62+
63+
return (
64+
<span
65+
className={cn('change-value', {
66+
'text-green-600': isTrendingUp,
67+
'text-red-500': !isTrendingUp,
68+
})}
69+
>
70+
{isTrendingUp && '+'}
71+
{formatPercentage(coin.price_change_percentage_24h)}
72+
</span>
73+
);
74+
},
75+
},
76+
{
77+
header: 'Market Cap',
78+
cellClassName: 'market-cap-cell',
79+
cell: (coin: CoinMarketData) => formatPrice(coin.market_cap),
80+
},
81+
];
82+
83+
return (
84+
<main id='coins-page'>
85+
<div className='content'>
86+
<h4>All Coins</h4>
87+
88+
<DataTable
89+
tableClassName='coins-table'
90+
columns={columns}
91+
data={coinsData}
92+
rowKey={(coin) => coin.id}
93+
/>
9894

9995
<CoinsPagination
10096
currentPage={currentPage}

0 commit comments

Comments
 (0)