dev_arc_aws/src/pages/BookNest.tsx

379 lines
13 KiB
TypeScript
Raw Normal View History

import { useState } from 'react'
import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts'
import {
Link2,
FolderOpen,
Star,
Plus,
Server,
Bot,
Cloud,
Network,
GitBranch,
GitFork,
Box,
Terminal as TerminalIcon,
Database,
Shield,
Workflow,
FileCode,
Router,
Wifi,
BookOpen,
GraduationCap,
SquarePlay,
Briefcase,
Wallet,
CreditCard,
PiggyBank,
TrendingUp,
Calendar,
Mail,
Image,
HardDrive,
FileText,
Plane,
Sparkles,
MessageSquare,
Zap,
Globe2,
Container,
} from 'lucide-react'
const quickAccess = [
{ label: 'Infrastructure', icons: [Server, Cloud, Box, Shield, Container], count: 5 },
{ label: 'Development', icons: [GitBranch, GitFork, FileCode, TerminalIcon, Container], count: 5 },
{ label: 'AI Tools', icons: [Bot, Sparkles, MessageSquare, Zap, Bot], count: 5 },
{ label: 'AWS', icons: [Cloud, Database, Server, Shield, Globe2], count: 5 },
{ label: 'Networking', icons: [Network, Wifi, Router, Shield, Globe2], count: 5 },
]
type Link = { name: string; icon: typeof Link2; favorited?: boolean }
const groups: { title: string; links: Link[] }[] = [
{
title: 'Infrastructure & Self Hosted',
links: [
{ name: 'CasaOS', icon: Server },
{ name: 'Proxmox (pve1)', icon: Box, favorited: true },
{ name: 'Proxmox (mtr)', icon: Box },
{ name: 'Portainer', icon: Container },
{ name: 'Cockpit', icon: Server },
{ name: 'Cloudflare', icon: Cloud },
{ name: 'NPM', icon: Network },
{ name: 'NetBird', icon: Wifi, favorited: true },
{ name: 'Linode', icon: Cloud },
{ name: 'RackNerd', icon: Server },
],
},
{
title: 'Development & Code',
links: [
{ name: 'GitHub', icon: GitBranch, favorited: true },
{ name: 'Gitea', icon: GitFork },
{ name: 'Trilium Notes', icon: FileText },
{ name: 'VSCode Web', icon: FileCode },
{ name: 'Docker Hub', icon: Container },
{ name: 'GitLab', icon: GitFork },
{ name: 'Terraform Registry', icon: Workflow },
{ name: 'Ansible Galaxy', icon: Workflow },
],
},
{
title: 'Lab & Networking',
links: [
{ name: 'GNS3', icon: Network },
{ name: 'EVE-NG', icon: Network },
{ name: 'OpenClaw', icon: Shield },
{ name: 'NetBird', icon: Wifi, favorited: true },
{ name: 'Cisco Docs', icon: BookOpen },
{ name: 'Juniper Docs', icon: BookOpen },
{ name: 'Wireshark', icon: Database },
{ name: 'iPerf3', icon: Zap },
],
},
{
title: 'AWS',
links: [
{ name: 'AWS Console', icon: Cloud, favorited: true },
{ name: 'IAM', icon: Shield },
{ name: 'EC2', icon: Server },
{ name: 'S3', icon: Database },
{ name: 'CloudFormation', icon: Workflow },
{ name: 'Route 53', icon: Globe2 },
{ name: 'VPC', icon: Network },
{ name: 'Billing', icon: CreditCard },
],
},
{
title: 'AI Tools',
links: [
{ name: 'ChatGPT', icon: Bot, favorited: true },
{ name: 'Claude', icon: Sparkles },
{ name: 'Gemini', icon: Sparkles },
{ name: 'PartyRock', icon: Zap },
{ name: 'Perplexity', icon: MessageSquare },
{ name: 'OpenWebUI', icon: Bot },
{ name: 'Ollama', icon: Bot },
],
},
{
title: 'Learning',
links: [
{ name: 'WGU', icon: GraduationCap },
{ name: 'Udemy', icon: BookOpen },
{ name: 'AWS Skill Builder', icon: GraduationCap },
{ name: 'YouTube', icon: SquarePlay },
{ name: 'LinkedIn Learning', icon: Briefcase },
{ name: 'Coursera', icon: GraduationCap },
],
},
{
title: 'Finance',
links: [
{ name: 'Bank', icon: Wallet },
{ name: 'Budget', icon: PiggyBank },
{ name: 'Investments', icon: TrendingUp },
{ name: 'Retirement', icon: PiggyBank },
{ name: 'Credit Cards', icon: CreditCard },
],
},
{
title: 'Life',
links: [
{ name: 'Calendar', icon: Calendar },
{ name: 'Email', icon: Mail },
{ name: 'Photos', icon: Image },
{ name: 'Drive', icon: HardDrive },
{ name: 'Notes', icon: FileText },
{ name: 'Travel', icon: Plane },
],
},
]
const favorites = [
{ name: 'Proxmox (pve1)', icon: Box },
{ name: 'GitHub', icon: GitBranch },
{ name: 'AWS Console', icon: Cloud },
{ name: 'ChatGPT', icon: Bot },
{ name: 'NetBird', icon: Wifi },
]
const recentlyUsed = [
{ name: 'Proxmox', time: '5m ago' },
{ name: 'GitHub', time: '15m ago' },
{ name: 'AWS Console', time: '1h ago' },
{ name: 'Trilium', time: '2h ago' },
{ name: 'GNS3', time: '3h ago' },
]
const linkHealthData = [
{ name: 'Online', value: 304, color: '#2ECC71' },
{ name: 'Warning', value: 6, color: '#E67E22' },
{ name: 'Offline', value: 2, color: '#E74C3C' },
]
const categoryBreakdownData = [
{ name: 'Infrastructure', value: 32, color: '#C8A434' },
{ name: 'Development', value: 24, color: '#3B82F6' },
{ name: 'AI Tools', value: 18, color: '#2ECC71' },
{ name: 'Learning', value: 10, color: '#E67E22' },
{ name: 'Finance', value: 8, color: '#7A7D85' },
{ name: 'Life', value: 8, color: '#8B5E3C' },
]
const cardBase: React.CSSProperties = {
backgroundColor: 'rgba(10, 10, 12, 0.92)',
border: '1px solid rgba(200, 164, 52, 0.08)',
borderRadius: '12px',
padding: '18px',
boxShadow: '0 0 20px rgba(200, 164, 52, 0.03)',
transition: 'border-color 0.2s ease',
position: 'relative',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
}
const sectionTitle: React.CSSProperties = {
fontSize: '11px',
textTransform: 'uppercase',
letterSpacing: '1.5px',
color: '#7A7D85',
fontWeight: 500,
marginBottom: '14px',
}
function Donut({ data, centerLabel }: { data: { name: string; value: number; color: string }[]; centerLabel?: string }) {
return (
<div className="flex items-center gap-3">
<div className="relative" style={{ width: '88px', height: '88px', flexShrink: 0 }}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie data={data} dataKey="value" innerRadius={28} outerRadius={42} paddingAngle={2} isAnimationActive animationDuration={1000}>
{data.map((entry) => (
<Cell key={entry.name} fill={entry.color} stroke="none" />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
{centerLabel && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<span style={{ fontSize: '14px', fontWeight: 700, color: '#E8E6E0' }}>{centerLabel}</span>
</div>
)}
</div>
<div className="flex flex-col gap-1.5">
{data.map((entry) => (
<div key={entry.name} className="flex items-center gap-1.5">
<span style={{ width: '7px', height: '7px', borderRadius: '50%', backgroundColor: entry.color, flexShrink: 0 }} />
<span style={{ fontSize: '11px', color: '#E8E6E0' }}>{entry.name}</span>
<span style={{ fontSize: '10px', color: '#7A7D85' }}>{entry.value}</span>
</div>
))}
</div>
</div>
)
}
function LinkRow({ link, favorited, onToggle }: { link: Link; favorited: boolean; onToggle: () => void }) {
const Icon = link.icon
return (
<div className="flex items-center justify-between group" style={{ padding: '6px 0' }}>
<div className="flex items-center gap-2.5 cursor-pointer" style={{ minWidth: 0 }}>
<Icon size={16} style={{ color: '#C8A434', flexShrink: 0 }} />
<span style={{ fontSize: '13px', color: '#E8E6E0', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{link.name}</span>
</div>
<button
onClick={onToggle}
className="cursor-pointer bg-transparent border-none p-0 flex items-center"
style={{ flexShrink: 0 }}
>
<Star size={14} fill={favorited ? '#C8A434' : 'none'} style={{ color: favorited ? '#C8A434' : '#4A4D55' }} />
</button>
</div>
)
}
export default function BookNest() {
const [favoritedSet, setFavoritedSet] = useState<Set<string>>(
() => new Set(groups.flatMap((g) => g.links.filter((l) => l.favorited).map((l) => l.name)))
)
function toggleFavorite(name: string) {
setFavoritedSet((prev) => {
const next = new Set(prev)
if (next.has(name)) next.delete(name)
else next.add(name)
return next
})
}
return (
<div className="flex h-full w-full flex-col overflow-y-auto" style={{ scrollbarWidth: 'none' }}>
<div className="grid w-full gap-5" style={{ gridTemplateColumns: '3fr 1fr', gridTemplateRows: 'auto 1fr' }}>
{/* Page stats + Add Bookmark — main column only, so sidebar can rise above it */}
<div className="flex items-center justify-between shrink-0" style={{ gridColumn: 1, gridRow: 1, marginTop: '180px' }}>
<div className="flex items-center gap-5" style={{ fontSize: '12px', color: '#7A7D85' }}>
<span className="flex items-center gap-1.5"><Link2 size={13} style={{ color: '#C8A434' }} /> <strong style={{ color: '#E8E6E0' }}>312</strong> Links</span>
<span className="flex items-center gap-1.5"><FolderOpen size={13} style={{ color: '#C8A434' }} /> <strong style={{ color: '#E8E6E0' }}>18</strong> Categories</span>
<span className="flex items-center gap-1.5"><Star size={13} style={{ color: '#C8A434' }} /> <strong style={{ color: '#E8E6E0' }}>12</strong> Favorites</span>
</div>
<button
className="flex items-center gap-2 cursor-pointer transition-colors whitespace-nowrap"
style={{
fontSize: '12px',
fontWeight: 600,
color: '#0A0B0D',
backgroundColor: '#C8A434',
border: 'none',
borderRadius: '8px',
padding: '9px 16px',
boxShadow: '0 0 14px rgba(200,164,52,0.2)',
}}
>
<Plus size={14} />
Add Bookmark
</button>
</div>
{/* Main column */}
<div className="flex flex-col gap-5" style={{ gridColumn: 1, gridRow: 2, marginTop: '20px' }}>
{/* Quick Access */}
<div className="grid grid-cols-5 gap-4">
{quickAccess.map((qa) => (
<div key={qa.label} style={{ ...cardBase, padding: '14px' }} className="hover:!border-gold/20">
<span style={{ fontSize: '11px', color: '#E8E6E0', fontWeight: 600, marginBottom: '10px' }}>{qa.label}</span>
<div className="flex items-center gap-1.5" style={{ marginBottom: '8px' }}>
{qa.icons.map((Icon, i) => (
<div key={i} style={{ width: '22px', height: '22px', borderRadius: '6px', backgroundColor: 'rgba(200,164,52,0.08)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Icon size={12} style={{ color: '#C8A434' }} />
</div>
))}
</div>
<span style={{ fontSize: '10px', color: '#7A7D85' }}>{qa.count} links</span>
</div>
))}
</div>
{/* Bookmark groups grid */}
<div className="grid grid-cols-4 gap-4">
{groups.map((group) => (
<div key={group.title} style={cardBase} className="hover:!border-gold/15">
<h3 style={sectionTitle}>{group.title}</h3>
<div className="flex flex-col" style={{ gap: '2px' }}>
{group.links.map((link) => (
<LinkRow
key={link.name}
link={link}
favorited={favoritedSet.has(link.name)}
onToggle={() => toggleFavorite(link.name)}
/>
))}
</div>
</div>
))}
</div>
</div>
{/* Right sidebar — spans both rows so Favorites reaches up near the hero */}
<div className="flex flex-col gap-5" style={{ gridColumn: 2, gridRow: '1 / span 2' }}>
<div style={{ ...cardBase, padding: '22px' }}>
<h3 style={{ ...sectionTitle, fontSize: '12px', marginBottom: '18px' }}>Favorites</h3>
<div className="flex flex-col" style={{ gap: '4px' }}>
{favorites.map((f) => (
<div key={f.name} className="flex items-center gap-3" style={{ padding: '8px 0' }}>
<f.icon size={17} style={{ color: '#C8A434' }} />
<span style={{ fontSize: '13px', color: '#E8E6E0' }}>{f.name}</span>
</div>
))}
</div>
</div>
<div style={cardBase}>
<h3 style={sectionTitle}>Recently Used</h3>
<div className="flex flex-col" style={{ gap: '8px' }}>
{recentlyUsed.map((r) => (
<div key={r.name} className="flex items-center justify-between">
<span style={{ fontSize: '12px', color: '#E8E6E0' }}>{r.name}</span>
<span style={{ fontSize: '10px', color: '#7A7D85' }}>{r.time}</span>
</div>
))}
</div>
</div>
<div style={cardBase}>
<h3 style={sectionTitle}>Link Health</h3>
<Donut data={linkHealthData} centerLabel="312" />
</div>
<div style={cardBase}>
<h3 style={sectionTitle}>Category Breakdown</h3>
<Donut data={categoryBreakdownData} />
</div>
</div>
</div>
</div>
)
}