189 lines
6.5 KiB
JavaScript
189 lines
6.5 KiB
JavaScript
import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom'
|
|
import { motion } from 'framer-motion'
|
|
import { useState } from 'react'
|
|
import { useAuthStore } from '../../store/authStore'
|
|
import {
|
|
HomeIcon,
|
|
RectangleStackIcon,
|
|
CurrencyDollarIcon,
|
|
CodeBracketIcon,
|
|
Cog6ToothIcon,
|
|
Bars3Icon,
|
|
XMarkIcon,
|
|
ArrowRightOnRectangleIcon,
|
|
PlusIcon,
|
|
SparklesIcon,
|
|
} from '@heroicons/react/24/outline'
|
|
|
|
const navigation = [
|
|
{ name: 'Overview', href: '/dashboard', icon: HomeIcon },
|
|
{ name: 'Paywalls', href: '/dashboard/paywalls', icon: RectangleStackIcon },
|
|
{ name: 'Sales', href: '/dashboard/sales', icon: CurrencyDollarIcon },
|
|
{ name: 'Embeds', href: '/dashboard/embeds', icon: CodeBracketIcon },
|
|
{ name: 'Settings', href: '/dashboard/settings', icon: Cog6ToothIcon },
|
|
]
|
|
|
|
export default function DashboardLayout() {
|
|
const location = useLocation()
|
|
const navigate = useNavigate()
|
|
const { user, logout } = useAuthStore()
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
|
|
const handleLogout = async () => {
|
|
await logout()
|
|
navigate('/login')
|
|
}
|
|
|
|
const isActive = (href) => {
|
|
if (href === '/dashboard') {
|
|
return location.pathname === '/dashboard'
|
|
}
|
|
return location.pathname.startsWith(href)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-dark-950">
|
|
{/* Mobile sidebar overlay */}
|
|
{sidebarOpen && (
|
|
<div
|
|
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
|
onClick={() => setSidebarOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
{/* Sidebar */}
|
|
<aside
|
|
className={`fixed inset-y-0 left-0 z-50 w-64 bg-dark-900 border-r border-dark-800 transform transition-transform duration-200 lg:translate-x-0 ${
|
|
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
|
}`}
|
|
>
|
|
<div className="flex flex-col h-full">
|
|
{/* Logo */}
|
|
<div className="flex items-center justify-between h-16 px-4 border-b border-dark-800">
|
|
<Link to="/dashboard" className="flex items-center gap-2">
|
|
<span className="text-2xl">⚡</span>
|
|
<span className="font-display font-bold text-lg">LNPaywall</span>
|
|
</Link>
|
|
<button
|
|
onClick={() => setSidebarOpen(false)}
|
|
className="lg:hidden p-2 text-dark-400 hover:text-white"
|
|
>
|
|
<XMarkIcon className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Create button */}
|
|
<div className="p-4">
|
|
<Link
|
|
to="/dashboard/paywalls/new"
|
|
className="btn btn-primary w-full"
|
|
>
|
|
<PlusIcon className="w-5 h-5" />
|
|
Create Paywall
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<nav className="flex-1 px-4 space-y-1">
|
|
{navigation.map((item) => (
|
|
<Link
|
|
key={item.name}
|
|
to={item.href}
|
|
onClick={() => setSidebarOpen(false)}
|
|
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${
|
|
isActive(item.href)
|
|
? 'bg-lightning/10 text-lightning'
|
|
: 'text-dark-400 hover:text-white hover:bg-dark-800'
|
|
}`}
|
|
>
|
|
<item.icon className="w-5 h-5" />
|
|
{item.name}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Pro upgrade */}
|
|
{!user?.isPro && (
|
|
<div className="p-4">
|
|
<Link
|
|
to="/dashboard/pro"
|
|
onClick={() => setSidebarOpen(false)}
|
|
className="block p-4 bg-gradient-to-r from-lightning/10 to-orange-500/10 border border-lightning/30 rounded-xl hover:border-lightning/50 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<SparklesIcon className="w-4 h-4 text-lightning" />
|
|
<span className="text-sm font-medium text-lightning">Upgrade to Pro</span>
|
|
</div>
|
|
<p className="text-xs text-dark-400">0% fees • Custom branding</p>
|
|
</Link>
|
|
</div>
|
|
)}
|
|
|
|
{/* User section */}
|
|
<div className="p-4 border-t border-dark-800">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-lightning to-orange-500 flex items-center justify-center text-white font-bold">
|
|
{user?.displayName?.[0]?.toUpperCase() || 'U'}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium truncate">
|
|
{user?.displayName}
|
|
{user?.isPro && (
|
|
<span className="ml-2 text-xs px-1.5 py-0.5 bg-lightning/10 text-lightning rounded">PRO</span>
|
|
)}
|
|
</p>
|
|
<p className="text-xs text-dark-400 truncate">{user?.email || user?.nostrPubkey?.slice(0, 16) + '...'}</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-dark-400 hover:text-white hover:bg-dark-800 rounded-xl transition-colors"
|
|
>
|
|
<ArrowRightOnRectangleIcon className="w-5 h-5" />
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main content */}
|
|
<div className="lg:pl-64">
|
|
{/* Top bar */}
|
|
<header className="sticky top-0 z-30 h-16 bg-dark-950/80 backdrop-blur-xl border-b border-dark-800/50">
|
|
<div className="flex items-center justify-between h-full px-4 lg:px-8">
|
|
<button
|
|
onClick={() => setSidebarOpen(true)}
|
|
className="lg:hidden p-2 text-dark-400 hover:text-white"
|
|
>
|
|
<Bars3Icon className="w-6 h-6" />
|
|
</button>
|
|
|
|
<div className="flex-1" />
|
|
|
|
<Link
|
|
to="/dashboard/paywalls/new"
|
|
className="btn btn-primary hidden sm:flex"
|
|
>
|
|
<PlusIcon className="w-5 h-5" />
|
|
Create Paywall
|
|
</Link>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Page content */}
|
|
<motion.main
|
|
key={location.pathname}
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.15 }}
|
|
className="p-4 lg:p-8"
|
|
>
|
|
<Outlet />
|
|
</motion.main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|