Initial commit
This commit is contained in:
188
frontend/src/components/layouts/DashboardLayout.jsx
Normal file
188
frontend/src/components/layouts/DashboardLayout.jsx
Normal file
@@ -0,0 +1,188 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user