From 233436a3f476a3758e99892e2a5b9ca50f6eed97 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 00:50:32 +0700 Subject: [PATCH 01/13] Enhance PartnerBar component with floating animation and improved hover effects --- components/PartnerBar.tsx | 61 ++++++++-- components/home/TrendingNFTs.tsx | 200 ++++++++++++++++++++++--------- 2 files changed, 192 insertions(+), 69 deletions(-) diff --git a/components/PartnerBar.tsx b/components/PartnerBar.tsx index 95ef6a7..5c1048e 100644 --- a/components/PartnerBar.tsx +++ b/components/PartnerBar.tsx @@ -1,3 +1,4 @@ +import { useEffect, useRef } from 'react'; import Image from 'next/image'; const partners = [ @@ -24,25 +25,63 @@ const partners = [ ]; export default function PartnerBar() { + const containerRef = useRef(null); + + // Add subtle floating animation + useEffect(() => { + const logoElements = containerRef.current?.querySelectorAll('.partner-logo'); + if (logoElements) { + logoElements.forEach((logo, index) => { + (logo as HTMLElement).style.animationDelay = `${index * 0.2}s`; + }); + } + }, []); + return ( -
{/* Increased padding */} -
-
+
+ {/* Background gradient */} +
+ + {/* Subtle grid pattern overlay */} +
+ +
+
+

Powered by Industry Leaders

+
+
+ + diff --git a/components/home/TrendingNFTs.tsx b/components/home/TrendingNFTs.tsx index d5b2279..dbb5847 100644 --- a/components/home/TrendingNFTs.tsx +++ b/components/home/TrendingNFTs.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from "react"; +import { motion } from "framer-motion"; // Define the interface for the API response interface TrendingNFTData { @@ -20,6 +21,37 @@ const TrendingNFTCollections: React.FC = () => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const [selectedCard, setSelectedCard] = useState(null); + + // Animation variants + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1 + } + } + }; + + const cardVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.5, + ease: "easeOut" + } + }, + hover: { + y: -10, + transition: { + duration: 0.3, + ease: "easeInOut" + } + } + }; // Fetch NFT data on component mount useEffect(() => { @@ -32,8 +64,6 @@ const TrendingNFTCollections: React.FC = () => { return res.json(); }) .then((json: TrendingNFTData) => { - // Debug: log the JSON response - console.log("Fetched NFT Data:", json); if (isMounted) { setData(json); setIsLoading(false); @@ -53,8 +83,21 @@ const TrendingNFTCollections: React.FC = () => { // Loading state if (isLoading) { return ( -
-

Loading NFT collections...

+
+
+

Loading NFT Collections

+
+ {[0, 1, 2].map((i) => ( +
+ ))} +
+
); } @@ -62,9 +105,16 @@ const TrendingNFTCollections: React.FC = () => { // Error state if (error) { return ( -
-

Error loading NFT collections

-

{error}

+
+
+
+ + + +

Error Loading NFT Collections

+

{error}

+
+
); } @@ -74,64 +124,98 @@ const TrendingNFTCollections: React.FC = () => { if (results.length === 0) { return ( -
-

No NFT collections available

+
+
+

No NFT Collections Available

+
); } - // Render the NFT collection cards by mapping over data.nfts.results return ( -
-

Trending NFT Collections

-

- Explore the latest trending NFT collections based on sales volume and activity. -

-
- {results.map((item, index) => ( -
-
- {item.name { - (e.currentTarget as HTMLImageElement).src = "https://placekitten.com/100/100"; - }} - /> -
-

{item.name || "Unknown"}

-

- {item.chains?.[0]?.[0] || "N/A"} -

+
+ {/* Decorative background elements */} +
+
+ +
+
+
+ Hot Collections +
+

Trending NFT Collections

+

+ Explore the latest trending NFT collections based on sales volume and activity. + Stay updated with the hottest projects in the NFT space. +

+
+ + + {results.map((item, index) => ( + setSelectedCard(index === selectedCard ? null : index)} + > +
+
+
+ {/* Subtle glow effect around the image */} +
+ {item.name { + (e.currentTarget as HTMLImageElement).src = "https://placekitten.com/100/100"; + }} + /> +
+
+

{item.name || "Unknown"}

+
+
+ {item.chains?.[0]?.[0] || "N/A"} +
+
+
-
- -
- Floor price - - {item.floorPrice ? `$${item.floorPrice}` : "N/A"} - -
-
- Trading volume - - {item.volume ? `$${item.volume}` : "N/A"} - -
- -
- No. of traders - - {item.traders ? item.traders.toLocaleString() : "N/A"} - -
-
- ))} +
+
+
+ Floor price + + {item.floorPrice ? `$${item.floorPrice}` : "N/A"} + +
+
+ Trading volume + + {item.volume ? `$${item.volume}` : "N/A"} + +
+
+ No. of traders + + {item.traders ? item.traders.toLocaleString() : "N/A"} + +
+
+
+ + ))} +
); From 61ea507087373fdacba98aa597df505ad7159919 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 01:33:05 +0700 Subject: [PATCH 02/13] No code changes detected --- components/SearchOnTop.tsx | 266 ++++++++++++++++++++++++++++++------- 1 file changed, 218 insertions(+), 48 deletions(-) diff --git a/components/SearchOnTop.tsx b/components/SearchOnTop.tsx index cc270a1..f051843 100644 --- a/components/SearchOnTop.tsx +++ b/components/SearchOnTop.tsx @@ -1,27 +1,49 @@ 'use client'; -import React, { useState } from 'react'; -import { useRouter, usePathname } from "next/navigation"; -import { FaSearch, FaMapPin, FaSun, FaEthereum } from 'react-icons/fa'; +import React, { useState, useRef, useEffect } from 'react'; +import { useRouter, usePathname } from "next/navigation"; +import { Search, X, Wallet, Network, ArrowRight } from 'lucide-react'; import { LoadingScreen } from "@/components/loading-screen"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; const SearchOnTop = () => { const pathname = usePathname(); const [searchQuery, setSearchQuery] = useState(''); const [isLoading, setIsLoading] = useState(false); const [searchType, setSearchType] = useState<"onchain" | "offchain">("onchain"); + const [ethPrice, setEthPrice] = useState({ price: '1,931.60', change: '+1.41%' }); + const [gasPrice, setGasPrice] = useState('0.462'); + const [expanded, setExpanded] = useState(false); const router = useRouter(); + const inputRef = useRef(null); + const searchBarRef = useRef(null); + + // Handle click outside to collapse expanded search + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (searchBarRef.current && !searchBarRef.current.contains(event.target as Node)) { + setExpanded(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + // Skip rendering on homepage if (pathname === "/") { return null; } - + const handleSearch = async (event: React.FormEvent) => { event.preventDefault(); if (!searchQuery.trim()) return; - setIsLoading(true); try { - await new Promise((resolve) => setTimeout(resolve, 2500)); // Simulated delay + setIsLoading(true); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Reduced delay for better UX if (searchType === "onchain") { router.push(`/search/?address=${encodeURIComponent(searchQuery)}&network=mainnet`); } else { @@ -31,59 +53,207 @@ const SearchOnTop = () => { console.error("Search error:", error); } finally { setIsLoading(false); + setExpanded(false); } }; + const clearSearch = () => { + setSearchQuery(''); + if (inputRef.current) { + inputRef.current.focus(); + } + }; + + const handleFocus = () => { + setExpanded(true); + }; + return ( <> -
- {/* ETH Price and Gas Data */} -
- ETH Price: $1,931.60 (+1.41%) - Gas: 0.462 Gwei -
+
+
+ {/* Market Data */} +
+
+
+ + + + + +
+
+ ${ethPrice.price} + + {ethPrice.change} + +
+
+
+
+ Gas: {gasPrice} Gwei +
+
+
- {/* Search Bar */} -
-
- - setSearchQuery(e.target.value)} - className="pl-10 pr-10 py-1 w-full text-gray-700 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
- -
- - - - +
+
+ + + setSearchQuery(e.target.value)} + onFocus={handleFocus} + className="flex-1 bg-transparent border-0 focus-visible:ring-0 text-white placeholder:text-gray-400 h-10" + /> + + {searchQuery.length > 0 && ( + + )} + +
+ +
+ + +
+
+ + {/* Expanded search features */} + {expanded && ( +
+
Quick Search
+
+ {[ + { name: "Popular Tokens", route: "/" }, + { name: "Recent Transactions", route: "/transactions" }, + { name: "My Wallets", route: "/" }, + { name: "Verified Contracts", route: "/" } + ].map(item => ( + + + + + + +

Search {item.name}

+
+
+
+ ))} +
+
+ )} +
+ + {/* Right side buttons - only visible on larger screens */} +
+ + + + + + +

My Wallets

+
+
+
+ + + + + + + +

Network Status

+
+
+
+ + + ); }; From 4f8be20f4df134055353712c57537ac1b5f09404 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 01:48:20 +0700 Subject: [PATCH 03/13] No code changes detected --- components/SearchOnTop.tsx | 218 ++++++++++++++++++++++++------------- 1 file changed, 145 insertions(+), 73 deletions(-) diff --git a/components/SearchOnTop.tsx b/components/SearchOnTop.tsx index f051843..ff59d95 100644 --- a/components/SearchOnTop.tsx +++ b/components/SearchOnTop.tsx @@ -7,19 +7,65 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import Image from "next/image"; const SearchOnTop = () => { const pathname = usePathname(); const [searchQuery, setSearchQuery] = useState(''); const [isLoading, setIsLoading] = useState(false); const [searchType, setSearchType] = useState<"onchain" | "offchain">("onchain"); - const [ethPrice, setEthPrice] = useState({ price: '1,931.60', change: '+1.41%' }); - const [gasPrice, setGasPrice] = useState('0.462'); + const [cryptoPrices, setCryptoPrices] = useState({ + eth: { price: '0.00', change: '0.00%' }, + bnb: { price: '0.00', change: '0.00%' } + }); + const [gasPrice, setGasPrice] = useState({ price: '0', speed: 'Standard' }); const [expanded, setExpanded] = useState(false); const router = useRouter(); const inputRef = useRef(null); const searchBarRef = useRef(null); + // Fetch real-time prices and gas data + useEffect(() => { + const fetchPrices = async () => { + try { + // Fetch crypto prices from CoinGecko + const priceResponse = await fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum,binancecoin&vs_currencies=usd&include_24hr_change=true' + ); + const priceData = await priceResponse.json(); + + setCryptoPrices({ + eth: { + price: priceData.ethereum.usd.toLocaleString(), + change: `${priceData.ethereum.usd_24h_change >= 0 ? '+' : ''}${priceData.ethereum.usd_24h_change.toFixed(2)}%` + }, + bnb: { + price: priceData.binancecoin.usd.toLocaleString(), + change: `${priceData.binancecoin.usd_24h_change >= 0 ? '+' : ''}${priceData.binancecoin.usd_24h_change.toFixed(2)}%` + } + }); + + // Fetch gas prices from Etherscan API + const gasResponse = await fetch('/api/etherscan?module=gastracker&action=gasoracle'); + const gasData = await gasResponse.json(); + + if (gasData.status === "1") { + setGasPrice({ + price: gasData.result.ProposeGasPrice, + speed: 'Standard' + }); + } + } catch (error) { + console.error("Error fetching market data:", error); + } + }; + + fetchPrices(); + // Refresh data every 60 seconds + const interval = setInterval(fetchPrices, 60000); + return () => clearInterval(interval); + }, []); + // Handle click outside to collapse expanded search useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -72,44 +118,70 @@ const SearchOnTop = () => { <>
- {/* Market Data */} -
+ {/* Market Data */} +
+ {/* ETH Price */}
-
- - - - - +
+ + + + + + + +
- ${ethPrice.price} - - {ethPrice.change} - + ${cryptoPrices.eth.price} + + {cryptoPrices.eth.change} +
-
-
- Gas: {gasPrice} Gwei + + {/* BNB Price */} +
+
+ + + +
+
+ ${cryptoPrices.bnb.price} + + {cryptoPrices.bnb.change} +
-
+ + {/* Gas Price */} +
+
+ + + + + + Gas: {Number(gasPrice.price).toFixed(2)} Gwei +
+
+
{/* Search Component */} -
+
- -
{ {searchQuery.length > 0 && ( )}
@@ -167,42 +239,42 @@ const SearchOnTop = () => { > -
-
+
+ - {/* Expanded search features */} - {expanded && ( -
+ {/* Expanded search features */} + {expanded && ( +
Quick Search
{[ - { name: "Popular Tokens", route: "/" }, - { name: "Recent Transactions", route: "/transactions" }, - { name: "My Wallets", route: "/" }, - { name: "Verified Contracts", route: "/" } + { name: "Popular Tokens", route: "/" }, + { name: "Recent Transactions", route: "/transactions" }, + { name: "My Wallets", route: "/" }, + { name: "Verified Contracts", route: "/" } ].map(item => ( - - - - - - -

Search {item.name}

-
-
-
+ + + + + + +

Search {item.name}

+
+
+
))}
-
- )} -
+
+ )} +
{/* Right side buttons - only visible on larger screens */}
From c5b21000dc6c047a275012fd14969d7f61447fc3 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 11:13:03 +0700 Subject: [PATCH 04/13] okela --- package-lock.json | 345 +++++++++++++++++++++++++++++++++------------- 1 file changed, 250 insertions(+), 95 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9eba6d..d94f7b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -309,7 +309,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -318,6 +317,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -332,6 +345,70 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", @@ -360,6 +437,43 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.26.9", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", @@ -511,6 +625,20 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helpers": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", @@ -2667,7 +2795,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -3087,7 +3214,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -3101,7 +3227,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3111,7 +3236,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -3135,7 +3259,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -6265,7 +6388,7 @@ "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -6275,7 +6398,7 @@ "version": "19.0.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -8553,7 +8676,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8581,7 +8703,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, "license": "MIT" }, "node_modules/anymatch": { @@ -8632,7 +8753,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -9085,7 +9205,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9221,7 +9340,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -9363,6 +9481,39 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bs58": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", @@ -9512,7 +9663,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9580,7 +9730,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -9605,7 +9754,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -9841,7 +9989,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9859,6 +10006,13 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "license": "ISC" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT", + "peer": true + }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -9954,7 +10108,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -10029,7 +10182,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -10572,7 +10724,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, "license": "Apache-2.0" }, "node_modules/diffie-hellman": { @@ -10602,7 +10753,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, "license": "MIT" }, "node_modules/doctrine": { @@ -10690,7 +10840,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/eccrypto": { @@ -10773,6 +10922,13 @@ "node": ">=4.0.0" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.119", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.119.tgz", + "integrity": "sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==", + "license": "ISC", + "peer": true + }, "node_modules/elliptic": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", @@ -10826,7 +10982,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/encode-utf8": { @@ -11131,6 +11286,16 @@ "@esbuild/win32-x64": "0.19.12" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -12851,11 +13016,17 @@ "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", "license": "MIT" }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/fastq": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -12884,7 +13055,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -13019,7 +13189,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -13113,7 +13282,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -13232,6 +13400,16 @@ "node": ">=8" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -13322,7 +13500,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -13343,7 +13520,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -13356,7 +13532,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -13366,7 +13541,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -13965,7 +14139,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -14017,7 +14190,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -14068,7 +14240,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14127,7 +14298,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -14179,7 +14349,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -14368,7 +14537,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/isomorphic-ws": { @@ -14417,7 +14585,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -14510,7 +14677,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -14773,7 +14939,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -14786,7 +14951,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/lit": { @@ -15030,7 +15194,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -15040,7 +15203,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -15144,7 +15306,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -15247,7 +15408,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -15451,6 +15611,13 @@ "integrity": "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==", "license": "MIT" }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT", + "peer": true + }, "node_modules/nodemailer": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", @@ -15530,7 +15697,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -15869,7 +16035,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -15956,7 +16121,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15966,14 +16130,12 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -16024,7 +16186,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16123,7 +16284,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -16151,7 +16311,6 @@ "version": "8.5.1", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -16180,7 +16339,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -16198,7 +16356,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" @@ -16218,7 +16375,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -16254,7 +16410,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -16280,7 +16435,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16294,7 +16448,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, "node_modules/preact": { @@ -16480,7 +16633,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -16804,7 +16956,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -16828,7 +16979,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -16953,7 +17103,6 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -16994,7 +17143,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -17221,7 +17369,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -17519,7 +17666,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -17532,7 +17678,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17618,7 +17763,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -17820,7 +17964,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -17839,7 +17982,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -17854,7 +17996,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17864,14 +18005,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -17997,7 +18136,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -18014,7 +18152,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18027,7 +18164,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18104,7 +18240,6 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -18149,7 +18284,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -18205,7 +18339,6 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -18252,7 +18385,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -18269,7 +18401,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -18351,7 +18482,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -18361,7 +18491,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -18462,7 +18591,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -18494,7 +18622,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/ts-mixer": { @@ -18644,7 +18771,6 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -18855,6 +18981,37 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -20308,7 +20465,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -20486,7 +20642,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -20505,7 +20660,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -20523,7 +20677,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -20533,14 +20686,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -20555,7 +20706,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -20568,7 +20718,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -20696,11 +20845,17 @@ "node": ">=0.10.32" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC", + "peer": true + }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" From 24d96020dd034ee52bf4b631e00ac82b89daed14 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:24:25 +0700 Subject: [PATCH 05/13] Add API caching and new endpoints for market data; update header with Market Overview link --- app/NFT/collection/[collectionId]/page.tsx | 698 ++++++++++++++++++ app/NFT/collection/page.tsx | 360 +++++++++ app/api/analytics/chain-stats/route.ts | 49 ++ app/api/analytics/defi-tvl/route.ts | 79 ++ app/api/analytics/exchange-volumes/route.ts | 60 ++ app/api/analytics/fear-greed-index/route.ts | 39 + app/api/analytics/gas-prices/route.ts | 62 ++ app/api/analytics/market-sentiment/route.ts | 51 ++ app/api/analytics/nft-stats/route.ts | 85 +++ app/api/analytics/trending-coins/route.ts | 43 ++ app/api/analytics/whale-alerts/route.ts | 151 ++++ app/api/binance/klines/route.ts | 82 ++ app/api/coinmarketcap/global-metrics/route.ts | 45 ++ app/api/coinmarketcap/historical/route.ts | 92 +++ app/api/coinmarketcap/listings/route.ts | 61 ++ app/api/coinmarketcap/route.ts | 97 +++ app/api/market/altcoin-season/route.ts | 100 +++ app/api/market/fear-greed/route.ts | 38 + app/market-overview/page.tsx | 353 +++++++++ app/page.tsx | 686 ++++++++--------- components/Header.tsx | 3 + components/NFT/NFTNavigation.tsx | 96 +++ components/home/DemoShowcase.tsx | 182 +++++ components/home/FeatureCard.tsx | 71 ++ components/market-overview/AltcoinIndex.tsx | 130 ++++ .../market-overview/BlockchainStatsCard.tsx | 141 ++++ components/market-overview/CryptoCard.tsx | 78 ++ components/market-overview/DefiTvlCard.tsx | 146 ++++ components/market-overview/DominanceCard.tsx | 71 ++ .../market-overview/ExchangeVolumeCard.tsx | 156 ++++ components/market-overview/FearGreedIndex.tsx | 121 +++ components/market-overview/GasPriceCard.tsx | 107 +++ components/market-overview/MarketCapChart.tsx | 130 ++++ .../market-overview/MarketIndexCard.tsx | 107 +++ .../market-overview/MarketSentimentCard.tsx | 162 ++++ components/market-overview/NftStatsCard.tsx | 117 +++ .../market-overview/StakingYieldsCard.tsx | 0 .../market-overview/TrendingCoinsCard.tsx | 131 ++++ .../market-overview/WhaleAlertsCard.tsx | 144 ++++ lib/api/alchemyNFTApi.ts | 277 +++++++ package-lock.json | 345 +++------ public/Img/market-overview.png | Bin 0 -> 275006 bytes services/apiCache.ts | 46 ++ services/binanceApiService.ts | 83 +++ services/coinMarketCapService.ts | 286 +++++++ 45 files changed, 5776 insertions(+), 585 deletions(-) create mode 100644 app/NFT/collection/[collectionId]/page.tsx create mode 100644 app/NFT/collection/page.tsx create mode 100644 app/api/analytics/chain-stats/route.ts create mode 100644 app/api/analytics/defi-tvl/route.ts create mode 100644 app/api/analytics/exchange-volumes/route.ts create mode 100644 app/api/analytics/fear-greed-index/route.ts create mode 100644 app/api/analytics/gas-prices/route.ts create mode 100644 app/api/analytics/market-sentiment/route.ts create mode 100644 app/api/analytics/nft-stats/route.ts create mode 100644 app/api/analytics/trending-coins/route.ts create mode 100644 app/api/analytics/whale-alerts/route.ts create mode 100644 app/api/binance/klines/route.ts create mode 100644 app/api/coinmarketcap/global-metrics/route.ts create mode 100644 app/api/coinmarketcap/historical/route.ts create mode 100644 app/api/coinmarketcap/listings/route.ts create mode 100644 app/api/coinmarketcap/route.ts create mode 100644 app/api/market/altcoin-season/route.ts create mode 100644 app/api/market/fear-greed/route.ts create mode 100644 app/market-overview/page.tsx create mode 100644 components/NFT/NFTNavigation.tsx create mode 100644 components/home/DemoShowcase.tsx create mode 100644 components/home/FeatureCard.tsx create mode 100644 components/market-overview/AltcoinIndex.tsx create mode 100644 components/market-overview/BlockchainStatsCard.tsx create mode 100644 components/market-overview/CryptoCard.tsx create mode 100644 components/market-overview/DefiTvlCard.tsx create mode 100644 components/market-overview/DominanceCard.tsx create mode 100644 components/market-overview/ExchangeVolumeCard.tsx create mode 100644 components/market-overview/FearGreedIndex.tsx create mode 100644 components/market-overview/GasPriceCard.tsx create mode 100644 components/market-overview/MarketCapChart.tsx create mode 100644 components/market-overview/MarketIndexCard.tsx create mode 100644 components/market-overview/MarketSentimentCard.tsx create mode 100644 components/market-overview/NftStatsCard.tsx create mode 100644 components/market-overview/StakingYieldsCard.tsx create mode 100644 components/market-overview/TrendingCoinsCard.tsx create mode 100644 components/market-overview/WhaleAlertsCard.tsx create mode 100644 lib/api/alchemyNFTApi.ts create mode 100644 public/Img/market-overview.png create mode 100644 services/apiCache.ts create mode 100644 services/binanceApiService.ts create mode 100644 services/coinMarketCapService.ts diff --git a/app/NFT/collection/[collectionId]/page.tsx b/app/NFT/collection/[collectionId]/page.tsx new file mode 100644 index 0000000..d6b1ece --- /dev/null +++ b/app/NFT/collection/[collectionId]/page.tsx @@ -0,0 +1,698 @@ + +'use client'; +import { useState, useEffect } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import Link from 'next/link'; +import Image from 'next/image'; +import { + fetchCollectionInfo, + fetchCollectionNFTs, +} from '@/lib/api/alchemyNFTApi'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardFooter } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + ArrowLeft, + Info, + ExternalLink, + Copy, + CheckCircle, + Grid, + List, + Search +} from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; +import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from '@/components/ui/pagination'; +import ParticlesBackground from '@/components/ParticlesBackground'; + +interface NFT { + id: string; + tokenId: string; + name: string; + description: string; + imageUrl: string; + attributes: Array<{ + trait_type: string; + value: string; + }>; +} + +export default function CollectionDetailsPage() { + const params = useParams(); + const router = useRouter(); + const { toast } = useToast(); + const collectionId = params?.collectionId as string; + + const [collection, setCollection] = useState(null); + const [nfts, setNfts] = useState([]); + const [loading, setLoading] = useState(true); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [searchQuery, setSearchQuery] = useState(''); + const [network, setNetwork] = useState('0x1'); // Default to Ethereum Mainnet + const [copied, setCopied] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [pageSize] = useState(12); + const [sortBy, setSortBy] = useState('tokenId'); + const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc'); + const [selectedAttributes, setSelectedAttributes] = useState< + Record + >({}); + const [attributeFilters, setAttributeFilters] = useState< + Record + >({}); + + // Check network + useEffect(() => { + const checkNetwork = async () => { + if (window.ethereum) { + try { + const chainId = await window.ethereum.request({ + method: 'eth_chainId', + }); + if (chainId === '0x1') { + setNetwork('0x1'); + } else if (chainId === '0xaa36a7') { + setNetwork('0xaa36a7'); + } + } catch (error) { + console.error('Error checking network:', error); + } + } + }; + checkNetwork(); + }, []); + + // Load collection data + useEffect(() => { + const loadCollectionData = async () => { + if (!collectionId) return; + + setLoading(true); + try { + const metadata = await fetchCollectionInfo(collectionId, network); + setCollection(metadata); + + const nftData = await fetchCollectionNFTs( + collectionId, + network, + currentPage, + pageSize, + sortBy, + sortDir, + searchQuery, + selectedAttributes + ); + + setNfts(nftData.nfts); + setTotalPages(Math.ceil(nftData.totalCount / pageSize)); + + // Extract attributes for filtering + const attributeMap: Record = {}; + nftData.nfts.forEach((nft: NFT) => { + if (nft.attributes) { + nft.attributes.forEach((attr) => { + if (!attributeMap[attr.trait_type]) { + attributeMap[attr.trait_type] = []; + } + if (!attributeMap[attr.trait_type].includes(attr.value)) { + attributeMap[attr.trait_type].push(attr.value); + } + }); + } + }); + setAttributeFilters(attributeMap); + } catch (error) { + console.error('Error loading collection data:', error); + toast({ + title: 'Error', + description: 'Failed to load collection data. Please try again.', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }; + + loadCollectionData(); + }, [ + collectionId, + network, + currentPage, + pageSize, + sortBy, + sortDir, + searchQuery, + selectedAttributes, + toast + ]); + + const handleCopyAddress = () => { + if (!collectionId) return; + navigator.clipboard.writeText(collectionId); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + toast({ + title: 'Address copied', + description: 'The collection address has been copied to your clipboard.', + }); + }; + + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + setCurrentPage(1); // Reset to first page when searching + }; + + const handleSortChange = (value: string) => { + const [field, direction] = value.split('-'); + setSortBy(field); + setSortDir(direction as 'asc' | 'desc'); + setCurrentPage(1); + }; + + const handleAttributeFilter = (traitType: string, value: string) => { + setSelectedAttributes((prev) => { + const newFilters = { ...prev }; + if (!newFilters[traitType]) { + newFilters[traitType] = []; + } + + if (newFilters[traitType].includes(value)) { + newFilters[traitType] = newFilters[traitType].filter( + (v) => v !== value + ); + } else { + newFilters[traitType].push(value); + } + + if (newFilters[traitType].length === 0) { + delete newFilters[traitType]; + } + + return newFilters; + }); + setCurrentPage(1); + }; + + const clearFilters = () => { + setSelectedAttributes({}); + setSearchQuery(''); + setCurrentPage(1); + }; + + const isAttributeSelected = (traitType: string, value: string) => { + return selectedAttributes[traitType]?.includes(value) || false; + }; + + const formatAddress = (address: string) => { + if (!address) return ''; + return `${address.substring(0, 6)}...${address.substring( + address.length - 4 + )}`; + }; + + const getEtherscanLink = (address: string) => { + const baseUrl = + network === '0x1' + ? 'https://etherscan.io' + : 'https://sepolia.etherscan.io'; + return `${baseUrl}/address/${address}`; + }; + + const renderSkeleton = () => ( +
+
+ +
+ + + +
+
+
+ {[...Array(8)].map((_, i) => ( + + ))} +
+
+ ); + + const renderNFTCard = (nft: NFT) => { + let imageUrl = nft.imageUrl; + if (imageUrl && imageUrl.startsWith('ipfs://')) { + imageUrl = `https://ipfs.io/ipfs/${imageUrl.slice(7)}`; + } + + return ( + +
+ {imageUrl ? ( + {nft.name { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement!.classList.add('bg-gray-800', 'flex', 'items-center', 'justify-center'); + const icon = document.createElement('div'); + icon.innerHTML = ''; + target.parentElement!.appendChild(icon); + }} + /> + ) : ( +
+ +
+ )} +
+ +

+ {nft.name || `NFT #${nft.tokenId}`} +

+

+ ID: {parseInt(nft.tokenId, 16).toString()} +

+
+ +
+ {nft.attributes?.slice(0, 3).map((attr, i) => ( + + {attr.trait_type}: {attr.value} + + ))} + {nft.attributes?.length > 3 && ( + + +{nft.attributes.length - 3} more + + )} +
+
+
+ ); + }; + + const renderNFTList = (nft: NFT) => { + let imageUrl = nft.imageUrl; + if (imageUrl && imageUrl.startsWith('ipfs://')) { + imageUrl = `https://ipfs.io/ipfs/${imageUrl.slice(7)}`; + } + + return ( + +
+ {imageUrl ? ( + {nft.name { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement!.classList.add('bg-gray-800', 'flex', 'items-center', 'justify-center'); + const icon = document.createElement('div'); + icon.innerHTML = ''; + target.parentElement!.appendChild(icon); + }} + /> + ) : ( +
+ +
+ )} +
+
+
+
+

+ {nft.name || `NFT #${nft.tokenId}`} +

+

+ ID: {parseInt(nft.tokenId, 16).toString()} +

+
+
+ {nft.attributes?.slice(0, 3).map((attr, i) => ( + + {attr.trait_type}: {attr.value} + + ))} + {nft.attributes?.length > 3 && ( + + +{nft.attributes.length - 3} more + + )} +
+
+
+
+ ); + }; + + return ( +
+ + +
+ + + {loading ? ( + renderSkeleton() + ) : collection ? ( + <> +
+ {collection.imageUrl ? ( +
+ {collection.name} { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement!.classList.add('bg-gray-800', 'flex', 'items-center', 'justify-center'); + const icon = document.createElement('div'); + icon.innerHTML = ''; + target.parentElement!.appendChild(icon); + }} + /> +
+ ) : ( +
+ +
+ )} + +
+
+

{collection.name}

+ + {network === '0x1' ? 'Ethereum' : 'Sepolia'} + +
+ +
+
+ +
+ + + Etherscan + +
+ +

{collection.description}

+ +
+ {collection.totalSupply && ( + + Total Items: {collection.totalSupply} + + )} + {collection.symbol && ( + + Symbol: {collection.symbol} + + )} +
+
+
+ +
+
+
+

Filters

+ +
+ +
+ {Object.entries(attributeFilters).map(([traitType, values]) => ( +
+

{traitType}

+ {values.sort().map((value) => ( +
+ +
+ ))} +
+ ))} +
+
+ +
+
+
+ + +
+ +
+ + +
+ + +
+
+
+ + {nfts.length === 0 ? ( +
+

+ No NFTs found for this collection. +

+ {(searchQuery || + Object.keys(selectedAttributes).length > 0) && ( + + )} +
+ ) : ( + <> +
+ {nfts.map((nft) => + viewMode === 'grid' + ? renderNFTCard(nft) + : renderNFTList(nft) + )} +
+ + {totalPages > 1 && ( + + + + { + e.preventDefault(); + if (currentPage > 1) + setCurrentPage(currentPage - 1); + }} + className={ + currentPage === 1 + ? 'pointer-events-none opacity-50' + : '' + } + /> + + + {[...Array(totalPages)].map((_, i) => { + const pageNumber = i + 1; + // Show first page, last page, and pages around current page + if ( + pageNumber === 1 || + pageNumber === totalPages || + (pageNumber >= currentPage - 1 && + pageNumber <= currentPage + 1) + ) { + return ( + + { + e.preventDefault(); + setCurrentPage(pageNumber); + }} + isActive={pageNumber === currentPage} + > + {pageNumber} + + + ); + } + + // Show ellipsis for gaps + if ( + (pageNumber === 2 && currentPage > 3) || + (pageNumber === totalPages - 1 && + currentPage < totalPages - 2) + ) { + return ( + + ... + + ); + } + + return null; + })} + + + { + e.preventDefault(); + if (currentPage < totalPages) + setCurrentPage(currentPage + 1); + }} + className={ + currentPage === totalPages + ? 'pointer-events-none opacity-50' + : '' + } + /> + + + + )} + + )} +
+
+ + ) : ( +
+ +

Collection not found

+

+ The collection you're looking for doesn't exist or couldn't be loaded. +

+ + + +
+ )} +
+
+ ); +} diff --git a/app/NFT/collection/page.tsx b/app/NFT/collection/page.tsx new file mode 100644 index 0000000..f94b468 --- /dev/null +++ b/app/NFT/collection/page.tsx @@ -0,0 +1,360 @@ + +'use client'; +import { useState, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import Image from 'next/image'; +import Link from 'next/link'; +import { + fetchPopularCollections, + fetchUserNFTs +} from '@/lib/api/alchemyNFTApi'; +import { useWallet } from '@/components/Faucet/walletcontext'; +import ParticlesBackground from '@/components/ParticlesBackground'; +import { Card, CardContent, CardFooter } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { + Search, + Wallet, + TrendingUp, + ArrowLeft, + ExternalLink, + Grid, + Info +} from 'lucide-react'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; +import { useToast } from '@/hooks/use-toast'; + +export default function NFTCollectionPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { account, connectWallet } = useWallet(); + const { toast } = useToast(); + + const [collections, setCollections] = useState([]); + const [userNFTs, setUserNFTs] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [activeTab, setActiveTab] = useState('popular'); + const [chainId, setChainId] = useState('0x1'); // Default to Ethereum Mainnet + + const supportedChains = [ + { id: '0x1', name: 'Ethereum', icon: '/icons/eth.svg' }, + { id: '0x89', name: 'Polygon', icon: '/icons/matic.svg' }, + { id: '0xa', name: 'Optimism', icon: '/icons/op.svg' }, + { id: '0xa4b1', name: 'Arbitrum', icon: '/icons/arb.svg' }, + { id: '0x38', name: 'BSC', icon: '/icons/bnb.svg' }, + ]; + + // Check network and load initial data + useEffect(() => { + const checkNetwork = async () => { + if (window.ethereum) { + try { + const chainId = await window.ethereum.request({ + method: 'eth_chainId', + }); + if (Object.keys(supportedChains).includes(chainId)) { + setChainId(chainId); + } + } catch (error) { + console.error('Error checking network:', error); + } + } + }; + + checkNetwork(); + loadPopularCollections(); + }, []); + + // Load user NFTs when account changes + useEffect(() => { + if (account && activeTab === 'my-nfts') { + loadUserNFTs(); + } + }, [account, activeTab, chainId]); + + const loadPopularCollections = async () => { + setLoading(true); + try { + const data = await fetchPopularCollections(chainId); + setCollections(data); + } catch (error) { + console.error('Error loading collections:', error); + toast({ + title: 'Error', + description: 'Failed to load collections', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }; + + const loadUserNFTs = async () => { + if (!account) return; + + setLoading(true); + try { + const response = await fetchUserNFTs(account, chainId); + + // Group NFTs by collection + const grouped = response.ownedNfts.reduce((acc, nft) => { + const contract = nft.contract.address; + if (!acc[contract]) { + acc[contract] = { + contractAddress: contract, + name: nft.contract.name || 'Unknown Collection', + symbol: nft.contract.symbol || '', + count: 0, + imageUrl: nft.media[0]?.gateway || '', + }; + } + acc[contract].count++; + return acc; + }, {} as Record); + + setUserNFTs(Object.values(grouped)); + } catch (error) { + console.error('Error loading user NFTs:', error); + toast({ + title: 'Error', + description: 'Failed to load your NFTs', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (!searchQuery) return; + + const isAddress = /^0x[a-fA-F0-9]{40}$/.test(searchQuery); + + if (isAddress) { + router.push(`/NFT/collection/${searchQuery}`); + } else { + toast({ + title: 'Invalid input', + description: 'Please enter a valid contract address', + variant: 'destructive', + }); + } + }; + + const handleTabChange = (value: string) => { + setActiveTab(value); + if (value === 'my-nfts' && account) { + loadUserNFTs(); + } else if (value === 'popular') { + loadPopularCollections(); + } + }; + + const handleConnectWallet = (e: React.MouseEvent) => { + e.preventDefault(); + connectWallet(); + }; + + const renderCollectionCard = (collection: any) => ( + + +
+ {collection.imageUrl ? ( + {collection.name} { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement!.classList.add('bg-gray-800', 'flex', 'items-center', 'justify-center'); + const icon = document.createElement('div'); + icon.innerHTML = ''; + target.parentElement!.appendChild(icon); + }} + /> + ) : ( +
+ +
+ )} +
+ + + +

{collection.name}

+ + {collection.floorPrice && ( +

+ Floor: {collection.floorPrice} ETH +

+ )} + {collection.count && ( +

+ Owned: {collection.count} +

+ )} +
+ +
+ + {collection.totalSupply ? `${collection.totalSupply} items` : 'View Collection'} + +
+ e.stopPropagation()} + > + + +
+
+ ); + + return ( +
+ + +
+
+
+
+ + + +

NFT Collections

+
+ +
+
+ + setSearchQuery(e.target.value)} + className="pl-10 bg-gray-800/50 border-gray-700" + /> + + + + {!account && ( + + )} +
+
+ +
+ + + + Popular Collections + + + My NFTs + + + + + {loading ? ( +
+ {[...Array(8)].map((_, i) => ( + + + + + + + + + + + ))} +
+ ) : ( + <> + {activeTab === 'popular' && ( +
+ {collections.map((collection) => ( +
+ {renderCollectionCard(collection)} +
+ ))} +
+ )} + + {activeTab === 'my-nfts' && ( + <> + {!account ? ( +
+ +

Connect your wallet to view your NFTs

+

+ Connect your wallet to view all your NFT collections across different blockchains. +

+ +
+ ) : userNFTs.length === 0 ? ( +
+ +

No NFTs found

+

+ We couldn't find any NFTs in your wallet. If you believe this is an error, try switching networks. +

+
+ {supportedChains.map((chain) => ( + + ))} +
+
+ ) : ( +
+ {userNFTs.map((collection) => ( +
+ {renderCollectionCard(collection)} +
+ ))} +
+ )} + + )} + + )} +
+
+
+
+ ); +} diff --git a/app/api/analytics/chain-stats/route.ts b/app/api/analytics/chain-stats/route.ts new file mode 100644 index 0000000..0207696 --- /dev/null +++ b/app/api/analytics/chain-stats/route.ts @@ -0,0 +1,49 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + // Try to fetch real data from Blockchain.info + const response = await axios.get("https://api.blockchain.info/stats"); + + if (response.data) { + return NextResponse.json({ + data: { + hashRate: response.data.hash_rate, + difficulty: response.data.difficulty, + latestHeight: response.data.n_blocks_total, + unconfirmedTx: response.data.n_tx_unconfirmed, + mempool: response.data.mempool_size, + btcMined: response.data.n_btc_mined, + marketPrice: response.data.market_price_usd, + transactionRate: response.data.n_tx_per_block, + minutesBetweenBlocks: response.data.minutes_between_blocks, + totalFees: response.data.total_fees_btc + }, + timestamp: Date.now() + }); + } else { + throw new Error("Invalid response from Blockchain.info API"); + } + } catch (error) { + console.error("Failed to fetch blockchain stats:", error); + + // Return simulated data if the API call fails + return NextResponse.json({ + data: { + hashRate: 180000000000000, // 180 EH/s + difficulty: 53950000000000, + latestHeight: 820000, + unconfirmedTx: 5000, + mempool: 8500, + btcMined: 19250000, + marketPrice: 60000, + transactionRate: 2500, + minutesBetweenBlocks: 9.75, + totalFees: 1.25 + }, + timestamp: Date.now(), + simulated: true + }); + } +} \ No newline at end of file diff --git a/app/api/analytics/defi-tvl/route.ts b/app/api/analytics/defi-tvl/route.ts new file mode 100644 index 0000000..8726ecb --- /dev/null +++ b/app/api/analytics/defi-tvl/route.ts @@ -0,0 +1,79 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET(request: Request) { + try { + const url = new URL(request.url); + const protocol = url.searchParams.get('protocol') || 'all'; + + // Choose endpoint based on whether we're getting global data or protocol-specific + const endpoint = protocol === 'all' + ? 'https://api.llama.fi/charts' + : `https://api.llama.fi/protocol/${protocol}`; + + const response = await axios.get(endpoint); + + if (!response.data) { + throw new Error("Invalid response from DefiLlama API"); + } + + // For global data, we get the TVL history directly + if (protocol === 'all') { + // Get the last 30 days of data + const data = response.data.slice(-30).map((item: any) => ({ + date: new Date(item.date * 1000).toLocaleDateString(), + tvl: item.totalLiquidityUSD + })); + + return NextResponse.json({ + data, + totalTvl: data[data.length - 1]?.tvl || 0, + timestamp: Date.now() + }); + } + // For protocol data, we need to extract the TVL from the protocol object + else { + const tvlData = response.data.tvl.slice(-30).map((item: any) => ({ + date: new Date(item.date * 1000).toLocaleDateString(), + tvl: item.totalLiquidityUSD + })); + + return NextResponse.json({ + name: response.data.name, + symbol: response.data.symbol, + data: tvlData, + totalTvl: tvlData[tvlData.length - 1]?.tvl || 0, + chains: response.data.chains, + timestamp: Date.now() + }); + } + } catch (error: any) { + console.error("Error fetching DeFi TVL data:", error.message); + + // Return simulated data if the API call fails + const dates = []; + const now = new Date(); + let simulatedTvl = 150000000000; // $150B starting point + + for (let i = 30; i > 0; i--) { + const date = new Date(now); + date.setDate(now.getDate() - i); + + // Random daily change between -3% and +3% + const dailyChange = simulatedTvl * (Math.random() * 0.06 - 0.03); + simulatedTvl += dailyChange; + + dates.push({ + date: date.toLocaleDateString(), + tvl: simulatedTvl + }); + } + + return NextResponse.json({ + data: dates, + totalTvl: simulatedTvl, + timestamp: Date.now(), + simulated: true + }); + } +} diff --git a/app/api/analytics/exchange-volumes/route.ts b/app/api/analytics/exchange-volumes/route.ts new file mode 100644 index 0000000..5169d04 --- /dev/null +++ b/app/api/analytics/exchange-volumes/route.ts @@ -0,0 +1,60 @@ +import { NextResponse } from 'next/server'; +import axios from "axios"; + +export async function GET() { + try { + // Fetch real exchange data from CoinGecko's free API + const response = await axios.get( + "https://api.coingecko.com/api/v3/exchanges?per_page=5&page=1" + ); + + if (!response.data || !Array.isArray(response.data)) { + throw new Error("Invalid response from CoinGecko API"); + } + + // Map to the format our frontend expects + // Assign colors to make visualization consistent + const colors = ['#F0B90B', '#1652F0', '#1A1B1F', '#1F94E0', '#26A17B']; + + const exchangeData = { + data: response.data.map((exchange, index) => ({ + name: exchange.name, + volume: exchange.trade_volume_24h_btc * + (response.data[0].trade_volume_24h_btc > 10000 ? 1 : 60000), // Convert to USD if needed + color: colors[index % colors.length] + })), + timestamp: Date.now() + }; + + // Calculate total volume + exchangeData.data.sort((a, b) => b.volume - a.volume); + const totalVolume = exchangeData.data.reduce((sum, ex) => sum + ex.volume, 0); + + return NextResponse.json({ + ...exchangeData, + totalVolume + }, { status: 200 }); + } catch (error) { + console.error('Error in exchange volumes API:', error); + + // Fallback to simulated data if the API call fails + const exchangeData = { + data: [ + { name: 'Binance', volume: 25000000000 + (Math.random() * 5000000000), color: '#F0B90B' }, + { name: 'Coinbase', volume: 12000000000 + (Math.random() * 2000000000), color: '#1652F0' }, + { name: 'OKX', volume: 8000000000 + (Math.random() * 1000000000), color: '#1A1B1F' }, + { name: 'Huobi', volume: 5000000000 + (Math.random() * 1000000000), color: '#1F94E0' }, + { name: 'KuCoin', volume: 3000000000 + (Math.random() * 800000000), color: '#26A17B' }, + ], + timestamp: Date.now(), + simulated: true + }; + + const totalVolume = exchangeData.data.reduce((sum, ex) => sum + ex.volume, 0); + + return NextResponse.json({ + ...exchangeData, + totalVolume + }, { status: 200 }); + } +} diff --git a/app/api/analytics/fear-greed-index/route.ts b/app/api/analytics/fear-greed-index/route.ts new file mode 100644 index 0000000..27e31ce --- /dev/null +++ b/app/api/analytics/fear-greed-index/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + // Alternative Fear & Greed Index API (free, no API key required) + const response = await axios.get("https://api.alternative.me/fng/"); + + if (response.data && response.data.data && response.data.data[0]) { + const data = response.data.data[0]; + return NextResponse.json({ + value: parseInt(data.value), + valueText: data.value_classification, + timestamp: Date.now() + }); + } else { + throw new Error("Invalid response from Alternative.me API"); + } + } catch (error: any) { + console.error("Error fetching Fear & Greed Index:", error.message); + + // Return simulated data if the API call fails + const simulatedValue = Math.floor(Math.random() * 20) + 15; // Random value between 15-35 + let valueText = "Fear"; + + if (simulatedValue <= 20) valueText = "Extreme Fear"; + else if (simulatedValue <= 40) valueText = "Fear"; + else if (simulatedValue <= 60) valueText = "Neutral"; + else if (simulatedValue <= 80) valueText = "Greed"; + else valueText = "Extreme Greed"; + + return NextResponse.json({ + value: simulatedValue, + valueText, + timestamp: Date.now(), + simulated: true + }); + } +} \ No newline at end of file diff --git a/app/api/analytics/gas-prices/route.ts b/app/api/analytics/gas-prices/route.ts new file mode 100644 index 0000000..b70c0e8 --- /dev/null +++ b/app/api/analytics/gas-prices/route.ts @@ -0,0 +1,62 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + // Try ETH Gas Station API first (which is free) + const response = await axios.get('https://ethgasstation.info/api/ethgasAPI.json'); + + if (response.data) { + // ETH Gas Station returns values in tenths of Gwei, so divide by 10 + return NextResponse.json({ + slow: Math.round(response.data.safeLow / 10), + average: Math.round(response.data.average / 10), + fast: Math.round(response.data.fast / 10), + fastest: Math.round(response.data.fastest / 10), + baseFee: Math.round(response.data.avgBaseFee / 10), + timestamp: Date.now() + }); + } else { + throw new Error("Invalid response from ETH Gas Station API"); + } + } catch (error) { + try { + // Fallback to Etherscan API if available + const apiKey = process.env.ETHERSCAN_API_KEY; + + if (!apiKey) { + throw new Error("Etherscan API key is not configured"); + } + + const etherscanResponse = await axios.get( + `https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=${apiKey}` + ); + + if (etherscanResponse.data && etherscanResponse.data.status === "1" && etherscanResponse.data.result) { + const { SafeGasPrice, ProposeGasPrice, FastGasPrice, suggestBaseFee } = etherscanResponse.data.result; + + return NextResponse.json({ + slow: parseInt(SafeGasPrice), + average: parseInt(ProposeGasPrice), + fast: parseInt(FastGasPrice), + baseFee: parseFloat(suggestBaseFee), + timestamp: Date.now() + }); + } else { + throw new Error("Invalid response from Etherscan API"); + } + } catch (fallbackError) { + console.error("Error fetching gas prices:", fallbackError); + + // Return simulated data if both API calls fail + return NextResponse.json({ + slow: Math.floor(Math.random() * 20) + 10, + average: Math.floor(Math.random() * 30) + 25, + fast: Math.floor(Math.random() * 50) + 50, + baseFee: (Math.random() * 10 + 5).toFixed(2), + timestamp: Date.now(), + simulated: true + }); + } + } +} diff --git a/app/api/analytics/market-sentiment/route.ts b/app/api/analytics/market-sentiment/route.ts new file mode 100644 index 0000000..b453681 --- /dev/null +++ b/app/api/analytics/market-sentiment/route.ts @@ -0,0 +1,51 @@ +import { NextResponse } from 'next/server'; +import axios from 'axios'; + +export async function GET() { + try { + // Try to fetch real Fear & Greed Index data + // This is an unofficial API endpoint that provides the data + const response = await axios.get('https://api.alternative.me/fng/'); + + if (response.data && response.data.data && response.data.data[0]) { + const fngData = response.data.data[0]; + + // Map the fear & greed value to our format + // F&G index is 0-100, where 0 is extreme fear and 100 is extreme greed + const sentimentData = { + score: parseInt(fngData.value), + // Add some variance for social and news, but keep them related to the main score + socialMediaScore: Math.min(Math.max(parseInt(fngData.value) + Math.floor(Math.random() * 20) - 10, 0), 100), + newsScore: Math.min(Math.max(parseInt(fngData.value) + Math.floor(Math.random() * 20) - 10, 0), 100), + classification: fngData.value_classification, + redditMentions: Math.floor(Math.random() * 30000) + 10000, // Still simulated + twitterMentions: Math.floor(Math.random() * 100000) + 50000, // Still simulated + timestamp: Date.now() + }; + + return NextResponse.json(sentimentData, { status: 200 }); + } else { + throw new Error("Invalid response from Fear & Greed API"); + } + } catch (error) { + console.error('Error in market sentiment API:', error); + + // Return simulated data if the API call fails + const baseScore = Math.random() < 0.6 ? + Math.floor(Math.random() * 20) + 45 : + Math.floor(Math.random() * 40) + 30; + + const socialMediaVariance = Math.floor(Math.random() * 20) - 10; + const newsVariance = Math.floor(Math.random() * 20) - 10; + + return NextResponse.json({ + score: baseScore, + socialMediaScore: Math.min(Math.max(baseScore + socialMediaVariance, 0), 100), + newsScore: Math.min(Math.max(baseScore + newsVariance, 0), 100), + redditMentions: Math.floor(Math.random() * 30000) + 10000, + twitterMentions: Math.floor(Math.random() * 100000) + 50000, + timestamp: Date.now(), + simulated: true + }, { status: 200 }); + } +} diff --git a/app/api/analytics/nft-stats/route.ts b/app/api/analytics/nft-stats/route.ts new file mode 100644 index 0000000..5e9e1d8 --- /dev/null +++ b/app/api/analytics/nft-stats/route.ts @@ -0,0 +1,85 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + const apiKey = process.env.MORALIS_API_KEY; + + if (!apiKey) { + throw new Error("Moralis API key is not configured"); + } + + // Get NFT collections stats using Moralis API + const response = await axios.get( + "https://deep-index.moralis.io/api/v2/nft/collections/stats", + { + headers: { + "X-API-Key": apiKey, + }, + params: { + limit: 5, + chain: "eth" + } + } + ); + + if (response.data && response.data.result) { + return NextResponse.json({ + collections: response.data.result, + timestamp: Date.now() + }); + } else { + throw new Error("Invalid response from Moralis API"); + } + } catch (error: any) { + console.error("Error fetching NFT stats:", error.message); + + // Return simulated data if API call fails + return NextResponse.json({ + collections: [ + { + name: "Bored Ape Yacht Club", + symbol: "BAYC", + floorPrice: 72.45, + volume24h: 456.32, + totalVolume: 890745.23, + owners: 6213 + }, + { + name: "CryptoPunks", + symbol: "PUNK", + floorPrice: 64.21, + volume24h: 387.16, + totalVolume: 754221.54, + owners: 3311 + }, + { + name: "Azuki", + symbol: "AZUKI", + floorPrice: 14.62, + volume24h: 165.84, + totalVolume: 246731.12, + owners: 5120 + }, + { + name: "Doodles", + symbol: "DOODLE", + floorPrice: 8.75, + volume24h: 94.36, + totalVolume: 124563.74, + owners: 4892 + }, + { + name: "Moonbirds", + symbol: "MOONBIRD", + floorPrice: 6.32, + volume24h: 57.91, + totalVolume: 89471.65, + owners: 7854 + } + ], + timestamp: Date.now(), + simulated: true + }); + } +} diff --git a/app/api/analytics/trending-coins/route.ts b/app/api/analytics/trending-coins/route.ts new file mode 100644 index 0000000..3eae21e --- /dev/null +++ b/app/api/analytics/trending-coins/route.ts @@ -0,0 +1,43 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + // CoinGecko API is free and doesn't require an API key + const response = await axios.get("https://api.coingecko.com/api/v3/search/trending"); + + if (response.data && response.data.coins) { + const trendingCoins = response.data.coins.slice(0, 7).map((item: any) => ({ + id: item.item.id, + name: item.item.name, + symbol: item.item.symbol, + thumb: item.item.thumb, + price_btc: item.item.price_btc, + market_cap_rank: item.item.market_cap_rank, + score: item.item.score + })); + + return NextResponse.json({ + coins: trendingCoins, + timestamp: Date.now() + }); + } else { + throw new Error("Invalid response from CoinGecko API"); + } + } catch (error: any) { + console.error("Error fetching trending coins:", error.message); + + // Return simulated data if the API call fails + return NextResponse.json({ + coins: [ + { id: 'bitcoin', name: 'Bitcoin', symbol: 'BTC', market_cap_rank: 1, price_btc: 1, thumb: '/icons/btc.svg', score: 0 }, + { id: 'ethereum', name: 'Ethereum', symbol: 'ETH', market_cap_rank: 2, price_btc: 0.05, thumb: '/icons/eth.svg', score: 0 }, + { id: 'solana', name: 'Solana', symbol: 'SOL', market_cap_rank: 5, price_btc: 0.0012, thumb: '/icons/sol.svg', score: 0 }, + { id: 'cardano', name: 'Cardano', symbol: 'ADA', market_cap_rank: 8, price_btc: 0.00002, thumb: '/icons/ada.svg', score: 0 }, + { id: 'polkadot', name: 'Polkadot', symbol: 'DOT', market_cap_rank: 12, price_btc: 0.00018, thumb: '/icons/dot.svg', score: 0 }, + ], + timestamp: Date.now(), + simulated: true + }); + } +} diff --git a/app/api/analytics/whale-alerts/route.ts b/app/api/analytics/whale-alerts/route.ts new file mode 100644 index 0000000..e094515 --- /dev/null +++ b/app/api/analytics/whale-alerts/route.ts @@ -0,0 +1,151 @@ +import { NextResponse } from 'next/server'; +import axios from 'axios'; + +export async function GET(req: Request) { + try { + const apiKey = process.env.ETHERSCAN_API_KEY; + + if (!apiKey) { + throw new Error("Etherscan API key is not configured"); + } + + // Extract query parameters + const url = new URL(req.url); + const limit = Number(url.searchParams.get('limit')) || 10; + + // Get recent ETH transactions from a block + // We'll take the most recent block and look for large transactions + const blockNumberResponse = await axios.get( + `https://api.etherscan.io/api?module=proxy&action=eth_blockNumber&apikey=${apiKey}` + ); + + if (!blockNumberResponse.data || !blockNumberResponse.data.result) { + throw new Error("Failed to get latest block number"); + } + + const blockNumber = parseInt(blockNumberResponse.data.result, 16); + + // Get block details including transactions + const blockResponse = await axios.get( + `https://api.etherscan.io/api?module=proxy&action=eth_getBlockByNumber&tag=0x${blockNumber.toString(16)}&boolean=true&apikey=${apiKey}` + ); + + if (!blockResponse.data || !blockResponse.data.result || !blockResponse.data.result.transactions) { + throw new Error("Failed to get block details"); + } + + // Extract and filter large transactions (> 10 ETH) + const transactions = blockResponse.data.result.transactions + .filter((tx: any) => { + const valueInEth = parseInt(tx.value, 16) / 1e18; + return valueInEth > 10; // Only transactions > 10 ETH + }) + .map((tx: any) => { + const valueInEth = parseInt(tx.value, 16) / 1e18; + const timestamp = parseInt(blockResponse.data.result.timestamp, 16) * 1000; + + return { + id: tx.hash, + symbol: 'ETH', + amount: parseFloat(valueInEth.toFixed(2)), + value: valueInEth * 3000, // Approximate USD value + from: tx.from, + to: tx.to, + type: 'transfer', + timestamp + }; + }) + .slice(0, limit); + + if (transactions.length > 0) { + const whaleData = { + transactions, + totalValue: transactions.reduce((sum: number, tx: { value: number }) => sum + tx.value, 0), + timestamp: Date.now() + }; + + return NextResponse.json(whaleData, { status: 200 }); + } + + // If no large transactions found or not enough, throw to use simulated data + throw new Error("Not enough large transactions found in latest block"); + } catch (error) { + console.error('Error in whale alerts API:', error); + + // Define mock transaction data as fallback + const cryptoAddresses = [ + '0x6b75d8af000000e20b7a7ddf000ba0d00b', + '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', + '0x73bceb1cd57c711feac4224d062b0f6ff338501e', + 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', + '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + ]; + + const exchanges = [ + 'Binance', + 'Coinbase', + 'Kraken', + 'Huobi', + 'KuCoin', + 'FTX', + 'Unknown' + ]; + + const cryptoSymbols = ['BTC', 'ETH', 'BNB', 'SOL', 'USDT', 'USDC']; + + // Extract query parameters + const url = new URL(req.url); + const limit = Number(url.searchParams.get('limit')) || 10; + + // Generate simulated whale transactions + const transactions = Array.from({ length: limit }, (_, i) => { + const isFromExchange = Math.random() > 0.5; + const isToExchange = !isFromExchange && Math.random() > 0.7; + const symbol = cryptoSymbols[Math.floor(Math.random() * cryptoSymbols.length)]; + const amount = symbol === 'BTC' ? + Math.random() * 500 + 50 : + symbol === 'ETH' ? + Math.random() * 5000 + 500 : + Math.random() * 100000 + 10000; + + const baseValue = + symbol === 'BTC' ? amount * 60000 : + symbol === 'ETH' ? amount * 3500 : + symbol === 'BNB' ? amount * 600 : + symbol === 'SOL' ? amount * 150 : + amount; + + // Add some randomness to value + const value = baseValue * (0.9 + Math.random() * 0.2); + + const timestamp = Date.now() - Math.floor(Math.random() * 24 * 60 * 60 * 1000); + + return { + id: `tx_${Date.now()}_${i}`, + symbol, + amount: parseFloat(amount.toFixed(symbol === 'BTC' ? 2 : symbol === 'ETH' ? 1 : 0)), + value: parseFloat(value.toFixed(0)), + from: isFromExchange ? + exchanges[Math.floor(Math.random() * (exchanges.length - 1))] : + cryptoAddresses[Math.floor(Math.random() * cryptoAddresses.length)], + to: isToExchange ? + exchanges[Math.floor(Math.random() * (exchanges.length - 1))] : + cryptoAddresses[Math.floor(Math.random() * cryptoAddresses.length)], + type: isFromExchange ? 'withdrawal' : isToExchange ? 'deposit' : 'transfer', + timestamp + }; + }); + + // Sort by most recent first + transactions.sort((a, b) => b.timestamp - a.timestamp); + + const whaleData = { + transactions, + totalValue: transactions.reduce((sum: number, tx: { value: number }) => sum + tx.value, 0), + timestamp: Date.now(), + simulated: true + }; + + return NextResponse.json(whaleData, { status: 200 }); + } +} diff --git a/app/api/binance/klines/route.ts b/app/api/binance/klines/route.ts new file mode 100644 index 0000000..f7d4d2d --- /dev/null +++ b/app/api/binance/klines/route.ts @@ -0,0 +1,82 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET(request: Request) { + try { + const url = new URL(request.url); + const symbol = url.searchParams.get("symbol"); + const interval = url.searchParams.get("interval") || "1d"; + const limit = url.searchParams.get("limit") || "30"; + + if (!symbol) { + return new NextResponse( + JSON.stringify({ error: "Symbol parameter is required" }), + { status: 400 } + ); + } + + // Binance API doesn't require auth for basic endpoints + const response = await axios.get( + "https://api.binance.com/api/v3/klines", + { + params: { + symbol: `${symbol.toUpperCase()}USDT`, + interval, + limit + } + } + ); + + // Transform to the format our charts expect + const prices = response.data.map((kline: any) => [kline[0], parseFloat(kline[4])]); + + // Estimate market cap and volume + const market_cap_multiplier = getMarketCapMultiplier(symbol); + const market_caps = prices.map(([time, price]: [number, number]) => + [time, price * market_cap_multiplier] + ); + + const volumes = response.data.map((kline: any) => + [kline[0], parseFloat(kline[5]) * parseFloat(kline[4])] + ); + + return NextResponse.json({ + prices, + market_caps, + total_volumes: volumes + }); + } catch (error: any) { + console.error("Binance API Error:", error.response?.data || error.message); + + if (error.response?.status === 400 && error.response?.data?.msg?.includes('Invalid symbol')) { + const url = new URL(request.url); + console.warn(`Symbol ${url.searchParams.get("symbol")}USDT not found on Binance`); + return new NextResponse( + JSON.stringify({ + error: "Invalid symbol", + details: "The requested trading pair does not exist on Binance" + }), + { status: 400 } + ); + } + + return new NextResponse( + JSON.stringify({ + error: "Failed to fetch data from Binance", + details: error.response?.data?.msg || error.message + }), + { status: error.response?.status || 500 } + ); + } +} + +function getMarketCapMultiplier(symbol: string): number { + // Rough estimates of circulating supply for major coins + switch (symbol.toLowerCase()) { + case 'btc': return 19500000; // ~19.5M BTC in circulation + case 'eth': return 120000000; // ~120M ETH in circulation + case 'bnb': return 153000000; // ~153M BNB in circulation + case 'sol': return 430000000; // ~430M SOL in circulation + default: return 100000000; // Default fallback + } +} diff --git a/app/api/coinmarketcap/global-metrics/route.ts b/app/api/coinmarketcap/global-metrics/route.ts new file mode 100644 index 0000000..a70f1ed --- /dev/null +++ b/app/api/coinmarketcap/global-metrics/route.ts @@ -0,0 +1,45 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + // Using CoinGecko's free global data endpoint instead of CoinMarketCap + const response = await axios.get( + "https://api.coingecko.com/api/v3/global" + ); + + if (!response.data || !response.data.data) { + throw new Error("Invalid response from CoinGecko API"); + } + + // Transform CoinGecko response to match the expected format + const data = { + status: { + timestamp: new Date().toISOString(), + error_code: 0, + error_message: null, + }, + data: { + active_cryptocurrencies: response.data.data.active_cryptocurrencies, + total_cryptocurrencies: response.data.data.active_cryptocurrencies, // CoinGecko doesn't provide total count + total_market_cap: response.data.data.total_market_cap, + total_volume_24h: response.data.data.total_volume, + btc_dominance: response.data.data.market_cap_percentage.btc, + eth_dominance: response.data.data.market_cap_percentage.eth, + market_cap_change_24h: response.data.data.market_cap_change_percentage_24h_usd, + } + }; + + return NextResponse.json(data); + } catch (error: any) { + console.error("CoinGecko API Error:", error.message); + + return new NextResponse( + JSON.stringify({ + error: "Failed to fetch data from CoinGecko", + details: error.response?.data?.error || error.message + }), + { status: error.response?.status || 500 } + ); + } +} diff --git a/app/api/coinmarketcap/historical/route.ts b/app/api/coinmarketcap/historical/route.ts new file mode 100644 index 0000000..0d52593 --- /dev/null +++ b/app/api/coinmarketcap/historical/route.ts @@ -0,0 +1,92 @@ +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + const url = new URL(request.url); + const symbol = url.searchParams.get("symbol") || "BTC"; + const time_period = url.searchParams.get("time_period") || "30d"; + const days = parseInt(time_period.replace('d', '')) || 30; + + console.log(`Historical data requested for ${symbol} over ${days} days`); + console.warn("CoinMarketCap free API does not support historical data, returning simulated data"); + + // Generate simulated data + const data = generateMockHistoricalData(symbol, days); + + return NextResponse.json(data); + } catch (error: any) { + console.error("Error in historical data endpoint:", error.message); + + return new NextResponse( + JSON.stringify({ + error: "Failed to generate historical data", + details: error.message + }), + { status: 500 } + ); + } +} + +function generateMockHistoricalData(symbol: string, days: number) { + // Start with a base price depending on the symbol + let basePrice = 0; + switch (symbol.toLowerCase()) { + case 'btc': basePrice = 60000; break; + case 'eth': basePrice = 3000; break; + case 'sol': basePrice = 130; break; + case 'bnb': basePrice = 550; break; + default: basePrice = 100; + } + + const prices = []; + const market_caps = []; + const total_volumes = []; + + const now = Date.now(); + const oneDayMs = 24 * 60 * 60 * 1000; + + // Create a consistently upward or downward trend for the overall chart + const trendDirection = Math.random() > 0.5 ? 1 : -1; + const trendStrength = Math.random() * 0.01 + 0.005; // Between 0.5% and 1.5% daily trend + + for (let i = days; i >= 0; i--) { + const timestamp = now - (i * oneDayMs); + + // Add some random fluctuation plus the overall trend + const dailyTrend = trendDirection * trendStrength * basePrice * (days - i) / days; + const randomChange = basePrice * (Math.random() * 0.06 - 0.03); // Random -3% to +3% + basePrice += randomChange + dailyTrend; + + if (basePrice < 0) basePrice = Math.abs(randomChange); // Prevent negative prices + + prices.push([timestamp, basePrice]); + market_caps.push([timestamp, basePrice * getMarketCapMultiplier(symbol)]); + total_volumes.push([timestamp, basePrice * getVolumeMultiplier(symbol) * (0.7 + Math.random() * 0.6)]); + } + + return { + prices, + market_caps, + total_volumes + }; +} + +function getMarketCapMultiplier(symbol: string): number { + // Rough estimates of circulating supply for major coins + switch (symbol.toLowerCase()) { + case 'btc': return 19500000; // ~19.5M BTC in circulation + case 'eth': return 120000000; // ~120M ETH in circulation + case 'bnb': return 153000000; // ~153M BNB in circulation + case 'sol': return 430000000; // ~430M SOL in circulation + default: return 100000000; // Default fallback + } +} + +function getVolumeMultiplier(symbol: string): number { + // Volume multipliers based on typical daily trading volume as % of market cap + switch (symbol.toLowerCase()) { + case 'btc': return 500000; // Higher volume for BTC + case 'eth': return 300000; + default: return 200000; + } +} diff --git a/app/api/coinmarketcap/listings/route.ts b/app/api/coinmarketcap/listings/route.ts new file mode 100644 index 0000000..37f163c --- /dev/null +++ b/app/api/coinmarketcap/listings/route.ts @@ -0,0 +1,61 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET(request: Request) { + try { + const apiKey = process.env.COINMARKETCAP_API_KEY; + const url = new URL(request.url); + const limit = url.searchParams.get("limit") || "10"; + const sort = url.searchParams.get("sort") || "market_cap"; + const sort_dir = url.searchParams.get("sort_dir") || "desc"; + + if (!apiKey) { + console.warn("CoinMarketCap API key is not configured"); + return new NextResponse( + JSON.stringify({ error: "API key is not configured" }), + { status: 500 } + ); + } + + const response = await axios.get( + "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", + { + headers: { + "X-CMC_PRO_API_KEY": apiKey, + }, + params: { + limit, + sort, + sort_dir, + convert: "USD" + } + } + ); + + return NextResponse.json(response.data); + } catch (error: any) { + // Log detailed error for debugging + if (error.response?.data) { + const errorData = error.response.data; + console.error(`CoinMarketCap API Error (${error.response.status}):`, errorData); + + // Check for specific error codes from CoinMarketCap + if (errorData.status?.error_code === 1002) { + console.warn("CoinMarketCap API Key is invalid or authorization failed"); + } else if (errorData.status?.error_code === 1006) { + console.warn("CoinMarketCap API request exceeds available plan limit"); + } + } else { + console.error("CoinMarketCap API Error:", error.message); + } + + return new NextResponse( + JSON.stringify({ + error: "Failed to fetch data from CoinMarketCap", + details: error.response?.data?.status?.error_message || error.message, + code: error.response?.data?.status?.error_code + }), + { status: error.response?.status || 500 } + ); + } +} diff --git a/app/api/coinmarketcap/route.ts b/app/api/coinmarketcap/route.ts new file mode 100644 index 0000000..bba2835 --- /dev/null +++ b/app/api/coinmarketcap/route.ts @@ -0,0 +1,97 @@ +import { NextResponse } from 'next/server'; + +const CMC_API_KEY = process.env.COINMARKETCAP_API_KEY; +const CMC_API_URL = 'https://pro-api.coinmarketcap.com/v1'; + +export async function GET(request: Request) { + const url = new URL(request.url); + const endpoint = url.pathname.split('/coinmarketcap/')[1]; + + try { + if (!CMC_API_KEY) { + console.warn('CoinMarketCap API key not configured, using fallback data'); + throw new Error('API key not configured'); + } + + // Select the appropriate endpoint + let apiEndpoint = ''; + switch (endpoint) { + case 'global-metrics': + apiEndpoint = `${CMC_API_URL}/global-metrics/quotes/latest`; + break; + case 'listings': + apiEndpoint = `${CMC_API_URL}/cryptocurrency/listings/latest`; + break; + default: + throw new Error('Invalid endpoint'); + } + + // Forward query parameters + const queryParams = new URLSearchParams(); + url.searchParams.forEach((value, key) => { + queryParams.append(key, value); + }); + + // Add default parameters if not provided + if (!queryParams.has('convert')) { + queryParams.append('convert', 'USD'); + } + + const response = await fetch(`${apiEndpoint}?${queryParams}`, { + headers: { + 'X-CMC_PRO_API_KEY': CMC_API_KEY, + 'Accept': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`CoinMarketCap API error: ${response.status}`); + } + + const data = await response.json(); + + return NextResponse.json(data); + } catch (error: any) { + console.error('CoinMarketCap API proxy error:', error); + + // Return fallback data based on the endpoint + if (url.pathname.includes('global-metrics')) { + return NextResponse.json({ + data: { + quote: { + USD: { + total_market_cap: 2300000000000, + total_volume_24h: 115000000000, + total_market_cap_yesterday_percentage_change: 2.5 + } + }, + total_cryptocurrencies: 10423, + active_market_pairs: 814, + btc_dominance: 48.5, + eth_dominance: 17.3, + last_updated: new Date().toISOString() + } + }); + } else { + return NextResponse.json({ + data: [ + { + id: 1, + name: 'Bitcoin', + symbol: 'BTC', + cmc_rank: 1, + quote: { + USD: { + price: 65000, + market_cap: 1300000000000, + volume_24h: 28000000000, + percent_change_24h: 2.5 + } + } + }, + // Add more fallback tokens as needed + ] + }); + } + } +} \ No newline at end of file diff --git a/app/api/market/altcoin-season/route.ts b/app/api/market/altcoin-season/route.ts new file mode 100644 index 0000000..b9deebb --- /dev/null +++ b/app/api/market/altcoin-season/route.ts @@ -0,0 +1,100 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +// Calculate Altcoin Season Index based on BTC dominance and altcoin performance +export async function GET() { + try { + // Step 1: Get BTC dominance from Binance API (uses CoinMarketCap data) + const dominanceResponse = await axios.get(`https://api.binance.com/api/v3/ticker/24hr?symbol=BTCUSDT`); + + // Step 2: Get data for top altcoins + const altcoinsSymbols = ["ETHUSDT", "BNBUSDT", "SOLUSDT", "ADAUSDT"]; + const altcoinPromises = altcoinsSymbols.map(symbol => + axios.get(`https://api.binance.com/api/v3/ticker/24hr?symbol=${symbol}`) + ); + + const altcoinResponses = await Promise.allSettled(altcoinPromises); + + // Step 3: Calculate altcoin performance + let altcoinPerformance = 0; + let successfulResponses = 0; + + altcoinResponses.forEach(response => { + if (response.status === 'fulfilled' && response.value.data) { + const priceChangePercent = parseFloat(response.value.data.priceChangePercent); + if (!isNaN(priceChangePercent)) { + altcoinPerformance += priceChangePercent; + successfulResponses++; + } + } + }); + + if (successfulResponses > 0) { + altcoinPerformance /= successfulResponses; + } + + // Step 4: Get BTC dominance from CoinMarketCap if possible + let btcDominance = 60; // Default value if we can't get real data + + try { + const cmcResponse = await axios.get('/api/coinmarketcap/global-metrics'); + if (cmcResponse.data && cmcResponse.data.data && cmcResponse.data.data.btc_dominance) { + btcDominance = cmcResponse.data.data.btc_dominance; + } + } catch (error) { + console.error("Could not fetch BTC dominance from CoinMarketCap", error); + } + + // Step 5: Calculate Altcoin Season Index (0-100 scale) + // Higher BTC dominance means less altcoin season + // Higher altcoin performance relative to BTC means more altcoin season + const btcPerformance = parseFloat(dominanceResponse.data.priceChangePercent); + + // Base index on BTC dominance (inverted, scaled to 0-75) + let altcoinIndex = 100 - Math.min(100, Math.max(0, btcDominance * 1.25)); + + // Adjust based on relative performance of altcoins vs BTC (adds or subtracts up to 25 points) + if (!isNaN(btcPerformance) && !isNaN(altcoinPerformance)) { + const performanceDiff = altcoinPerformance - btcPerformance; + altcoinIndex += Math.min(25, Math.max(-25, performanceDiff * 2)); + } + + // Ensure the index stays within 0-100 range + altcoinIndex = Math.min(100, Math.max(0, altcoinIndex)); + + // Calculate the season text + let seasonText = "Neutral"; + if (altcoinIndex < 25) seasonText = "Bitcoin Season"; + else if (altcoinIndex < 45) seasonText = "Bitcoin Favored"; + else if (altcoinIndex < 55) seasonText = "Neutral"; + else if (altcoinIndex < 75) seasonText = "Altcoin Favored"; + else seasonText = "Altcoin Season"; + + return NextResponse.json({ + value: Math.round(altcoinIndex), + valueText: seasonText, + btcDominance: btcDominance, + timestamp: Date.now() / 1000 + }); + } catch (error) { + console.error("Error calculating Altcoin Season Index:", error); + + // If API calls fail, return simulated data + const simulatedValue = Math.floor(Math.random() * 100) + 1; + let valueText = "Neutral"; + + if (simulatedValue <= 25) valueText = "Bitcoin Season"; + else if (simulatedValue < 45) valueText = "Bitcoin Favored"; + else if (simulatedValue < 55) valueText = "Neutral"; + else if (simulatedValue < 75) valueText = "Altcoin Favored"; + else valueText = "Altcoin Season"; + + return NextResponse.json({ + value: simulatedValue, + valueText, + btcDominance: 60 + (Math.random() * 10 - 5), + timestamp: Date.now() / 1000, + simulated: true + }); + } +} diff --git a/app/api/market/fear-greed/route.ts b/app/api/market/fear-greed/route.ts new file mode 100644 index 0000000..66ebcd8 --- /dev/null +++ b/app/api/market/fear-greed/route.ts @@ -0,0 +1,38 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; + +export async function GET() { + try { + // The Alternative.me Fear & Greed Index API is free and doesn't require an API key + const response = await axios.get("https://api.alternative.me/fng/"); + + if (response.data && response.data.data && response.data.data[0]) { + return NextResponse.json({ + value: parseInt(response.data.data[0].value), + valueText: response.data.data[0].value_classification, + timestamp: response.data.data[0].timestamp + }); + } else { + throw new Error("Invalid response format from Fear & Greed API"); + } + } catch (error) { + console.error("Error fetching Fear & Greed Index:", error); + + // If the API call fails, generate a realistic simulated value + const simulatedValue = Math.floor(Math.random() * 100) + 1; + let valueText = "Neutral"; + + if (simulatedValue <= 25) valueText = "Extreme Fear"; + else if (simulatedValue <= 40) valueText = "Fear"; + else if (simulatedValue <= 60) valueText = "Neutral"; + else if (simulatedValue <= 80) valueText = "Greed"; + else valueText = "Extreme Greed"; + + return NextResponse.json({ + value: simulatedValue, + valueText, + timestamp: Date.now() / 1000, + simulated: true + }); + } +} diff --git a/app/market-overview/page.tsx b/app/market-overview/page.tsx new file mode 100644 index 0000000..9cfc5c4 --- /dev/null +++ b/app/market-overview/page.tsx @@ -0,0 +1,353 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import ParticlesBackground from '@/components/ParticlesBackground'; +import { + ArrowUpRight, Clock, Info, ChevronDown +} from 'lucide-react'; +import { toast } from "sonner"; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Skeleton } from '@/components/ui/skeleton'; +import Link from 'next/link'; +import { + fetchGlobalMarketData, + fetchTopCryptocurrencies, + fetchHistoricalData +} from '@/services/coinMarketCapService'; +import CryptoCard from '@/components/market-overview/CryptoCard'; +import MarketCapChart from '@/components/market-overview/MarketCapChart'; +import DominanceCard from '@/components/market-overview/DominanceCard'; +import FearGreedIndex from '@/components/market-overview/FearGreedIndex'; +import AltcoinIndex from '@/components/market-overview/AltcoinIndex'; +import MarketIndexCard from '@/components/market-overview/MarketIndexCard'; +import WhaleAlertsCard from '@/components/market-overview/WhaleAlertsCard'; +import TrendingCoinsCard from '@/components/market-overview/TrendingCoinsCard'; +import BlockchainStatsCard from '@/components/market-overview/BlockchainStatsCard'; +import GasPriceCard from '@/components/market-overview/GasPriceCard'; +import ExchangeVolumeCard from '@/components/market-overview/ExchangeVolumeCard'; +import DefiTvlCard from '@/components/market-overview/DefiTvlCard'; +import NftStatsCard from '@/components/market-overview/NftStatsCard'; +import MarketSentimentCard from '@/components/market-overview/MarketSentimentCard'; + +export default function MarketOverview() { + const [marketData, setMarketData] = useState(null); + const [topTokens, setTopTokens] = useState([]); + const [loading, setLoading] = useState(true); + const [chartTimeframe, setChartTimeframe] = useState('30d'); + const [btcHistoricalData, setBtcHistoricalData] = useState(null); + const [dataLastUpdated, setDataLastUpdated] = useState(null); + const [dataSource, setDataSource] = useState('live'); + + useEffect(() => { + const fetchMarketData = async () => { + setLoading(true); + try { + // Fetch global market data from CoinMarketCap + const data = await fetchGlobalMarketData(); + setMarketData(data); + + // Fetch top tokens from CoinMarketCap + const tokens = await fetchTopCryptocurrencies(5); + setTopTokens(tokens); + + // Try Binance API first for BTC historical data + try { + const btcData = await fetch(`/api/binance/klines?symbol=BTC&interval=1d&limit=30`) + .then(res => { + if (!res.ok) throw new Error('Failed to fetch from Binance'); + return res.json(); + }); + + setBtcHistoricalData(btcData); + setDataSource('binance'); + } catch (binanceError) { + console.warn('Failed to fetch from Binance, using simulated data:', binanceError); + + // Fallback to simulated data + const btcData = await fetchHistoricalData('BTC', 30); + setBtcHistoricalData(btcData); + setDataSource('simulated'); + } + + setDataLastUpdated(new Date()); + } catch (error) { + console.error('Error fetching market data:', error); + toast.error('Failed to load market data. Using backup data.'); + + // Set simulated data as fallback + const data = { + total_market_cap: { usd: 1000000000000 }, + total_volume: { usd: 50000000000 }, + market_cap_percentage: { btc: 60, eth: 20 }, + active_cryptocurrencies: 5000, + markets: 10000 + }; + setMarketData(data); + + const tokens = [ + { id: 1, name: 'Bitcoin', symbol: 'BTC', current_price: 50000, price_change_percentage_24h: 2 }, + { id: 2, name: 'Ethereum', symbol: 'ETH', current_price: 4000, price_change_percentage_24h: 3 }, + { id: 3, name: 'Binance Coin', symbol: 'BNB', current_price: 600, price_change_percentage_24h: 1 }, + { id: 4, name: 'Cardano', symbol: 'ADA', current_price: 2, price_change_percentage_24h: -1 }, + { id: 5, name: 'Solana', symbol: 'SOL', current_price: 150, price_change_percentage_24h: 4 } + ]; + setTopTokens(tokens); + + const btcData = await fetchHistoricalData('BTC', 30); + setBtcHistoricalData(btcData); + + setDataLastUpdated(new Date()); + } finally { + setLoading(false); + } + }; + + fetchMarketData(); + + // Refresh data every 5 minutes instead of 2 to avoid API rate limits + const interval = setInterval(fetchMarketData, 5 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + // Format large numbers + const formatNumber = (num: number, decimals = 2): string => { + if (num >= 1000000000000) { + return `$${(num / 1000000000000).toFixed(decimals)}T`; + } else if (num >= 1000000000) { + return `$${(num / 1000000000).toFixed(decimals)}B`; + } else if (num >= 1000000) { + return `$${(num / 1000000).toFixed(decimals)}M`; + } else if (num >= 1000) { + return `$${(num / 1000).toFixed(decimals)}K`; + } else { + return `$${num.toFixed(decimals)}`; + } + }; + + const renderSkeleton = () => ( +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ +
+ + +
+ +
+ + +
+
+ ); + + return ( +
+ + +
+
+
+

Crypto Market Overview

+
+
+ + {dataLastUpdated ? + `Updated ${dataLastUpdated.toLocaleTimeString()}` : + 'Fetching data...'} +
+ + + {dataSource === 'simulated' && ( + + Simulated Data + + )} +
+
+

+ Live cryptocurrency market data powered by Binance and CoinMarketCap APIs, including Bitcoin price, market dominance, and sentiment indicators. +

+
+ + {loading ? ( + renderSkeleton() + ) : marketData && topTokens.length > 0 ? ( + <> + {/* Top Cryptocurrencies Cards */} +
+ {topTokens.map((token, index) => ( + + ))} +
+ +
+ {/* Bitcoin Price Chart (2/3 width) */} +
+ + + Bitcoin Price +
+ + + 24h + 7d + 30d + 1y + + +
+
+ +
+
+
Price
+
+ {btcHistoricalData && btcHistoricalData.prices && btcHistoricalData.prices.length > 0 + ? `$${Number(btcHistoricalData.prices[btcHistoricalData.prices.length-1][1]).toLocaleString()}` + : `$${formatNumber(60000)}`} +
+
+
+
24h Volume
+
+ {btcHistoricalData && btcHistoricalData.total_volumes && btcHistoricalData.total_volumes.length > 0 + ? formatNumber(btcHistoricalData.total_volumes[btcHistoricalData.total_volumes.length-1][1]) + : formatNumber(30000000000)} +
+
+
+ + +
+
+
+ + {/* Bitcoin Dominance (1/3 width) */} +
+ +
+
+ + {/* Analytics Section 1: Market Sentiment */} +
+ {/* Fear & Greed Index */} +
+ +
+ + {/* Altcoin Season Index */} +
+ +
+ + {/* Market Index */} +
+ +
+
+ + {/* Analytics Section 2: Market Activity */} +
+ {/* Whale Alerts */} +
+ +
+ + {/* Trending Coins */} +
+ +
+
+ + {/* Analytics Section 3: Market Metrics */} +
+ + + +
+ + {/* Analytics Section 4: DeFi & NFT Metrics */} +
+ + +
+ + {/* Analytics Section 5: Additional Market Stats */} +
+ + + + + Market Statistics + + + + +
+
Active Cryptocurrencies
+
+ {marketData?.active_cryptocurrencies?.toLocaleString() || '0'} +
+
+
+
Active Markets
+
+ {marketData?.markets?.toLocaleString() || '0'} +
+
+
+
BTC Market Cap
+
+ {formatNumber((marketData?.total_market_cap?.usd || 0) * ((marketData?.market_cap_percentage?.btc || 0) / 100))} +
+
+
+
ETH Market Cap
+
+ {formatNumber((marketData?.total_market_cap?.usd || 0) * ((marketData?.market_cap_percentage?.eth || 0) / 100))} +
+
+
+
+
+ +
+ +
+ View Detailed Price Table + +
+ +
+ + ) : ( +
+

+ Unable to load market data. Please try again later. +

+
+ )} +
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index ef3d4f3..0b912ea 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,19 +1,36 @@ 'use client'; - import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; import Image from 'next/image'; +import { motion } from 'framer-motion'; import { FaFacebookF, FaGithub, FaLinkedinIn } from 'react-icons/fa'; import ParticlesBackground from '@/components/ParticlesBackground'; +import { Card, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { ArrowRight, TrendingUp, Wallet, Box } from 'lucide-react'; +import DemoShowcase from '@/components/home/DemoShowcase'; import EthPriceLine from '@/components/home/EthPriceLine'; import CryptoPathExplorer from '@/components/home/CryptoExplorer'; import TrendingProjects from '@/components/home/TrendingProjects'; import TrendingNFTCollections from '@/components/home/TrendingNFTs'; +import PartnerBar from '@/components/PartnerBar'; import FAQ from './FAQ'; import AOS from 'aos'; import 'aos/dist/aos.css'; -import PartnerBar from '@/components/PartnerBar'; import toast from 'react-hot-toast'; +// FeatureCardProps interface should include language +interface FeatureCardProps { + icon: React.ReactNode; + title: string; + description: string; + href: string; + imageUrl?: string; + delay: number; + language: Language; // Add language prop +} + +// Tab types type Tab = 'sgd' | 'web3'; // Language types @@ -66,7 +83,22 @@ const translations = { pleaseEnterEmail: "Please enter your email address", pleaseEnterValidEmail: "Please enter a valid email address", errorOccurred: "An error occurred while registering!", - registrationSuccessful: "Registration successful! Please check your email." + registrationSuccessful: "Registration successful! Please check your email.", + exploreFuture: "Explore the Future of Blockchain", + cryptoPathProvides: "CryptoPath provides powerful tools to navigate the decentralized landscape. Track transactions, explore NFTs, and gain insights into the crypto market.", + exploreMarkets: "Explore Markets", + discoverNFTs: "Discover NFTs", + powerfulTools: "Powerful Blockchain Tools", + exploreFeatureRich: "Explore our feature-rich platform designed for both beginners and experienced crypto enthusiasts", + marketAnalytics: "Market Analytics", + marketAnalyticsDesc: "Real-time price data, market trends, and comprehensive analysis of cryptocurrencies.", + nftMarketplace: "NFT Marketplace", + nftMarketplaceDesc: "Buy, sell, and create NFTs on the PATH token ecosystem, or explore NFT collections across the blockchain.", + transactionExplorer: "Transaction Explorer", + transactionExplorerDesc: "Track and analyze blockchain transactions with detailed visualizations and insights.", + getStarted: "Get Started", + tryDemo: "Try Demo", + explore: "Explore" }, vi: { vietnamPremierCrypto: "Nền Tảng Khám Phá Blockchain Hàng Đầu Việt Nam", @@ -113,7 +145,22 @@ const translations = { pleaseEnterEmail: "Vui lòng nhập địa chỉ email của bạn", pleaseEnterValidEmail: "Vui lòng nhập địa chỉ email hợp lệ", errorOccurred: "Đã xảy ra lỗi khi đăng ký!", - registrationSuccessful: "Đăng ký thành công! Vui lòng kiểm tra email của bạn." + registrationSuccessful: "Đăng ký thành công! Vui lòng kiểm tra email của bạn.", + exploreFuture: "Khám Phá Tương Lai Của Blockchain", + cryptoPathProvides: "CryptoPath cung cấp các công cụ mạnh mẽ để điều hướng trong không gian phi tập trung. Theo dõi giao dịch, khám phá NFT và nhận thông tin chi tiết về thị trường tiền điện tử.", + exploreMarkets: "Khám Phá Thị Trường", + discoverNFTs: "Khám Phá NFTs", + powerfulTools: "Công Cụ Blockchain Mạnh Mẽ", + exploreFeatureRich: "Khám phá nền tảng đầy tính năng của chúng tôi được thiết kế cho cả người mới bắt đầu và những người đam mê tiền điện tử có kinh nghiệm", + marketAnalytics: "Phân Tích Thị Trường", + marketAnalyticsDesc: "Dữ liệu giá thời gian thực, xu hướng thị trường và phân tích toàn diện về tiền điện tử.", + nftMarketplace: "Thị Trường NFT", + nftMarketplaceDesc: "Mua, bán và tạo NFT trên hệ sinh thái token PATH, hoặc khám phá các bộ sưu tập NFT trên blockchain.", + transactionExplorer: "Khám Phá Giao Dịch", + transactionExplorerDesc: "Theo dõi và phân tích các giao dịch blockchain với hình ảnh trực quan và chi tiết chi tiết.", + getStarted: "Bắt Đầu", + tryDemo: "Dùng Thử", + explore: "Khám Phá" } }; @@ -147,7 +194,7 @@ const teamMembers = [ }, ]; -const HomePage = () => { +const LandingPage = () => { const [activeTab, setActiveTab] = useState('sgd'); const [email, setEmail] = useState(''); const [emailError, setEmailError] = useState(''); @@ -155,10 +202,13 @@ const HomePage = () => { const [isSuccess, setIsSuccess] = useState(false); const [language, setLanguage] = useState('en'); + // Declare t only once + const t = translations[language]; + useEffect(() => { AOS.init({ - duration: 1000, // Animation duration (in ms) - once: true, // Whether animation should happen only once while scrolling down + duration: 1000, + once: true, }); // Initialize language based on browser preference @@ -192,13 +242,13 @@ const HomePage = () => { // Email validation with language-specific messages if (!email) { - setEmailError(translations[language].pleaseEnterEmail); + setEmailError(t.pleaseEnterEmail); // Use t directly return; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { - setEmailError(translations[language].pleaseEnterValidEmail); + setEmailError(t.pleaseEnterValidEmail); // Use t directly return; } @@ -217,7 +267,7 @@ const HomePage = () => { const data = await response.json(); if (!response.ok) { - throw new Error(data.message || translations[language].errorOccurred); + throw new Error(data.message || t.errorOccurred); // Use t directly } // Handle success @@ -225,60 +275,78 @@ const HomePage = () => { setIsSuccess(true); // Success message based on language - toast.success(translations[language].registrationSuccessful); + toast.success(t.registrationSuccessful); // Use t directly } catch (error) { console.error(language === 'en' ? 'Error:' : 'Lỗi:', error); - toast.error(error instanceof Error ? error.message : translations[language].errorOccurred); + toast.error(error instanceof Error ? error.message : t.errorOccurred); // Use t directly } finally { setIsSubmitting(false); } }; - const t = translations[language]; - return (
- - -
- {/* Description Section */} -
+ + + {/* Hero Section */} +
+ {/* CryptoPath Explorer Section */} +
-

- {t.vietnamPremierCrypto} -

-

- {t.joinAllInOne}{t.appInVietnam} -

-
-
- - {emailError &&

{emailError}

} - {isSuccess &&

- {t.signUpSuccess} -

} -
- +
+
+ + + - + + + + + +
@@ -291,324 +359,272 @@ const HomePage = () => {
- - - {/* Trade Like a Pro Section */} -
-
-

{t.tradeLikePro}{t.aPro}

-

- {t.getLowestFees} -

-
-
-
- -
-
-
-
-
+ + {/* Trending Projects & Partner Bar */} + + - {/* Dynamic Content Section */} -
-
-
- CryptoPath Content -
-
-

{t.oneApplication}{t.infinitePotential}

-

- {activeTab === 'sgd' ? t.exploreNFTMarketplace : t.exploreDecentralized} -

-
- - -
-
-
-
- - {/* Evolution Illustration Section */} + {/* Demo Showcase Section */} + + + {/* Trade Like a Pro Section */} +
-

{t.accompanyingYou}{t.everyStep}

-

- {t.fromCryptoTransactions} -
- {t.believeInYourself} +

{t.tradeLikePro}{t.aPro}

+

+ {t.getLowestFees}

-
-
-
+
- {/* Meet the Team */} -
-
-

- {t.meetTheTeam}{t.team} -

-

- {t.willingToListen} + {/* Features Section */} +

+
+ +

{t.powerfulTools}

+

+ {t.exploreFeatureRich}

+
+ +
+ } + title={t.marketAnalytics} + description={t.marketAnalyticsDesc} + href="/pricetable" + imageUrl="/feature-market.png" + delay={0.1} + language={language} + /> + + } + title={t.nftMarketplace} + description={t.nftMarketplaceDesc} + href="/NFT" + imageUrl="/feature-nft.png" + delay={0.2} + language={language} + /> + + } + title={t.transactionExplorer} + description={t.transactionExplorerDesc} + href="/search" + imageUrl="/feature-transaction.png" + delay={0.3} + language={language} + /> +
+
+
+ + {/* Dynamic Content Section */} +
+
+
+ CryptoPath Content
+
+

{t.oneApplication}{t.infinitePotential}

+

+ {activeTab === 'sgd' ? t.exploreNFTMarketplace : t.exploreDecentralized} +

+
+ + +
+
+
+
-
-
- {teamMembers.map((member) => ( -
- + {/* Trending NFTs Section */} + + + {/* Evolution Illustration Section */} +
+

{t.accompanyingYou}{t.everyStep}

+

+ {t.fromCryptoTransactions} +
+ {t.believeInYourself} +

+
+
+
+ +
+
+
+ + {/* Meet the Team */} +
+
+

+ {t.meetTheTeam}{t.team} +

+

+ {t.willingToListen} +

+
+ +
+
+ {teamMembers.map((member) => ( +
{/* Profile Image */}
{`${member.name}'s
- - {/* Name & Role */} -

{member.name}

-

{member.role}

-

{member.bio}

- - {/* Social Icons */} -
- {member.facebook && ( - - - - )} - {member.github && ( - - - - )} - {member.linkedin && ( - - - - )} -
+ {/* Name and Role */} +
+

{member.name}

+

{member.role}

+
+ {/* Bio */} +

{member.bio}

+ {/* Social Links */} + - ))} -
-
-
- - {/* CryptoPath Introduction and Trusted Leaders Section */} -
-
-

{t.whatIsCryptoPath}{t.cryptoPath}

-

- {t.hearFromTopIndustry} -
- {t.whyCryptoPathIsFavorite} -

- -
-
- {/* Video 1: YouTube Embed */} -
- -
-

{t.whatIsCryptocurrency}

-

{t.explainingNewCurrency}

-
- - {/* Video 2: YouTube Embed */} -
- -
-

{t.redefiningSystem}

-

{t.welcomeToWeb3}

+ ))} +
+
+
+ + {/* FAQ Section */} + + + {/* CTA Section */} +
+
+
+
+
+

{t.readyToStart}

+

+ {t.joinThousands} +

-
- - {/* Video 3: YouTube Embed */} -
- -
-

{t.whatIsBlockchain}

-

{t.understandBlockchain}

+ +
+ + + + + +
+
+
+ ); +}; - {/* Trusted Leaders Section */} -
-
-

- {t.trustedBy} {t.industryLeaders} -

-
-
-
- Facebook -

Facebook

-
-
- Apple -

Apple

-
-
- Amazon -

Amazon

-
-
- Netflix -

Netflix

-
-
- Google -

Google

-
-
- -
-
- Minh Duy Nguyen -
-
-

- {t.testimonialText} -

-

Nguyen Minh Duy

-

{t.founderOf}

-
+// FeatureCard component updated to use language prop +const FeatureCard = ({ icon, title, description, href, imageUrl, delay, language }: FeatureCardProps) => { + const [isHovered, setIsHovered] = useState(false); + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + +
+ {imageUrl && ( +
+
+
+ )}
-
- - {/* CTA Section (New) */} -
-
-

{t.readyToStart}

-

- {t.joinThousands} -

-
- - + + +
{icon}
+

{title}

+

{description}

+ +
+ {translations[language].explore} {/* Use language prop */} +
-
-
- - {/* Insert FAQ component here - Pass language to FAQ component */} - -
-
+ + + + ); }; -export default HomePage; \ No newline at end of file +export default LandingPage; \ No newline at end of file diff --git a/components/Header.tsx b/components/Header.tsx index 6e2e689..40d03fa 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -179,6 +179,9 @@ const Header = () => { Home + + Market Overview + PriceTable diff --git a/components/NFT/NFTNavigation.tsx b/components/NFT/NFTNavigation.tsx new file mode 100644 index 0000000..58bcf52 --- /dev/null +++ b/components/NFT/NFTNavigation.tsx @@ -0,0 +1,96 @@ + +import React from 'react'; +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { ArrowRight, BadgePercent, Grid3X3, Wallet, ExternalLink } from 'lucide-react'; + +interface NavCardProps { + icon: React.ReactNode; + title: string; + description: string; + href: string; + color: string; + delay: number; + isActive?: boolean; + image?: string; +} + +const NavCard = ({ icon, title, description, href, color, delay, isActive, image }: NavCardProps) => { + return ( + + + + {image && ( +
+ {title} +
+
+ )} + +
+ {icon} +
+

{title}

+

{description}

+
+ Explore + +
+
+
+ +
+ ); +}; + +export default function NFTNavigation({ currentPath }: { currentPath?: string }) { + return ( +
+ } + title="PATH NFT Marketplace" + description="Buy, sell, and create NFTs on the PATH token ecosystem. List your digital assets and trade with other users." + href="/NFT" + color="border-blue-500/30" + delay={0.1} + isActive={currentPath === '/NFT'} + image="/Img/Web3.webp" + /> + + } + title="NFT Collection Scanner" + description="Explore NFT collections across all EVM-based blockchains. Browse popular collections or connect your wallet to view your own NFTs." + href="/NFT/collection" + color="border-purple-500/30" + delay={0.2} + isActive={currentPath?.startsWith('/NFT/collection')} + image="/Img/Web3.webp" + /> + +
+
+
+

New to NFTs?

+

Learn how to connect your wallet and explore the world of NFTs

+
+ +
+
+
+ ); +} diff --git a/components/home/DemoShowcase.tsx b/components/home/DemoShowcase.tsx new file mode 100644 index 0000000..2473cfb --- /dev/null +++ b/components/home/DemoShowcase.tsx @@ -0,0 +1,182 @@ + +'use client'; +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import Image from 'next/image'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react'; + +const pageShowcases = [ + { + title: 'Crypto Price Dashboard', + description: 'Track real-time prices and market data across thousands of cryptocurrencies.', + image: '/Img/Exchange.webp', + path: '/pricetable', + color: 'from-blue-500 to-purple-600' + }, + { + title: 'Market Overview', + description: 'Get comprehensive insights into global crypto market metrics and trends.', + image: '/Img/market-overview.png', // Fixed path by removing 'public/' + path: '/market-overview', + color: 'from-green-500 to-teal-600' + }, + { + title: 'NFT Marketplace', + description: 'Buy, sell, and create unique digital assets on the PATH token ecosystem.', + image: '/Img/Web3.webp', + path: '/NFT', + color: 'from-purple-500 to-pink-600' + }, + { + title: 'NFT Collection Scanner', + description: 'Explore popular NFT collections or connect your wallet to browse your own NFTs.', + image: '/Img/Web3.webp', + path: '/NFT/collection', + color: 'from-indigo-500 to-blue-600' + }, + { + title: 'Transaction Explorer', + description: 'Search and analyze blockchain transactions with detailed visualizations.', + image: '/Img/Web3.webp', + path: '/search', + color: 'from-orange-500 to-red-600' + } +]; + +export default function DemoShowcase() { + const [activeIndex, setActiveIndex] = useState(0); + const [autoplay, setAutoplay] = useState(true); + + // Auto rotation for slides + useEffect(() => { + if (!autoplay) return; + + const interval = setInterval(() => { + setActiveIndex((prev) => (prev + 1) % pageShowcases.length); + }, 5000); + + return () => clearInterval(interval); + }, [autoplay]); + + const nextSlide = () => { + setAutoplay(false); + setActiveIndex((prev) => (prev + 1) % pageShowcases.length); + }; + + const prevSlide = () => { + setAutoplay(false); + setActiveIndex((prev) => (prev - 1 + pageShowcases.length) % pageShowcases.length); + }; + + const goToSlide = (index: number) => { + setAutoplay(false); + setActiveIndex(index); + }; + + return ( +
+
+ +

Explore Our Platform

+

+ See what CryptoPath has to offer with our comprehensive suite of blockchain tools +

+
+ +
+
+ {pageShowcases.map((showcase, index) => ( + +
+
+ {showcase.title} +
+
+ +
+
+

{showcase.title}

+ + {index + 1}/{pageShowcases.length} + +
+

{showcase.description}

+
+ + + + + View Demo + + +
+
+
+
+ ))} +
+ + + + + +
+ {pageShowcases.map((_, index) => ( +
+
+
+
+ ); +} diff --git a/components/home/FeatureCard.tsx b/components/home/FeatureCard.tsx new file mode 100644 index 0000000..e49a2c3 --- /dev/null +++ b/components/home/FeatureCard.tsx @@ -0,0 +1,71 @@ +import { useState } from 'react'; +import Link from 'next/link'; +import { ArrowRight } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { motion } from 'framer-motion'; + +interface FeatureCardProps { + icon: React.ReactNode; + title: string; + description: string; + href: string; + imageUrl?: string; + delay: number; + language: 'en' | 'vi'; +} + +// Translation object +const translations = { + en: { + explore: 'Explore', + }, + vi: { + explore: 'Khám Phá', + }, +}; + +export default function FeatureCard({ + icon, + title, + description, + href, + imageUrl, + delay, + language +}: FeatureCardProps) { + const [isHovered, setIsHovered] = useState(false); + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + +
+ {imageUrl && ( +
+
+
+ )} +
+ + +
{icon}
+

{title}

+

{description}

+ +
+ {translations[language].explore} + +
+
+ + + + ); +} \ No newline at end of file diff --git a/components/market-overview/AltcoinIndex.tsx b/components/market-overview/AltcoinIndex.tsx new file mode 100644 index 0000000..ee5adac --- /dev/null +++ b/components/market-overview/AltcoinIndex.tsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle } from 'lucide-react'; +import axios from 'axios'; + +interface AltcoinSeasonData { + value: number; + valueText: string; + btcDominance: number; + timestamp: number; + simulated?: boolean; +} + +export default function AltcoinIndex() { + const [altcoinIndex, setAltcoinIndex] = useState(50); + const [btcDominance, setBtcDominance] = useState(60); + const [isLoading, setIsLoading] = useState(true); + const [seasonText, setSeasonText] = useState("Neutral"); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchAltcoinSeasonData = async () => { + try { + const response = await axios.get('/api/market/altcoin-season'); + setAltcoinIndex(response.data.value); + setSeasonText(response.data.valueText); + setBtcDominance(response.data.btcDominance); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch Altcoin Season Index:", error); + // Generate a fallback value + const fallbackValue = Math.floor(Math.random() * 100) + 1; + setAltcoinIndex(fallbackValue); + setBtcDominance(60 + (Math.random() * 10 - 5)); + setIsSimulated(true); + + // Set season text based on value + if (fallbackValue <= 25) setSeasonText("Bitcoin Season"); + else if (fallbackValue < 45) setSeasonText("Bitcoin Favored"); + else if (fallbackValue < 55) setSeasonText("Neutral"); + else if (fallbackValue < 75) setSeasonText("Altcoin Favored"); + else setSeasonText("Altcoin Season"); + } finally { + setIsLoading(false); + } + }; + + fetchAltcoinSeasonData(); + }, []); + + // Determine season based on the altcoin index + const getSeason = (value: number): { text: string; color: string } => { + if (value <= 25) return { text: 'Bitcoin Season', color: 'from-orange-500 to-yellow-500' }; + if (value < 45) return { text: 'Bitcoin Favored', color: 'from-yellow-500 to-yellow-300' }; + if (value < 55) return { text: 'Neutral', color: 'from-blue-500 to-purple-500' }; + if (value < 75) return { text: 'Altcoin Favored', color: 'from-blue-500 to-blue-300' }; + return { text: 'Altcoin Season', color: 'from-blue-700 to-blue-500' }; + }; + + const season = getSeason(altcoinIndex); + + return ( + + + + + Altcoin Season Index + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : ( +
+
+
{altcoinIndex}/100
+
+ {seasonText} +
+
+ +
+
+
+ + Bitcoin Season + +
+
+ + Altcoin Season + +
+
+
+
+
+
+ 0 + 25 + 75 + 100 +
+ +
+
+ BTC Dominance: + {btcDominance.toFixed(1)}% +
+
+
+
+ )} +
+
+ ); +} diff --git a/components/market-overview/BlockchainStatsCard.tsx b/components/market-overview/BlockchainStatsCard.tsx new file mode 100644 index 0000000..49ad160 --- /dev/null +++ b/components/market-overview/BlockchainStatsCard.tsx @@ -0,0 +1,141 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, Database, Layers } from 'lucide-react'; +import axios from 'axios'; + +interface BlockchainStats { + hashRate: number; + difficulty: number; + latestHeight: number; + unconfirmedTx: number; + mempool: number; + btcMined: number; + marketPrice: number; + transactionRate: number; + minutesBetweenBlocks: number; + totalFees: number; +} + +interface ChainStatsData { + data: BlockchainStats; + timestamp: number; + simulated?: boolean; +} + +export default function BlockchainStatsCard() { + const [chainData, setChainData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchChainData = async () => { + try { + const response = await axios.get('/api/analytics/chain-stats'); + setChainData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch blockchain stats:", error); + } finally { + setIsLoading(false); + } + }; + + fetchChainData(); + + // Refresh every 5 minutes + const interval = setInterval(fetchChainData, 5 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + const formatHashRate = (hashRate: number): string => { + if (hashRate >= 1000000000000000) return `${(hashRate / 1000000000000000).toFixed(2)} EH/s`; + if (hashRate >= 1000000000000) return `${(hashRate / 1000000000000).toFixed(2)} TH/s`; + if (hashRate >= 1000000000) return `${(hashRate / 1000000000).toFixed(2)} GH/s`; + if (hashRate >= 1000000) return `${(hashRate / 1000000).toFixed(2)} MH/s`; + return `${hashRate.toFixed(2)} H/s`; + }; + + const formatDifficulty = (difficulty: number): string => { + if (difficulty >= 1000000000000) return `${(difficulty / 1000000000000).toFixed(2)} T`; + if (difficulty >= 1000000000) return `${(difficulty / 1000000000).toFixed(2)} G`; + if (difficulty >= 1000000) return `${(difficulty / 1000000).toFixed(2)} M`; + return `${difficulty.toFixed(2)}`; + }; + + // Function to safely access data + const safeValue = (value: any, defaultVal: string = '0') => { + return value !== undefined && value !== null ? value : defaultVal; + }; + + return ( + + + + + + Bitcoin Network Stats + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : chainData && chainData.data ? ( +
+
+
Hash Rate
+
+ {formatHashRate(chainData.data.hashRate || 0)} +
+
+
+
Difficulty
+
+ {formatDifficulty(chainData.data.difficulty || 0)} +
+
+
+
Block Height
+
+ {(chainData.data.latestHeight || 0).toLocaleString()} +
+
+
+
Mempool Size
+
+ {(chainData.data.mempool || 0).toLocaleString()} tx +
+
+
+
Avg Block Time
+
+ {typeof chainData.data.minutesBetweenBlocks === 'number' ? + chainData.data.minutesBetweenBlocks.toFixed(2) : '0.00'} min +
+
+
+
Tx Rate
+
+ {typeof chainData.data.transactionRate === 'number' ? + chainData.data.transactionRate.toFixed(2) : '0.00'}/sec +
+
+
+ ) : ( +
+ Unable to fetch blockchain data +
+ )} +
+
+ ); +} diff --git a/components/market-overview/CryptoCard.tsx b/components/market-overview/CryptoCard.tsx new file mode 100644 index 0000000..234e4c2 --- /dev/null +++ b/components/market-overview/CryptoCard.tsx @@ -0,0 +1,78 @@ + +import React from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { ArrowUpRight, ArrowDownRight } from 'lucide-react'; +import Image from 'next/image'; + +interface CryptoCardProps { + name: string; + symbol: string; + price: number; + change24h: number; + icon: string; + fallbackIcon: string | null; +} + +export default function CryptoCard({ + name, + symbol, + price, + change24h, + icon, + fallbackIcon +}: CryptoCardProps) { + const isPositive = change24h >= 0; + + // Format price based on value + const formatPrice = (price: number): string => { + if (price >= 1000) { + return `$${price.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`; + } else if (price >= 1) { + return `$${price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; + } else { + return `$${price.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 6 })}`; + } + }; + + return ( + + +
+
+ {/* Try to load the specific icon, fall back to placeholder */} +
+ {symbol} { + // If specific icon fails, try fallback or use placeholder + if (fallbackIcon) { + (e.target as HTMLImageElement).src = fallbackIcon; + } else { + (e.target as HTMLImageElement).src = '/icons/token-placeholder.png'; + } + }} + /> +
+
+ {symbol} +
+ +
+ {formatPrice(price)} +
+ +
+ {isPositive ? ( + + ) : ( + + )} + {isPositive ? '+' : ''}{change24h.toFixed(2)}% +
+
+
+ ); +} diff --git a/components/market-overview/DefiTvlCard.tsx b/components/market-overview/DefiTvlCard.tsx new file mode 100644 index 0000000..6d371c1 --- /dev/null +++ b/components/market-overview/DefiTvlCard.tsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, LineChart, ExternalLink } from 'lucide-react'; +import axios from 'axios'; +import { + LineChart as RechartsLineChart, + Line, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer +} from 'recharts'; +import Link from 'next/link'; + +interface TvlDataPoint { + date: string; + tvl: number; +} + +interface TvlData { + data: TvlDataPoint[]; + totalTvl: number; + timestamp: number; + simulated?: boolean; +} + +export default function DefiTvlCard() { + const [tvlData, setTvlData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchTvlData = async () => { + try { + const response = await axios.get('/api/analytics/defi-tvl'); + setTvlData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch TVL data:", error); + } finally { + setIsLoading(false); + } + }; + + fetchTvlData(); + + // Refresh every 12 hours + const interval = setInterval(fetchTvlData, 12 * 60 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + const formatTvl = (value: number): string => { + if (value >= 1000000000000) return `$${(value / 1000000000000).toFixed(2)}T`; + if (value >= 1000000000) return `$${(value / 1000000000).toFixed(2)}B`; + if (value >= 1000000) return `$${(value / 1000000).toFixed(2)}M`; + if (value >= 1000) return `$${(value / 1000).toFixed(2)}K`; + return `$${value.toFixed(2)}`; + }; + + return ( + + + + + + DeFi Total Value Locked + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : tvlData && tvlData.data ? ( + <> +
+ + + + + [formatTvl(value), "TVL"]} + labelFormatter={(label) => `Date: ${label}`} + contentStyle={{ + backgroundColor: '#1F2937', + borderColor: '#374151', + borderRadius: '4px', + color: 'white' + }} + /> + + + +
+
+
+ {formatTvl(tvlData.totalTvl)} +
+

+ Total Value Locked across all DeFi protocols +

+ + View on DefiLlama + + +
+ + ) : ( +
+ No DeFi TVL data available +
+ )} +
+
+ ); +} diff --git a/components/market-overview/DominanceCard.tsx b/components/market-overview/DominanceCard.tsx new file mode 100644 index 0000000..79c9f0a --- /dev/null +++ b/components/market-overview/DominanceCard.tsx @@ -0,0 +1,71 @@ + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info } from 'lucide-react'; + +interface DominanceCardProps { + btcDominance: number; + ethDominance: number; + othersDominance: number; +} + +export default function DominanceCard({ + btcDominance, + ethDominance, + othersDominance +}: DominanceCardProps) { + return ( + + + + Bitcoin Dominance + + + + +
+
+
+
+
+ Bitcoin +
+ {btcDominance.toFixed(1)}% +
+
+
+
+ Ethereum +
+ {ethDominance.toFixed(1)}% +
+
+
+
+ Others +
+ {othersDominance.toFixed(1)}% +
+
+ +
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/components/market-overview/ExchangeVolumeCard.tsx b/components/market-overview/ExchangeVolumeCard.tsx new file mode 100644 index 0000000..d55a30b --- /dev/null +++ b/components/market-overview/ExchangeVolumeCard.tsx @@ -0,0 +1,156 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle } from 'lucide-react'; +import axios from 'axios'; +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip as RechartsTooltip, + ResponsiveContainer, + Cell +} from 'recharts'; + +interface ExchangeVolume { + name: string; + volume: number; + color: string; +} + +interface ExchangeVolumeData { + data: ExchangeVolume[]; + totalVolume: number; + timestamp: number; + simulated?: boolean; +} + +export default function ExchangeVolumeCard() { + const [volumeData, setVolumeData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchExchangeVolumes = async () => { + try { + const response = await axios.get('/api/analytics/exchange-volumes'); + setVolumeData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch exchange volume data:", error); + + // Fallback data + const fallbackData: ExchangeVolumeData = { + data: [ + { name: 'Binance', volume: 25000000000, color: '#F0B90B' }, + { name: 'Coinbase', volume: 12000000000, color: '#1652F0' }, + { name: 'OKX', volume: 8000000000, color: '#1A1B1F' }, + { name: 'Huobi', volume: 5000000000, color: '#1F94E0' }, + { name: 'KuCoin', volume: 3000000000, color: '#26A17B' }, + ], + totalVolume: 53000000000, + timestamp: Date.now(), + simulated: true + }; + + setVolumeData(fallbackData); + setIsSimulated(true); + } finally { + setIsLoading(false); + } + }; + + fetchExchangeVolumes(); + + // Refresh every 1 hour + const interval = setInterval(fetchExchangeVolumes, 60 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + const formatVolume = (volume: number): string => { + if (volume >= 1000000000) { + return `$${(volume / 1000000000).toFixed(2)}B`; + } else if (volume >= 1000000) { + return `$${(volume / 1000000).toFixed(2)}M`; + } else { + return `$${volume.toLocaleString()}`; + } + }; + + return ( + + + + + Exchange Volume (24h) + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : volumeData ? ( + <> +
+ + + + + [formatVolume(value), "Volume"]} + contentStyle={{ + backgroundColor: '#1F2937', + borderColor: '#374151', + borderRadius: '4px', + color: 'white' + }} + /> + + {volumeData.data.map((entry, index) => ( + + ))} + + + +
+
+
+ {formatVolume(volumeData.totalVolume)} +
+

Total 24h Trading Volume

+
+ + ) : ( +
+ Unable to fetch exchange volume data +
+ )} +
+
+ ); +} diff --git a/components/market-overview/FearGreedIndex.tsx b/components/market-overview/FearGreedIndex.tsx new file mode 100644 index 0000000..c4dc209 --- /dev/null +++ b/components/market-overview/FearGreedIndex.tsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, ExternalLink } from 'lucide-react'; +import Link from 'next/link'; +import axios from 'axios'; + +interface FearGreedData { + value: number; + valueText: string; + timestamp: number; + simulated?: boolean; +} + +export default function FearGreedIndex() { + const [fgIndex, setFgIndex] = useState(50); + const [fgText, setFgText] = useState("Neutral"); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchFearGreedIndex = async () => { + try { + // Try to fetch from real API + const response = await fetch('/api/analytics/fear-greed-index'); + + if (response.ok) { + const data = await response.json(); + setFgIndex(data.value); + setFgText(data.valueText); + setIsSimulated(data.simulated || false); + } else { + throw new Error('API request failed'); + } + } catch (error) { + console.error("Failed to fetch Fear & Greed Index:", error); + + // Generate fallback data + const fallbackValue = Math.floor(Math.random() * 20) + 15; // Random value between 15-35 + setFgIndex(fallbackValue); + setIsSimulated(true); + + // Set text based on value + if (fallbackValue <= 20) setFgText("Extreme Fear"); + else if (fallbackValue <= 40) setFgText("Fear"); + else if (fallbackValue <= 60) setFgText("Neutral"); + else if (fallbackValue <= 80) setFgText("Greed"); + else setFgText("Extreme Greed"); + } finally { + setIsLoading(false); + } + }; + + fetchFearGreedIndex(); + }, []); + + // Determine color based on the fear & greed index + const getSentiment = (value: number): { text: string; color: string } => { + if (value <= 20) return { text: "Extreme Fear", color: 'from-red-600 to-red-500' }; + if (value <= 40) return { text: "Fear", color: 'from-orange-600 to-orange-500' }; + if (value <= 60) return { text: "Neutral", color: 'from-yellow-500 to-yellow-400' }; + if (value <= 80) return { text: "Greed", color: 'from-green-500 to-green-400' }; + return { text: "Extreme Greed", color: 'from-green-700 to-green-600' }; + }; + + const sentiment = getSentiment(fgIndex); + const rotation = (fgIndex / 100) * 180 - 90; // Convert to -90 to 90 deg range + + return ( + + + + + Fear and Greed Index + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : ( + <> +
+ {/* Gauge background */} +
+ + {/* Gauge foreground */} +
+
+ {/* Needle */} +
+ + {/* Needle base */} +
+
+
+
+ +
+
{fgIndex}
+
+ {fgText} +
+
+ + )} +
+
+ ); +} diff --git a/components/market-overview/GasPriceCard.tsx b/components/market-overview/GasPriceCard.tsx new file mode 100644 index 0000000..967e0e5 --- /dev/null +++ b/components/market-overview/GasPriceCard.tsx @@ -0,0 +1,107 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, Fuel } from 'lucide-react'; +import axios from 'axios'; + +interface GasPriceData { + slow: number; + average: number; + fast: number; + baseFee: number; + timestamp: number; + simulated?: boolean; +} + +export default function GasPriceCard() { + const [gasData, setGasData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchGasData = async () => { + try { + const response = await axios.get('/api/analytics/gas-prices'); + setGasData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch gas prices:", error); + } finally { + setIsLoading(false); + } + }; + + fetchGasData(); + + // Refresh every 30 seconds + const interval = setInterval(fetchGasData, 30000); + return () => clearInterval(interval); + }, []); + + const getGasColor = (price: number): string => { + if (price < 20) return 'text-green-500'; + if (price < 50) return 'text-yellow-500'; + if (price < 100) return 'text-orange-500'; + return 'text-red-500'; + }; + + return ( + + + + + + Ethereum Gas Prices + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : gasData ? ( +
+
+
+
Slow
+
+ {gasData.slow} Gwei +
+
+
+
Average
+
+ {gasData.average} Gwei +
+
+
+
Fast
+
+ {gasData.fast} Gwei +
+
+
+ +
+
+ Base Fee: + {gasData.baseFee} Gwei +
+
+
+ ) : ( +
+ Unable to fetch gas prices +
+ )} +
+
+ ); +} diff --git a/components/market-overview/MarketCapChart.tsx b/components/market-overview/MarketCapChart.tsx new file mode 100644 index 0000000..128b7be --- /dev/null +++ b/components/market-overview/MarketCapChart.tsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from 'recharts'; +import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; + +interface MarketCapChartProps { + data: any; + timeframe: string; +} + +export default function MarketCapChart({ data, timeframe }: MarketCapChartProps) { + // Return empty chart if no data + if (!data || !data.prices || data.prices.length === 0) { + return ( +
+

No chart data available

+
+ ); + } + + // Process chart data based on timeframe + const processChartData = () => { + const dataPoints: { date: string; price: number; volume: number }[] = []; + let interval = 1; // Default interval + + // Adjust interval based on timeframe and data length to avoid overcrowding + if (timeframe === '1y' && data.prices.length > 30) { + interval = Math.floor(data.prices.length / 30); + } else if (timeframe === '30d' && data.prices.length > 30) { + interval = Math.floor(data.prices.length / 30); + } + + // Format date based on timeframe + const formatDate = (timestamp: number) => { + const date = new Date(timestamp); + if (timeframe === '24h') { + return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } else if (timeframe === '7d') { + return date.toLocaleDateString([], { weekday: 'short' }); + } else { + return date.toLocaleDateString([], { month: 'short', day: 'numeric' }); + } + }; + + // Select points based on interval + for (let i = 0; i < data.prices.length; i += interval) { + const [timestamp, price] = data.prices[i]; + const volume = data.total_volumes[i] ? data.total_volumes[i][1] : 0; + + dataPoints.push({ + date: formatDate(timestamp), + price, + volume, + }); + } + + return dataPoints; + }; + + const chartData = processChartData(); + const isPositive = chartData[0].price <= chartData[chartData.length - 1].price; + const chartColor = isPositive ? '#10B981' : '#EF4444'; + + // Format Y-axis values for Bitcoin prices + const formatYAxis = (value: number) => { + if (value >= 1000) return `$${(value / 1000).toFixed(1)}K`; + return `$${value.toFixed(0)}`; + }; + + return ( +
+ + + + + + + + + + + + { + if (active && payload && payload.length) { + return ( +
+

{payload[0].payload.date}

+

Price: ${payload[0].value?.toLocaleString()}

+
+ ); + } + return null; + }} + /> + +
+
+
+ ); +} diff --git a/components/market-overview/MarketIndexCard.tsx b/components/market-overview/MarketIndexCard.tsx new file mode 100644 index 0000000..98f3ba5 --- /dev/null +++ b/components/market-overview/MarketIndexCard.tsx @@ -0,0 +1,107 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, ArrowUpRight, ArrowDownRight } from 'lucide-react'; + +interface MarketIndex { + name: string; + symbol: string; + price: number; + change: number; + changePercent: number; +} + +export default function MarketIndexCard() { + const [indices, setIndices] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchMarketIndices = async () => { + try { + // In a real app, this would be an API call + // Using simulated data for demonstration + const simulatedIndices: MarketIndex[] = [ + { + name: 'S&P 500', + symbol: 'SPX', + price: 4500 + (Math.random() * 200 - 100), + change: Math.random() * 40 - 20, + changePercent: (Math.random() * 2 - 1) + }, + { + name: 'Nasdaq', + symbol: 'NDX', + price: 15000 + (Math.random() * 500 - 250), + change: Math.random() * 100 - 50, + changePercent: (Math.random() * 2 - 1) + }, + { + name: 'Dow Jones', + symbol: 'DJI', + price: 35000 + (Math.random() * 1000 - 500), + change: Math.random() * 200 - 100, + changePercent: (Math.random() * 2 - 1) + }, + { + name: 'Gold', + symbol: 'XAU', + price: 2000 + (Math.random() * 100 - 50), + change: Math.random() * 30 - 15, + changePercent: (Math.random() * 2 - 1) + } + ]; + + setIndices(simulatedIndices); + } catch (error) { + console.error("Failed to fetch market indices:", error); + } finally { + setIsLoading(false); + } + }; + + fetchMarketIndices(); + + const interval = setInterval(fetchMarketIndices, 60 * 1000); + return () => clearInterval(interval); + }, []); + + return ( + + + + Traditional Markets + + + + + {isLoading ? ( +
+
+
+ ) : ( +
+ {indices.map((index) => ( +
+
+
{index.name}
+
{index.symbol}
+
+
+
{index.price.toLocaleString(undefined, { maximumFractionDigits: 2 })}
+
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {index.changePercent >= 0 ? ( + + ) : ( + + )} + {index.changePercent >= 0 ? '+' : ''} + {index.changePercent.toFixed(2)}% +
+
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/components/market-overview/MarketSentimentCard.tsx b/components/market-overview/MarketSentimentCard.tsx new file mode 100644 index 0000000..5ff0e39 --- /dev/null +++ b/components/market-overview/MarketSentimentCard.tsx @@ -0,0 +1,162 @@ +import React, { useState, useEffect, JSX } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, TrendingUp, TrendingDown, Minus } from 'lucide-react'; +import axios from 'axios'; + +interface SentimentData { + score: number; + socialMediaScore: number; + newsScore: number; + redditMentions: number; + twitterMentions: number; + timestamp: number; + simulated?: boolean; +} + +export default function MarketSentimentCard() { + const [sentimentData, setSentimentData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchSentimentData = async () => { + try { + const response = await axios.get('/api/analytics/market-sentiment'); + setSentimentData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch sentiment data:", error); + } finally { + setIsLoading(false); + } + }; + + fetchSentimentData(); + + // Refresh every 30 minutes + const interval = setInterval(fetchSentimentData, 30 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + const getSentimentText = (score: number): { text: string; color: string; icon: JSX.Element } => { + if (score >= 70) return { + text: 'Extremely Bullish', + color: 'text-green-500', + icon: + }; + if (score >= 60) return { + text: 'Bullish', + color: 'text-green-400', + icon: + }; + if (score >= 45) return { + text: 'Slightly Bullish', + color: 'text-green-300', + icon: + }; + if (score > 55) return { + text: 'Neutral', + color: 'text-gray-400', + icon: + }; + if (score > 40) return { + text: 'Slightly Bearish', + color: 'text-red-300', + icon: + }; + if (score > 30) return { + text: 'Bearish', + color: 'text-red-400', + icon: + }; + return { + text: 'Extremely Bearish', + color: 'text-red-500', + icon: + }; + }; + + return ( + + + + + Market Sentiment Analysis + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : sentimentData ? ( +
+
+
{sentimentData.score.toFixed(0)}/100
+ + {(() => { + const sentiment = getSentimentText(sentimentData.score); + return ( +
+ {sentiment.icon} + {sentiment.text} +
+ ); + })()} +
+ +
+
+
= 55 ? 'bg-green-500' : + sentimentData.score >= 45 ? 'bg-yellow-500' : 'bg-red-500' + }`} + style={{ width: `${sentimentData.score}%` }} + >
+
+
+ +
+
+
Social Media
+
= 55 ? 'text-green-500' : + sentimentData.socialMediaScore >= 45 ? 'text-yellow-500' : 'text-red-500' + }`}> + {sentimentData.socialMediaScore.toFixed(0)}/100 +
+
+
+
News
+
= 55 ? 'text-green-500' : + sentimentData.newsScore >= 45 ? 'text-yellow-500' : 'text-red-500' + }`}> + {sentimentData.newsScore.toFixed(0)}/100 +
+
+
+ +
+
Reddit: {sentimentData.redditMentions.toLocaleString()} mentions
+
Twitter: {sentimentData.twitterMentions.toLocaleString()} mentions
+
+
+ ) : ( +
+ Unable to fetch sentiment data +
+ )} +
+
+ ); +} diff --git a/components/market-overview/NftStatsCard.tsx b/components/market-overview/NftStatsCard.tsx new file mode 100644 index 0000000..cf578c3 --- /dev/null +++ b/components/market-overview/NftStatsCard.tsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, Image as ImageIcon, ExternalLink } from 'lucide-react'; +import axios from 'axios'; +import Link from 'next/link'; + +interface NftCollection { + name: string; + symbol: string; + floorPrice: number; + volume24h: number; + totalVolume: number; + owners: number; +} + +interface NftStatsData { + collections: NftCollection[]; + timestamp: number; + simulated?: boolean; +} + +export default function NftStatsCard() { + const [nftData, setNftData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchNftData = async () => { + try { + const response = await axios.get('/api/analytics/nft-stats'); + setNftData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch NFT stats:", error); + } finally { + setIsLoading(false); + } + }; + + fetchNftData(); + + // Refresh every 6 hours + const interval = setInterval(fetchNftData, 6 * 60 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + return ( + + + + + + Top NFT Collections + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : nftData && nftData.collections && nftData.collections.length > 0 ? ( +
+
+ + + + + + + + + + {nftData.collections.map((collection, index) => ( + + + + + + ))} + +
CollectionFloor Price24h Vol
+
{collection.name}
+
{collection.symbol}
+
+
{collection.floorPrice.toFixed(2)} ETH
+
+
{collection.volume24h.toFixed(1)} ETH
+
+
+ +
+ + View NFT Explorer + + +
+
+ ) : ( +
+ No NFT stats available +
+ )} +
+
+ ); +} diff --git a/components/market-overview/StakingYieldsCard.tsx b/components/market-overview/StakingYieldsCard.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/market-overview/TrendingCoinsCard.tsx b/components/market-overview/TrendingCoinsCard.tsx new file mode 100644 index 0000000..5460e90 --- /dev/null +++ b/components/market-overview/TrendingCoinsCard.tsx @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Info, AlertTriangle, Flame, ExternalLink } from 'lucide-react'; +import axios from 'axios'; +import Image from 'next/image'; +import Link from 'next/link'; + +interface TrendingCoin { + id: string; + name: string; + symbol: string; + thumb: string; + price_btc: number; + market_cap_rank: number; + score: number; +} + +interface TrendingData { + coins: TrendingCoin[]; + timestamp: number; + simulated?: boolean; +} + +export default function TrendingCoinsCard() { + const [trendingData, setTrendingData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSimulated, setIsSimulated] = useState(false); + + useEffect(() => { + const fetchTrendingData = async () => { + try { + const response = await axios.get('/api/analytics/trending-coins'); + setTrendingData(response.data); + setIsSimulated(response.data.simulated || false); + } catch (error) { + console.error("Failed to fetch trending coins:", error); + } finally { + setIsLoading(false); + } + }; + + fetchTrendingData(); + + // Refresh every hour + const interval = setInterval(fetchTrendingData, 60 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + const formatBtcPrice = (price: number): string => { + if (price < 0.00001) return price.toExponential(2); + return price.toFixed(8); + }; + + return ( + + + + + + Trending Coins + + + {isSimulated && ( + + + Estimated + + )} + + + + {isLoading ? ( +
+
+
+ ) : trendingData && trendingData.coins.length > 0 ? ( +
+ {trendingData.coins.map((coin, index) => ( +
+
+ {index + 1}. +
+ {coin.name} { + // If image fails, show first letter of coin name + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.parentElement!.innerHTML = coin.symbol.charAt(0).toUpperCase(); + target.parentElement!.style.display = 'flex'; + target.parentElement!.style.justifyContent = 'center'; + target.parentElement!.style.alignItems = 'center'; + }} + /> +
+
+
{coin.name}
+
{coin.symbol}
+
+
+
+
Price in BTC
+
₿ {formatBtcPrice(coin.price_btc)}
+
+
+ ))} + +
+ + View More on CoinGecko + + +
+
+ ) : ( +
+ No trending coins data available +
+ )} +
+
+ ); +} diff --git a/components/market-overview/WhaleAlertsCard.tsx b/components/market-overview/WhaleAlertsCard.tsx new file mode 100644 index 0000000..72cb375 --- /dev/null +++ b/components/market-overview/WhaleAlertsCard.tsx @@ -0,0 +1,144 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Badge } from '@/components/ui/badge'; +import { ArrowRight, Wallet } from 'lucide-react'; + +interface WhaleTransaction { + id: string; + symbol: string; + amount: number; + value: number; + from: string; + to: string; + type: 'withdrawal' | 'deposit' | 'transfer'; + timestamp: number; +} + +interface WhaleData { + transactions: WhaleTransaction[]; + totalValue: number; + timestamp: number; + simulated: boolean; +} + +export default function WhaleAlertsCard() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch('/api/analytics/whale-alerts?limit=5'); + if (!response.ok) throw new Error('Failed to fetch whale alerts'); + const whaleData = await response.json(); + setData(whaleData); + } catch (error) { + console.error('Error fetching whale alerts:', error); + } finally { + setLoading(false); + } + }; + + fetchData(); + // Refresh every 2 minutes + const interval = setInterval(fetchData, 2 * 60 * 1000); + return () => clearInterval(interval); + }, []); + + const formatAddress = (address: string) => { + if (address.includes('.') || address.includes(' ')) return address; // Exchange name + return `${address.slice(0, 6)}...${address.slice(-4)}`; + }; + + const formatValue = (value: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + notation: 'compact', + maximumFractionDigits: 1 + }).format(value); + }; + + const formatAmount = (amount: number, symbol: string) => { + const precision = symbol === 'BTC' ? 2 : 1; + return `${amount.toFixed(precision)} ${symbol}`; + }; + + const getTransactionColor = (type: string) => { + switch (type) { + case 'withdrawal': return 'text-red-400'; + case 'deposit': return 'text-green-400'; + default: return 'text-blue-400'; + } + }; + + if (loading) { + return ( + + + + + Whale Alerts + + + + {[...Array(5)].map((_, i) => ( + + ))} + + + ); + } + + return ( + + +
+ + + Whale Alerts + + {data?.simulated && ( + + Simulated Data + + )} +
+
+ + {data?.transactions.map((tx) => ( +
+
+
+ {formatAddress(tx.from)} + + {formatAddress(tx.to)} +
+
+ + {formatAmount(tx.amount, tx.symbol)} + + + ({formatValue(tx.value)}) + +
+
+ + {tx.type} + +
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/lib/api/alchemyNFTApi.ts b/lib/api/alchemyNFTApi.ts new file mode 100644 index 0000000..fb63ff7 --- /dev/null +++ b/lib/api/alchemyNFTApi.ts @@ -0,0 +1,277 @@ + +import { toast } from "sonner"; + +const ALCHEMY_API_KEY = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY || 'demo'; + +const CHAIN_ID_TO_NETWORK: Record = { + '0x1': 'eth-mainnet', + '0x5': 'eth-goerli', + '0xaa36a7': 'eth-sepolia', + '0x89': 'polygon-mainnet', + '0x13881': 'polygon-mumbai', + '0xa': 'optimism-mainnet', + '0xa4b1': 'arbitrum-mainnet', + '0x38': 'bsc-mainnet', +}; + +interface AlchemyNFTResponse { + ownedNfts: any[]; + totalCount: number; + pageKey?: string; +} + +interface CollectionMetadata { + name: string; + symbol: string; + totalSupply: string; + description: string; + imageUrl: string; +} + +export async function fetchUserNFTs(address: string, chainId: string, pageKey?: string): Promise { + if (!address) { + throw new Error("Address is required to fetch NFTs"); + } + + const network = CHAIN_ID_TO_NETWORK[chainId as keyof typeof CHAIN_ID_TO_NETWORK] || 'eth-mainnet'; + + try { + const apiUrl = `https://${network}.g.alchemy.com/nft/v2/${ALCHEMY_API_KEY}/getNFTs`; + const url = new URL(apiUrl); + url.searchParams.append('owner', address); + url.searchParams.append('withMetadata', 'true'); + url.searchParams.append('excludeFilters[]', 'SPAM'); + url.searchParams.append('pageSize', '100'); + + if (pageKey) { + url.searchParams.append('pageKey', pageKey); + } + + const response = await fetch(url.toString()); + + if (!response.ok) { + throw new Error(`API request failed with status ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error(`Error fetching NFTs for ${address}:`, error); + toast.error("Failed to load NFTs"); + return { ownedNfts: [], totalCount: 0 }; + } +} + +export async function fetchCollectionInfo(contractAddress: string, chainId: string): Promise { + if (!contractAddress) { + throw new Error("Contract address is required"); + } + + const network = CHAIN_ID_TO_NETWORK[chainId as keyof typeof CHAIN_ID_TO_NETWORK] || 'eth-mainnet'; + + try { + const apiUrl = `https://${network}.g.alchemy.com/nft/v2/${ALCHEMY_API_KEY}/getContractMetadata`; + const url = new URL(apiUrl); + url.searchParams.append('contractAddress', contractAddress); + + const response = await fetch(url.toString()); + + if (!response.ok) { + throw new Error(`API request failed with status ${response.status}`); + } + + const data = await response.json(); + + return { + name: data.contractMetadata.name || 'Unknown Collection', + symbol: data.contractMetadata.symbol || '', + totalSupply: data.contractMetadata.totalSupply || '0', + description: data.contractMetadata.openSea?.description || '', + imageUrl: data.contractMetadata.openSea?.imageUrl || '', + }; + } catch (error) { + console.error(`Error fetching collection info for ${contractAddress}:`, error); + toast.error("Failed to load collection info"); + return { + name: 'Unknown Collection', + symbol: '', + totalSupply: '0', + description: '', + imageUrl: '', + }; + } +} + +interface NFTItem { + id: { + tokenId: string; + }; + title?: string; + description?: string; + media?: Array<{gateway?: string}>; + metadata?: { + attributes?: Array<{trait_type: string, value: string}> + }; +} + +interface CollectionNFT { + id: string; + tokenId: string; + name: string; + description: string; + imageUrl: string; + attributes: Array<{ + trait_type: string; + value: string; + }>; +} + +interface CollectionNFTsResponse { + nfts: CollectionNFT[]; + totalCount: number; + pageKey?: string; +} + +export async function fetchCollectionNFTs( + contractAddress: string, + chainId: string, + page: number = 1, + pageSize: number = 20, + sortBy: string = 'tokenId', + sortDirection: 'asc' | 'desc' = 'asc', + searchQuery: string = '', + attributes: Record = {} +): Promise { + if (!contractAddress) { + throw new Error("Contract address is required"); + } + + const network = CHAIN_ID_TO_NETWORK[chainId as keyof typeof CHAIN_ID_TO_NETWORK] || 'eth-mainnet'; + + try { + const apiUrl = `https://${network}.g.alchemy.com/nft/v2/${ALCHEMY_API_KEY}/getNFTsForCollection`; + const url = new URL(apiUrl); + url.searchParams.append('contractAddress', contractAddress); + url.searchParams.append('withMetadata', 'true'); + url.searchParams.append('startToken', ((page - 1) * pageSize).toString()); + url.searchParams.append('limit', pageSize.toString()); + + const response = await fetch(url.toString()); + + if (!response.ok) { + throw new Error(`API request failed with status ${response.status}`); + } + + const data = await response.json(); + + // Process NFTs + let nfts = data.nfts.map((nft: NFTItem) => ({ + id: `${contractAddress}-${nft.id.tokenId || ''}`, + tokenId: nft.id.tokenId || '', + name: nft.title || `NFT #${parseInt(nft.id.tokenId || '0', 16).toString()}`, + description: nft.description || '', + imageUrl: nft.media?.[0]?.gateway || '', + attributes: nft.metadata?.attributes || [], + })); + + // Apply filters + if (searchQuery) { + const query = searchQuery.toLowerCase(); + nfts = nfts.filter((nft: CollectionNFT) => + nft.name.toLowerCase().includes(query) || + nft.tokenId.toLowerCase().includes(query) + ); + } + + // Apply attribute filters + if (Object.keys(attributes).length > 0) { + nfts = nfts.filter((nft: CollectionNFT) => { + for (const [traitType, values] of Object.entries(attributes)) { + const nftAttribute = nft.attributes.find((attr: {trait_type: string, value: string}) => attr.trait_type === traitType); + if (!nftAttribute || !values.includes(nftAttribute.value)) { + return false; + } + } + return true; + }); + } + + // Apply sorting + nfts.sort((a: CollectionNFT, b: CollectionNFT) => { + if (sortBy === 'tokenId') { + const idA = parseInt(a.tokenId, 16) || 0; + const idB = parseInt(b.tokenId, 16) || 0; + return sortDirection === 'asc' ? idA - idB : idB - idA; + } else if (sortBy === 'name') { + return sortDirection === 'asc' + ? a.name.localeCompare(b.name) + : b.name.localeCompare(a.name); + } + return 0; + }); + + return { + nfts: nfts, + totalCount: data.totalCount || nfts.length, + pageKey: data.pageKey + }; + } catch (error) { + console.error(`Error fetching NFTs for collection ${contractAddress}:`, error); + toast.error("Failed to load collection NFTs"); + return { nfts: [], totalCount: 0 }; + } +} + +export async function fetchPopularCollections(chainId: string = '0x1'): Promise { + try { + // In a production app, you would fetch this from a backend API + // For now, we'll use a mock response + return [ + { + id: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', + name: 'Bored Ape Yacht Club', + imageUrl: 'https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format', + floorPrice: 30.5, + totalSupply: 10000, + }, + { + id: '0x60e4d786628fea6478f785a6d7e704777c86a7c6', + name: 'Mutant Ape Yacht Club', + imageUrl: 'https://i.seadn.io/gae/lHexKRMpw-aoSyB1WdFBff5yfANLReFxHzt1DOj_sg7mS14yARpuvYcUtsyyx-Nkpk6WTcUPFoG53VnLJezYi8hAs0OxNZwlw6Y-dmI?w=500&auto=format', + floorPrice: 12.2, + totalSupply: 20000, + }, + { + id: '0xed5af388653567af2f388e6224dc7c4b3241c544', + name: 'Azuki', + imageUrl: 'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format', + floorPrice: 8.9, + totalSupply: 10000, + }, + { + id: '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb', + name: 'CryptoPunks', + imageUrl: 'https://i.seadn.io/gae/BdxvLseXcfl57BiuQcQYdJ64v-aI8din7WPk0Pgo3qQFhAUH-B6i-dCqqc_mCkRIzULmwzwecnohLhrcH8A9mpWIZqA7ygc52Sr81hE?w=500&auto=format', + floorPrice: 50.2, + totalSupply: 10000, + }, + { + id: '0x8a90cab2b38dba80c64b7734e58ee1db38b8992e', + name: 'Doodles', + imageUrl: 'https://i.seadn.io/gae/7B0qai02OdHA8P_EOVK672qUliyjQdQDGNrACxs7WnTgZAkJa_wWURnIFKeOh5VTf8cfTqW3wQpozGedaC9mteKphEOtztls02RlWQ?w=500&auto=format', + floorPrice: 3.8, + totalSupply: 10000, + }, + { + id: '0x1a92f7381b9f03921564a437210bb9396471050c', + name: 'Cool Cats', + imageUrl: 'https://i.seadn.io/gae/LIov33kogXOK4XZd2ESj29sqm_Hww5JSdO7AFn5wjt8xgnJJ0UpNV9yITqxra3s_LMEW1AnnrgOVB_hDpjJRA1uF4skI5Sdi_9rULi8?w=500&auto=format', + floorPrice: 2.1, + totalSupply: 9999, + }, + ]; + } catch (error) { + console.error('Error fetching popular collections:', error); + toast.error("Failed to load popular collections"); + return []; + } +} diff --git a/package-lock.json b/package-lock.json index d94f7b5..a9eba6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -309,6 +309,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -317,20 +318,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -345,70 +332,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", @@ -437,43 +360,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.26.9", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", @@ -625,20 +511,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", @@ -2795,6 +2667,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -3214,6 +3087,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -3227,6 +3101,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3236,6 +3111,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -3259,6 +3135,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -6388,7 +6265,7 @@ "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -6398,7 +6275,7 @@ "version": "19.0.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -8676,6 +8553,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8703,6 +8581,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, "license": "MIT" }, "node_modules/anymatch": { @@ -8753,6 +8632,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -9205,6 +9085,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9340,6 +9221,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -9481,39 +9363,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/bs58": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", @@ -9663,6 +9512,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9730,6 +9580,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -9754,6 +9605,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -9989,6 +9841,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -10006,13 +9859,6 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "license": "ISC" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT", - "peer": true - }, "node_modules/cookie": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", @@ -10108,6 +9954,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -10182,6 +10029,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -10724,6 +10572,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/diffie-hellman": { @@ -10753,6 +10602,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, "license": "MIT" }, "node_modules/doctrine": { @@ -10840,6 +10690,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/eccrypto": { @@ -10922,13 +10773,6 @@ "node": ">=4.0.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.119", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.119.tgz", - "integrity": "sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==", - "license": "ISC", - "peer": true - }, "node_modules/elliptic": { "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", @@ -10982,6 +10826,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/encode-utf8": { @@ -11286,16 +11131,6 @@ "@esbuild/win32-x64": "0.19.12" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -13016,17 +12851,11 @@ "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", "license": "MIT" }, - "node_modules/fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "license": "CC0-1.0", - "peer": true - }, "node_modules/fastq": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -13055,6 +12884,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -13189,6 +13019,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -13282,6 +13113,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -13400,16 +13232,6 @@ "node": ">=8" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -13500,6 +13322,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -13520,6 +13343,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -13532,6 +13356,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -13541,6 +13366,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -14139,6 +13965,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -14190,6 +14017,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -14240,6 +14068,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14298,6 +14127,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -14349,6 +14179,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -14537,6 +14368,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/isomorphic-ws": { @@ -14585,6 +14417,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -14677,6 +14510,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -14939,6 +14773,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -14951,6 +14786,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/lit": { @@ -15194,6 +15030,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -15203,6 +15040,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -15306,6 +15144,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -15408,6 +15247,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -15611,13 +15451,6 @@ "integrity": "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==", "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT", - "peer": true - }, "node_modules/nodemailer": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", @@ -15697,6 +15530,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -16035,6 +15869,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -16121,6 +15956,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16130,12 +15966,14 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -16186,6 +16024,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16284,6 +16123,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -16311,6 +16151,7 @@ "version": "8.5.1", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16339,6 +16180,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -16356,6 +16198,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" @@ -16375,6 +16218,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16410,6 +16254,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -16435,6 +16280,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -16448,6 +16294,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, "node_modules/preact": { @@ -16633,6 +16480,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -16956,6 +16804,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -16979,6 +16828,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -17103,6 +16953,7 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -17143,6 +16994,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -17369,6 +17221,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -17666,6 +17519,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -17678,6 +17532,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17763,6 +17618,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -17964,6 +17820,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -17982,6 +17839,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -17996,6 +17854,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18005,12 +17864,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18136,6 +17997,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -18152,6 +18014,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18164,6 +18027,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18240,6 +18104,7 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -18284,6 +18149,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -18339,6 +18205,7 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -18385,6 +18252,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -18401,6 +18269,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -18482,6 +18351,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -18491,6 +18361,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -18591,6 +18462,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -18622,6 +18494,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/ts-mixer": { @@ -18771,6 +18644,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -18981,37 +18855,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -20465,6 +20308,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -20642,6 +20486,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -20660,6 +20505,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -20677,6 +20523,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -20686,12 +20533,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -20706,6 +20555,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -20718,6 +20568,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -20845,17 +20696,11 @@ "node": ">=0.10.32" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC", - "peer": true - }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/public/Img/market-overview.png b/public/Img/market-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb705719ebfcab5cdff919c03f9335330fde680 GIT binary patch literal 275006 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 z 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%0DV8IM8Va)b#3Xlltq| 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 z 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 z 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 literal 0 HcmV?d00001 diff --git a/services/apiCache.ts b/services/apiCache.ts new file mode 100644 index 0000000..b085333 --- /dev/null +++ b/services/apiCache.ts @@ -0,0 +1,46 @@ +interface CacheItem { + data: T; + timestamp: number; +} + +interface CacheConfig { + globalMarketData: number; // 5 minutes + topCryptos: number; // 5 minutes + historicalData: number; // 30 minutes +} + +class APICache { + private cache: Map>; + private config: CacheConfig; + + constructor() { + this.cache = new Map(); + this.config = { + globalMarketData: 5 * 60 * 1000, // 5 minutes + topCryptos: 5 * 60 * 1000, // 5 minutes + historicalData: 30 * 60 * 1000 // 30 minutes + }; + } + + get(key: string, category: keyof CacheConfig): T | null { + const item = this.cache.get(key); + if (!item) return null; + + const ttl = this.config[category]; + if (Date.now() - item.timestamp > ttl) { + this.cache.delete(key); + return null; + } + + return item.data; + } + + set(key: string, data: T): void { + this.cache.set(key, { + data, + timestamp: Date.now() + }); + } +} + +export const apiCache = new APICache(); \ No newline at end of file diff --git a/services/binanceApiService.ts b/services/binanceApiService.ts new file mode 100644 index 0000000..cebaf30 --- /dev/null +++ b/services/binanceApiService.ts @@ -0,0 +1,83 @@ +import axios from 'axios'; + +const BINANCE_API_URL = 'https://api.binance.com/api/v3'; + +interface BinanceKline { + openTime: number; + open: string; + high: string; + low: string; + close: string; + volume: string; + closeTime: number; + quoteAssetVolume: string; + trades: number; + takerBuyBaseAssetVolume: string; + takerBuyQuoteAssetVolume: string; + ignored: string; +} + +/** + * Convert symbol to Binance trading pair format + */ +const getBinancePair = (symbol: string): string => { + // Convert common symbols to Binance format (BTCUSDT, ETHUSDT, etc.) + return `${symbol.toUpperCase()}USDT`; +}; + +/** + * Get historical price data from Binance + */ +export const fetchHistoricalPriceData = async (symbol: string, days: number = 30): Promise => { + try { + const interval = days <= 7 ? '1h' : '1d'; + const limit = days <= 7 ? days * 24 : days; + + const pair = getBinancePair(symbol); + + const response = await axios.get(`${BINANCE_API_URL}/klines`, { + params: { + symbol: pair, + interval, + limit: Math.min(limit, 1000) // Binance has a limit of 1000 candles + } + }); + + if (!response.data || !Array.isArray(response.data)) { + throw new Error('Invalid response from Binance API'); + } + + // Transform to the format our charts expect - using closing price (index 4) + const prices = response.data.map((kline: any) => [kline[0], parseFloat(kline[4])]); + + // Use actual traded volume for better accuracy + const volumes = response.data.map((kline: any) => [kline[0], parseFloat(kline[5]) * parseFloat(kline[4])]); + + // For market caps, we estimate based on circulating supply + const market_cap_multiplier = getMarketCapMultiplier(symbol); + const market_caps = prices.map(([time, price]) => [time, price * market_cap_multiplier]); + + return { + prices, + market_caps, + total_volumes: volumes + }; + } catch (error) { + console.error('Error fetching data from Binance:', error); + throw error; + } +}; + +/** + * Helper to estimate market cap based on approximate circulating supply + */ +function getMarketCapMultiplier(symbol: string): number { + // Rough estimates of circulating supply for major coins + switch (symbol.toLowerCase()) { + case 'btc': return 19500000; // ~19.5M BTC in circulation + case 'eth': return 120000000; // ~120M ETH in circulation + case 'bnb': return 153000000; // ~153M BNB in circulation + case 'sol': return 430000000; // ~430M SOL in circulation + default: return 100000000; // Default fallback + } +} diff --git a/services/coinMarketCapService.ts b/services/coinMarketCapService.ts new file mode 100644 index 0000000..6033bc9 --- /dev/null +++ b/services/coinMarketCapService.ts @@ -0,0 +1,286 @@ +import axios from 'axios'; +import { apiCache } from './apiCache'; + +// Configuration +const MAX_RETRIES = 3; +const INITIAL_RETRY_DELAY = 1000; // 1 second +const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes +const API_PROXY_URL = '/api/coinmarketcap'; + +// Interface definitions +export interface GlobalMarketData { + total_market_cap: { [key: string]: number }; + total_volume: { [key: string]: number }; + market_cap_percentage: { [key: string]: number }; + market_cap_change_percentage_24h_usd: number; + active_cryptocurrencies: number; + markets: number; + last_updated: string; +} + +export interface TokenData { + id: number; + name: string; + symbol: string; + slug: string; + cmc_rank: number; + quote: { + USD: { + price: number; + volume_24h: number; + market_cap: number; + percent_change_1h: number; + percent_change_24h: number; + percent_change_7d: number; + percent_change_30d: number; + last_updated: string; + } + } +} + +// Helper functions +async function retryWithBackoff( + operation: () => Promise, + retries = MAX_RETRIES, + delay = INITIAL_RETRY_DELAY +): Promise { + try { + return await operation(); + } catch (error: any) { + if (retries === 0 || (error.response && error.response.status !== 429)) { + throw error; + } + + console.warn(`Request failed, retrying in ${delay}ms...`); + await new Promise(resolve => setTimeout(resolve, delay)); + + return retryWithBackoff(operation, retries - 1, delay * 2); + } +} + +// Data conversion functions +const convertToGlobalMarketData = (response: any): GlobalMarketData => { + try { + console.log('API Response:', JSON.stringify(response, null, 2)); + + if (response.status && response.status.error_code) { + throw new Error(`API Error: ${response.status.error_message}`); + } + + const data = response.data || response; + + return { + total_market_cap: { + usd: data.total_market_cap?.usd || + data.quote?.USD?.total_market_cap || + data.total_market_cap || 0 + }, + total_volume: { + usd: data.total_volume?.usd || + data.quote?.USD?.total_volume_24h || + data.total_volume || 0 + }, + market_cap_percentage: { + btc: data.market_cap_percentage?.btc || + data.btc_dominance || + data.dominance?.btc || 0, + eth: data.market_cap_percentage?.eth || + data.eth_dominance || + data.dominance?.eth || 0, + }, + market_cap_change_percentage_24h_usd: + data.market_cap_change_percentage_24h_usd || + data.quote?.USD?.total_market_cap_yesterday_percentage_change || 0, + active_cryptocurrencies: + data.active_cryptocurrencies || + data.total_cryptocurrencies || 0, + markets: + data.markets || + data.active_market_pairs || 0, + last_updated: + data.last_updated || + new Date().toISOString() + }; + } catch (error) { + console.error('Error converting global market data:', error); + return getSimulatedGlobalData(); + } +}; + +const convertToStandardTokenData = (tokens: TokenData[]): any[] => { + try { + return tokens.map(token => { + if (!token.quote || !token.quote.USD) { + throw new Error(`Invalid token data structure for token: ${token.id}`); + } + + return { + id: token.id?.toString() || '0', + name: token.name || 'Unknown', + symbol: token.symbol || 'N/A', + current_price: token.quote.USD.price || 0, + market_cap: token.quote.USD.market_cap || 0, + total_volume: token.quote.USD.volume_24h || 0, + price_change_percentage_24h: token.quote.USD.percent_change_24h || 0, + price_change_percentage_7d: token.quote.USD.percent_change_7d || 0, + price_change_percentage_30d: token.quote.USD.percent_change_30d || 0, + market_cap_rank: token.cmc_rank || 0 + }; + }).filter(token => token.name !== 'Unknown'); + } catch (error) { + console.error('Error converting token data:', error); + return []; + } +}; + +// API functions +export const fetchGlobalMarketData = async (): Promise => { + const cachedData = apiCache.get('globalMarketData', 'globalMarketData'); + if (cachedData) { + return cachedData; + } + + try { + const response = await retryWithBackoff(async () => { + return await axios.get(`${API_PROXY_URL}/global-metrics`); + }); + + const data = convertToGlobalMarketData(response.data); + apiCache.set('globalMarketData', data); + return data; + } catch (error) { + console.error('Error fetching global market data:', error); + const fallbackData = getSimulatedGlobalData(); + apiCache.set('globalMarketData', fallbackData); + return fallbackData; + } +}; + +export const fetchTopCryptocurrencies = async (limit: number = 10): Promise => { + const cacheKey = `topCryptos_${limit}`; + const cachedData = apiCache.get(cacheKey, 'topCryptos'); + if (cachedData) { + return cachedData; + } + + try { + const response = await retryWithBackoff(async () => { + return await axios.get(`${API_PROXY_URL}/listings`, { + params: { + limit, + sort: 'market_cap', + sort_dir: 'desc', + } + }); + }); + + const data = convertToStandardTokenData(response.data.data); + apiCache.set(cacheKey, data); + return data; + } catch (error) { + console.error('Error fetching top cryptocurrencies:', error); + const fallbackData = getSimulatedTopTokens(limit); + apiCache.set(cacheKey, fallbackData); + return fallbackData; + } +}; + +export const fetchHistoricalData = async ( + symbol: string, + days: number = 30, + interval: string = 'daily' +): Promise => { + const cacheKey = `historicalData_${symbol}_${days}`; + const cachedData = apiCache.get(cacheKey, 'historicalData'); + if (cachedData) { + return cachedData; + } + + try { + // Try to get data from Binance API if possible + const binanceData = await retryWithBackoff(async () => { + const binanceApi = await import('@/services/binanceApiService'); + return await binanceApi.fetchHistoricalPriceData(symbol, days); + }).catch(() => null); + + if (binanceData) { + apiCache.set(cacheKey, binanceData); + return binanceData; + } + + // Fallback to simulated data + console.warn(`Using simulated data for ${symbol} historical prices`); + const fallbackData = getSimulatedHistoricalData(symbol, days); + apiCache.set(cacheKey, fallbackData); + return fallbackData; + } catch (error) { + console.error(`Error fetching historical data for ${symbol}:`, error); + const fallbackData = getSimulatedHistoricalData(symbol, days); + apiCache.set(cacheKey, fallbackData); + return fallbackData; + } +}; + +// Simulated data generators +function getSimulatedGlobalData(): GlobalMarketData { + return { + total_market_cap: { + usd: 2300000000000 // $2.3T + }, + total_volume: { + usd: 115000000000 // $115B + }, + market_cap_percentage: { + btc: 48.5, + eth: 17.3, + }, + market_cap_change_percentage_24h_usd: 2.34, + active_cryptocurrencies: 10423, + markets: 814, + last_updated: new Date().toISOString() + }; +} + +function getSimulatedTopTokens(limit: number): any[] { + const mockTokens = [ + { id: '1', name: 'Bitcoin', symbol: 'btc', current_price: 64253.12, price_change_percentage_24h: 2.41, market_cap: 1260000000000, market_cap_rank: 1 }, + { id: '2', name: 'Ethereum', symbol: 'eth', current_price: 3427.81, price_change_percentage_24h: 1.58, market_cap: 412000000000, market_cap_rank: 2 }, + { id: '3', name: 'Tether', symbol: 'usdt', current_price: 0.9998, price_change_percentage_24h: 0.01, market_cap: 110000000000, market_cap_rank: 3 }, + { id: '4', name: 'BNB', symbol: 'bnb', current_price: 587.33, price_change_percentage_24h: 0.92, market_cap: 85000000000, market_cap_rank: 4 }, + { id: '5', name: 'Solana', symbol: 'sol', current_price: 143.38, price_change_percentage_24h: 3.76, market_cap: 65000000000, market_cap_rank: 5 }, + ]; + + return mockTokens.slice(0, limit); +} + +function getSimulatedHistoricalData(symbol: string, days: number): any { + const basePrice = symbol.toLowerCase() === 'btc' ? 68000 : + symbol.toLowerCase() === 'eth' ? 3500 : + symbol.toLowerCase() === 'sol' ? 140 : + symbol.toLowerCase() === 'bnb' ? 600 : 100; + + const prices = []; + const market_caps = []; + const total_volumes = []; + + const now = Date.now(); + const oneDayMs = 24 * 60 * 60 * 1000; + + let currentPrice = basePrice; + + for (let i = days; i >= 0; i--) { + const timestamp = now - (i * oneDayMs); + const randomChange = currentPrice * (Math.random() * 0.06 - 0.03); // -3% to +3% + currentPrice += randomChange; + + prices.push([timestamp, currentPrice]); + market_caps.push([timestamp, currentPrice * 19500000]); // Simulated market cap + total_volumes.push([timestamp, currentPrice * 500000 * (0.7 + Math.random() * 0.6)]); // Simulated volume + } + + return { + prices, + market_caps, + total_volumes + }; +} From c7536b636744a97a69c83649edb5c889449633b1 Mon Sep 17 00:00:00 2001 From: Minh Duy - Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:57:55 +0700 Subject: [PATCH 06/13] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 28024fe..2f57433 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ CryptoPath is a blockchain transaction visualization system that simplifies bloc - Leverage a graph database (currently using static demo data) for efficient data storage and retrieval. ## Team Members -- **Le Nguyen Dang Duy** (105028557) - Frontend Lead / Graph Visualization -- **Phan Cong Hung** (104995595) - Backend & Data Integration Lead -- **Nguyen Minh Duy** (104974743) - Full-Stack Developer / UI & UX +- **Le Nguyen Dang Duy** (105028557) - **Frontend Developer / Graph Visualization** +- **Nguyen Minh Duy** (104974743) - **Team Leader / Full-Stack Developer / Product Experience Architect** +- **Phan Cong Hung** (104995595) - **Backend & Frontend Developer / API Integration** ## Project Structure ### Frontend From 54f71e4a46cf6ce481ced1b989eb43849f550205 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 14:05:03 +0700 Subject: [PATCH 07/13] Add NFT layout component with navigation and breadcrumb support --- app/NFT/layout.tsx | 136 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 app/NFT/layout.tsx diff --git a/app/NFT/layout.tsx b/app/NFT/layout.tsx new file mode 100644 index 0000000..7bae892 --- /dev/null +++ b/app/NFT/layout.tsx @@ -0,0 +1,136 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { usePathname, useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import { LayoutGrid, Layers, Bookmark, Activity, ChevronRight, Info } from 'lucide-react'; + +export default function NFTLayout({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + const router = useRouter(); + const [mounted, setMounted] = useState(false); + + // Determine active section based on URL + const isMarketplace = pathname === '/NFT'; + const isCollections = pathname.includes('/NFT/collection'); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) return null; + + return ( +

+
+ {/* Navigation Tabs */} +
+
+ + + + + + +
+
+ + {/* Breadcrumb Navigation */} + + + {/* Info Banner */} + {isMarketplace && ( +
+ +
+

About PATH Marketplace

+

+ This marketplace is exclusive to CryptoPath ecosystem NFTs. Connect your wallet to start trading PATH NFTs. + You'll need PATH tokens for transactions, which you can get from our Faucet. +

+
+
+ )} + + {/* Page Title */} +

+ {isMarketplace ? ( + <>PATH NFT Marketplace + ) : ( + <>NFT Collections Explorer + )} +

+ + {/* Main Content */} +
{children}
+
+
+ ); +} From 3a37eec1c031627686cfa0af58590c576d0a1fc8 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 15:06:51 +0700 Subject: [PATCH 08/13] Enhance NFT components with improved image loading, validation, and new market stats display --- app/NFT/page.tsx | 492 +++++++++++++++---------- components/NFT/FeaturedCollections.tsx | 181 +++++++++ components/NFT/ListForm.tsx | 70 +++- components/NFT/MintForm.tsx | 130 ++++++- components/NFT/NFTCard.tsx | 36 +- components/NFT/NFTDetailsModal.tsx | 79 ++-- components/NFT/NFTMarketStats.tsx | 90 +++++ components/NFT/NFTNavigation.tsx | 343 ++++++++++++++--- components/NFT/PriceChart.tsx | 146 ++++++++ components/NFT/TradingHistory.tsx | 273 ++++++++++++++ components/NFT/WhitelistForm.tsx | 144 +++++++- lib/api/alchemyNFTApi.ts | 170 ++++++--- next.config.js | 2 +- 13 files changed, 1775 insertions(+), 381 deletions(-) create mode 100644 components/NFT/FeaturedCollections.tsx create mode 100644 components/NFT/NFTMarketStats.tsx create mode 100644 components/NFT/PriceChart.tsx create mode 100644 components/NFT/TradingHistory.tsx diff --git a/app/NFT/page.tsx b/app/NFT/page.tsx index fea0126..4221c81 100644 --- a/app/NFT/page.tsx +++ b/app/NFT/page.tsx @@ -8,7 +8,9 @@ import MintForm from '@/components/NFT/MintForm'; import WhitelistForm from '@/components/NFT/WhitelistForm'; import { useWallet } from '@/components/Faucet/walletcontext'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import ParticlesBackground from '@/components/ParticlesBackground'; +import NFTMarketStats from '@/components/NFT/NFTMarketStats'; +import PriceChart from '@/components/NFT/PriceChart'; +import { toast } from '@/hooks/use-toast'; // Contract addresses const NFT_CONTRACT_ADDRESS = "0xdf5d4038723f6605A3eCd7776FFe25f3b1Be39a0"; @@ -53,6 +55,13 @@ interface NFTData { listings: any[]; } +interface NFTMetadata { + name: string; + image: string; + description?: string; + [key: string]: any; +} + export default function NFTMarketplace() { const { account, connectWallet } = useWallet(); const [activeTab, setActiveTab] = useState<'market' | 'owned' | 'listings' | 'mint' | 'whitelist'>('market'); @@ -67,6 +76,16 @@ export default function NFTMarketplace() { listings: [] }); const [isInitialLoad, setIsInitialLoad] = useState(true); + + // Market statistics + const [marketStats, setMarketStats] = useState({ + totalVolume: '12,450.35', + dailyVolume: '1,245.62', + avgPrice: '125.75', + listedCount: 48, + soldCount: 152, + priceChange: 8.5 + }); const isOwner = useMemo(() => account?.toLowerCase() === ownerAddress.toLowerCase(), @@ -118,18 +137,49 @@ export default function NFTMarketplace() { }, [account, checkWhitelistStatus]); // Fetch PATH balance - const fetchPathBalance = useCallback(async (account: string) => { + const fetchPathBalance = useCallback(async (address: string) => { try { const provider = getProvider(); const tokenContract = new ethers.Contract(PATH_TOKEN_ADDRESS, TOKEN_ABI, provider); - const balance = await tokenContract.balanceOf(account); + const balance = await tokenContract.balanceOf(address); setPathBalance(parseFloat(ethers.utils.formatUnits(balance, 18)).toFixed(4)); } catch (error) { console.error("Error fetching PATH balance:", error); } }, []); - // Fetch NFT data + // Fetch metadata with timeout and retry + const fetchMetadata = async (uri: string, retries = 3): Promise => { + const timeout = new Promise((_, reject) => + setTimeout(() => reject(new Error('Metadata fetch timeout')), 5000) + ); + + for (let i = 0; i < retries; i++) { + try { + const response = await Promise.race([ + fetch(uri), + timeout + ]) as Response; + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + if (!data.name || !data.image) { + throw new Error('Invalid metadata format'); + } + + return data as NFTMetadata; + } catch (error) { + if (i === retries - 1) throw error; + await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); + } + } + throw new Error('Failed to fetch metadata after retries'); + }; + + // Fetch NFT data with improved error handling const fetchNFTs = useCallback(async () => { if (!account) return; @@ -139,8 +189,8 @@ export default function NFTMarketplace() { const listedIds = await contract.getAllListings().catch(() => []); - // Market NFTs - const marketNFTs = await Promise.all( + // Market NFTs with improved error handling + const marketNFTs = await Promise.allSettled( listedIds.map(async (id: ethers.BigNumber) => { try { const [uri, listing, owner] = await Promise.all([ @@ -148,7 +198,9 @@ export default function NFTMarketplace() { contract.listings(id), contract.ownerOf(id).catch(() => '0x0') ]); - const metadata = await fetch(uri.toString()).then(res => res.json()); + + const metadata = await fetchMetadata(uri); + return { id: id.toString(), ...metadata, @@ -164,46 +216,67 @@ export default function NFTMarketplace() { }) ); - // Owned NFTs + // Owned NFTs with improved pagination const totalSupply = await contract.totalSupply().catch(() => ethers.BigNumber.from(0)); + const pageSize = 20; // Process in smaller chunks const allIds = Array.from({ length: totalSupply.toNumber() }, (_, i) => i); - - const ownedNFTs = await Promise.all( - allIds.map(async (id) => { - try { - const [owner, listing] = await Promise.all([ - contract.ownerOf(id).catch(() => '0x0'), - contract.listings(id) - ]); - if (owner.toLowerCase() === account.toLowerCase() && !listing.isListed) { - const uri = await contract.tokenURI(id); - const metadata = await fetch(uri.toString()).then(res => res.json()); - return { - id: id.toString(), - ...metadata, - owner: owner, - isListed: false - }; + const ownedNFTs = []; + + for (let i = 0; i < allIds.length; i += pageSize) { + const pageIds = allIds.slice(i, i + pageSize); + const pageResults = await Promise.allSettled( + pageIds.map(async (id) => { + try { + const [owner, listing] = await Promise.all([ + contract.ownerOf(id).catch(() => '0x0'), + contract.listings(id) + ]); + + if (owner.toLowerCase() === account.toLowerCase() && !listing.isListed) { + const uri = await contract.tokenURI(id); + const metadata = await fetchMetadata(uri); + + return { + id: id.toString(), + ...metadata, + owner: owner, + isListed: false + }; + } + return null; + } catch (error) { + console.error(`Error processing owned NFT ${id}:`, error); + return null; } - return null; - } catch (error) { - return null; - } - }) - ); + }) + ); + + ownedNFTs.push(...pageResults + .filter(result => result.status === 'fulfilled' && result.value !== null) + .map(result => (result as PromiseFulfilledResult).value) + ); + } + + const validMarketNFTs = marketNFTs + .filter(result => result.status === 'fulfilled' && result.value !== null) + .map(result => (result as PromiseFulfilledResult).value); setNftData({ - market: marketNFTs.filter(nft => nft !== null), - owned: ownedNFTs.filter(nft => nft !== null), - listings: marketNFTs.filter(nft => - nft !== null && - nft.isListed && + market: validMarketNFTs, + owned: ownedNFTs, + listings: validMarketNFTs.filter(nft => + nft.isListed && nft.seller?.toLowerCase() === account.toLowerCase() ) }); setIsInitialLoad(false); } catch (error) { console.error("Error fetching NFTs:", error); + toast({ + title: "Error", + description: "Failed to fetch NFTs. Please try again later.", + variant: "destructive" + }); } }, [account]); @@ -259,12 +332,24 @@ export default function NFTMarketplace() { const signer = provider.getSigner(); const contract = new ethers.Contract(NFT_CONTRACT_ADDRESS, NFT_ABI, signer); + // Validate the token URI before minting + await fetchMetadata(tokenURI); + const tx = await contract.mintNFT(recipient, tokenURI); await tx.wait(); await refreshData(); + toast({ + title: "Success", + description: "NFT minted successfully!", + variant: "default" + }); } catch (error) { console.error("Minting failed:", error); - alert(error instanceof Error ? error.message : "Mint failed"); + toast({ + title: "Error", + description: error instanceof Error ? error.message : "Mint failed", + variant: "destructive" + }); } finally { setProcessing(false); } @@ -273,7 +358,11 @@ export default function NFTMarketplace() { // Handle buy NFT const handleBuyNFT = async (tokenId: string, price: string) => { if (!account) { - alert("Please connect wallet!"); + toast({ + title: "Error", + description: "Please connect wallet first!", + variant: "destructive" + }); return; } @@ -298,9 +387,18 @@ export default function NFTMarketplace() { await tx.wait(); await refreshData(); + toast({ + title: "Success", + description: "NFT purchased successfully!", + variant: "default" + }); } catch (error) { console.error("Purchase failed:", error); - alert("Transaction failed! Check console for details."); + toast({ + title: "Error", + description: "Transaction failed! Check console for details.", + variant: "destructive" + }); } finally { setProcessing(false); } @@ -309,7 +407,11 @@ export default function NFTMarketplace() { // Handle list NFT const handleListNFT = async (tokenId: string, price: string) => { if (!account) { - alert("Please connect wallet first!"); + toast({ + title: "Error", + description: "Please connect wallet first!", + variant: "destructive" + }); return; } @@ -331,9 +433,18 @@ export default function NFTMarketplace() { ); await tx.wait(); await refreshData(); + toast({ + title: "Success", + description: "NFT listed successfully!", + variant: "default" + }); } catch (error) { console.error("Listing failed:", error); - alert(error instanceof Error ? error.message : "Unknown error"); + toast({ + title: "Error", + description: error instanceof Error ? error.message : "Unknown error", + variant: "destructive" + }); } finally { setProcessing(false); } @@ -342,7 +453,11 @@ export default function NFTMarketplace() { // Handle unlist NFT const handleUnlistNFT = async (tokenId: string) => { if (!account) { - alert("Please connect wallet first!"); + toast({ + title: "Error", + description: "Please connect wallet first!", + variant: "destructive" + }); return; } @@ -362,13 +477,18 @@ export default function NFTMarketplace() { const tx = await contract.unlistNFT(tokenId); await tx.wait(); await refreshData(); + toast({ + title: "Success", + description: "NFT unlisted successfully!", + variant: "default" + }); } catch (error: unknown) { console.error("Unlisting failed:", error); - alert( - error instanceof Error - ? error.message - : "Unknown error occurred during unlisting" - ); + toast({ + title: "Error", + description: error instanceof Error ? error.message : "Unknown error occurred during unlisting", + variant: "destructive" + }); } finally { setProcessing(false); } @@ -386,164 +506,150 @@ export default function NFTMarketplace() { const tx = await contract.updateWhitelist(address, status); await tx.wait(); - alert('Whitelist updated successfully!'); + toast({ + title: "Success", + description: "Whitelist updated successfully!", + variant: "default" + }); } catch (error) { console.error("Whitelist update failed:", error); - alert(error instanceof Error ? error.message : "Update failed"); + toast({ + title: "Error", + description: error instanceof Error ? error.message : "Update failed", + variant: "destructive" + }); } finally { setProcessing(false); } }; return ( -
- -
- {/* Header section */} -
-

- NFT Marketplace -

-
- {account && ( -
- - {pathBalance} - - PATH -
- )} - -
-
- - - - {!account ? ( -
- Please connect your wallet to view NFTs -
- ) : ( - <> - {isInitialLoad ? ( -
- {[...Array(8)].map((_, i) => ( -
- ))} -
- ) : ( - <> - {activeTab === 'mint' ? ( - - - Mint - - - - - - ) : activeTab === 'whitelist' ? ( - - - Whitelist Management - - - - - - ) : ( - <> -
- {paginatedData.map((nft, index) => ( -
- handleBuyNFT(tokenId, price || '0') - : activeTab === 'owned' - ? (tokenId, price) => handleListNFT(tokenId, price || '0') - : handleUnlistNFT - } - processing={processing} - /> -
- ))} -
- - {totalPages > 1 && ( -
- +
+

Trade exclusive NFTs in the PATH ecosystem using PATH tokens

+
+ + {/* Market Statistics */} + + + {/* Price Chart */} +
+ +
+ + {/* Tabs Navigation */} + + + {!account ? ( +
+ Please connect your wallet to view NFTs +
+ ) : ( + <> + {isInitialLoad ? ( +
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+ ) : ( + <> + {activeTab === 'mint' ? ( + + + Mint + + + + + + ) : activeTab === 'whitelist' ? ( + + + Whitelist Management + + + + + + ) : ( + <> +
+ {paginatedData.map((nft, index) => ( +
+ handleBuyNFT(tokenId, price || '0') + : activeTab === 'owned' + ? (tokenId, price) => handleListNFT(tokenId, price || '0') + : handleUnlistNFT + } + processing={processing} />
- )} - - )} - - )} - - )} - - {processing && ( -
-
-
-

Processing Transaction

-
- {[...Array(3)].map((_, i) => ( -
- ))} -
+ ))} +
+ + {totalPages > 1 && ( +
+ +
+ )} + + )} + + )} + + )} + + {processing && ( +
+
+
+

Processing Transaction

+
+ {[...Array(3)].map((_, i) => ( +
+ ))}
- )} -
+
+ )}
); } \ No newline at end of file diff --git a/components/NFT/FeaturedCollections.tsx b/components/NFT/FeaturedCollections.tsx new file mode 100644 index 0000000..64007dd --- /dev/null +++ b/components/NFT/FeaturedCollections.tsx @@ -0,0 +1,181 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { ExternalLink, Sparkles, Zap, TrendingUp, Users } from 'lucide-react'; + +interface Collection { + id: string; + name: string; + description: string; + image: string; + bannerImage?: string; + floorPrice?: string; + volume24h?: string; + totalItems?: number; + owners?: number; + verified?: boolean; + category: string; +} + +interface FeaturedCollectionsProps { + collections: Collection[]; +} + +export default function FeaturedCollections({ collections = [] }: FeaturedCollectionsProps) { + // If no real data, use sample data + const sampleCollections: Collection[] = collections.length > 0 ? collections : [ + { + id: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', + name: 'Bored Ape Yacht Club', + description: 'The Bored Ape Yacht Club is a collection of 10,000 unique Bored Ape NFTs— unique digital collectibles living on the Ethereum blockchain.', + image: 'https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?auto=format&dpr=1&w=1000', + bannerImage: 'https://i.seadn.io/gae/i5dYZRkVCUK97bfprQ3WXyrT9BnLSZtVKGJlKQ919uaUB0sxbngVCioaiyu9r6snqfi2aaTyIvv6DHm4m2R3y7hMajbsv14pSZK8mhs?auto=format&dpr=1&w=3840', + floorPrice: '30.5', + volume24h: '450.23', + totalItems: 10000, + owners: 6350, + verified: true, + category: 'Art' + }, + { + id: '0x60e4d786628fea6478f785a6d7e704777c86a7c6', + name: 'Mutant Ape Yacht Club', + description: 'The MUTANT APE YACHT CLUB is a collection of up to 20,000 Mutant Apes that can only be created by exposing an existing Bored Ape to a vial of MUTANT SERUM.', + image: 'https://i.seadn.io/gae/lHexKRMpw-aoSyB1WdFBff5yfANLReFxHzt1DOj_sg7mS14yARpuvYcUtsyyx-Nkpk6WTcUPF6rLh2D4Xw?auto=format&dpr=1&w=1000', + floorPrice: '10.2', + volume24h: '250.15', + totalItems: 19423, + owners: 12340, + verified: true, + category: 'Art' + }, + { + id: '0xed5af388653567af2f388e6224dc7c4b3241c544', + name: 'Azuki', + description: 'Azuki starts with a collection of 10,000 avatars that give you membership access to The Garden: a corner of the internet where artists, builders, and web3 enthusiasts meet to create a decentralized future.', + image: 'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?auto=format&dpr=1&w=1000', + floorPrice: '8.75', + volume24h: '175.45', + totalItems: 10000, + owners: 5120, + verified: true, + category: 'PFP' + } + ]; + + return ( +
+
+

+ + Featured Collections +

+ + + +
+ +
+ {sampleCollections.map((collection) => ( + +
+ {collection.bannerImage ? ( + {`${collection.name} + ) : ( +
+ )} +
+
+ {collection.name} +
+
+ {collection.verified && ( +
+ + Verified + +
+ )} +
+ + + + + {collection.name} + + + + {collection.category} + + + + +

+ {collection.description} +

+ +
+
+

Floor Price

+

+ {collection.floorPrice} ETH +

+
+
+

Volume (24h)

+

+ {collection.volume24h} ETH + {parseFloat(collection.volume24h || '0') > 200 && ( + + )} +

+
+
+
+ + +
+ + {collection.owners?.toLocaleString()} owners +
+
+ {collection.totalItems?.toLocaleString()} items +
+ + + +
+
+ ))} +
+ +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/components/NFT/ListForm.tsx b/components/NFT/ListForm.tsx index c7a9a2d..ae13d9b 100644 --- a/components/NFT/ListForm.tsx +++ b/components/NFT/ListForm.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, FormEvent } from 'react'; export default function ListForm({ onSubmit, @@ -8,31 +8,77 @@ export default function ListForm({ onCancel: () => void; }) { const [price, setPrice] = useState(''); + const [error, setError] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + + // Clear previous error + setError(''); + + // Validate price + if (!price) { + setError('Please enter a price'); + return; + } + + const numPrice = parseFloat(price); + if (isNaN(numPrice)) { + setError('Please enter a valid number'); + return; + } + + if (numPrice <= 0) { + setError('Price must be greater than 0'); + return; + } + + if (numPrice > 1000000) { + setError('Price cannot exceed 1,000,000 PATH'); + return; + } + + // Submit if validation passes + onSubmit(price); + }; return ( -
- setPrice(e.target.value)} - placeholder="Enter price in PATH" - className="w-full p-2 bg-gray-700 rounded text-sm" - /> +
+
+ { + setPrice(e.target.value); + setError(''); // Clear error when input changes + }} + placeholder="Enter price in PATH" + className={`w-full p-2 bg-gray-700 rounded text-sm ${ + error ? 'border border-red-500' : '' + }`} + /> + {error && ( +

{error}

+ )} +
-
+ ); } \ No newline at end of file diff --git a/components/NFT/MintForm.tsx b/components/NFT/MintForm.tsx index cfeaeb1..bcab115 100644 --- a/components/NFT/MintForm.tsx +++ b/components/NFT/MintForm.tsx @@ -16,30 +16,109 @@ export default function MintForm({ const [tokenURI, setTokenURI] = useState(''); const [recipient, setRecipient] = useState(''); const [isWhitelisted, setIsWhitelisted] = useState(false); + const [validatingURI, setValidatingURI] = useState(false); + const [uriError, setUriError] = useState(null); // Validate Ethereum address const isValidAddress = (address: string) => utils.isAddress(address); - // Validate IPFS/HTTP URI - const isValidURI = (uri: string) => uri.startsWith('ipfs://') || uri.startsWith('https://'); + // Validate metadata format + const validateMetadata = async (uri: string): Promise => { + try { + setValidatingURI(true); + setUriError(null); + + if (!uri.startsWith('ipfs://') && !uri.startsWith('https://')) { + setUriError('URI must start with ipfs:// or https://'); + return false; + } + + // For IPFS URIs, only validate the format + if (uri.startsWith('ipfs://')) { + const cid = uri.replace('ipfs://', '').split('/')[0]; + if (!cid || cid.length < 32) { + setUriError('Invalid IPFS CID format'); + return false; + } + return true; + } + + // For HTTP URIs, validate the metadata format + const response = await fetch(uri); + if (!response.ok) { + setUriError('Failed to fetch metadata'); + return false; + } + + const metadata = await response.json(); + if (!metadata.name || typeof metadata.name !== 'string') { + setUriError('Metadata must include a name property'); + return false; + } + if (!metadata.image || typeof metadata.image !== 'string') { + setUriError('Metadata must include an image property'); + return false; + } + if (!metadata.image.startsWith('ipfs://') && !metadata.image.startsWith('https://')) { + setUriError('Image URI must start with ipfs:// or https://'); + return false; + } + + return true; + } catch (error) { + setUriError(error instanceof Error ? error.message : 'Invalid metadata format'); + return false; + } finally { + setValidatingURI(false); + } + }; + + // Validate URI when it changes + useEffect(() => { + const timer = setTimeout(() => { + if (tokenURI) { + validateMetadata(tokenURI); + } else { + setUriError(null); + } + }, 500); + + return () => clearTimeout(timer); + }, [tokenURI]); // Check whitelist status when recipient changes useEffect(() => { const verifyWhitelist = async () => { if (isValidAddress(recipient)) { - const status = await checkWhitelist(recipient); - setIsWhitelisted(status); + try { + const status = await checkWhitelist(recipient); + setIsWhitelisted(status); + } catch (error) { + console.error('Failed to check whitelist status:', error); + setIsWhitelisted(false); + } } else { setIsWhitelisted(false); } }; - verifyWhitelist(); + + if (recipient) { + verifyWhitelist(); + } }, [recipient, checkWhitelist]); // Combined disable conditions const isDisabled = processing || !isValidAddress(recipient) || - !isValidURI(tokenURI); + validatingURI || + !!uriError || + !tokenURI; + + const handleSubmit = async () => { + if (await validateMetadata(tokenURI)) { + onSubmit(recipient, tokenURI); + } + }; return (
@@ -109,15 +188,23 @@ export default function MintForm({ value={recipient} onChange={(e) => setRecipient(e.target.value)} placeholder="0x..." - className="w-full px-4 py-3 bg-gray-800 border-2 border-gray-700 rounded-xl - focus:border-orange-400 focus:ring-4 focus:ring-orange-400/20 - transition-all placeholder-gray-500 text-white font-mono text-sm" + className={`w-full px-4 py-3 bg-gray-800 border-2 rounded-xl + transition-all placeholder-gray-500 text-white font-mono text-sm + ${!isValidAddress(recipient) && recipient + ? 'border-red-500 focus:ring-red-500/20' + : 'border-gray-700 focus:border-orange-400 focus:ring-4 focus:ring-orange-400/20' + }`} /> - {!isValidAddress(recipient) && recipient !== '' && ( + {!isValidAddress(recipient) && recipient && (

⚠ Invalid BSC address

- )} + )} + {isValidAddress(recipient) && !isWhitelisted && ( +

+ ⚠ Address is not whitelisted +

+ )}
{/* Metadata URI Input */} @@ -128,13 +215,22 @@ export default function MintForm({ value={tokenURI} onChange={(e) => setTokenURI(e.target.value)} placeholder="ipfs://Qm... or https://" - className="w-full px-4 py-3 bg-gray-800 border-2 border-gray-700 rounded-xl - focus:border-orange-400 focus:ring-4 focus:ring-orange-400/20 - transition-all placeholder-gray-500 text-white text-sm" + className={`w-full px-4 py-3 bg-gray-800 border-2 rounded-xl + transition-all placeholder-gray-500 text-white text-sm + ${uriError + ? 'border-red-500 focus:ring-red-500/20' + : 'border-gray-700 focus:border-orange-400 focus:ring-4 focus:ring-orange-400/20' + }`} /> - {!isValidURI(tokenURI) && tokenURI !== '' && ( + {validatingURI && ( +

+ + Validating metadata... +

+ )} + {uriError && (

- ⚠ URI must start with ipfs:// or https:// + ⚠ {uriError}

)}
@@ -142,7 +238,7 @@ export default function MintForm({ {/* Mint Button */} + + + {/* Wallet Section */} +
+ {hasWallet && ( +
+ + {pathBalance} + + PATH +
+ )} + +
- - - - - ); -}; +
+ + -export default function NFTNavigation({ currentPath }: { currentPath?: string }) { - return ( -
- } - title="PATH NFT Marketplace" - description="Buy, sell, and create NFTs on the PATH token ecosystem. List your digital assets and trade with other users." - href="/NFT" - color="border-blue-500/30" - delay={0.1} - isActive={currentPath === '/NFT'} - image="/Img/Web3.webp" - /> - - } - title="NFT Collection Scanner" - description="Explore NFT collections across all EVM-based blockchains. Browse popular collections or connect your wallet to view your own NFTs." - href="/NFT/collection" - color="border-purple-500/30" - delay={0.2} - isActive={currentPath?.startsWith('/NFT/collection')} - image="/Img/Web3.webp" - /> + {/* Category Filters */} + {activeSection === 'marketplace' ? ( +
+ All NFTs + Art + Collectibles + Gaming + Membership + Virtual Land + Music + Photography + Sports + Utility +
+ ) : ( +
+ All Collections + Verified + Art + Gaming + PFP + Photography + Music + Metaverse +
+ )} + + {/* Feature Cards */} +
+ } + title="PATH NFT Marketplace" + description="Buy, sell, and create NFTs on the PATH token ecosystem. List your digital assets and trade with other users." + href="/NFT" + color="border-blue-500/30" + delay={0.1} + isActive={activeSection === 'marketplace'} + image="/Img/Web3.webp" + /> + + } + title="NFT Collection Scanner" + description="Explore NFT collections across all EVM-based blockchains. Browse popular collections or connect your wallet to view your own NFTs." + href="/NFT/collection" + color="border-purple-500/30" + delay={0.2} + isActive={activeSection === 'collections'} + image="/Img/Web3.webp" + /> +
-
+ {/* Tutorial Section */} +

New to NFTs?

diff --git a/components/NFT/PriceChart.tsx b/components/NFT/PriceChart.tsx new file mode 100644 index 0000000..8457fbd --- /dev/null +++ b/components/NFT/PriceChart.tsx @@ -0,0 +1,146 @@ +'use client'; +import { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from 'recharts'; + +interface PricePoint { + date: string; + price: number; +} + +interface PriceChartProps { + data: PricePoint[]; + tokenId?: string; +} + +export default function PriceChart({ data, tokenId }: PriceChartProps) { + const [timeRange, setTimeRange] = useState<'1h' | '24h' | '7d' | '30d'>('24h'); + const [chartData, setChartData] = useState([]); + + // Filter data based on selected time range + useEffect(() => { + if (!data || data.length === 0) { + setChartData([ + { date: '00:00', price: 100 }, + { date: '04:00', price: 120 }, + { date: '08:00', price: 90 }, + { date: '12:00', price: 150 }, + { date: '16:00', price: 180 }, + { date: '20:00', price: 200 }, + { date: '24:00', price: 160 }, + ]); // Placeholder data + return; + } + + const now = new Date(); + let filterDate = new Date(); + + switch (timeRange) { + case '1h': + filterDate.setHours(now.getHours() - 1); + break; + case '24h': + filterDate.setDate(now.getDate() - 1); + break; + case '7d': + filterDate.setDate(now.getDate() - 7); + break; + case '30d': + filterDate.setDate(now.getDate() - 30); + break; + } + + const filtered = data.filter((point) => { + const pointDate = new Date(point.date); + return pointDate >= filterDate; + }); + + setChartData(filtered); + }, [data, timeRange]); + + const formatYAxis = (value: number) => { + return `${value} PATH`; + }; + + // Calculate price change + const priceChange = chartData.length >= 2 + ? ((chartData[chartData.length - 1].price - chartData[0].price) / chartData[0].price) * 100 + : 0; + + const isPriceUp = priceChange >= 0; + + return ( + + +
+ + {tokenId ? `NFT #${tokenId} Price History` : 'Market Price Trends'} + + setTimeRange(v as any)}> + + 1H + 24H + 7D + 30D + + +
+
+ +
+
+

Current Price

+

+ {chartData.length > 0 ? chartData[chartData.length - 1].price : 0} PATH +

+
+
+ {priceChange.toFixed(2)}% +
+
+ +
+ + + + + + [`${value} PATH`, 'Price']} + /> + + + +
+
+
+ ); +} diff --git a/components/NFT/TradingHistory.tsx b/components/NFT/TradingHistory.tsx new file mode 100644 index 0000000..2c30d9d --- /dev/null +++ b/components/NFT/TradingHistory.tsx @@ -0,0 +1,273 @@ +import { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { ExternalLink, TrendingUp, ArrowUpRight, ShoppingCart, AlertTriangle } from 'lucide-react'; + +type EventType = 'Sale' | 'Transfer' | 'Mint' | 'List'; + +interface Trade { + id: string; + event: EventType; + tokenId: string; + from: string; + to: string; + price?: string; + timestamp: string; + txHash: string; +} + +interface TradingHistoryProps { + trades: Trade[]; + tokenId?: string; + loadMore?: () => void; + hasMore?: boolean; + loading?: boolean; +} + +type TabType = 'all' | 'sales' | 'transfers' | 'mints' | 'lists'; + +export default function TradingHistory({ + trades = [], + tokenId, + loadMore, + hasMore = false, + loading = false +}: TradingHistoryProps) { + const [activeTab, setActiveTab] = useState('all'); + const [error, setError] = useState(null); + + // Filter trades based on active tab + const filteredTrades = trades.filter(trade => { + if (activeTab === 'all') return true; + return trade.event.toLowerCase() === activeTab.slice(0, -1); + }); + + // Format addresses to be more readable + const formatAddress = (address: string) => { + return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`; + }; + + // Format dates to be more readable + const formatDate = (dateStr: string) => { + try { + const date = new Date(dateStr); + return new Intl.DateTimeFormat('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }).format(date); + } catch (error) { + console.error('Invalid date format:', error); + return 'Invalid date'; + } + }; + + // Format price with proper decimal places + const formatPrice = (price?: string) => { + if (!price) return null; + try { + const numPrice = parseFloat(price); + return numPrice.toLocaleString(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 4 + }); + } catch (error) { + console.error('Invalid price format:', error); + return price; + } + }; + + // Event badge styling with hover effects + const getEventBadge = (event: EventType) => { + const styles = { + Sale: 'bg-green-600 hover:bg-green-700', + Transfer: 'bg-blue-600 hover:bg-blue-700', + Mint: 'bg-purple-600 hover:bg-purple-700', + List: 'bg-orange-600 hover:bg-orange-700' + }; + + return ( + + {event} + + ); + }; + + const handleTabChange = (value: string) => { + setActiveTab(value as TabType); + }; + + return ( + + + + + {tokenId ? `NFT #${tokenId} Trading History` : 'Recent Transactions'} + + + + + + All + Sales + Transfers + Mints + Lists + + + + {error ? ( +
+ +

{error}

+
+ ) : ( + + + + Event + {!tokenId && Token ID} + Price + From + To + Date + + + + + {filteredTrades.map((trade) => ( + + + {getEventBadge(trade.event)} + + {!tokenId && ( + + + #{trade.tokenId} + + + + )} + + {trade.price ? ( +
+ {formatPrice(trade.price)} + PATH +
+ ) : ( + -- + )} +
+ +
+ + + {trade.from.substring(0, 2)} + + + {formatAddress(trade.from)} + +
+
+ +
+ + + {trade.to.substring(0, 2)} + + + {formatAddress(trade.to)} + +
+
+ + {formatDate(trade.timestamp)} + + + + + + +
+ ))} + + {loading && ( + + +
+
+ Loading transactions... +
+ + + )} + + {filteredTrades.length === 0 && !loading && ( + + +
+ +
+

No trading history available

+

+ {activeTab === 'all' + ? 'Be the first to make a transaction!' + : `No ${activeTab.slice(0, -1)} events found` + } +

+
+
+
+
+ )} + +
+ )} + + {hasMore && filteredTrades.length > 0 && ( +
+ +
+ )} +
+
+ ); +} diff --git a/components/NFT/WhitelistForm.tsx b/components/NFT/WhitelistForm.tsx index 85e8478..9cc88d9 100644 --- a/components/NFT/WhitelistForm.tsx +++ b/components/NFT/WhitelistForm.tsx @@ -1,32 +1,82 @@ -// components/NFT/WhitelistForm.tsx -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { utils } from 'ethers'; -import { Loader2, CheckCircle, XCircle } from 'lucide-react'; +import { Loader2, CheckCircle, XCircle, AlertCircle } from 'lucide-react'; +import { toast } from '@/hooks/use-toast'; interface WhitelistFormProps { onSubmit: (address: string, status: boolean) => Promise; isOwner: boolean; } +interface Transaction { + address: string; + status: boolean; + timestamp: number; +} + export default function WhitelistForm({ onSubmit, isOwner }: WhitelistFormProps) { const [address, setAddress] = useState(''); const [processing, setProcessing] = useState(false); const [status, setStatus] = useState<'success' | 'error' | null>(null); + const [errorMessage, setErrorMessage] = useState(''); + const [previousTransactions, setPreviousTransactions] = useState([]); + + // Load previous transactions from localStorage + useEffect(() => { + const saved = localStorage.getItem('whitelistTransactions'); + if (saved) { + try { + const parsed = JSON.parse(saved); + setPreviousTransactions(parsed); + } catch (error) { + console.error('Failed to parse saved transactions:', error); + } + } + }, []); + + // Save transactions to localStorage + const saveTransaction = (address: string, status: boolean) => { + const transaction = { + address, + status, + timestamp: Date.now() + }; + const newTransactions = [transaction, ...previousTransactions].slice(0, 10); // Keep last 10 + setPreviousTransactions(newTransactions); + localStorage.setItem('whitelistTransactions', JSON.stringify(newTransactions)); + }; const isValidAddress = (addr: string) => utils.isAddress(addr); - const handleSubmit = async (status: boolean) => { - if (!isValidAddress(address)) return; + const handleSubmit = async (newStatus: boolean) => { + if (!isValidAddress(address)) { + setErrorMessage('Invalid address format'); + return; + } setProcessing(true); setStatus(null); + setErrorMessage(''); + try { - await onSubmit(address, status); + await onSubmit(address, newStatus); setStatus('success'); + saveTransaction(address, newStatus); + toast({ + title: 'Success', + description: `Address ${newStatus ? 'added to' : 'removed from'} whitelist`, + variant: 'default' + }); setAddress(''); } catch (error) { console.error(error); setStatus('error'); + setErrorMessage(error instanceof Error ? error.message : 'Operation failed'); + toast({ + title: 'Error', + description: error instanceof Error ? error.message : 'Operation failed', + variant: 'destructive' + }); } finally { setProcessing(false); } @@ -49,12 +99,19 @@ export default function WhitelistForm({ onSubmit, isOwner }: WhitelistFormProps) value={address} onChange={(e) => setAddress(e.target.value)} placeholder="0x..." - className="w-full px-4 py-3 bg-gray-800 border-2 border-gray-700 rounded-xl - focus:border-orange-400 focus:ring-4 focus:ring-orange-400/20 - transition-all placeholder-gray-500 text-white font-mono text-sm" + className={`w-full px-4 py-3 bg-gray-800 border-2 rounded-xl + transition-all placeholder-gray-500 text-white font-mono text-sm + ${ + errorMessage + ? 'border-red-500 focus:ring-red-500/20' + : 'border-gray-700 focus:border-orange-400 focus:ring-4 focus:ring-orange-400/20' + }`} /> {!isValidAddress(address) && address !== '' && ( -

⚠ Invalid address format

+

+ + Invalid address format +

)}
@@ -62,29 +119,80 @@ export default function WhitelistForm({ onSubmit, isOwner }: WhitelistFormProps)
{status === 'success' && ( -

✓ Operation successful!

+

+ + Operation successful! +

)} {status === 'error' && ( -

⚠ Operation failed

+

+ + {errorMessage} +

+ )} + + {/* Recent Transactions */} + {previousTransactions.length > 0 && ( +
+

Recent Transactions

+
+ {previousTransactions.map((tx, index) => ( +
+
+ {tx.status ? ( + + ) : ( + + )} + + {tx.address.slice(0, 6)}...{tx.address.slice(-4)} + +
+ + {new Date(tx.timestamp).toLocaleString()} + +
+ ))} +
+
)}
diff --git a/lib/api/alchemyNFTApi.ts b/lib/api/alchemyNFTApi.ts index fb63ff7..7c7f6d5 100644 --- a/lib/api/alchemyNFTApi.ts +++ b/lib/api/alchemyNFTApi.ts @@ -1,4 +1,3 @@ - import { toast } from "sonner"; const ALCHEMY_API_KEY = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY || 'demo'; @@ -221,57 +220,122 @@ export async function fetchCollectionNFTs( } } -export async function fetchPopularCollections(chainId: string = '0x1'): Promise { - try { - // In a production app, you would fetch this from a backend API - // For now, we'll use a mock response - return [ - { - id: '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', - name: 'Bored Ape Yacht Club', - imageUrl: 'https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?w=500&auto=format', - floorPrice: 30.5, - totalSupply: 10000, - }, - { - id: '0x60e4d786628fea6478f785a6d7e704777c86a7c6', - name: 'Mutant Ape Yacht Club', - imageUrl: 'https://i.seadn.io/gae/lHexKRMpw-aoSyB1WdFBff5yfANLReFxHzt1DOj_sg7mS14yARpuvYcUtsyyx-Nkpk6WTcUPFoG53VnLJezYi8hAs0OxNZwlw6Y-dmI?w=500&auto=format', - floorPrice: 12.2, - totalSupply: 20000, - }, - { - id: '0xed5af388653567af2f388e6224dc7c4b3241c544', - name: 'Azuki', - imageUrl: 'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format', - floorPrice: 8.9, - totalSupply: 10000, - }, - { - id: '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb', - name: 'CryptoPunks', - imageUrl: 'https://i.seadn.io/gae/BdxvLseXcfl57BiuQcQYdJ64v-aI8din7WPk0Pgo3qQFhAUH-B6i-dCqqc_mCkRIzULmwzwecnohLhrcH8A9mpWIZqA7ygc52Sr81hE?w=500&auto=format', - floorPrice: 50.2, - totalSupply: 10000, - }, - { - id: '0x8a90cab2b38dba80c64b7734e58ee1db38b8992e', - name: 'Doodles', - imageUrl: 'https://i.seadn.io/gae/7B0qai02OdHA8P_EOVK672qUliyjQdQDGNrACxs7WnTgZAkJa_wWURnIFKeOh5VTf8cfTqW3wQpozGedaC9mteKphEOtztls02RlWQ?w=500&auto=format', - floorPrice: 3.8, - totalSupply: 10000, - }, - { - id: '0x1a92f7381b9f03921564a437210bb9396471050c', - name: 'Cool Cats', - imageUrl: 'https://i.seadn.io/gae/LIov33kogXOK4XZd2ESj29sqm_Hww5JSdO7AFn5wjt8xgnJJ0UpNV9yITqxra3s_LMEW1AnnrgOVB_hDpjJRA1uF4skI5Sdi_9rULi8?w=500&auto=format', - floorPrice: 2.1, - totalSupply: 9999, - }, - ]; - } catch (error) { - console.error('Error fetching popular collections:', error); - toast.error("Failed to load popular collections"); - return []; +// Mocked API service for NFT data +// In a real application, this would connect to Alchemy or another provider +export async function fetchPopularCollections(chainId: string): Promise { + // In a production app, this would fetch from Alchemy API + // For this demo, we'll return mock data + return [ + { + id: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", + name: "Bored Ape Yacht Club", + totalSupply: 10000, + floorPrice: "30.5", + imageUrl: "https://i.seadn.io/gae/Ju9CkWtV-1Okvf45wo8UctR-M9He2PjILP0oOvxE89AyiPPGtrR3gysu1Zgy0hjd2xKIgjJJtWIc0ybj4Vd7wv8t3pxDGHoJBzDB?auto=format&dpr=1&w=1000" + }, + { + id: "0x60e4d786628fea6478f785a6d7e704777c86a7c6", + name: "Mutant Ape Yacht Club", + totalSupply: 19423, + floorPrice: "10.2", + imageUrl: "https://i.seadn.io/gae/lHexKRMpw-aoSyB1WdFBff5yfANLReFxHzt1DOj_sg7mS14yARpuvYcUtsyyx-Nkpk6WTcUPF6rLh2D4Xw?auto=format&dpr=1&w=1000" + }, + { + id: "0xed5af388653567af2f388e6224dc7c4b3241c544", + name: "Azuki", + totalSupply: 10000, + floorPrice: "8.75", + imageUrl: "https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?auto=format&dpr=1&w=1000" + }, + { + id: "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", + name: "CryptoPunks", + totalSupply: 10000, + floorPrice: "54.95", + imageUrl: "https://i.seadn.io/gae/BdxvLseXcfl57BiuQcQYdJ64v-aI8din7WPk0Pgo3qQFhAUH-B6i-dCqqc_mCkRIzULmwzwecnohLhrcH8A9mpWIZqA7ygc52Sr81hE?auto=format&dpr=1&w=1000" + }, + { + id: "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e", + name: "Doodles", + totalSupply: 10000, + floorPrice: "5.25", + imageUrl: "https://i.seadn.io/gae/7B0qai02OdHA8P_EOVK672qUliyjQdQDGNrACxs7WnTgZAkJa_wWURnIFKeOh5VTf8cfTqW3wQpozGedaC9mteKphEOtztls02RlWQ?auto=format&dpr=1&w=1000" + }, + { + id: "0xdf5d4038723f6605a3ecd7776ffe25f3b1be39a0", + name: "PATH NFT Collection", + totalSupply: 1000, + floorPrice: "0.5", + imageUrl: "/images/path-token.png" + } + ]; +} + +// Function to fetch marketplace trading history +export async function fetchTradeHistory(tokenId?: string): Promise { + // This would normally connect to a blockchain indexer service + // For now, we'll return mock data + return [ + { + id: '1', + event: 'Sale', + tokenId: tokenId || '123', + from: '0x1234567890abcdef1234567890abcdef12345678', + to: '0xabcdef1234567890abcdef1234567890abcdef12', + price: '120.5', + timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString(), // 2 hours ago + txHash: '0xabc123def456' + }, + { + id: '2', + event: 'Transfer', + tokenId: tokenId || '123', + from: '0xabcdef1234567890abcdef1234567890abcdef12', + to: '0x9876543210abcdef1234567890abcdef12345678', + timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(), // 1 day ago + txHash: '0xdef456abc789' + }, + { + id: '3', + event: 'Mint', + tokenId: tokenId || '123', + from: '0x0000000000000000000000000000000000000000', + to: '0x1234567890abcdef1234567890abcdef12345678', + timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3).toISOString(), // 3 days ago + txHash: '0x789abc123def' + }, + { + id: '4', + event: 'List', + tokenId: tokenId || '123', + from: '0x1234567890abcdef1234567890abcdef12345678', + to: '0x0000000000000000000000000000000000000000', + price: '100', + timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(), // 2 days ago + txHash: '0x456def789abc' + } + ]; +} + +// Function to fetch price history data for charts +export async function fetchPriceHistory(tokenId?: string): Promise { + // This would normally fetch real historical price data + // For now, generate some mock data + const now = Date.now(); + const data = []; + + // Generate 30 days of price data + for (let i = 30; i >= 0; i--) { + const date = new Date(now - 1000 * 60 * 60 * 24 * i); + const basePrice = tokenId ? 100 : 120; // Different base for collection vs single NFT + const randomFactor = 0.3 * Math.sin(i / 2) + 0.2 * Math.cos(i); + const volatility = 0.1; + + data.push({ + date: date.toISOString().split('T')[0], + price: basePrice * (1 + randomFactor + volatility * (Math.random() - 0.5)) + }); } + + return data; } diff --git a/next.config.js b/next.config.js index 54a91cc..e45acde 100644 --- a/next.config.js +++ b/next.config.js @@ -260,7 +260,7 @@ const nextConfig = { // Add important settings for Vercel deployment experimental: { // Allow more time for API routes that make external calls - serverComponentsExternalPackages: [], + serverExternalPackages: [], }, // Add extra security headers async headers() { From fde3c88d07216c9218abd975dcf203d429ba3cb0 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 15:15:26 +0700 Subject: [PATCH 09/13] Add wallet connection button to NFT layout with account display --- app/NFT/layout.tsx | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/app/NFT/layout.tsx b/app/NFT/layout.tsx index 7bae892..ef9b177 100644 --- a/app/NFT/layout.tsx +++ b/app/NFT/layout.tsx @@ -4,12 +4,15 @@ import React, { useState, useEffect } from 'react'; import { usePathname, useRouter } from 'next/navigation'; import Link from 'next/link'; import { motion } from 'framer-motion'; -import { LayoutGrid, Layers, Bookmark, Activity, ChevronRight, Info } from 'lucide-react'; +import { LayoutGrid, Layers, Bookmark, Activity, ChevronRight, Info, Wallet } from 'lucide-react'; +import { Button } from '@/components/ui/button'; // Add Button import +import { useWallet } from '@/components/Faucet/walletcontext'; // Add wallet context import export default function NFTLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const router = useRouter(); const [mounted, setMounted] = useState(false); + const { account, connectWallet } = useWallet(); // Add wallet context hooks // Determine active section based on URL const isMarketplace = pathname === '/NFT'; @@ -24,9 +27,9 @@ export default function NFTLayout({ children }: { children: React.ReactNode }) { return (
- {/* Navigation Tabs */} -
-
+ {/* Navigation Tabs with Wallet Button */} +
+
+ + {/* Connect Wallet Button */} +
{/* Breadcrumb Navigation */} From bccc77e6331afd82a5f73c7ef950f33ce3874817 Mon Sep 17 00:00:00 2001 From: Mordred <95609626+TTMordred@users.noreply.github.com> Date: Sun, 16 Mar 2025 15:24:31 +0700 Subject: [PATCH 10/13] final --- app/NFT/layout.tsx | 7 +++++-- components/NFT/PriceChart.tsx | 2 +- next.config.js | 5 +---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/NFT/layout.tsx b/app/NFT/layout.tsx index ef9b177..165487b 100644 --- a/app/NFT/layout.tsx +++ b/app/NFT/layout.tsx @@ -71,8 +71,11 @@ export default function NFTLayout({ children }: { children: React.ReactNode }) {
{/* Connect Wallet Button */} - +
)}
) : ( - - Login - + Login )} {isOpen && ( -
-
) : ( - - Login - + Login )}
@@ -406,4 +354,4 @@ const Header = () => { ); }; -export default Header; +export default Header; \ No newline at end of file diff --git a/components/portfolio/ActivityTable.tsx b/components/portfolio/ActivityTable.tsx new file mode 100644 index 0000000..690fe9f --- /dev/null +++ b/components/portfolio/ActivityTable.tsx @@ -0,0 +1,150 @@ +"use client"; +import React, { useState } from "react"; +import { ArrowDownUp, ArrowDown, ArrowUp, ExternalLink, ChevronLeft, ChevronRight } from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { ethers } from "ethers"; + +interface Transaction { + hash: string; + timeStamp: string; + from: string; + to: string; + value: string; + isError: string; +} + +interface ActivityTableProps { + transactions: Transaction[]; + walletAddress: string; + isLoading: boolean; +} + +const ActivityTable: React.FC = ({ transactions, walletAddress, isLoading }) => { + const [currentPage, setCurrentPage] = useState(1); + const txPerPage = 5; + + const indexOfLastTx = currentPage * txPerPage; + const indexOfFirstTx = indexOfLastTx - txPerPage; + const currentTxs = transactions.slice(indexOfFirstTx, indexOfLastTx); + const totalPages = Math.ceil(transactions.length / txPerPage); + + const nextPage = () => currentPage < totalPages && setCurrentPage(currentPage + 1); + const prevPage = () => currentPage > 1 && setCurrentPage(currentPage - 1); + + const formatAddress = (address: string) => + address ? `${address.slice(0, 6)}...${address.slice(-4)}` : "Unknown"; + + const formatEthValue = (value: string) => + ethers.utils.formatEther(value || "0").substring(0, 6); + + const getTransactionType = (tx: Transaction) => + tx.to.toLowerCase() === walletAddress.toLowerCase() ? "incoming" : "outgoing"; + + return ( +
+
+
+
+ +
+

Activity

+
+ {transactions.length > 0 && ( +
+ {indexOfFirstTx + 1}-{Math.min(indexOfLastTx, transactions.length)} of {transactions.length} +
+ )} +
+ +
+ {isLoading ? ( + [...Array(5)].map((_, i) => ) + ) : transactions.length > 0 ? ( + currentTxs.map((tx, index) => ( +
+
+
+
+ {tx.isError === "1" ? ( + "✕" + ) : getTransactionType(tx) === "incoming" ? ( + + ) : ( + + )} +
+
+ {tx.isError === "1" + ? "Failed" + : getTransactionType(tx) === "incoming" + ? "Received" + : "Sent"} +
+
+
+ {formatDistanceToNow(parseInt(tx.timeStamp) * 1000, { addSuffix: true })} +
+
+
+

From: {formatAddress(tx.from)}

+

To: {formatAddress(tx.to)}

+

Value: {formatEthValue(tx.value)} ETH

+ + View on Etherscan + +
+
+ )) + ) : ( +
+ +

No activity found

+
+ )} +
+ + {transactions.length > txPerPage && ( +
+ + +
+ )} +
+ ); +}; + +export default ActivityTable; \ No newline at end of file diff --git a/components/portfolio/Allocation.tsx b/components/portfolio/Allocation.tsx new file mode 100644 index 0000000..d1cbdd7 --- /dev/null +++ b/components/portfolio/Allocation.tsx @@ -0,0 +1,143 @@ +"use client"; +import React from "react"; +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts"; +import { CircleDollarSign, PieChart as PieChartIcon } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; + +interface Token { + name: string; + symbol: string; + balance: string; + tokenAddress: string; + decimals: number; + value: number; +} + +interface AllocationChartProps { + tokens: Token[]; + ethBalance: string; + isLoading: boolean; +} + +const AllocationChart: React.FC = ({ tokens, ethBalance, isLoading }) => { + const calculateAllocation = () => { + const ethValue = parseFloat(ethBalance || "0"); + + if (tokens.length === 0 && ethValue <= 0) { + return []; + } + + // Danh sách màu cố định, đảm bảo mỗi coin có màu riêng biệt + const colors = [ + "#f6b355", // ETH + "#FF6B6B", // Token 1 + "#48dbfb", // Token 2 + "#1dd1a1", // Token 3 + "#feca57", // Token 4 + "#ff9ff3", // Token 5 + "#54a0ff", // Token 6 + ]; + + const assets = ethValue > 0 ? [{ name: "ETH", value: ethValue, color: colors[0] }] : []; + const uniqueTokens = new Map(); + + tokens.forEach((token, index) => { + if (uniqueTokens.has(token.symbol) || token.value <= 0) return; + + uniqueTokens.set(token.symbol, true); + const colorIndex = assets.length; // Đảm bảo màu không trùng với ETH hoặc token khác + assets.push({ + name: token.symbol, + value: token.value, + color: colors[colorIndex % colors.length], // Lấy màu từ danh sách + }); + }); + + const topAssets = assets.sort((a, b) => b.value - a.value).slice(0, 7); + const totalValue = topAssets.reduce((sum, asset) => sum + asset.value, 0); + return totalValue > 0 ? topAssets : []; + }; + + const data = calculateAllocation(); + + const formatTooltipValue = (value: number) => `${value.toFixed(4)} ETH`; + + const renderLegend = (props: any) => { + const { payload } = props; + return ( +
    + {payload.map((entry: any, index: number) => ( +
  • + + {entry.value} +
  • + ))} +
+ ); + }; + + return ( +
+
+
+ +
+

Asset Allocation

+
+ +
+ {isLoading ? ( +
+ +
+ ) : data.length > 0 ? ( + + + + {data.map((entry, index) => ( + + ))} + + + + + + ) : ( +
+ +

No assets found to display allocation

+
+ )} +
+
+ ); +}; + +export default AllocationChart; \ No newline at end of file diff --git a/components/portfolio/BalanceCard.tsx b/components/portfolio/BalanceCard.tsx new file mode 100644 index 0000000..c56d5e3 --- /dev/null +++ b/components/portfolio/BalanceCard.tsx @@ -0,0 +1,90 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { ArrowUpRight, Wallet } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; + +interface BalanceCardProps { + balance: string; + isLoading: boolean; +} + +const BalanceCard: React.FC = ({ balance, isLoading }) => { + const [currentTime, setCurrentTime] = useState(""); + + useEffect(() => { + setCurrentTime(new Date().toLocaleTimeString()); + }, [balance]); + + const formattedBalance = isLoading || !balance ? "0.0000" : parseFloat(balance).toFixed(4); + + return ( +
+ {/* Hiệu ứng hover border */} +
{ + e.currentTarget.style.borderColor = "#f6b355"; + e.currentTarget.style.opacity = "0.8"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.borderColor = "transparent"; + e.currentTarget.style.opacity = "0"; + }} + /> + +
+
+
+ +
+

+ ETH Balance +

+
+ + Etherscan + + +
+ +
+ {isLoading ? ( + + ) : ( +
+ + {formattedBalance} + + ETH +
+ )} +
+ {isLoading ? ( + + ) : ( + Last updated: {currentTime || "Just now"} + )} +
+
+
+ ); +}; + +export default BalanceCard; \ No newline at end of file diff --git a/components/portfolio/HistoryCard.tsx b/components/portfolio/HistoryCard.tsx new file mode 100644 index 0000000..f50feb4 --- /dev/null +++ b/components/portfolio/HistoryCard.tsx @@ -0,0 +1,104 @@ +"use client"; +import React, { useState } from "react"; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"; +import { AreaChart, Clock } from "lucide-react"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Skeleton } from "@/components/ui/skeleton"; +import { ethers } from "ethers"; + +interface Transaction { + hash: string; + timeStamp: string; + value: string; // Chuỗi thập phân từ API (wei) +} + +interface HistoryChartProps { + transactions: Transaction[]; + isLoading: boolean; +} + +const HistoryChart: React.FC = ({ transactions, isLoading }) => { + const [selectedPeriod, setSelectedPeriod] = useState("all"); + + const processChartData = (txs: Transaction[], period: string) => { + if (!txs || txs.length === 0) return []; + + const sortedTxs = [...txs].sort((a, b) => parseInt(a.timeStamp) - parseInt(b.timeStamp)); + let filteredTxs = sortedTxs; + const now = Math.floor(Date.now() / 1000); + + if (period === "1d") { + filteredTxs = sortedTxs.filter((tx) => now - parseInt(tx.timeStamp) < 86400); + } else if (period === "7d") { + filteredTxs = sortedTxs.filter((tx) => now - parseInt(tx.timeStamp) < 604800); + } else if (period === "30d") { + filteredTxs = sortedTxs.filter((tx) => now - parseInt(tx.timeStamp) < 2592000); + } + + const groupedData: Record = {}; + + filteredTxs.forEach((tx) => { + const timestampMs = parseInt(tx.timeStamp) * 1000; + if (isNaN(timestampMs)) return; + + const date = new Date(timestampMs).toLocaleDateString(); + // Chuyển đổi giá trị từ wei sang ETH + const valueInWei = tx.value || "0"; // Đảm bảo không có undefined + const valueInEth = parseFloat(ethers.utils.formatEther(valueInWei)); + + if (groupedData[date]) { + groupedData[date].value += valueInEth; + } else { + groupedData[date] = { date, value: valueInEth }; + } + }); + + return Object.values(groupedData); + }; + + const chartData = processChartData(transactions, selectedPeriod); + + return ( +
+
+
+
+ +
+

Transaction History

+
+ + + 1D + 7D + 30D + All + + +
+ +
+ {isLoading ? ( + + ) : chartData.length > 0 ? ( + + + + + + `${value.toFixed(4)} ETH`} /> + + + + ) : ( +
+ +

No transaction history available

+
+ )} +
+
+ ); +}; + +export default HistoryChart; \ No newline at end of file diff --git a/components/portfolio/NFTsCard.tsx b/components/portfolio/NFTsCard.tsx new file mode 100644 index 0000000..76f8dc8 --- /dev/null +++ b/components/portfolio/NFTsCard.tsx @@ -0,0 +1,147 @@ +"use client"; +import React, { useState } from "react"; +import { Image, ChevronRight, ChevronLeft } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Button } from "@/components/ui/button"; + +interface NFT { + name: string; + collectionName: string; + description: string; + tokenId: string; + contract: string; + imageUrl?: string; +} + +interface NFTsCardProps { + nfts: NFT[]; + isLoading: boolean; +} + +const NFTsCard: React.FC = ({ nfts, isLoading }) => { + const [currentPage, setCurrentPage] = useState(1); + const nftsPerPage = 2; + + const indexOfLastNFT = currentPage * nftsPerPage; + const indexOfFirstNFT = indexOfLastNFT - nftsPerPage; + const currentNFTs = nfts.slice(indexOfFirstNFT, indexOfLastNFT); + const totalPages = Math.ceil(nfts.length / nftsPerPage); + + const nextPage = () => { + if (currentPage < totalPages) setCurrentPage(currentPage + 1); + }; + + const prevPage = () => { + if (currentPage > 1) setCurrentPage(currentPage - 1); + }; + + return ( +
+
+
+
+ +
+

NFT Collection

+
+ {nfts.length > 0 && ( +
+ {indexOfFirstNFT + 1}-{Math.min(indexOfLastNFT, nfts.length)} of {nfts.length} +
+ )} +
+ +
+ {isLoading ? ( + [...Array(2)].map((_, i) => ( + + )) + ) : nfts.length > 0 ? ( + currentNFTs.map((nft, index) => ( +
+
+ {nft.imageUrl ? ( + {nft.name} { + const target = e.target as HTMLImageElement | null; + if (target) { + target.style.display = "none"; + const nextSibling = target.nextSibling as HTMLElement | null; + if (nextSibling) { + nextSibling.style.display = "block"; + } + } + }} + + /> + ) : ( +
+ +
+ )} +
+
+
+

+ {nft.name || `#${nft.tokenId}`} +

+

{nft.collectionName || "Unknown Collection"}

+

+ {nft.description || "No description available"} +

+

+ Token ID: {nft.tokenId.length > 8 ? `${nft.tokenId.substring(0, 8)}...` : nft.tokenId} +

+
+
+ )) + ) : ( +
+ +

No NFTs found for this wallet

+
+ )} +
+ + {nfts.length > nftsPerPage && ( +
+ + +
+ )} +
+ ); +}; + +export default NFTsCard; \ No newline at end of file diff --git a/components/portfolio/TokenCard.tsx b/components/portfolio/TokenCard.tsx new file mode 100644 index 0000000..f58d5be --- /dev/null +++ b/components/portfolio/TokenCard.tsx @@ -0,0 +1,121 @@ +"use client"; +import React, { useState } from "react"; +import { Coins, ChevronLeft, ChevronRight } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Button } from "@/components/ui/button"; + +interface Token { + name: string; + symbol: string; + balance: string; // Chuỗi thập phân đã định dạng + tokenAddress: string; + decimals: number; + logo?: string; + value: number; +} + +interface TokensCardProps { + tokens: Token[]; + isLoading: boolean; +} + +const TokensCard: React.FC = ({ tokens, isLoading }) => { + const [currentPage, setCurrentPage] = useState(1); + const tokensPerPage = 3; // Giới hạn 5 token mỗi trang + + // Tính toán chỉ số token hiển thị trên trang hiện tại + const indexOfLastToken = currentPage * tokensPerPage; + const indexOfFirstToken = indexOfLastToken - tokensPerPage; + const currentTokens = tokens.slice(indexOfFirstToken, indexOfLastToken); + const totalPages = Math.ceil(tokens.length / tokensPerPage); + + // Hàm chuyển trang + const nextPage = () => { + if (currentPage < totalPages) setCurrentPage(currentPage + 1); + }; + + const prevPage = () => { + if (currentPage > 1) setCurrentPage(currentPage - 1); + }; + + return ( +
+
+
+ +
+

Tokens

+
+ +
+ {isLoading ? ( + [...Array(5)].map((_, i) => ( + + )) + ) : tokens.length > 0 ? ( + currentTokens.map((token, index) => ( +
+
+ {token.logo ? ( + {token.symbol} (e.currentTarget.src = "/placeholder-token.png")} + /> + ) : ( + + )} +
+
+
{token.name}
+
+ {parseFloat(token.balance).toFixed(4)} {token.symbol} +
+
+
+ )) + ) : ( +
+ +

No tokens found

+
+ )} +
+ + {/* Nút chuyển trang */} + {tokens.length > tokensPerPage && ( +
+ +
+ Page {currentPage} of {totalPages} +
+ +
+ )} +
+ ); +}; + +export default TokensCard; \ No newline at end of file diff --git a/components/portfolio/WalletSearch.tsx b/components/portfolio/WalletSearch.tsx new file mode 100644 index 0000000..4a7c9d5 --- /dev/null +++ b/components/portfolio/WalletSearch.tsx @@ -0,0 +1,93 @@ +"use client"; +import React, { useState } from 'react'; +import { Search, ArrowRight } from 'lucide-react'; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { toast } from '@/components/ui/use-toast'; + +interface WalletSearchProps { + onSearch: (address: string) => void; + isLoading: boolean; +} + +const WalletSearch: React.FC = ({ onSearch, isLoading }) => { + const [address, setAddress] = useState(''); + const [isFocused, setIsFocused] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!address.trim()) { + toast({ + title: "Invalid Address", + description: "Please enter a valid wallet address", + variant: "destructive", + }); + return; + } + + if (address.trim().length < 26) { + toast({ + title: "Address too short", + description: "Please enter a complete wallet address", + variant: "destructive", + }); + return; + } + + onSearch(address.trim()); + }; + + return ( +
+
+

+ Wallet Portfolio Scanner +

+

+ Enter an Ethereum wallet address to view its assets and activity in real-time +

+
+ +
+
+
+ +
+ setAddress(e.target.value)} + placeholder="Enter wallet address (0x...)" + className="pl-10 pr-20 py-6 backdrop-blur-xl bg-shark-800/70 border border-amber/20 text-gray-200 placeholder:text-gray-500 focus:border-amber focus:ring-amber w-full" + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + style={{ + boxShadow: isFocused ? '0 0 10px 2px rgba(246, 179, 85, 0.4)' : 'none' + }} + /> + +
+
+
+ ); +}; + +export default WalletSearch; diff --git a/components/portfolio_service/alchemyService.tsx b/components/portfolio_service/alchemyService.tsx new file mode 100644 index 0000000..8e27b92 --- /dev/null +++ b/components/portfolio_service/alchemyService.tsx @@ -0,0 +1,235 @@ +"use client"; +import { ethers } from "ethers"; + +const ALCHEMY_API_KEY = "vHX215j9gH01Qc94rX2eEAsLeYohIu9X"; +const API_BASE_URL = `https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`; + +export interface WalletData { + balance: string; + tokens: Token[]; + nfts: NFT[]; + transactions: Transaction[]; +} + +export interface Token { + name: string; + symbol: string; + balance: string; + tokenAddress: string; + decimals: number; + logo?: string; + value: number; +} + +export interface NFT { + name: string; + collectionName: string; + description: string; + tokenId: string; + contract: string; + imageUrl?: string; +} + +export interface Transaction { + hash: string; + timeStamp: string; + from: string; + to: string; + value: string; + isError: string; +} + +const fetchAlchemyApi = async (endpoint: string, params: Record) => { + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: params.method, + params: params.params, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error: ${response.status} - ${response.statusText}`); + } + + const data = await response.json(); + if (data.error) { + console.error("Alchemy API error:", data.error); + throw new Error(`API error: ${data.error.message || "Unknown error"}`); + } + + return data.result; + } catch (error) { + console.error("Fetch Alchemy API failed:", error); + throw error; // Ném lỗi để hàm gọi xử lý + } +}; + +export const getWalletBalance = async (address: string): Promise => { + if (!ethers.utils.isAddress(address)) { + console.warn("Invalid Ethereum address:", address); + return "0"; + } + try { + const result = await fetchAlchemyApi("", { + method: "eth_getBalance", + params: [address, "latest"], + }); + const balance = ethers.utils.formatEther(result || "0"); + console.log(`Balance for ${address}: ${balance} ETH`); + return balance; + } catch (error) { + console.error("Error fetching balance:", error); + return "0"; + } +}; + +export const getWalletTokens = async (address: string): Promise => { + if (!ethers.utils.isAddress(address)) { + console.warn("Invalid Ethereum address:", address); + return []; + } + try { + const tokenBalances = await fetchAlchemyApi("", { + method: "alchemy_getTokenBalances", + params: [address, "DEFAULT_TOKENS"], // Lấy danh sách token mặc định + }); + + if (!tokenBalances?.tokenBalances) return []; + + const tokens = await Promise.all( + tokenBalances.tokenBalances + .filter((token: any) => token.tokenBalance && token.tokenBalance !== "0x0") + .map(async (token: any) => { + const metadata = await fetchAlchemyApi("", { + method: "alchemy_getTokenMetadata", + params: [token.contractAddress], + }); + + const balanceInWei = ethers.BigNumber.from(token.tokenBalance); + const decimals = metadata.decimals || 18; + const balance = ethers.utils.formatUnits(balanceInWei, decimals); + + return { + name: metadata.name || "Unknown Token", + symbol: metadata.symbol || "UNK", + balance, + tokenAddress: token.contractAddress, + decimals, + logo: metadata.logo || undefined, + value: parseFloat(balance) * 0.001, // Giá trị giả lập + }; + }) + ); + console.log(`Tokens for ${address}:`, tokens); + return tokens; + } catch (error) { + console.error("Error fetching tokens:", error); + return []; + } +}; + +export const getWalletNFTs = async (address: string): Promise => { + if (!ethers.utils.isAddress(address)) { + console.warn("Invalid Ethereum address:", address); + return []; + } + try { + const nfts = await fetchAlchemyApi("", { + method: "alchemy_getNFTs", + params: [{ owner: address, withMetadata: true }], // Đúng cú pháp cho alchemy_getNFTs + }); + + if (!nfts?.ownedNfts) return []; + + const nftList = nfts.ownedNfts.map((nft: any) => ({ + name: nft.metadata?.name || nft.title || `NFT #${nft.id.tokenId}`, + collectionName: nft.contract?.name || "Unknown Collection", + description: nft.metadata?.description || nft.description || "", + tokenId: nft.id.tokenId, + contract: nft.contract.address, + imageUrl: nft.metadata?.image || nft.media?.[0]?.gateway || nft.image?.thumbnailUrl, + })); + console.log(`NFTs for ${address}:`, nftList); + return nftList; + } catch (error) { + console.error("Error fetching NFTs:", error); + return []; + } +}; + +export const getWalletTransactions = async (address: string): Promise => { + if (!ethers.utils.isAddress(address)) { + console.warn("Invalid Ethereum address:", address); + return []; + } + try { + const transfersFrom = await fetchAlchemyApi("", { + method: "alchemy_getAssetTransfers", + params: [ + { + fromBlock: "0x0", + toBlock: "latest", + fromAddress: address, + category: ["external"], // Chỉ lấy giao dịch ETH + withMetadata: true, + }, + ], + }); + + const transfersTo = await fetchAlchemyApi("", { + method: "alchemy_getAssetTransfers", + params: [ + { + fromBlock: "0x0", + toBlock: "latest", + toAddress: address, + category: ["external"], + withMetadata: true, + }, + ], + }); + + const allTransfers = [...(transfersFrom?.transfers || []), ...(transfersTo?.transfers || [])]; + if (!allTransfers.length) { + console.log(`No transactions found for ${address}`); + return []; + } + + const txs = allTransfers.map((tx: any) => ({ + hash: tx.hash || "", + timeStamp: + tx.metadata?.blockTimestamp + ? Math.floor(new Date(tx.metadata.blockTimestamp).getTime() / 1000).toString() + : "0", + from: tx.from || "", + to: tx.to || "", + value: tx.value ? ethers.utils.parseEther(tx.value.toString()).toString() : "0", + isError: "0", // Alchemy không cung cấp thông tin lỗi, mặc định là 0 + })); + console.log(`Transactions for ${address}:`, txs); + return txs; + } catch (error) { + console.error("Error fetching transactions:", error); + return []; + } +}; + +export const getWalletData = async (address: string): Promise => { + try { + const [balance, tokens, nfts, transactions] = await Promise.all([ + getWalletBalance(address), + getWalletTokens(address), + getWalletNFTs(address), + getWalletTransactions(address), + ]); + return { balance, tokens, nfts, transactions }; + } catch (error) { + console.error("Error fetching wallet data:", error); + return { balance: "0", tokens: [], nfts: [], transactions: [] }; + } +}; \ No newline at end of file diff --git a/next.config.js b/next.config.js index e33f880..54a91cc 100644 --- a/next.config.js +++ b/next.config.js @@ -258,7 +258,10 @@ const nextConfig = { return config; }, // Add important settings for Vercel deployment - experimental: {}, + experimental: { + // Allow more time for API routes that make external calls + serverComponentsExternalPackages: [], + }, // Add extra security headers async headers() { return [