diff --git a/README.md b/README.md index 6928208..af1c481 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,24 @@ ```bash # Clone the repository git clone https://github.com/TTMordred/CryptoPath.git +``` # Navigate to project directory +``` cd cryptopath +``` # Install dependencies +```s npm install npm install next --legacy-peer-deps +``` # Set up environment variables -touch .env.local ```s +touch .env.local +``` + Populate `.env.local` with: ```dotenv ETHERSCAN_API_KEY=YOUR_API_KEY diff --git a/app/login/page.tsx b/app/login/page.tsx index 0d8bfee..fe41df5 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,35 +1,54 @@ -"use client"; - -import Link from "next/link"; -import { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import ParticlesBackground from "@/components/ParticlesBackground"; -import { toast } from "sonner"; -import { supabase } from "@/src/integrations/supabase/client"; -import { Web3OnboardProvider, init, useConnectWallet } from "@web3-onboard/react"; -import injectedModule from "@web3-onboard/injected-wallets"; -import walletConnectModule from "@web3-onboard/walletconnect"; -import coinbaseModule from "@web3-onboard/coinbase"; -import infinityWalletModule from "@web3-onboard/infinity-wallet"; -import safeModule from "@web3-onboard/gnosis"; -import trezorModule from "@web3-onboard/trezor"; -import magicModule from "@web3-onboard/magic"; -import dcentModule from "@web3-onboard/dcent"; -import sequenceModule from "@web3-onboard/sequence"; -import tahoModule from "@web3-onboard/taho"; -import trustModule from "@web3-onboard/trust"; -import okxModule from "@web3-onboard/okx"; -import frontierModule from "@web3-onboard/frontier"; -import { useAuth } from "@/lib/context/AuthContext"; -import { useSettings } from "@/components/context/SettingsContext"; -import { Subscription } from "@supabase/supabase-js"; -import bcrypt from "bcryptjs"; // Thêm bcryptjs để hash mật khẩu -import crypto from "crypto"; // Dùng crypto để mã hóa dữ liệu +'use client'; + +import Link from 'next/link'; +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import ParticlesBackground from '@/components/ParticlesBackground'; +import { toast } from 'sonner'; +import { supabase} from '@/src/integrations/supabase/client'; +import { PostgrestError } from '@supabase/supabase-js'; +import { Web3OnboardProvider, init, useConnectWallet} from '@web3-onboard/react'; + +import injectedModule from '@web3-onboard/injected-wallets'; +import walletConnectModule from '@web3-onboard/walletconnect'; +import coinbaseModule from '@web3-onboard/coinbase'; +import infinityWalletModule from '@web3-onboard/infinity-wallet'; +import safeModule from '@web3-onboard/gnosis'; +import trezorModule from '@web3-onboard/trezor'; +import magicModule from '@web3-onboard/magic'; +import dcentModule from '@web3-onboard/dcent'; +import sequenceModule from '@web3-onboard/sequence'; +import tahoModule from '@web3-onboard/taho'; +import trustModule from '@web3-onboard/trust'; +import okxModule from '@web3-onboard/okx'; +import frontierModule from '@web3-onboard/frontier'; +import { useAuth } from '@/lib/context/AuthContext'; +import { useSettings } from '@/components/context/SettingsContext'; + +// Định nghĩa kiểu cho profile +interface Profile { + id: string; + username: string | null; + display_name: string | null; + avatar_url: string | null; + created_at: string; + updated_at: string; + auth_provider: string | null; + profile_image: string | null; + background_image: string | null; + wallets: { address: string; is_default: boolean }[] | null; +} + +// Định nghĩa kiểu cho account +interface Account { + address: `0x${string}`; + ens: string | null; +} // Web3-Onboard configuration -const INFURA_KEY = "7d389678fba04ceb9510b2be4fff5129"; +const INFURA_KEY = '7d389678fba04ceb9510b2be4fff5129'; const walletConnect = walletConnectModule({ - projectId: "b773e42585868b9b143bb0f1664670f1", + projectId: 'b773e42585868b9b143bb0f1664670f1', optionalChains: [1, 137], }); @@ -45,38 +64,35 @@ const wallets = [ dcentModule(), walletConnect, safeModule(), - magicModule({ apiKey: "pk_live_E9B0C0916678868E" }), - trezorModule({ email: "test@test.com", appUrl: "https://www.blocknative.com" }), + magicModule({ apiKey: 'pk_live_E9B0C0916678868E' }), + trezorModule({ email: 'test@test.com', appUrl: 'https://www.blocknative.com' }), ]; const chains = [ - { id: "0x1", token: "ETH", label: "Ethereum Mainnet", rpcUrl: `https://mainnet.infura.io/v3/${INFURA_KEY}` }, - { id: "11155111", token: "ETH", label: "Sepolia", rpcUrl: "https://rpc.sepolia.org/" }, - { id: "0x13881", token: "MATIC", label: "Polygon - Mumbai", rpcUrl: "https://matic-mumbai.chainstacklabs.com" }, - { id: "0x38", token: "BNB", label: "Binance", rpcUrl: "https://bsc-dataseed.binance.org/" }, - { id: "0xA", token: "OETH", label: "OP Mainnet", rpcUrl: "https://mainnet.optimism.io" }, - { id: "0xA4B1", token: "ARB-ETH", label: "Arbitrum", rpcUrl: "https://rpc.ankr.com/arbitrum" }, - { id: "0xa4ec", token: "ETH", label: "Celo", rpcUrl: "https://1rpc.io/celo" }, - { id: "666666666", token: "DEGEN", label: "Degen", rpcUrl: "https://rpc.degen.tips" }, - { id: "2192", token: "SNAX", label: "SNAX Chain", rpcUrl: "https://mainnet.snaxchain.io" }, + { id: '0x1', token: 'ETH', label: 'Ethereum Mainnet', rpcUrl: `https://mainnet.infura.io/v3/${INFURA_KEY}` }, + { id: '11155111', token: 'ETH', label: 'Sepolia', rpcUrl: 'https://rpc.sepolia.org/' }, + { id: '0x13881', token: 'MATIC', label: 'Polygon - Mumbai', rpcUrl: 'https://matic-mumbai.chainstacklabs.com' }, + { id: '0x38', token: 'BNB', label: 'Binance', rpcUrl: 'https://bsc-dataseed.binance.org/' }, + { id: '0xA', token: 'OETH', label: 'OP Mainnet', rpcUrl: 'https://mainnet.optimism.io' }, + { id: '0xA4B1', token: 'ARB-ETH', label: 'Arbitrum', rpcUrl: 'https://rpc.ankr.com/arbitrum' }, + { id: '0xa4ec', token: 'ETH', label: 'Celo', rpcUrl: 'https://1rpc.io/celo' }, + { id: '666666666', token: 'DEGEN', label: 'Degen', rpcUrl: 'https://rpc.degen.tips' }, + { id: '2192', token: 'SNAX', label: 'SNAX Chain', rpcUrl: 'https://mainnet.snaxchain.io' }, ]; const appMetadata = { - name: "CryptoPath", - description: "Login to CryptoPath with your wallet", + name: 'CryptoPath', + description: 'Login to CryptoPath with your wallet', recommendedInjectedWallets: [ - { name: "MetaMask", url: "https://metamask.io" }, - { name: "Coinbase", url: "https://wallet.coinbase.com/" }, + { name: 'MetaMask', url: 'https://metamask.io' }, + { name: 'Coinbase', url: 'https://wallet.coinbase.com/' }, ], }; const web3Onboard = init({ wallets, chains, appMetadata }); -// Debounce utility with proper TypeScript types -const debounce = void>( - func: T, - wait: number -): ((...args: Parameters) => void) => { +// Debounce utility với kiểu TypeScript +const debounce = void>(func: T, wait: number) => { let timeout: NodeJS.Timeout | undefined; return (...args: Parameters) => { clearTimeout(timeout); @@ -84,91 +100,42 @@ const debounce = void>( }; }; -// Khóa bí mật để mã hóa dữ liệu (nên lưu trong biến môi trường trong thực tế) -const ENCRYPTION_KEY = process.env.NEXT_PUBLIC_ENCRYPTION_KEY || "your-secret-key-here-32bytes-long"; -const IV_LENGTH = 16; // Độ dài IV cho AES - -// Hàm mã hóa dữ liệu -const encryptData = (text: string): string => { - const iv = crypto.randomBytes(IV_LENGTH); - const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(ENCRYPTION_KEY), iv); - let encrypted = cipher.update(text); - encrypted = Buffer.concat([encrypted, cipher.final()]); - return iv.toString("hex") + ":" + encrypted.toString("hex"); -}; - -// Hàm giải mã dữ liệu -const decryptData = (text: string): string => { - const [iv, encryptedText] = text.split(":"); - const decipher = crypto.createDecipheriv( - "aes-256-cbc", - Buffer.from(ENCRYPTION_KEY), - Buffer.from(iv, "hex") - ); - let decrypted = decipher.update(Buffer.from(encryptedText, "hex")); - decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted.toString(); -}; - function LoginPageContent() { const router = useRouter(); - const { signInWithWalletConnect, signIn } = useAuth(); + const { signInWithWalletConnect } = useAuth(); const { updateProfile, addWallet, syncWithSupabase } = useSettings(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [emailError, setEmailError] = useState(""); - const [passwordError, setPasswordError] = useState(""); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [emailError, setEmailError] = useState(''); + const [passwordError, setPasswordError] = useState(''); const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); const [{ wallet, connecting }, connect, disconnect] = useConnectWallet(); const [isLoggedOut, setIsLoggedOut] = useState(false); - - interface Account { - address: string; - ens: string | null; - } - const [account, setAccount] = useState(null); const formatWalletAddress = (walletAddress: string): string => { - if (!walletAddress) return ""; + if (!walletAddress) return ''; return `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}`; }; - // Check session and listen to auth state changes useEffect(() => { const checkExistingSession = async () => { - const { data: { session }, error } = await supabase.auth.getSession(); - if (error) { - console.error("Error checking session:", error.message); - return; - } - if (session) { - console.log("Initial session found, redirecting to dashboard"); - router.push("/"); - } + const { data: { session } } = await supabase.auth.getSession(); + if (session) router.push('/'); }; checkExistingSession(); const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => { - console.log("Auth state changed:", event); - if (event === "SIGNED_IN" && session) { - console.log("User signed in, redirecting to dashboard"); - router.push("/"); - } else if (event === "SIGNED_OUT") { - console.log("User signed out, staying on login page"); - setIsLoggedOut(true); - } + if (event === 'SIGNED_IN' && session) router.push('/'); + else if (event === 'SIGNED_OUT') setIsLoggedOut(true); }); - return () => { - subscription.unsubscribe(); - }; + return () => subscription.unsubscribe(); }, [router]); - // Handle wallet connection and authentication useEffect(() => { if (wallet?.provider && !isLoggedOut) { const { address, ens } = wallet.accounts[0]; @@ -177,28 +144,10 @@ function LoginPageContent() { const authenticateWithWallet = async () => { try { setIsLoading(true); - const hashedAddress = await bcrypt.hash(address, 10); // Hash địa chỉ ví const { data, error } = await signInWithWalletConnect(address); - if (error) { - if (error.message.includes("User already registered")) { - toast.info("User already exists. Logging in instead."); - const { data: loginData, error: loginError } = await supabase.auth.signInWithPassword({ - email: `${address}@cryptopath.local`, - password: hashedAddress, // Sử dụng hashed address làm mật khẩu - }); - if (loginError) throw new Error(loginError.message); - if (!loginData.session) throw new Error("No session returned from login"); - const encryptedToken = encryptData(JSON.stringify(loginData.session.access_token)); - localStorage.setItem("userToken", encryptedToken); - } else { - throw new Error(error.message); - } - } else if (!data || !data.session) { - throw new Error("No session data returned from sign-in"); - } else { - const encryptedToken = encryptData(JSON.stringify(data.session.access_token)); - localStorage.setItem("userToken", encryptedToken); - } + if (error) throw new Error(error.message); + + if (!data || !data.session) throw new Error('No session data returned from sign-in'); updateProfile({ username: ens?.name || formatWalletAddress(address), @@ -208,21 +157,24 @@ function LoginPageContent() { addWallet(address); await syncWithSupabase(); - const publicUserData = { - walletAddress: address, - name: ens?.name || formatWalletAddress(address), - isLoggedIn: true, - }; - const encryptedUserData = encryptData(JSON.stringify(publicUserData)); - localStorage.setItem("userDisplayInfo", encryptedUserData); - - console.log("Wallet login successful, redirecting to dashboard"); - toast.success("Successfully authenticated with wallet"); - router.push("/"); + // Cập nhật wallets trong profiles với cấu trúc đúng + const { error: profileError } = await supabase + .from('profiles') + .upsert({ + id: data.session.user.id, + username: ens?.name || formatWalletAddress(address), + wallets: [{ address, is_default: true }], // Đảm bảo khớp kiểu { address: string; is_default: boolean } + updated_at: new Date().toISOString(), + }); + + if (profileError) throw new Error(profileError.message); + + toast.success('Successfully authenticated with wallet'); + router.push('/'); } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - console.error("Wallet authentication error:", error); - toast.error(`Authentication failed: ${errorMessage}`); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error('Wallet authentication error:', error); + toast.error(`Authentication failed: ${message}`); } finally { setIsLoading(false); } @@ -232,65 +184,60 @@ function LoginPageContent() { } }, [wallet, router, isLoggedOut, signInWithWalletConnect, updateProfile, addWallet, syncWithSupabase]); - // Handle email/password login const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setEmailError(""); - setPasswordError(""); + setEmailError(''); + setPasswordError(''); setIsLoading(true); try { - const hashedPassword = await bcrypt.hash(password, 10); // Hash mật khẩu trước khi gửi - const { data, error } = await signIn(email, hashedPassword); // Gửi hashed password + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + if (error) { - if (error.message.includes("email")) setEmailError(error.message); - else if (error.message.includes("password")) setPasswordError(error.message); + if (error.message.includes('email')) setEmailError(error.message); + else if (error.message.includes('password')) setPasswordError(error.message); else toast.error(error.message); return; } if (!data.user || !data.session) { - toast.error("Something went wrong with the login"); + toast.error('Login failed: No user data returned.'); return; } - const { data: profileData } = await supabase - .from("profiles") - .select("*") - .eq("id", data.user.id) + const { data: profileData, error: profileError } = await supabase + .from('profiles') + .select('*') + .eq('id', data.user.id) .single(); + if (profileError) { + console.error('Error fetching profile:', profileError); + toast.error('Failed to load profile data.'); + return; + } + updateProfile({ - username: profileData?.display_name || email.split("@")[0], - profileImage: profileData?.profile_image || null, - backgroundImage: profileData?.background_image || null, + username: profileData.username || email.split('@')[0], + profileImage: profileData.profile_image || null, + backgroundImage: profileData.background_image || null, }); await syncWithSupabase(); - const publicUserData = { - id: data.user.id, - name: profileData?.display_name || email.split("@")[0], - email, - isLoggedIn: true, - }; - const encryptedUserData = encryptData(JSON.stringify(publicUserData)); - const encryptedToken = encryptData(JSON.stringify(data.session.access_token)); - localStorage.setItem("currentUser", encryptedUserData); - localStorage.setItem("userToken", encryptedToken); - - console.log("Email login successful, redirecting to dashboard"); - toast.success("Login successful!"); - router.push("/"); + toast.success('Login successful!'); + router.push('/'); } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - console.error("Login error:", error); - toast.error(`An unexpected error occurred: ${errorMessage}`); + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error('Login error:', error); + toast.error(`An unexpected error occurred: ${message}`); } finally { setIsLoading(false); } }; - // Handle wallet connect/disconnect with debounce const handleWalletConnect = debounce(async () => { if (!wallet) { await connect(); @@ -299,9 +246,7 @@ function LoginPageContent() { setAccount(null); setIsLoggedOut(true); await supabase.auth.signOut(); - localStorage.removeItem("userDisplayInfo"); - localStorage.removeItem("userToken"); - router.push("/login"); + router.push('/login'); } }, 1000); @@ -312,13 +257,13 @@ function LoginPageContent() {
-
+

Welcome back

- Login to your{" "} + Login to your{' '} CryptoPath account

@@ -329,7 +274,7 @@ function LoginPageContent() { type="email" placeholder="m@example.com" required - className="w-full px-3 py-2 border border-white rounded-md bg-black text-white" + className="w-full px-3 py-2 border rounded-[20px] bg-black text-white" value={email} onChange={(e) => setEmail(e.target.value)} disabled={isLoading} @@ -343,9 +288,9 @@ function LoginPageContent() {
setPassword(e.target.value)} disabled={isLoading} @@ -353,18 +298,48 @@ function LoginPageContent() { @@ -373,7 +348,7 @@ function LoginPageContent() {
@@ -412,16 +394,23 @@ function LoginPageContent() { type="button" onClick={handleWalletConnect} disabled={connecting || isLoading} - className="flex items-center justify-center w-full border border-white rounded-md py-2 px-4 hover:bg-gray-800" + className="flex items-center justify-center w-full ] border rounded-[20px] py-2 px-4 hover: transition-transform duration-200 hover:-translate-y-1" > - - + + Login with Wallet
- Don't have an account?{" "} + Don't have an account?{' '} Sign up
@@ -429,9 +418,9 @@ function LoginPageContent() {
- By clicking continue, you agree to our{" "} - Terms of Service{" "} - and{" "} + By clicking continue, you agree to our{' '} + Terms of Service{' '} + and{' '} Privacy Policy.
diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 7bb00c3..f307580 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -1,8 +1,7 @@ - 'use client'; import Link from 'next/link'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useRouter } from 'next/navigation'; import ParticlesBackground from '@/components/ParticlesBackground'; import { toast } from 'sonner'; @@ -11,7 +10,6 @@ import { supabase } from '@/src/integrations/supabase/client'; export default function SignupPage() { const router = useRouter(); - // Form state const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -24,7 +22,6 @@ export default function SignupPage() { const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); - // Helper function for email validation const validateEmail = (email: string) => { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email.toLowerCase()); @@ -33,7 +30,6 @@ export default function SignupPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // Reset error messages setNameError(''); setEmailError(''); setPasswordError(''); @@ -65,10 +61,10 @@ export default function SignupPage() { setIsLoading(true); try { - // Register the user with Supabase Auth + // Đăng ký với Supabase Auth const { data, error } = await supabase.auth.signUp({ email, - password, + password, // Gửi mật khẩu gốc options: { data: { full_name: name.trim(), @@ -77,13 +73,32 @@ export default function SignupPage() { }); if (error) { - if (error.message.includes('email')) { - setEmailError(error.message); - } else if (error.message.includes('password')) { - setPasswordError(error.message); - } else { - toast.error(error.message); - } + if (error.message.includes('email')) setEmailError(error.message); + else if (error.message.includes('password')) setPasswordError(error.message); + else toast.error(error.message); + return; + } + + if (!data.user) { + toast.error('User creation failed.'); + return; + } + + // Thêm thông tin vào bảng profiles + const { error: profileError } = await supabase + .from('profiles') + .insert({ + id: data.user.id, + username: email.split('@')[0], + display_name: name.trim(), + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + auth_provider: 'email', + }); + + if (profileError) { + console.error('Error inserting profile:', profileError); + toast.error('Failed to create profile. Please try again.'); return; } @@ -101,225 +116,218 @@ export default function SignupPage() { return ( <>
- - {/* Signup Form */} -
-
-
-
-
- -
-
-

Create an account

-

- Sign up for your{' '} - CryptoPath account -

-
-
- - setName(e.target.value)} - disabled={isLoading} - /> - {nameError && {nameError}} -
-
- - setEmail(e.target.value)} - disabled={isLoading} - /> - {emailError && {emailError}} -
-
- -
+ +
+
+
+
+
+ +
+
+

Create an account

+

+ Sign up for your{' '} + CryptoPath account +

+
+
+ setPassword(e.target.value)} + className="w-full px-3 py-2 bg-black border rounded-[20px] text-white" + value={name} + onChange={(e) => setName(e.target.value)} disabled={isLoading} /> - + {nameError && {nameError}}
- {passwordError && {passwordError}} -
-
- -
+
+ setConfirmPassword(e.target.value)} + className="w-full px-3 py-2 bg-black border rounded-[20px] text-white" + value={email} + onChange={(e) => setEmail(e.target.value)} disabled={isLoading} /> - + {emailError && {emailError}} +
+
+ +
+ setPassword(e.target.value)} + disabled={isLoading} + /> + +
+ {passwordError && {passwordError}} +
+
+ +
+ setConfirmPassword(e.target.value)} + disabled={isLoading} + /> + +
+ {confirmPasswordError && ( + {confirmPasswordError} + )} +
+ +
+ Already have an account?{' '} + + Log in +
- {confirmPasswordError && ( - {confirmPasswordError} - )} -
- -
- Already have an account?{' '} - - Log in -
-
- + +
-
-
- By clicking continue, you agree to our{' '} - - Terms of Service - {' '} - and{' '} - - Privacy Policy - . +
+ By clicking continue, you agree to our{' '} + + Terms of Service + {' '} + and{' '} + + Privacy Policy + . +
-
); -} +} \ No newline at end of file diff --git a/components/table/CoinCard.tsx b/components/table/CoinCard.tsx index 568dde4..bd831f8 100644 --- a/components/table/CoinCard.tsx +++ b/components/table/CoinCard.tsx @@ -32,7 +32,7 @@ const CoinCard: React.FC = ({ coin, onCardClick }) => {
-

+

Top Movers (24h)

diff --git a/components/transactions/RevenueGraph.tsx b/components/transactions/RevenueGraph.tsx index 1091581..9b2111c 100644 --- a/components/transactions/RevenueGraph.tsx +++ b/components/transactions/RevenueGraph.tsx @@ -3,7 +3,7 @@ import { useEffect, useState, useCallback, useMemo, memo } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; -import { fetchHistoricalData, fetchAvailableCoins, CryptoMarketData, CoinOption, TOKEN_CONTRACTS } from "@/services/cryptoService"; +import { fetchAvailableCoins, CoinOption, TOKEN_CONTRACTS } from "@/services/cryptoService"; import { Loader2, AlertCircle, RefreshCcw, TrendingUp, ChevronDown } from "lucide-react"; import { Select, @@ -141,21 +141,22 @@ const RevenueGraph: React.FC = ({ onCoinChange }) => { setLoading(true); setError(null); - const coinData = await fetchHistoricalData(selectedCoin.id, 30); - + // Use Binance API for fetching historical data + const response = await fetch(`https://api.binance.com/api/v3/klines?symbol=${selectedCoin.symbol.toUpperCase()}USDT&interval=1d&limit=30`); + const data = await response.json(); + if (!mounted) return; - if (!coinData.prices || !coinData.total_volumes || - coinData.prices.length === 0 || coinData.total_volumes.length === 0) { + if (!data || data.length === 0) { throw new Error('No data available for this coin'); } - const chartData: ChartData[] = coinData.prices.map((price, index) => { - const date = new Date(price[0]); + const chartData: ChartData[] = data.map((item: any) => { + const date = new Date(item[0]); return { date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), - price: Number(price[1].toFixed(2)), - volume: Number((coinData.total_volumes[index][1] / 1000000).toFixed(2)) + price: Number(item[4]), // Closing price + volume: Number(item[5]) // Volume }; }); @@ -225,7 +226,7 @@ const RevenueGraph: React.FC = ({ onCoinChange }) => { )} {volumeValue && (

- Volume: {volumeValue.value.toFixed(0)}M + Volume: {volumeValue.value.toLocaleString()}

)}
@@ -272,18 +273,18 @@ const RevenueGraph: React.FC = ({ onCoinChange }) => { value={coin.id} className="text-gray-300 hover:bg-gray-700/50 focus:bg-gray-700/50 focus:text-white transition-colors duration-200" > -
+
{coin.symbol} - {coin.name}
))} -
+
-
- +
+ {loading ? ( @@ -300,9 +301,9 @@ const RevenueGraph: React.FC = ({ onCoinChange }) => { className="h-[400px]" > - + {chartConfig.gradients} - = ({ onCoinChange }) => { stroke="#666" tickLine={false} axisLine={false} - tickFormatter={(value) => `${value.toFixed(0)}M`} + tickFormatter={(value) => `${value.toLocaleString()}`} tick={{ fill: '#9ca3af' }} - width={70} + width={90} // Increase width to ensure numbers are fully visible padding={{ top: 20 }} /> - - + - + - + /> + )} - - + + {selectedCoin && ( @@ -381,5 +382,4 @@ const RevenueGraph: React.FC = ({ onCoinChange }) => { }; RevenueGraph.displayName = "RevenueGraph"; -export default RevenueGraph; - +export default RevenueGraph;