Fix lucide-react icon export errors in BookNest page
This commit is contained in:
parent
106af334a3
commit
2bc140db33
2 changed files with 361 additions and 0 deletions
|
|
@ -4,6 +4,7 @@ import Sidebar from './components/Sidebar'
|
|||
import TopBar from './components/TopBar'
|
||||
import Glance from './pages/Glance'
|
||||
import Infrastructure from './pages/Infrastructure'
|
||||
import BookNest from './pages/BookNest'
|
||||
|
||||
function App() {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||||
|
|
@ -55,6 +56,7 @@ function App() {
|
|||
<Routes>
|
||||
<Route path="/" element={<Glance />} />
|
||||
<Route path="/infrastructure" element={<Infrastructure />} />
|
||||
<Route path="/booknest" element={<BookNest />} />
|
||||
</Routes>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
359
src/pages/BookNest.tsx
Normal file
359
src/pages/BookNest.tsx
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
import { useState } from 'react'
|
||||
import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts'
|
||||
import {
|
||||
Link2,
|
||||
FolderOpen,
|
||||
Star,
|
||||
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 gap-5 overflow-y-auto" style={{ scrollbarWidth: 'none' }}>
|
||||
{/* Page stats */}
|
||||
<div className="flex items-center gap-5 shrink-0" 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>
|
||||
|
||||
<div className="grid w-full gap-5" style={{ gridTemplateColumns: '3fr 1fr' }}>
|
||||
{/* Main column */}
|
||||
<div className="flex flex-col gap-5">
|
||||
{/* 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 */}
|
||||
<div className="flex flex-col gap-5">
|
||||
<div style={cardBase}>
|
||||
<h3 style={sectionTitle}>Favorites</h3>
|
||||
<div className="flex flex-col" style={{ gap: '2px' }}>
|
||||
{favorites.map((f) => (
|
||||
<div key={f.name} className="flex items-center gap-2.5" style={{ padding: '6px 0' }}>
|
||||
<f.icon size={15} style={{ color: '#C8A434' }} />
|
||||
<span style={{ fontSize: '12px', 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>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue