Skip to content
Merged
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
6 changes: 3 additions & 3 deletions app/coins/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ const Page = async ({ params }: NextPageProps) => {
fetcher<CoinDetailsData>(`/coins/${id}`, {
dex_pair_format: 'contract_address',
}),
fetcher<OHLCData>(`/coins/${id}/ohlc`, {
fetcher<OHLCData[]>(`/coins/${id}/ohlc`, {
vs_currency: 'usd',
days: 1,
// interval: 'hourly',
precision: 'full',
}),
]);

console.log(coinOHLCData);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove debug console.log before merge.

console.log(coinOHLCData) is a debug artifact that should not be shipped to production. It will log potentially large OHLC arrays to the server console on every coin page request.

🧹 Proposed fix
-  console.log(coinOHLCData);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(coinOHLCData);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/coins/`[id]/page.tsx at line 25, Remove the დარჩ debug logging from the
coin page and ensure `app/coins/[id]/page.tsx` no longer calls
`console.log(coinOHLCData)` in the page render flow. Update the `Page` component
(or the relevant data-fetching path that builds `coinOHLCData`) so it simply
returns the page data without writing the OHLC payload to the server console.

const platform = coinData.asset_platform_id
? coinData.detail_platforms?.[coinData.asset_platform_id]
: null;
const network = platform?.geckoterminal_url.split('/')[3] || null;
const network = platform?.geckoterminal_url?.split('/')[3] || null;
const contractAddress = platform?.contract_address || null;

const pool = await getPools(id, network, contractAddress);
Expand Down
17 changes: 7 additions & 10 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,10 @@
}

.exchange-table {
@apply bg-dark-500 rounded-xl overflow-hidden mt-5;
@apply bg-dark-500 rounded-xl max-h-fit overflow-hidden mt-5;

.exchange-name {
@apply text-green-500 font-bold;
@apply pl-5 max-w-20! py-5 text-green-500 font-bold;

a {
@apply absolute inset-0 z-10;
Expand Down Expand Up @@ -628,16 +628,16 @@

#search-modal {
.trigger {
@apply px-6 hover:bg-transparent! font-medium transition-all h-full cursor-pointer text-base text-purple-100 flex items-center gap-2;
@apply px-6 hover:bg-transparent! font-medium transition-all h-full cursor-pointer text-base text-purple-100 flex items-center gap-2 hover:text-white;
}

.kbd {
@apply pointer-events-none hidden sm:inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100;
}
}

.dialog {
@apply bg-dark-400! max-w-sm sm:max-w-md md:max-w-2xl mx-auto;
}
.dialog {
@apply bg-dark-400! max-w-sm sm:max-w-md md:max-w-2xl mx-auto;

.cmd-input {
@apply bg-dark-500!;
Expand All @@ -658,15 +658,12 @@
.group {
@apply bg-dark-500 text-purple-100;
}
}

.dialog {
.empty {
@apply py-6 text-center text-sm text-gray-400;
}

.search-item {
@apply grid grid-cols-4 gap-4 items-center justify-between data-[selected=true]:bg-dark-400 transition-all !cursor-pointer hover:bg-dark-400 py-3;
@apply grid grid-cols-4 gap-4 items-center justify-between data-[selected=true]:bg-dark-400! transition-all cursor-pointer! hover:bg-dark-400! py-3;

.coin-info {
@apply flex gap-4 items-center col-span-2;
Expand Down
51 changes: 51 additions & 0 deletions components/LiveDataWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DataTable from '@/components/DataTable';
import { formatCurrency, timeAgo } from '@/lib/utils';
import { useState } from 'react';
import CoinHeader from './CoinHeader';
import Link from 'next/link';
// import CoinHeader from '@/components/CoinHeader';

const LiveDataWrapper = ({
Expand Down Expand Up @@ -57,6 +58,44 @@ const LiveDataWrapper = ({
},
];

const exchangeColumns: DataTableColumn<Ticker>[] = [
{
header: 'Exchange',
cellClassName: 'exchange-name',
cell: (exchange) => (
<>
{exchange.market.name ? exchange.market.name : '-'}
<Link href={exchange.trade_url} target="_blank" />
</>
),
Comment thread
markvalenzuela72 marked this conversation as resolved.
},
{
header: 'Pair',
cellClassName: 'pair',
cell: (exchange) => (
<p>
{exchange.base} / {exchange.target}
</p>
),
},
{
header: 'Price',
cellClassName: 'price-cell',
cell: (exchange) =>
exchange.converted_last.usd
? formatCurrency(exchange.converted_last.usd)
: '-',
},
{
header: 'Last Traded',
headClassName: 'text-end',
cellClassName: 'time-cell',
cell: (exchange) =>
exchange.timestamp ? timeAgo(exchange.timestamp) : '-',
},
];

const recentExchanges = coin.tickers.slice(0, 10);
return (
<section id="live-data-wrapper">
<CoinHeader
Expand Down Expand Up @@ -102,6 +141,18 @@ const LiveDataWrapper = ({
/>
</div>
)}
<Separator className="divider" />
{exchangeColumns && (
<div className="exchange-section">
<h4>Exchange Listings</h4>
<DataTable
columns={exchangeColumns}
data={recentExchanges}
rowKey={(_, index) => index}
tableClassName="exchange-table"
/>
</div>
)}
</section>
);
};
Expand Down
189 changes: 189 additions & 0 deletions components/SearchModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
'use client';

import Image from 'next/image';
import { useRouter } from 'next/navigation';
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { Button } from './ui/button';
import { fetcher, searchCoins } from '@/lib/coingecko.actions';
import { Search as SearchIcon } from 'lucide-react';
import { useState } from 'react';
import { cn } from '@/lib/utils';
import useSWR from 'swr';
import { useDebounce, useKey } from 'react-use';

const TRENDING_LIMIT = 8;
const SEARCH_LIMIT = 20;

const SearchItem = ({ coin, onSelect, isActiveName }: SearchItemProps) => {
return (
<CommandItem
value={coin.id}
onSelect={() => onSelect(coin.id)}
className="search-item"
>
<div className="coin-info">
<Image src={coin.thumb} alt={coin.name} width={40} height={40} />

<div>
<p className={cn('font-bold', isActiveName && 'text-white')}>
{coin.name}
</p>
<p className="coin-symbol">{coin.symbol}</p>
</div>
</div>
</CommandItem>
);
};
Comment thread
markvalenzuela72 marked this conversation as resolved.
export async function getTrendingCoins(): Promise<TrendingCoin[]> {
const res = await fetcher<{ coins: TrendingCoin[] }>(
'/search/trending',
undefined,
300,
);

return res.coins;
}

const SearchModal = () => {
const router = useRouter();
const [open, setOpen] = useState(false);

const [searchQuery, setSearchQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState('');

const { data: initialTrendingCoins = [] } = useSWR(
'trending-coins',
getTrendingCoins,
{
revalidateOnFocus: false,
},
);

useDebounce(
() => {
setDebouncedQuery(searchQuery.trim());
},
300,
[searchQuery],
);

const {
data: searchResults = [],
isValidating: isSearching,
error: searchError,
} = useSWR<SearchCoin[]>(
debouncedQuery ? ['coin-search', debouncedQuery] : null,
([, query]) => searchCoins(query as string),
{
revalidateOnFocus: false,
},
);

useKey(
(event) =>
event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey),
(event) => {
event.preventDefault();
setOpen((prev) => !prev);
},
{},
[setOpen],
);

const handleSelect = (coinId: string) => {
setOpen(false);
setSearchQuery('');
setDebouncedQuery('');
router.push(`/coins/${coinId}`);
};

const hasQuery = debouncedQuery.length > 0;
const trendingCoins = initialTrendingCoins.slice(0, TRENDING_LIMIT);
const showTrending = !hasQuery && trendingCoins.length > 0;

const isSearchEmpty = !isSearching && !hasQuery && !showTrending;
const isTrendingListVisible = !isSearching && showTrending;

const isNoResults = !isSearching && hasQuery && searchResults.length === 0;
const isResultsVisible = !isSearching && hasQuery && searchResults.length > 0;

return (
<div id="search-modal">
<Button variant="ghost" onClick={() => setOpen(true)} className="trigger">
<SearchIcon size={18} />
Search
<kbd className="kbd">
<span className="text-xs">⌘</span>K
</kbd>
</Button>

<CommandDialog
open={open}
onOpenChange={setOpen}
className="dialog"
data-search-modal
>
<div className="cmd-input">
<CommandInput
placeholder="Search for a token by name or symbol..."
value={searchQuery}
onValueChange={setSearchQuery}
/>
</div>

<CommandList className="list custom-scrollbar">
{isSearching && <div className="empty">Searching...</div>}

{isSearchEmpty && (
<div className="empty">Type to search for coins...</div>
)}

{isTrendingListVisible && (
<CommandGroup className="group">
{trendingCoins.map(({ item }) => (
<SearchItem
key={item.id}
coin={item}
onSelect={handleSelect}
isActiveName={false}
/>
))}
</CommandGroup>
)}
{(searchError || isNoResults) && (
<CommandEmpty>
{searchError
? 'Failed to load results. Please try again.'
: 'No coins found.'}
</CommandEmpty>
)}

{isResultsVisible && (
<CommandGroup
heading={<p className="heading">Search Results</p>}
className="group"
>
{searchResults.slice(0, SEARCH_LIMIT).map((coin) => (
<SearchItem
key={coin.id}
coin={coin}
onSelect={handleSelect}
isActiveName
/>
))}
</CommandGroup>
)}
</CommandList>
</CommandDialog>
</div>
);
};

export default SearchModal;
Loading