status dashboard and other things

This commit is contained in:
boilerrat 2025-01-08 22:37:16 -05:00
parent fdd851489c
commit 0a8d7355e0
11 changed files with 468 additions and 87 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
VITE_CG_API_KEY=CG-6tRLtAyJ9t5YdthDmRPhwKRD
VITE_CG_API_URL=https://pro-api.coingecko.com/api/v3

View File

@ -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
<div className="bg-black font-['Space_Mono']">
{/* Navigation can use Space Mono for clean reading */}
<Navigation />
<main className="relative">
{/* Hero section uses Courier Prime for the terminal look */}
<section id="home" className="relative z-10 font-['Courier_Prime']">
{/* Hero keeps min-h-screen as it's the landing section */}
<section id="home" className="relative z-10 min-h-screen font-['Courier_Prime']">
<Hero />
</section>
{/* Content sections use Space Mono for better readability */}
<div className="relative z-10">
<Expertise />
</div>
{/* Content sections with reduced spacing */}
<div className="space-y-16 relative z-10"> {/* Main content wrapper with consistent spacing */}
<section className="py-12">
<Expertise />
</section>
<div className="relative z-10">
<Achievements />
</div>
<section className="py-12">
<ReactorStatus />
</section>
<div className="relative z-10">
<Skills />
</div>
<section className="py-12">
<Achievements />
</section>
<div className="relative z-10">
<NFTGrid />
</div>
<section className="py-12">
<Skills />
</section>
<div className="relative z-10">
<Contact />
<section className="py-12">
<NFTGrid />
</section>
<section className="py-12">
<Contact />
</section>
</div>
{/* Background Elements */}

View File

@ -59,12 +59,11 @@ const GrantChart: React.FC<GrantChartProps> = ({ data, className = '' }) => (
tickLine={false}
/>
<YAxis
dataKey="name"
type="category"
width={200}
tick={(props) => <CustomYAxisTick {...props} />}
dataKey="name"
type="category"
width={200}
tick={(props) => <CustomYAxisTick {...props} />}
/>
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="amount" fill="#3b82f6" radius={[0, 4, 4, 0]} />
</BarChart>
@ -86,9 +85,9 @@ const AchievementCard: React.FC<AchievementCardProps> = ({ title, description, d
export const Achievements: React.FC<AchievementsProps> = ({ className = '', grants = defaultGrants, achievements = defaultAchievements }) => {
return (
<section id="achievements" className={`min-h-screen bg-black text-white p-8 ${className}`}>
<div className="max-w-6xl mx-auto space-y-16">
<div className="space-y-8">
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto space-y-12">
<div className="space-y-6">
<div className="flex items-center gap-4">
<BarChart3 size={32} className="text-green-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-green-500 to-blue-500 bg-clip-text text-transparent">
@ -97,7 +96,7 @@ export const Achievements: React.FC<AchievementsProps> = ({ className = '', gran
</div>
<GrantChart data={grants} />
</div>
<div className="space-y-8">
<div className="space-y-6">
<div className="flex items-center gap-4">
<Award size={32} className="text-yellow-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-yellow-500 to-orange-500 bg-clip-text text-transparent">
@ -111,6 +110,6 @@ export const Achievements: React.FC<AchievementsProps> = ({ className = '', gran
</div>
</div>
</div>
</section>
</div>
);
};
};

View File

@ -44,7 +44,7 @@ const CategorySection: FC<CategorySectionProps> = ({
links,
className = ''
}) => (
<div className={`space-y-6 ${className}`}>
<div className={`space-y-4 ${className}`}>
<h3 className="text-xl font-medium text-gray-400">
{category}
</h3>
@ -61,14 +61,14 @@ export const Contact: FC<ContactProps> = ({
socialLinks = defaultSocialLinks
}) => {
return (
<section id="contact" className={`min-h-screen bg-black text-white p-8 ${className}`}>
<div className="max-w-6xl mx-auto space-y-8">
<div className="flex items-center gap-4">
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4 mb-6">
<h2 className="text-3xl font-bold bg-gradient-to-r from-green-500 to-blue-500 bg-clip-text text-transparent">
Connect With Me
</h2>
</div>
<div className="grid gap-12 mt-8">
<div className="grid gap-8">
{socialLinks.map((category, index) => (
<CategorySection
key={index}
@ -78,7 +78,7 @@ export const Contact: FC<ContactProps> = ({
))}
</div>
</div>
</section>
</div>
);
};

View File

@ -35,7 +35,7 @@ const Section: React.FC<SectionProps> = ({
gradientTo,
id
}) => (
<section id={id} className="space-y-8">
<section id={id} className="space-y-6">
<div className="flex items-center gap-4">
<Icon size={32} className={`text-${gradientFrom}`} />
<h2 className={`text-3xl font-bold bg-gradient-to-r from-${gradientFrom} to-${gradientTo} bg-clip-text text-transparent`}>
@ -107,8 +107,8 @@ export const Expertise: React.FC<ExpertiseProps> = ({
web3Experience = defaultWeb3Experience
}) => {
return (
<div className={`min-h-screen bg-black text-white p-8 ${className}`}>
<div className="max-w-6xl mx-auto space-y-16">
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto space-y-12">
<Section
id="industry"
title="Industrial Expertise"

View File

@ -78,10 +78,10 @@ export const Hero: React.FC<HeroProps> = ({
}, [introText]);
return (
<div className={`min-h-screen bg-black text-white flex flex-col items-center justify-center p-4 ${className}`}>
<div className="w-full max-w-3xl">
<div className={`min-h-screen bg-black text-white flex flex-col items-center justify-center ${className}`}>
<div className="w-full max-w-3xl px-4">
{/* Terminal Window */}
<div className="bg-gray-900 rounded-lg overflow-hidden shadow-xl mb-8">
<div className="bg-gray-900 rounded-lg overflow-hidden shadow-xl mb-6">
<div className="bg-gray-800 px-4 py-2 flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
@ -95,9 +95,9 @@ export const Hero: React.FC<HeroProps> = ({
{/* Quick Links Section */}
{animationState.showContent && (
<div className="space-y-8 opacity-0 animate-fade-in">
<div className="space-y-6 opacity-0 animate-fade-in">
<div className="text-center">
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent mb-4">
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent mb-3">
Bridging Industry & Web3
</h1>
<p className="text-gray-400 text-lg">
@ -105,7 +105,7 @@ export const Hero: React.FC<HeroProps> = ({
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{quickLinks.map((link, index) => (
<a
key={index}

View File

@ -26,9 +26,11 @@ const NFTCard: React.FC<NFTCardProps> = ({
className="w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500"
/>
{/* Overlay */}
<div className={`absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent
transition-opacity duration-300 flex items-end justify-between p-6
${isHovered ? 'opacity-100' : 'opacity-0'}`}>
<div
className={`absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent
transition-opacity duration-300 flex items-end justify-between p-6
${isHovered ? 'opacity-100' : 'opacity-0'}`}
>
<h3 className="text-white text-xl font-medium">{name}</h3>
{link && (
<a
@ -80,11 +82,11 @@ export const NFTGrid: React.FC<NFTGridProps> = ({
nfts = defaultNFTs
}) => {
const [hoveredNFT, setHoveredNFT] = useState<number | null>(null);
return (
<section id="nfts" className={`min-h-screen bg-black p-8 flex items-center justify-center ${className}`}>
<div className="max-w-6xl w-full">
<div className="mb-8">
<div className={`bg-black ${className}`}>
<div className="max-w-6xl w-full mx-auto">
<div className="mb-6">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
PFP's
@ -93,7 +95,7 @@ export const NFTGrid: React.FC<NFTGridProps> = ({
<p className="text-gray-400 text-lg mt-2">My Online Appearances</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{nfts.map((nft) => (
<NFTCard
key={nft.id}
@ -104,6 +106,6 @@ export const NFTGrid: React.FC<NFTGridProps> = ({
))}
</div>
</div>
</section>
</div>
);
};

View File

@ -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 (
<div className="bg-gray-800 p-2 rounded border border-gray-700">
<p className="text-blue-400">{formatPrice(payload[0].value)}</p>
<p className="text-gray-400 text-sm">
{new Date(parseInt(label)).toLocaleTimeString()}
</p>
</div>
);
}
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<Array<{ time: number; price: number }>>([]);
const [priceStats, setPriceStats] = useState<PriceStat[]>([
{ label: '24h High', value: '...' },
{ label: '24h Low', value: '...' },
{ label: '24h Volume', value: '...' }
]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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 (
<div className="flex items-center justify-center h-full">
<div className="text-blue-500 flex items-center gap-2">
<Atom className="animate-spin" size={20} />
<span>Loading price data...</span>
</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-red-500">{error}</div>
</div>
);
}
if (!priceData.length) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-gray-400">No price data available</div>
</div>
);
}
return (
<ResponsiveContainer width="100%" height="100%">
<LineChart data={priceData}>
<XAxis
dataKey="time"
stroke="#4B5563"
tick={{ fill: '#9CA3AF', fontSize: 12 }}
tickFormatter={(timestamp) => new Date(timestamp).toLocaleTimeString()}
/>
<YAxis
stroke="#4B5563"
tick={{ fill: '#9CA3AF', fontSize: 12 }}
domain={['auto', 'auto']}
tickFormatter={(value) => formatPrice(value)}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="price"
stroke="#3B82F6"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
);
};
return (
<div className={`grid grid-cols-1 md:grid-cols-2 gap-6 ${className}`}>
{/* Reactor Status Panel */}
<div className="bg-gray-900 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<Atom className="text-blue-500 animate-spin" />
<h3 className="text-lg font-bold text-white">Reactor Status</h3>
</div>
<div className="flex items-center gap-2">
<Activity className="text-green-500" />
<span className="text-sm text-gray-400">
{currentTime.toLocaleTimeString()}
</span>
</div>
</div>
<div className="space-y-6">
{/* Power Level */}
<div className="bg-gray-800 rounded-lg p-4">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-400">Power Level</span>
<Power className="text-blue-500" />
</div>
<div className="relative h-4 bg-gray-700 rounded-full overflow-hidden">
<div
className="absolute h-full bg-blue-500 transition-all duration-1000"
style={{ width: `${power}%` }}
/>
</div>
<div className="mt-2 text-right text-sm text-gray-400">
{power.toFixed(1)}% Capacity
</div>
</div>
{/* Status Message */}
<div className="bg-gray-800 rounded-lg p-4">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-400">Current Status</span>
<Coffee className="text-green-500" />
</div>
<div className={`text-lg font-medium text-blue-400 ${isAnimating ? 'animate-pulse' : ''}`}>
{status}
</div>
</div>
{/* Activity Indicators */}
<div className="grid grid-cols-3 gap-4">
{statusIndicators.map((item, index) => (
<div key={index} className="bg-gray-800 rounded-lg p-3 text-center">
<div className="text-sm text-gray-400">{item.label}</div>
<div className="text-white font-medium">{item.value}</div>
</div>
))}
</div>
</div>
</div>
{/* ETH Price Monitor */}
<div className="bg-gray-900 rounded-lg p-6">
<div className="space-y-4 mb-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<TrendingUp className="text-blue-500" />
<h3 className="text-lg font-bold text-white">ETH Price Monitor</h3>
</div>
<div className="flex items-center gap-2">
<Wallet className="text-green-500" />
<span className="text-sm text-gray-400">Live Feed</span>
</div>
</div>
{priceData.length > 0 && (
<div className="bg-gray-800 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-1">Current Price</div>
<div className="text-2xl font-bold text-blue-400">
{formatPrice(priceData[priceData.length - 1].price)}
</div>
</div>
)}
</div>
<div className="space-y-6">
{/* Price Chart */}
<div className="bg-gray-800 rounded-lg p-4 h-[300px]">
{renderPriceChart()}
</div>
{/* Price Stats */}
<div className="grid grid-cols-3 gap-4">
{priceStats.map((item, index) => (
<div key={index} className="bg-gray-800 rounded-lg p-3 text-center">
<div className="text-sm text-gray-400">{item.label}</div>
<div className="text-white font-medium">{item.value}</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default ReactorStatus;

View File

@ -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;
}

View File

@ -3,20 +3,20 @@ import { ChevronRight } from 'lucide-react';
import type { SkillsProps, SkillCardProps, SkillBarProps, Skill } from './types';
const SkillBar: FC<SkillBarProps> = ({
level,
maxLevel = 5,
className = ''
}) => {
const percentage = (level / maxLevel) * 100;
return (
<div className={`h-2 w-32 bg-gray-800 rounded-full overflow-hidden ${className}`}>
<div
className="h-full bg-gradient-to-r from-emerald-400 to-cyan-500 rounded-full transform origin-left transition-transform duration-1000 ease-out"
style={{ transform: `scaleX(${percentage / 100})` }}
/>
</div>
);
};
level,
maxLevel = 5,
className = ''
}) => {
const percentage = (level / maxLevel) * 100;
return (
<div className={`h-2 w-32 bg-gray-800 rounded-full overflow-hidden ${className}`}>
<div
className="h-full bg-gradient-to-r from-emerald-400 to-cyan-500 rounded-full transform origin-left transition-transform duration-1000 ease-out"
style={{ transform: `scaleX(${percentage / 100})` }}
/>
</div>
);
};
const SkillCard: FC<SkillCardProps> = ({
skill,
@ -114,25 +114,25 @@ const defaultSkills: Skill[] = [
];
export const Skills: FC<SkillsProps> = ({
className = '',
skills = defaultSkills
}) => {
return (
<section id="skills" className={`min-h-screen bg-black text-white p-8 ${className}`}>
<div className="max-w-6xl mx-auto space-y-8">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-emerald-400 to-cyan-500 bg-clip-text text-transparent">
Professional Skills
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
{skills.map((skill, index) => (
<SkillCard key={index} skill={skill} />
))}
</div>
className = '',
skills = defaultSkills
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-emerald-400 to-cyan-500 bg-clip-text text-transparent">
Professional Skills
</h2>
</div>
</section>
);
};
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
{skills.map((skill, index) => (
<SkillCard key={index} skill={skill} />
))}
</div>
</div>
</div>
);
};
export default Skills;

76
src/utils/api.ts Normal file
View File

@ -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<PriceData> {
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)}`;
}