'use client' import { useState, useEffect } from 'react' import useSWR from 'swr' import { Header } from '@/components/header' import { CoinCard } from '@/components/coin-card' import { Input } from '@/components/ui/input' import { Coin } from '@/types' import { LgStar, LgFolderInvoices, LgBox, LgIdea, LgSearch, LgClock, LgComboChart, LgCancel } from '@/components/icons' const fetcher = (url: string) => fetch(url).then((res) => res.json()) type SortOption = 'newest' | 'marketCap' | 'volume' | 'price' | 'verified' const iconMap = { newest: LgClock, marketCap: LgComboChart, volume: LgComboChart, // reusing combo chart price: LgFolderInvoices, verified: LgStar, } export default function Home() { const { data: coins, isLoading } = useSWR('/api/coins', fetcher, { refreshInterval: 15000, }) const { data: solPriceData } = useSWR('/api/sol-price', fetcher, { refreshInterval: 60000, }) const { data: noticeData } = useSWR('/api/notice', fetcher, { refreshInterval: 30000, }) const solPrice = solPriceData?.price || 0 const notice = noticeData?.notice const [search, setSearch] = useState('') const [sortBy, setSortBy] = useState('price') const [discordBannerVisible, setDiscordBannerVisible] = useState(true) // i thought of this but then i realized i hat5e my users // useEffect(() => { // try { // const hidden = localStorage.getItem('hideDiscordBanner'); // if (hidden === '1') setDiscordBannerVisible(false); // } catch (e) { // // ignore (SSR safety) // } // }, []) const filteredCoins = coins ?.filter((coin) => coin.name.toLowerCase().includes(search.toLowerCase()) || coin.ticker.toLowerCase().includes(search.toLowerCase()) ) .filter((coin) => sortBy === 'verified' ? coin.verified : true) .sort((a, b) => { // verified > boosted > regular if (a.verified && !b.verified) return -1; if (!a.verified && b.verified) return 1; // if both verified or both not verified, check boosted status if (a.boosted && !b.boosted) return -1; if (!a.boosted && b.boosted) return 1; switch (sortBy) { case 'marketCap': return b.marketCap - a.marketCap case 'volume': return b.volume24h - a.volume24h case 'price': return b.price - a.price case 'verified': return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() default: return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() } }) const totalMarketCap = coins?.reduce((sum, coin) => sum + coin.marketCap, 0) || 0; const totalHolders = coins?.reduce((sum, coin) => sum + coin.holders, 0) || 0; return (
{/* Announcement Banner */} {notice && (

Notice: {notice.message} {notice.reason && ` (Reason: ${notice.reason})`}

)} {discordBannerVisible && (

Join our Discord

Giveaways, events & notifications. Or... just connect with the community {`:)`}

Join Discord
)} {/* Hero Section */}

pummmp.fun

made w/ love by focat 💜

Logo

{coins?.length ?? 'N/A'}

Active Coins

{totalMarketCap ? (() => { const value = totalMarketCap * solPrice if (value >= 1_000_000) return `$${(value / 1_000_000).toFixed(2)}M+` if (value >= 1_000) return `$${(value / 1_000).toFixed(2)}K+` return `$${value.toLocaleString('en-US', { maximumFractionDigits: 2 })}` })() : 'N/A'}

Total Market Cap

{totalHolders ?? 'N/A'}

Total Holders

{/* Coins Section */}
{/* Filters */}
setSearch(e.target.value)} className="h-11 bg-card/50 pl-10" />
{[ { value: 'newest', label: 'Newest', iconKey: 'newest' }, { value: 'verified', label: 'Verified', iconKey: 'verified' }, { value: 'marketCap', label: 'Market Cap', iconKey: 'marketCap' }, { value: 'volume', label: 'Volume', iconKey: 'volume' }, { value: 'price', label: 'Price', iconKey: 'price' }, ].map((option) => { const Icon = iconMap[option.iconKey as keyof typeof iconMap] return ( )})}
{/* Coins Grid */} {isLoading ? (
{Array.from({ length: 8 }).map((_, i) => (
))}
) : filteredCoins && filteredCoins.length > 0 ? (
{filteredCoins.map((coin) => ( ))}
) : (

No coins found

{search ? 'Try a different search term' : 'Be the first to create a coin!'}

)}
) }