From 0a8d7355e01fc30b3fd24496a747214c536170e2 Mon Sep 17 00:00:00 2001 From: boilerrat <128boilerrat@gmail.com> Date: Wed, 8 Jan 2025 22:37:16 -0500 Subject: [PATCH] status dashboard and other things --- .env | 2 + src/App.tsx | 43 ++-- src/components/Achievements/index.tsx | 21 +- src/components/Contact/index.tsx | 12 +- src/components/Expertise/index.tsx | 6 +- src/components/Hero/index.tsx | 12 +- src/components/NFTGrid/index.tsx | 20 +- src/components/ReactorStatus/index.tsx | 273 +++++++++++++++++++++++++ src/components/ReactorStatus/types.ts | 24 +++ src/components/Skills/index.tsx | 66 +++--- src/utils/api.ts | 76 +++++++ 11 files changed, 468 insertions(+), 87 deletions(-) create mode 100644 .env create mode 100644 src/components/ReactorStatus/index.tsx create mode 100644 src/components/ReactorStatus/types.ts create mode 100644 src/utils/api.ts diff --git a/.env b/.env new file mode 100644 index 0000000..9d5fc7b --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +VITE_CG_API_KEY=CG-6tRLtAyJ9t5YdthDmRPhwKRD +VITE_CG_API_URL=https://pro-api.coingecko.com/api/v3 \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 4d04981..3da4daa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,39 +6,44 @@ import { Achievements } from './components/Achievements'; import { Skills } from './components/Skills'; import { NFTGrid } from './components/NFTGrid'; import { Contact } from './components/Contact'; +import ReactorStatus from './components/ReactorStatus'; const App: FC = () => { return ( - // Add base font for general content
- {/* Navigation can use Space Mono for clean reading */}
- {/* Hero section uses Courier Prime for the terminal look */} -
+ {/* Hero keeps min-h-screen as it's the landing section */} +
- {/* Content sections use Space Mono for better readability */} -
- -
+ {/* Content sections with reduced spacing */} +
{/* Main content wrapper with consistent spacing */} +
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- +
+ +
+ +
+ +
{/* Background Elements */} diff --git a/src/components/Achievements/index.tsx b/src/components/Achievements/index.tsx index e448e43..46c37db 100644 --- a/src/components/Achievements/index.tsx +++ b/src/components/Achievements/index.tsx @@ -59,12 +59,11 @@ const GrantChart: React.FC = ({ data, className = '' }) => ( tickLine={false} /> } + dataKey="name" + type="category" + width={200} + tick={(props) => } /> - } /> @@ -86,9 +85,9 @@ const AchievementCard: React.FC = ({ title, description, d export const Achievements: React.FC = ({ className = '', grants = defaultGrants, achievements = defaultAchievements }) => { return ( -
-
-
+
+
+

@@ -97,7 +96,7 @@ export const Achievements: React.FC = ({ className = '', gran

-
+

@@ -111,6 +110,6 @@ export const Achievements: React.FC = ({ className = '', gran

-
+
); -}; +}; \ No newline at end of file diff --git a/src/components/Contact/index.tsx b/src/components/Contact/index.tsx index 6a5a59f..8a1a8f3 100644 --- a/src/components/Contact/index.tsx +++ b/src/components/Contact/index.tsx @@ -44,7 +44,7 @@ const CategorySection: FC = ({ links, className = '' }) => ( -
+

{category}

@@ -61,14 +61,14 @@ export const Contact: FC = ({ socialLinks = defaultSocialLinks }) => { return ( -
-
-
+
+
+

Connect With Me

-
+
{socialLinks.map((category, index) => ( = ({ ))}
-
+
); }; diff --git a/src/components/Expertise/index.tsx b/src/components/Expertise/index.tsx index 0ae8214..7677590 100644 --- a/src/components/Expertise/index.tsx +++ b/src/components/Expertise/index.tsx @@ -35,7 +35,7 @@ const Section: React.FC = ({ gradientTo, id }) => ( -
+

@@ -107,8 +107,8 @@ export const Expertise: React.FC = ({ web3Experience = defaultWeb3Experience }) => { return ( -
-
+
+
= ({ }, [introText]); return ( -
-
+
+
{/* Terminal Window */} -
+
@@ -95,9 +95,9 @@ export const Hero: React.FC = ({ {/* Quick Links Section */} {animationState.showContent && ( -
+
-

+

Bridging Industry & Web3

@@ -105,7 +105,7 @@ export const Hero: React.FC = ({

-
+
{quickLinks.map((link, index) => ( = ({ className="w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" /> {/* Overlay */} -
+ ); }; \ No newline at end of file diff --git a/src/components/ReactorStatus/index.tsx b/src/components/ReactorStatus/index.tsx new file mode 100644 index 0000000..5197cb2 --- /dev/null +++ b/src/components/ReactorStatus/index.tsx @@ -0,0 +1,273 @@ +import { useState, useEffect } from 'react'; +import { Activity, Power, Atom, Coffee, TrendingUp, Wallet } from 'lucide-react'; +import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'; +import type { ReactorStatusProps, TooltipProps, PriceStat } from './types'; +import { getEthereumData, formatPrice, getHighLowPrices, formatVolume } from '../../utils/api'; + +const statusMessages = [ + 'Staking valves open', + 'Consensus achieved', + 'Smart contracts cooling', + 'Tokens well-contained', + 'DAOs running smoothly', + 'Radiation levels nominal', + 'Governance protocols stable', + 'Core temperature optimal', + 'NFTs properly shielded', + 'Blockchain fully operational' +]; + +const CustomTooltip = ({ active, payload, label }: TooltipProps) => { + if (active && payload && payload.length && label) { + return ( +
+

{formatPrice(payload[0].value)}

+

+ {new Date(parseInt(label)).toLocaleTimeString()} +

+
+ ); + } + return null; +}; + +const ReactorStatus = ({ className = '' }: ReactorStatusProps) => { + const [power, setPower] = useState(0); + const [status, setStatus] = useState('Initializing...'); + const [isAnimating, setIsAnimating] = useState(false); + const [currentTime, setCurrentTime] = useState(new Date()); + const [priceData, setPriceData] = useState>([]); + const [priceStats, setPriceStats] = useState([ + { label: '24h High', value: '...' }, + { label: '24h Low', value: '...' }, + { label: '24h Volume', value: '...' } + ]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + const data = await getEthereumData(); + console.log('Received ETH data:', data); + + if (!data?.prices?.length) { + throw new Error('Invalid price data received'); + } + + const formattedPrices = data.prices.map(([timestamp, price]) => ({ + time: timestamp, + price + })); + + const { high, low } = getHighLowPrices(data.prices); + const volume = data.total_volumes[data.total_volumes.length - 1][1]; + + console.log('Formatted prices:', formattedPrices); + setPriceData(formattedPrices); + setPriceStats([ + { label: '24h High', value: formatPrice(high) }, + { label: '24h Low', value: formatPrice(low) }, + { label: '24h Volume', value: formatVolume(volume) } + ]); + } catch (err) { + console.error('Error fetching ETH data:', err); + setError(err instanceof Error ? err.message : 'Failed to fetch price data'); + } finally { + setLoading(false); + } + }; + + // Initial fetch + fetchData(); + + // Set up intervals + const intervals = [ + setInterval(() => setCurrentTime(new Date()), 1000), + setInterval(() => { + setPower(prev => { + const fluctuation = Math.random() * 10 - 5; + return Math.min(Math.max(prev + fluctuation, 60), 100); + }); + }, 3000), + setInterval(() => { + const randomStatus = statusMessages[Math.floor(Math.random() * statusMessages.length)]; + setStatus(randomStatus); + setIsAnimating(true); + setTimeout(() => setIsAnimating(false), 500); + }, 5000), + setInterval(fetchData, 300000) // Fetch new data every 5 minutes + ]; + + // Cleanup + return () => intervals.forEach(clearInterval); + }, []); + + const statusIndicators = [ + { label: 'Core Temp', value: '37.2°C' }, + { label: 'Block Height', value: '#18M' }, + { label: 'Rad Level', value: '0.12 μSv' } + ]; + + const renderPriceChart = () => { + if (loading) { + return ( +
+
+ + Loading price data... +
+
+ ); + } + + if (error) { + return ( +
+
{error}
+
+ ); + } + + if (!priceData.length) { + return ( +
+
No price data available
+
+ ); + } + + return ( + + + new Date(timestamp).toLocaleTimeString()} + /> + formatPrice(value)} + /> + } /> + + + + ); + }; + + return ( +
+ {/* Reactor Status Panel */} +
+
+
+ +

Reactor Status

+
+
+ + + {currentTime.toLocaleTimeString()} + +
+
+ +
+ {/* Power Level */} +
+
+ Power Level + +
+
+
+
+
+ {power.toFixed(1)}% Capacity +
+
+ + {/* Status Message */} +
+
+ Current Status + +
+
+ {status} +
+
+ + {/* Activity Indicators */} +
+ {statusIndicators.map((item, index) => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+
+
+ + {/* ETH Price Monitor */} +
+
+
+
+ +

ETH Price Monitor

+
+
+ + Live Feed +
+
+ {priceData.length > 0 && ( +
+
Current Price
+
+ {formatPrice(priceData[priceData.length - 1].price)} +
+
+ )} +
+ +
+ {/* Price Chart */} +
+ {renderPriceChart()} +
+ + {/* Price Stats */} +
+ {priceStats.map((item, index) => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+
+
+
+ ); +}; + +export default ReactorStatus; \ No newline at end of file diff --git a/src/components/ReactorStatus/types.ts b/src/components/ReactorStatus/types.ts new file mode 100644 index 0000000..05113ca --- /dev/null +++ b/src/components/ReactorStatus/types.ts @@ -0,0 +1,24 @@ +// src/components/ReactorStatus/types.ts + +export interface ReactorStatusProps { + className?: string; + } + + export interface TooltipProps { + active?: boolean; + payload?: Array<{ + value: number; + dataKey?: string; + }>; + label?: string; + } + + export interface PriceStat { + label: string; + value: string; + } + + export interface PriceDataPoint { + time: number; + price: number; + } \ No newline at end of file diff --git a/src/components/Skills/index.tsx b/src/components/Skills/index.tsx index c2fb57d..d234e18 100644 --- a/src/components/Skills/index.tsx +++ b/src/components/Skills/index.tsx @@ -3,20 +3,20 @@ import { ChevronRight } from 'lucide-react'; import type { SkillsProps, SkillCardProps, SkillBarProps, Skill } from './types'; const SkillBar: FC = ({ - level, - maxLevel = 5, - className = '' - }) => { - const percentage = (level / maxLevel) * 100; - return ( -
-
-
- ); - }; + level, + maxLevel = 5, + className = '' +}) => { + const percentage = (level / maxLevel) * 100; + return ( +
+
+
+ ); +}; const SkillCard: FC = ({ skill, @@ -114,25 +114,25 @@ const defaultSkills: Skill[] = [ ]; export const Skills: FC = ({ - className = '', - skills = defaultSkills - }) => { - return ( -
-
-
-

- Professional Skills -

-
-
- {skills.map((skill, index) => ( - - ))} -
+ className = '', + skills = defaultSkills +}) => { + return ( +
+
+
+

+ Professional Skills +

-
- ); - }; +
+ {skills.map((skill, index) => ( + + ))} +
+
+
+ ); +}; export default Skills; \ No newline at end of file diff --git a/src/utils/api.ts b/src/utils/api.ts new file mode 100644 index 0000000..4e84268 --- /dev/null +++ b/src/utils/api.ts @@ -0,0 +1,76 @@ +// src/utils/api.ts + +const API_KEY = import.meta.env.VITE_CG_API_KEY; + +export interface PriceData { + prices: [number, number][]; + market_caps: [number, number][]; + total_volumes: [number, number][]; +} + +export async function getEthereumData(): Promise { + try { + console.log('Fetching ETH price data...'); + const response = await fetch( + `https://pro-api.coingecko.com/api/v3/coins/ethereum/market_chart?vs_currency=usd&days=1&interval=hourly&x_cg_pro_api_key=${API_KEY}` + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error('API Error Response:', errorText); + throw new Error(`API request failed: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + console.log('Raw API response:', data); + return data; + } catch (error) { + console.error('Error in getEthereumData:', error); + // Return mock data in case of API failure + const now = Date.now(); + const mockData: PriceData = { + prices: Array.from({ length: 24 }, (_, i) => { + const timestamp = now - (23 - i) * 3600000; + const basePrice = 2300; + const randomChange = Math.random() * 100 - 50; + return [timestamp, basePrice + randomChange] as [number, number]; + }), + market_caps: Array.from({ length: 24 }, (_, i) => { + const timestamp = now - (23 - i) * 3600000; + return [timestamp, 2300000000] as [number, number]; + }), + total_volumes: Array.from({ length: 24 }, (_, i) => { + const timestamp = now - (23 - i) * 3600000; + return [timestamp, 500000000] as [number, number]; + }) + }; + return mockData; + } +} + +export function formatPrice(price: number): string { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(price); +} + +export function getHighLowPrices(prices: [number, number][]): { high: number; low: number } { + const priceValues = prices.map(([, price]) => price); + return { + high: Math.max(...priceValues), + low: Math.min(...priceValues) + }; +} + +export function formatVolume(volume: number): string { + if (volume >= 1e9) { + return `$${(volume / 1e9).toFixed(1)}B`; + } + if (volume >= 1e6) { + return `$${(volume / 1e6).toFixed(1)}M`; + } + return `$${volume.toFixed(0)}`; +} \ No newline at end of file