Skip to content

Commit 03209f9

Browse files
authored
Merge pull request #87 from TTMordred/search_2
hmmmmmm, BIG UPDATE!!! VERY IMPROVE ALLLLL, search - home - nft - marketoverview - nftcollection,.... !!!! bug fix meee !!!!
2 parents c7536b6 + bccc77e commit 03209f9

56 files changed

Lines changed: 7502 additions & 605 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/NFT/collection/[collectionId]/page.tsx

Lines changed: 698 additions & 0 deletions
Large diffs are not rendered by default.

app/NFT/collection/page.tsx

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
2+
'use client';
3+
import { useState, useEffect } from 'react';
4+
import { useRouter, useSearchParams } from 'next/navigation';
5+
import Image from 'next/image';
6+
import Link from 'next/link';
7+
import {
8+
fetchPopularCollections,
9+
fetchUserNFTs
10+
} from '@/lib/api/alchemyNFTApi';
11+
import { useWallet } from '@/components/Faucet/walletcontext';
12+
import ParticlesBackground from '@/components/ParticlesBackground';
13+
import { Card, CardContent, CardFooter } from '@/components/ui/card';
14+
import { Input } from '@/components/ui/input';
15+
import { Button } from '@/components/ui/button';
16+
import {
17+
Search,
18+
Wallet,
19+
TrendingUp,
20+
ArrowLeft,
21+
ExternalLink,
22+
Grid,
23+
Info
24+
} from 'lucide-react';
25+
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
26+
import { Badge } from '@/components/ui/badge';
27+
import { Skeleton } from '@/components/ui/skeleton';
28+
import { useToast } from '@/hooks/use-toast';
29+
30+
export default function NFTCollectionPage() {
31+
const router = useRouter();
32+
const searchParams = useSearchParams();
33+
const { account, connectWallet } = useWallet();
34+
const { toast } = useToast();
35+
36+
const [collections, setCollections] = useState<any[]>([]);
37+
const [userNFTs, setUserNFTs] = useState<any[]>([]);
38+
const [loading, setLoading] = useState(true);
39+
const [searchQuery, setSearchQuery] = useState('');
40+
const [activeTab, setActiveTab] = useState('popular');
41+
const [chainId, setChainId] = useState('0x1'); // Default to Ethereum Mainnet
42+
43+
const supportedChains = [
44+
{ id: '0x1', name: 'Ethereum', icon: '/icons/eth.svg' },
45+
{ id: '0x89', name: 'Polygon', icon: '/icons/matic.svg' },
46+
{ id: '0xa', name: 'Optimism', icon: '/icons/op.svg' },
47+
{ id: '0xa4b1', name: 'Arbitrum', icon: '/icons/arb.svg' },
48+
{ id: '0x38', name: 'BSC', icon: '/icons/bnb.svg' },
49+
];
50+
51+
// Check network and load initial data
52+
useEffect(() => {
53+
const checkNetwork = async () => {
54+
if (window.ethereum) {
55+
try {
56+
const chainId = await window.ethereum.request({
57+
method: 'eth_chainId',
58+
});
59+
if (Object.keys(supportedChains).includes(chainId)) {
60+
setChainId(chainId);
61+
}
62+
} catch (error) {
63+
console.error('Error checking network:', error);
64+
}
65+
}
66+
};
67+
68+
checkNetwork();
69+
loadPopularCollections();
70+
}, []);
71+
72+
// Load user NFTs when account changes
73+
useEffect(() => {
74+
if (account && activeTab === 'my-nfts') {
75+
loadUserNFTs();
76+
}
77+
}, [account, activeTab, chainId]);
78+
79+
const loadPopularCollections = async () => {
80+
setLoading(true);
81+
try {
82+
const data = await fetchPopularCollections(chainId);
83+
setCollections(data);
84+
} catch (error) {
85+
console.error('Error loading collections:', error);
86+
toast({
87+
title: 'Error',
88+
description: 'Failed to load collections',
89+
variant: 'destructive',
90+
});
91+
} finally {
92+
setLoading(false);
93+
}
94+
};
95+
96+
const loadUserNFTs = async () => {
97+
if (!account) return;
98+
99+
setLoading(true);
100+
try {
101+
const response = await fetchUserNFTs(account, chainId);
102+
103+
// Group NFTs by collection
104+
const grouped = response.ownedNfts.reduce((acc, nft) => {
105+
const contract = nft.contract.address;
106+
if (!acc[contract]) {
107+
acc[contract] = {
108+
contractAddress: contract,
109+
name: nft.contract.name || 'Unknown Collection',
110+
symbol: nft.contract.symbol || '',
111+
count: 0,
112+
imageUrl: nft.media[0]?.gateway || '',
113+
};
114+
}
115+
acc[contract].count++;
116+
return acc;
117+
}, {} as Record<string, any>);
118+
119+
setUserNFTs(Object.values(grouped));
120+
} catch (error) {
121+
console.error('Error loading user NFTs:', error);
122+
toast({
123+
title: 'Error',
124+
description: 'Failed to load your NFTs',
125+
variant: 'destructive',
126+
});
127+
} finally {
128+
setLoading(false);
129+
}
130+
};
131+
132+
const handleSearch = (e: React.FormEvent) => {
133+
e.preventDefault();
134+
if (!searchQuery) return;
135+
136+
const isAddress = /^0x[a-fA-F0-9]{40}$/.test(searchQuery);
137+
138+
if (isAddress) {
139+
router.push(`/NFT/collection/${searchQuery}`);
140+
} else {
141+
toast({
142+
title: 'Invalid input',
143+
description: 'Please enter a valid contract address',
144+
variant: 'destructive',
145+
});
146+
}
147+
};
148+
149+
const handleTabChange = (value: string) => {
150+
setActiveTab(value);
151+
if (value === 'my-nfts' && account) {
152+
loadUserNFTs();
153+
} else if (value === 'popular') {
154+
loadPopularCollections();
155+
}
156+
};
157+
158+
const handleConnectWallet = (e: React.MouseEvent<HTMLButtonElement>) => {
159+
e.preventDefault();
160+
connectWallet();
161+
};
162+
163+
const renderCollectionCard = (collection: any) => (
164+
<Card className="overflow-hidden hover:shadow-lg transition-shadow cursor-pointer border border-gray-800 bg-black/40">
165+
<Link href={`/NFT/collection/${collection.id || collection.contractAddress}`}>
166+
<div className="aspect-square relative">
167+
{collection.imageUrl ? (
168+
<Image
169+
src={collection.imageUrl}
170+
alt={collection.name}
171+
fill
172+
className="object-cover"
173+
onError={(e) => {
174+
const target = e.target as HTMLImageElement;
175+
target.style.display = 'none';
176+
target.parentElement!.classList.add('bg-gray-800', 'flex', 'items-center', 'justify-center');
177+
const icon = document.createElement('div');
178+
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-400"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>';
179+
target.parentElement!.appendChild(icon);
180+
}}
181+
/>
182+
) : (
183+
<div className="w-full h-full flex items-center justify-center bg-gray-800">
184+
<Info className="h-12 w-12 text-gray-400" />
185+
</div>
186+
)}
187+
</div>
188+
</Link>
189+
<CardContent className="p-4">
190+
<Link href={`/NFT/collection/${collection.id || collection.contractAddress}`}>
191+
<h3 className="font-medium text-lg text-white truncate">{collection.name}</h3>
192+
</Link>
193+
{collection.floorPrice && (
194+
<p className="text-sm text-gray-400">
195+
Floor: {collection.floorPrice} ETH
196+
</p>
197+
)}
198+
{collection.count && (
199+
<p className="text-sm text-gray-400">
200+
Owned: {collection.count}
201+
</p>
202+
)}
203+
</CardContent>
204+
<CardFooter className="p-4 pt-0 flex justify-between">
205+
<div className="flex items-center gap-2">
206+
<Badge variant="outline" className="bg-gray-800 hover:bg-gray-700">
207+
{collection.totalSupply ? `${collection.totalSupply} items` : 'View Collection'}
208+
</Badge>
209+
</div>
210+
<Link
211+
href={`https://etherscan.io/address/${collection.id || collection.contractAddress}`}
212+
target="_blank"
213+
rel="noopener noreferrer"
214+
className="text-blue-400 hover:text-blue-300"
215+
onClick={(e) => e.stopPropagation()}
216+
>
217+
<ExternalLink className="h-4 w-4" />
218+
</Link>
219+
</CardFooter>
220+
</Card>
221+
);
222+
223+
return (
224+
<div className="relative min-h-screen bg-transparent font-exo2">
225+
<ParticlesBackground />
226+
227+
<div className="container mx-auto p-4 relative z-10">
228+
<div className="flex flex-col gap-8 mt-20 mb-10">
229+
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
230+
<div className="flex items-center gap-2">
231+
<Link href="/NFT">
232+
<Button variant="ghost" size="icon" className="rounded-full">
233+
<ArrowLeft className="h-5 w-5" />
234+
</Button>
235+
</Link>
236+
<h1 className="text-2xl md:text-3xl font-bold text-white">NFT Collections</h1>
237+
</div>
238+
239+
<div className="flex items-center gap-4 w-full md:w-auto">
240+
<form onSubmit={handleSearch} className="relative flex-1 md:w-80">
241+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
242+
<Input
243+
type="text"
244+
placeholder="Search by contract address..."
245+
value={searchQuery}
246+
onChange={(e) => setSearchQuery(e.target.value)}
247+
className="pl-10 bg-gray-800/50 border-gray-700"
248+
/>
249+
<Button type="submit" className="absolute right-1 top-1/2 transform -translate-y-1/2 h-7 px-2">
250+
Search
251+
</Button>
252+
</form>
253+
254+
{!account && (
255+
<Button onClick={handleConnectWallet} className="whitespace-nowrap">
256+
<Wallet className="mr-2 h-4 w-4" /> Connect Wallet
257+
</Button>
258+
)}
259+
</div>
260+
</div>
261+
262+
<div className="flex flex-col gap-6">
263+
<Tabs defaultValue={activeTab} onValueChange={handleTabChange} className="w-full">
264+
<TabsList className="grid w-full md:w-auto grid-cols-2 bg-gray-800/50 rounded-lg p-1">
265+
<TabsTrigger
266+
value="popular"
267+
className="data-[state=active]:bg-orange-500"
268+
>
269+
<TrendingUp className="mr-2 h-4 w-4" /> Popular Collections
270+
</TabsTrigger>
271+
<TabsTrigger
272+
value="my-nfts"
273+
className="data-[state=active]:bg-orange-500"
274+
disabled={!account}
275+
>
276+
<Wallet className="mr-2 h-4 w-4" /> My NFTs
277+
</TabsTrigger>
278+
</TabsList>
279+
</Tabs>
280+
281+
{loading ? (
282+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
283+
{[...Array(8)].map((_, i) => (
284+
<Card key={i} className="overflow-hidden border border-gray-800 bg-black/40">
285+
<Skeleton className="aspect-square w-full" />
286+
<CardContent className="p-4">
287+
<Skeleton className="h-6 w-3/4 mb-2" />
288+
<Skeleton className="h-4 w-1/2" />
289+
</CardContent>
290+
<CardFooter className="p-4 pt-0">
291+
<Skeleton className="h-8 w-full" />
292+
</CardFooter>
293+
</Card>
294+
))}
295+
</div>
296+
) : (
297+
<>
298+
{activeTab === 'popular' && (
299+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
300+
{collections.map((collection) => (
301+
<div key={collection.id} className="animate-fade-in-up">
302+
{renderCollectionCard(collection)}
303+
</div>
304+
))}
305+
</div>
306+
)}
307+
308+
{activeTab === 'my-nfts' && (
309+
<>
310+
{!account ? (
311+
<div className="flex flex-col items-center justify-center py-12 px-4 border border-dashed border-gray-700 rounded-xl bg-gray-900/50">
312+
<Wallet className="h-12 w-12 text-gray-500 mb-4" />
313+
<h3 className="text-xl font-medium text-white mb-2">Connect your wallet to view your NFTs</h3>
314+
<p className="text-gray-400 mb-4 text-center max-w-md">
315+
Connect your wallet to view all your NFT collections across different blockchains.
316+
</p>
317+
<Button onClick={handleConnectWallet}>
318+
Connect Wallet
319+
</Button>
320+
</div>
321+
) : userNFTs.length === 0 ? (
322+
<div className="flex flex-col items-center justify-center py-12 px-4 border border-dashed border-gray-700 rounded-xl bg-gray-900/50">
323+
<Info className="h-12 w-12 text-gray-500 mb-4" />
324+
<h3 className="text-xl font-medium text-white mb-2">No NFTs found</h3>
325+
<p className="text-gray-400 mb-4 text-center max-w-md">
326+
We couldn't find any NFTs in your wallet. If you believe this is an error, try switching networks.
327+
</p>
328+
<div className="flex flex-wrap gap-2 justify-center">
329+
{supportedChains.map((chain) => (
330+
<Button
331+
key={chain.id}
332+
variant={chainId === chain.id ? "secondary" : "outline"}
333+
className="flex items-center gap-2"
334+
onClick={() => setChainId(chain.id)}
335+
>
336+
<Image src={chain.icon} alt={chain.name} width={16} height={16} />
337+
{chain.name}
338+
</Button>
339+
))}
340+
</div>
341+
</div>
342+
) : (
343+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
344+
{userNFTs.map((collection) => (
345+
<div key={collection.contractAddress} className="animate-fade-in-up">
346+
{renderCollectionCard(collection)}
347+
</div>
348+
))}
349+
</div>
350+
)}
351+
</>
352+
)}
353+
</>
354+
)}
355+
</div>
356+
</div>
357+
</div>
358+
</div>
359+
);
360+
}

0 commit comments

Comments
 (0)