import { useState, useEffect, useRef } from "react";
// import { cn } from "@/lib/utils"; // cn is imported but not used, can be removed if not planned for use.
import {
TrendingUp,
TrendingDown,
// RefreshCw, // Imported but not used
Clock,
// ChevronDown, // Imported but not used
// ChevronUp, // Imported but not used
Eye,
EyeOff,
BarChart2,
Zap,
Clock3,
ArrowUpRight,
ArrowDownRight,
Menu,
Layers
} from "lucide-react";
// Mock crypto data
const cryptoData = [
{
id: "bitcoin",
name: "Bitcoin",
symbol: "BTC",
price: 48235.67,
change24h: 2.34,
volume24h: 28453921567,
marketCap: 924573622591,
sparkline: [46782, 47102, 47385, 46920, 47210, 47850, 48100, 47950, 48235],
logo: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iI2Y3OTMxYSIgZD0iTTE1Ljk2OSAzMS4xMDJjLTE0LjA5IDAtMTcuNTctMTMuMjQ1LTYuNDc5LTIwLjY3M2wsLjAwMi0xMS44OTJoMTIuOTUzbC4wMDIgMTEuODkxYzExLjA5MSA3LjQyOCA3LjYxIDIwLjY3NC02LjQ3OCAyMC42NzR6Ii8+PHBhdGggZmlsbD0iI2ZmZmJlYiIgZD0iTTIwLjgxOSAxNS44MzZ2LS41aDIuOTQ4di01LjAwMmgtMi44OTlsLjAwMi0zLjU1NWgtMy41NTVsLS4wMDEgMy41NTVoLTIuOTAxdi01aDIuODUzbC0uMDAyLTMuNTUzaC0zLjU1M2wtLjAwMiAzLjU1M2gtMi44NTZ2NWgyLjg1NnYuNWMtNC43NTcgMS44NzMtNC41NTcgOC42ODQuMzk5IDEwLjI3My00Ljk2MSAxLjU5NC01LjE1NiA4LjM5Ni0uMzg5IDEwLjI3MWgyLjg1MnYuNjJoMy41NTF2LS42MmgyLjkwMXY0Ljk4aC0yLjg1M3YuNjJoMy41NTJ2LS42MmgyLjg5OXYtNC45OGgtMi45MzdjNC43NjUtMS44OTEgNC41NjUtOC43ODQtLjQxLTEwLjI5MSA0Ljk3LTEuNTEyIDUuMTc1LTguNDI0LjM5OC0xMC4yNzN6Ii8+PHBhdGggZmlsbD0iI2Y3OTMxYSIgZD0iTTE4Ljc5MSAxOS4yNXYzLjVoLTUuNTgydi0zLjVoNS41ODJ6bTAtNy41djMuNWgtNS41ODJ2LTMuNWg1LjU4MnoiLz48L3N2Zz4="
},
{
id: "ethereum",
name: "Ethereum",
symbol: "ETH",
price: 3267.42,
change24h: -1.27,
volume24h: 15274362187,
marketCap: 392578623410,
sparkline: [3318, 3301, 3275, 3290, 3310, 3280, 3260, 3250, 3267],
logo: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIgZmlsbD0iIzYyN0VFQSIvPjxnIGZpbGw9IiNGRkYiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZmlsbC1vcGFjaXR5PSIuNjAyIiBkPSJNMTYuNDk4IDR2OC44N2w3LjQ5NyAzLjM1eiIvPjxwYXRoIGQ9Ik0xNi40OTggNHY4Ljg3bC03LjQ5NyAzLjM1eiIvPjxwYXRoIGZpbGwtb3BhY2l0eT0iLjYwMiIgZD0iTTE2LjQ5OCAyMC45Njh2Ni45OTdsMy41MDUuMDA1IDcuNTM4LTEwLjU2NXoiLz48cGF0aCBkPSJNMTYuNDk4IDI3Ljk2NXYtNi45OTdsLTcuNDk3LTMuNTYzeiIvPjxwYXRoIGZpbGwtb3BhY2l0eT0iLjIiIGQ9Ik0xNi40OTggMTkuNTdsNy40OTctMy41NjQtNy40OTctMy40MTZ6Ii8+PHBhdGggZmlsbC1vcGFjaXR5PSIuNjAyIiBkPSJNOS4wMDEgMTYuMDA2bDcuNDk3IDMuNTY0di02Ljk4eiIvPjwvZz48L2c+PC9zdmc+"
},
{
id: "solana",
name: "Solana",
symbol: "SOL",
price: 102.73,
change24h: 5.86,
volume24h: 2845391287,
marketCap: 44751928374,
sparkline: [97.21, 98.45, 99.12, 98.75, 99.45, 100.12, 101.34, 102.15, 102.73],
logo: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgNDAwIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6IzAwZmZiZDt9LmNscy0ye2ZpbGw6I2ZmZjt9LmNscy0ze2ZpbGw6IzkyZjBkZDt9PC9zdHlsZT48L2RlZnM+PGNpcmNsZSBjbGFzcz0iY2xzLTEiIGN4PSIyMDAiIGN5PSIyMDAiIHI9IjIwMCIvPjxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTI2OS4zLDk4LjAzaC0xMzlhNy4zLDcuMywwLDAsMC02LjE5LDMuNDFsLTMwLDUyYTcuMjgsNy4yOCwwLDAsMCwwLDcuMjlsMTIuMjQsMjEuMjRhNy4yOSw3LjI5LDAsMCwwLDEyLjgxLTEuODVsLjM2LTFhNy4zLDcuMywwLDAsMSwxMy43NS4wNmwuNDIsMXN2MGE3LjMsNy4zLDAsMCwwLDEyLjgxLjgzbDEuODUtNC00Mi0yMy40MUwxMzgsMTEzSDI2OS4zYTcuMyw3LjMsMCwwLDEsNy4zLDcuM3YuMDhhNy4zLDcuMywwLDAsMS03LjMsNy4zSDIwMy4xMWE3LjI4LDcuMjgsMCwwLDAtNi4xOSwzLjQxbC02Ljc2LDExLjc1YTcuMyw3LjMsMCwwLDAsMCw3LjMzbDE0LjYzLDI0Ljk1YTcuMjksNy4yOSwwLDAsMCw2LjI1LDMuNTJIMjY5LjNhNy4zLDcuMywwLDAsMSw3LjMsNy4zdjBhNy4zLDcuMywwLDAsMS03LjMsNy4zSDE3My42NWE3LjI5LDcuMjksMCwwLDAtNi4yNCwzLjUybC02LjgsNTIuMzNhNy4zLDcuMywwLDAsMCw3LjI2LDguMjhIMjY5LjNhNy4zLDcuMywwLDAsMSw3LjMsNy4zdjBhNy4zLDcuMywwLDAsMS03LjMsNy4zSDEzMS4wNmE3LjMsNy4zLDAsMCwxLTcuMy03LjN2LS4wOGE3LjMsNy4zLDAsMCwxLDIuNjctNS42N2w2LjQxLTUuMzdBNy4zLDcuMywwLDAsMCwxMzUuMjgsMjcwbC0uMzYtMWE3LjMsNy4zLDAsMCwwLTEzLjc1LS4wNmwtLjQyLDFhNy4zLDcuMywwLDAsMS0xMi44OCwyLjczTDExLjI2LDEyMC40MWE3LjI2LDcuMjYsMCwwLDEtMS0zLjc0di0xMGE3LjMsNy4zLDAsMCwxLDcuMy03LjNIMTMxYTcuMyw3LjMsMCwwLDEsNy4zLDcuM3YuMDhhNy4zLDcuMywwLDAsMS03LjMsNy4zSDgwLjA5YTcuMyw3LjMsMCwwLDAtNi4zNiwzLjcybC0xLjkzLDMuNTZhNy4yNiw3LjI2LDAsMCwwLC4wNyw3LjIxbDguMjgsMTQuOSw0MC40NSwyMi40OEwxNDAsMTYwSDIxMy45YTcuMjksNy4yOSwwLDAsMCw2LjI1LTMuNTJsMTQuNjMtMjUuMDFhNy4yOSs3LjI5LDAsMCwwLDAtNy4yOUwyMDQuNDgsMTA2LjVMMTc4LjMsMTEzSDEzOWw5Mi0xNWgyMS43MnYtLjA4YTcuMyw3LjMsMCwwLDEsMTQuNiwwdi4wOEEyLjc1LDIuNzUsMCwwLDEsMjY5LjMsOTguMDNaIi8+PHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMjU0LjE2LDI1Nkg2Ni41NWE1LDUsMCwwLDEtNS01VjYyLjY1YTUsNSwwLDAsMSw1LTVIMjU0LjE2YTUsNSwwLDAsMSw1LDV2MTguN0gyMzguODFWNjMuMzJINjcuMjFWMjUwLjM1SDIzOC44MXYtMTguN2gyMC4zNXYxOS4zNkE1LDUsMCwwLDEsMjU0LjE2LDI1NloiLz48L3N2Zz4="
},
{
id: "cardano",
name: "Cardano",
symbol: "ADA",
price: 0.5832,
change24h: 0.78,
volume24h: 925671498,
marketCap: 20565781234,
sparkline: [0.5775, 0.5790, 0.5810, 0.5795, 0.5820, 0.5830, 0.5825, 0.5820, 0.5832],
logo: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iLjUiIHkxPSIuOTg0IiB4Mj0iLjUiIHkyPSIuMDE2IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMDEzM0FFIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDM3NkQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PGcgZmlsbD0ibm9uZSI+PGNpcmNsZSBmaWxsPSJ1cmwoI2EpIiBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiLz48cGF0aCBkPSJNMjEuMzA3IDE2LjMxNWMuMDA4LS4xMDguMDMtLjE0My4wNzQtLjE1MmEuNDczLjQ3MyAwIDAwLjE3My0uMDY3Yy4yMDctLjExNi4zMS0uMzIuMzEtLjYxMyAwLS4yOTgtLjEwNC0uNTA4LS4zMS0uNjNhLjQ3My4Q3MyAwIDAwLS4xNzMtLjA2N2MtLjA0My0uMDA4LS4wNjUtLjA0Ni0uMDc0LS4xNTNhMS45MTcgMS45MTcgMCAwMC0uMDg2LS4zNzhjLS4wNjctLjIxLS4xNi0uNC0uMjgtLjU3MmEzLjMyNyAzLjMyNyAwIDAwLS40MTMtLjUxNWMuMTEtLjE2NC4xNzItLjM1LjE4Ny0uNTU1LjAwNy0uMTIzLjAzLS4xNi4wNzItLjE3NWwuMDkzLS4wMzhjLjIzNC0uMTE5LjM1MS0uMzQzLjM1MS0uNjczIDAtLjI5OC0uMTA0LS41MDctLjMxLS42MjlhLjQ5Ny40OTcgMCAwMC0uMTc0LS4wNjdjLS4wNDItLjAwOC0uMDY1LS4wNDUtLjA3NC0uMTUzYTQuOTg1IDQuOTg1IDAgMDAtLjE5LS44MzggNC45OCA0Ljk4IDAgMDAtLjMyOC0uNzU3Yy0uMTI5LS4yNTctLjI4LS41MDYtLjQ1LS43NDRhMTAuMTg2IDEwLjE4NiAwIDAwLS4yODYtLjM5NWMtLjEzNy0uMTgtLjI4Ni0uMzU0LS40NDMtLjUyNGE5LjMwOCAxMC4zMDggMCAwMC0uNTE2LS41MzEgNi42MDQgNi42MDQgMCAwMC0uNTM0LS40NjdjLS4xMzgtLjEwOC0uMjgtLjIxLS40MjYtLjMxMWFNuMjYzIDYuMjYzIDAgMDAtLjMxOC0uMThMMTYuNjkgNmwtLjIzOS0uMTE3YTguOTQ2IDguOTQ2IDAgMDAtLjczNi0uMzFjLS4yMDQtLjA3NC0uNDA4LS4xNDMtLjYxOC0uMjA2YTkuMTgzIDkuMTgzIDAgMDAtLjczNS0uMTYyIDQuOTk4IDQuOTk4IDAgMDAtLjU1Mi0uMDY3Yy0uMDc1LS4wMDYtLjExNi0uMDQyLS4xMjQtLjEwNmEuNDE0LjQxNCAwIDAwLS4wNjYtLjE3NkM5LjMyOSA3LjAyIDcuMTA0IDkuMjE4IDcuMTA0IDEyYzAgMi41MzYgMS45MzMgNC41NzcgNC4zMzEgNC41NzcgMi40MDMgMCA0LjMzNi0yLjA0IDQuMzM2LTQuNTc3YzAtLjExOC0uMDEtLjIzNi0uMDItLjM1NS0uMDExLS4wNzMuMDI2LS4wOTUuMDk1LS4wNjMuMDc4LjAzNC4xNTYuMDcuMjMuMTE0LjA3NC4wNDMuMTUuMDg3LjIyMi4xMzVsLjIwMy4xM2MuMDg2LjA1NS4xNjguMTE1LjI0Ny4xNzhhNi4xNCA2LjE0IDAgMDEuNTkuNTE1IDcuNTYgNy41NiAwIDAxMS4wMzYgMS4yNDZjLjExNi4xNy4yMjUuMzQ3LjMyMi41MjguMDk4LjE4Mi4xODcuMzY3LjI1My41NGEzLjU2OCAzLjU2OCAwIDAxLjE5Ny41MDQgMS40NiAxLjQ2IDAgMDEuMDcuMjg3LjQ3OS40NzkgMCAwMS4wNDMuMTM4Yy4wMDguMDQtLjAxOS4wNTYtLjA3Mi4wNDEtLjA0OS0uMDEyLS4wOTktLjAyLS4xNS0uMDMtLjA0OC0uMDEtLjA5OS0uMDIzLS4xNS0uMDM0YTYuOTI4IDYuOTI4IDAgMDEtLjM1Mi0uMDk4Yy0uMTEtLjAzNC0uMjIxLS4wNy0uMzI4LS4xMTJhNC43NTEgNC43NTEgMCAwMS0uMzIyLS4xMzYgMy4zMDEgMy4zMDEgMCAwMS0uMjM4LS4xMmMtLjA2OC0uMDQtLjA3Mi0uMDg3LS4wMDYtLjEzYy4wNjctLjQ1LjA2My0uOTAxLS4wMS0xLjM1ek0yNC4zMDIgMTAuODgyYy0uMDgtLjE4OC0uMTc3LS4zNjctLjI4LS41NDQtLjIzOC0uNDEzLS41Mi0uODA0LS44MzgtMS4xNzRhMy40MDQgMy40MDQgMCAwMC0uNDktLjQ2NCAyLjM0MyAyLjM0MyAwIDAwLS41Mi0uMzAzYy0uMDg0LS4wMzYtLjE3LS4wNy0uMjU2LS4xMDJhMy44MzMgMy44MzMgMCAwMC0uMjU5LS4wNzkgMS44NTggMS44NTggMCAwMC0uMjU1LS4wMzZDNS4zMyAyNC43MTIgNC4zMDYgMjQuNDg2IDQuMzA2IDI0YzAgLjM3Mi4xNy42ODUuNDI5Ljc5M2EuMzk4LjM5OCAwIDAwLjE3Mi4wNDZDNy43NzggMTQuNzA2IDExLjQ3NCAxNC40NyAxMS40NzQgMTRjMCAuMDQ1LjAxMi4wODcuMDM1LjEyMy4wMDYuMDA3LjAyMS4wMTQuMDQyLjAxOWwuMDY2LjAxMmMuMTE1LjAxNy4yMjkuMDE3LjM0NC0uMDExLjExMS0uMDI1LjIxLS4wNy4yOTMtLjEzNGE2Ljc1OCA2Ljc1OCAwIDAxLjQ4NC0uMjkgMy41NiAzLjU2IDAgMDEuNTk4LS4yNTljLjE2LS4wNTQuMzEtLjEwNi40NTItLjE1N2EyLjk5MyAyLjk5MyAwIDAxLjYwMi0uMTczbC4xMzQtLjAyNmMuMDQ0LS4wMS4wODgtLjAxMy4xMzItLjAxM2ExLjMzIDEuMzMgMCAwMC4xMzItLjAxMiAxLjY3MSAxLjY3MSAwIDAwLjM5My0uMDEyIDQuMzQ4IDQuMzQ4IDAgMDAuMzY0LS4wMzJsLjA2NS0uMDA2Yy4wMi0uMDAyLjAzMy0uMDA3LjA0MS0uMDE0LjAxLS4wMDcuMDE0LS4wMi4wMTQtLjAzM2wtLjAwMy0uMDIxYy4xNjgtLjg3MS4xMzQtMS43MzQtLjEtMi41ODN6bS0xLjQ5OSAxNS4xOTVjLS4wMjQuMDA1LS4wNDguMDExLS4wNzMuMDExLS4wMjIgMC0uMDQ0LS4wMDUtLjA2Ni0uMDEtLjA1My0uMDEyLS4xMDMtLjAzMy0uMTQ4LS4wNjFhMS4xMDggMS4xMDggMCAwMS0uMTQ3LS4wNzNjLS4wNTQtLjAzMi0uMTA3LS4wNy0uMTU4LS4xMTMtLjA0Mi0uMDM0LS4wODQtLjA3MS0uMTI0LS4xMThhMS4zMyAxLjMzIDAgMDEtLjA5LS4xMzJsLS4wMjEtLjA0MWMtLjAxNC0uMDMxLS4wMjYtLjA2Mi0uMDM5LS4wOTMtLjAxMi0uMDMyLS4wMTYtLjA2NC0uMDEyLS4wOTdsLjAwOS0uMDQ1Yy4wMzgtLjEzNC4xMDctLjI0My4yMDUtLjMyOS4wODYtLjA3NC4xOC0uMTI3LjI4LS4xNTlhMS4zMzIgMS4zMzIgMCAwMS4yODYtLjExNGwxLjIyMS0uMjk2Yy4xMTItLjAyNy4yMjItLjA0My4zMjgtLjA1MWExLjgzNiAxLjgzNiAwIDAxLjMyLS4wMTMgNC40MzIgNC40MzIgMCAwMTAuMDA0IDguNzk5IDEuODM2IDEuODM2IDAgMDEtLjMyLjAxMyAyLjIxNCAyLjIxNCAwIDAxLS4zMjkuMDUybC0xLjIyMS4yOTZhMS4zMzIgMS4zMzIgMCAwMS0uMjg2LjExNCAuOTYxLjk2MSAwIDAxLS4yOC4xNTljLS4wOTcuMDg2LS4xNjcuMTk1LS4yMDUuMzI5bC0uMDA5LjA0NGMtLjAwNC4wMzQuMDAxLjA2NS4wMTMuMDk4LjAxMy4wMy4wMjUuMDYyLjAzOS4wOTRsLjAyMS4wNDFjLjAyNC4wNDYuMDYxLjA5LjA5LjEzMi4wNS4wNDcuMTAyLjA4NC4xMjQuMTE4LjA1MS4wNDQuMTAyLjA4NC4xNTguMTE0LjA1LjAyNC4xMDQuMDQ5LjE0Ny4wNzNjLjA0Ni4wMjguMDk1LjA0OS4xNDkuMDYxLjAyMS4wMDYuMDQ0LjAxLjA2Ni4wMTEuMDI0IDAgLjA0OC0uMDA1LjA3My0uMDEyek0yNiA4LjE3Yy41MTYgMCAuOTM0LjQxOS45MzQuOTM0cy0uNDE4LjkzNC0uOTM0LjkzNC0uOTM0LS40MTktLjkzNC0uOTM0UzI1LjQ4NCA4LjE3IDI2IDguMTd6IiBmaWxsPSIjRkZGIi8+PC9nPjwvc3ZnPg=="
},
];
const trendingNews = [
{
title: "Bitcoin Surges Past $48K as Institutional Interest Grows",
source: "CryptoDaily",
time: "2 hours ago",
url: "#"
},
{
title: "Ethereum Layer 2 Solution Sees Record Transaction Volume",
source: "BlockchainNews",
time: "4 hours ago",
url: "#"
},
{
title: "Major Bank Announces Crypto Custody Services for Institutions",
source: "FinanceWeek",
time: "6 hours ago",
url: "#"
}
];
// Sparkline chart component
const SparklineChart = ({ data, color, width = 100, height = 30 }: { data: number[], color: string, width?: number, height?: number }) => {
// Basic sparkline logic
const minVal = Math.min(...data); // Renamed 'min' to 'minVal' to avoid conflict with potential built-ins
const maxVal = Math.max(...data); // Renamed 'max' to 'maxVal'
const range = maxVal - minVal;
const points = data.map((value, index) => {
const x = (data.length > 1 ? (index / (data.length - 1)) : 0.5) * width; // Handle single data point
const y = height - (range !== 0 ? ((value - minVal) / range) * height : height / 2); // Handle flat line (range is 0)
return `${x},${y}`;
}).join(' ');
return (
<svg width={width} height={height} className="overflow-visible">
<defs>
<linearGradient id={`gradient-${color.replace('#', '')}`} x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity="0.2" />
<stop offset="100%" stopColor={color} stopOpacity="0" />
</linearGradient>
</defs>
<polyline
points={points}
fill="none"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<polygon
points={`0,${height} ${points} ${width},${height}`}
fill={`url(#gradient-${color.replace('#', '')})`}
/>
</svg>
);
};
export const Component = () => {
const [showBalance, setShowBalance] = useState(true);
const [selectedCrypto, setSelectedCrypto] = useState(cryptoData[0]);
const [timeFrame, setTimeFrame] = useState<'1h' | '24h' | '7d' | '30d'>('24h');
const [currentTime, setCurrentTime] = useState(new Date());
const priceRef = useRef<HTMLDivElement>(null);
// Format currency
const formatCurrency = (value: number, maximumFractionDigits = 2) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits
}).format(value);
};
// Format large numbers with abbreviations
const formatNumber = (value: number) => {
if (value >= 1e9) {
return `$${(value / 1e9).toFixed(2)}B`;
} else if (value >= 1e6) {
return `$${(value / 1e6).toFixed(2)}M`;
} else if (value >= 1e3) {
return `$${(value / 1e3).toFixed(2)}K`;
}
return `$${value.toFixed(2)}`;
};
// Simulated real-time price updates
useEffect(() => {
const interval = setInterval(() => {
// Update crypto data with small random changes
// Note: This direct mutation of `cryptoData` is generally not recommended in React.
// For a real application, this data would likely come from state or a store.
cryptoData.forEach(crypto => {
const change = crypto.price * (Math.random() * 0.006 - 0.003);
crypto.price += change;
crypto.change24h += (change / crypto.price) * 100 * (Math.random() > 0.5 ? 1 : -1) * 0.1;
// Update sparkline
crypto.sparkline.shift();
crypto.sparkline.push(crypto.price);
});
// Update selected crypto if it's in the list
const updated = cryptoData.find(c => c.id === selectedCrypto.id);
if (updated) {
setSelectedCrypto({...updated}); // Create a new object to trigger re-render
// Add price change animation
if (priceRef.current) {
priceRef.current.classList.add('price-changed');
setTimeout(() => {
if (priceRef.current) {
priceRef.current.classList.remove('price-changed');
}
}, 1000);
}
}
// Update current time
setCurrentTime(new Date());
}, 5000);
return () => clearInterval(interval);
}, [selectedCrypto, selectedCrypto.id]); // Added selectedCrypto.id to dependencies for robustness
// Calculate portfolio value
const portfolioValue = 32567.42;
const portfolioChange = 783.21;
return (
<div className="w-full max-w-5xl mx-auto bg-gray-900 text-white rounded-xl overflow-hidden shadow-2xl border border-gray-800">
{/* Header with balance */}
<div className="p-4 md:p-6 bg-gradient-to-r from-gray-900 to-gray-800 border-b border-gray-800">
<div className="flex justify-between items-center mb-4">
<div className="flex items-center gap-2">
<div className="h-8 w-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
<Layers className="h-5 w-5 text-white" />
</div>
<h1 className="text-xl font-bold tracking-tight">CryptoVault</h1>
</div>
<div className="flex items-center gap-2">
<div className="text-xs text-gray-400 flex items-center gap-1">
<Clock size={12} />
<span>{currentTime.toLocaleTimeString()}</span>
</div>
<button className="bg-gray-800 hover:bg-gray-700 p-2 rounded-full transition-colors">
<Menu size={18} />
</button>
</div>
</div>
{/* Portfolio value */}
<div className="mb-6">
<div className="flex items-center gap-2 mb-1">
<h2 className="text-sm font-medium text-gray-400">Total Balance</h2>
<button
className="text-gray-500 hover:text-gray-400"
onClick={() => setShowBalance(!showBalance)}
>
{showBalance ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
<div className="flex items-end gap-3">
<div className="text-3xl font-bold">
{showBalance
? formatCurrency(portfolioValue)
: '••••••••'}
</div>
<div className={`text-sm font-medium flex items-center ${portfolioChange >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{portfolioChange >= 0
? <ArrowUpRight size={16} className="inline mr-0.5" />
: <ArrowDownRight size={16} className="inline mr-0.5" />}
{showBalance
? `${portfolioChange >= 0 ? '+' : ''}${formatCurrency(portfolioChange)} (${(portfolioChange / (portfolioValue - portfolioChange) * 100).toFixed(2)}%)`
: '•••••'}
</div>
</div>
</div>
{/* Time frame filters */}
<div className="flex gap-2">
{(['1h', '24h', '7d', '30d'] as const).map((time) => (
<button
key={time}
onClick={() => setTimeFrame(time)}
className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${
timeFrame === time
? 'bg-blue-600 text-white'
: 'bg-gray-800 text-gray-400 hover:bg-gray-700'
}`}
>
{time}
</button>
))}
</div>
</div>
{/* Main content */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 p-4 md:p-6">
{/* Left panel - Selected crypto */}
<div className="md:col-span-2 bg-gray-800 rounded-xl p-4 border border-gray-700 relative overflow-hidden">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -right-10 -top-10 w-40 h-40 bg-blue-600 opacity-10 blur-3xl rounded-full"></div>
<div className="absolute -left-10 -bottom-10 w-40 h-40 bg-purple-600 opacity-10 blur-3xl rounded-full"></div>
</div>
<div className="flex justify-between items-start mb-4 relative z-10">
<div className="flex items-center gap-3">
<div className="h-10 w-10 rounded-full overflow-hidden">
<img src={selectedCrypto.logo} alt={selectedCrypto.name} className="h-full w-full" />
</div>
<div>
<h3 className="text-xl font-bold flex items-center gap-2">
{selectedCrypto.name}
<span className="text-sm font-medium text-gray-400">{selectedCrypto.symbol}</span>
</h3>
<div
ref={priceRef}
className="text-2xl font-bold transition-colors"
>
{formatCurrency(selectedCrypto.price, selectedCrypto.price < 1 ? 4 : 2)}
</div>
</div>
</div>
<div className={`flex items-center gap-1 px-3 py-1 rounded-lg ${
selectedCrypto.change24h >= 0
? 'bg-green-500/10 text-green-400'
: 'bg-red-500/10 text-red-400'
}`}>
{selectedCrypto.change24h >= 0
? <TrendingUp size={16} />
: <TrendingDown size={16} />}
<span className="font-medium">
{selectedCrypto.change24h >= 0 ? '+' : ''}
{selectedCrypto.change24h.toFixed(2)}%
</span>
</div>
</div>
{/* Chart */}
<div className="h-52 w-full bg-gray-800/50 rounded-lg border border-gray-700/50 mb-4 overflow-hidden">
<div className="h-full w-full p-4">
<SparklineChart
data={selectedCrypto.sparkline}
color={selectedCrypto.change24h >= 0 ? '#10B981' : '#EF4444'}
width={typeof window !== 'undefined' && priceRef.current ? priceRef.current.closest('.md\\:col-span-2')?.clientWidth - 32 - 32 : 600} // Example for dynamic width
height={160}
/>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-4">
<div className="bg-gray-900/50 rounded-lg p-3 border border-gray-700/50">
<div className="text-xs text-gray-400 mb-1">Market Cap</div>
<div className="text-lg font-medium">{formatNumber(selectedCrypto.marketCap)}</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-3 border border-gray-700/50">
<div className="text-xs text-gray-400 mb-1">24h Volume</div>
<div className="text-lg font-medium">{formatNumber(selectedCrypto.volume24h)}</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-3 border border-gray-700/50">
<div className="text-xs text-gray-400 mb-1">All Time High</div>
<div className="text-lg font-medium">{formatCurrency(selectedCrypto.price * 1.5, 0)}</div> {/* Placeholder for ATH */}
</div>
</div>
{/* Action buttons */}
<div className="mt-6 flex gap-4">
<button className="flex-1 py-2 rounded-lg bg-gradient-to-r from-blue-600 to-blue-500 text-white font-medium text-sm hover:from-blue-700 hover:to-blue-600 transition-colors focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
Buy {selectedCrypto.symbol}
</button>
<button className="flex-1 py-2 rounded-lg bg-gray-700 text-white font-medium text-sm hover:bg-gray-600 transition-colors focus:ring-2 focus:ring-gray-500 focus:ring-opacity-50">
Sell {selectedCrypto.symbol}
</button>
</div>
</div>
{/* Right panel - Assets & News */}
<div className="flex flex-col gap-4">
{/* Assets list */}
<div className="bg-gray-800 rounded-xl p-4 border border-gray-700">
<h3 className="text-sm font-medium text-gray-400 mb-3">Top Assets</h3>
<div className="space-y-3">
{cryptoData.map((crypto) => (
<button
key={crypto.id}
className={`w-full flex items-center p-2 rounded-lg transition-colors ${
selectedCrypto.id === crypto.id
? 'bg-blue-600/10 border border-blue-600/30'
: 'hover:bg-gray-700/50 border border-transparent'
}`}
onClick={() => setSelectedCrypto(crypto)}
>
<div className="h-8 w-8 rounded-full overflow-hidden mr-3">
<img src={crypto.logo} alt={crypto.name} className="h-full w-full" />
</div>
<div className="flex-1 flex flex-col items-start min-w-0">
<div className="flex items-center w-full">
<span className="text-sm font-medium truncate">{crypto.name}</span>
<span className="text-xs text-gray-400 ml-1">{crypto.symbol}</span>
</div>
<div className="flex items-center w-full">
<span className="text-sm truncate">{formatCurrency(crypto.price, crypto.price < 1 ? 4 : 2)}</span>
<span className={`text-xs ml-2 ${
crypto.change24h >= 0 ? 'text-green-400' : 'text-red-400'
}`}>
{crypto.change24h >= 0 ? '+' : ''}
{crypto.change24h.toFixed(2)}%
</span>
</div>
</div>
<div className="w-16 h-12 flex items-center justify-center"> {/* Added flex for centering */}
<SparklineChart
data={crypto.sparkline}
color={crypto.change24h >= 0 ? '#10B981' : '#EF4444'}
width={64}
height={32}
/>
</div>
</button>
))}
</div>
</div>
{/* News */}
<div className="bg-gray-800 rounded-xl p-4 border border-gray-700">
<h3 className="text-sm font-medium text-gray-400 mb-3">Trending News</h3>
<div className="space-y-3">
{trendingNews.map((news, index) => (
<a // <-- FIX: Added opening <a> tag here
key={index}
href={news.url}
target="_blank" // Good practice for external links
rel="noopener noreferrer" // Good practice for security
className="block p-2 rounded-lg hover:bg-gray-700/30 transition-colors"
>
<h4 className="text-sm font-medium mb-1">{news.title}</h4>
<div className="flex items-center text-xs text-gray-400">
<span>{news.source}</span>
<span className="mx-1">•</span>
<span className="flex items-center">
<Clock3 size={10} className="mr-1" />
{news.time}
</span>
</div>
</a>
))}
</div>
</div>
{/* Quick actions */}
<div className="bg-gray-800 rounded-xl p-4 border border-gray-700">
<h3 className="text-sm font-medium text-gray-400 mb-3">Quick Actions</h3>
<div className="grid grid-cols-2 gap-2">
<button className="p-3 rounded-lg bg-blue-600/20 hover:bg-blue-600/30 transition-colors flex flex-col items-center">
<Zap size={18} className="text-blue-400 mb-1" />
<span className="text-xs">Swap</span>
</button>
<button className="p-3 rounded-lg bg-purple-600/20 hover:bg-purple-600/30 transition-colors flex flex-col items-center">
<BarChart2 size={18} className="text-purple-400 mb-1" />
<span className="text-xs">Trade</span>
</button>
</div>
</div>
</div>
</div>
{/* CSS for animations */}
<style jsx>{`
.price-changed {
animation: pulse 1s ease-in-out;
}
@keyframes pulse {
0% { color: white; }
50% { color: rgb(59, 130, 246); } /* Tailwind's blue-500 */
100% { color: white; }
}
`}</style>
</div>
);
};