Initial commit

This commit is contained in:
Michilis
2025-12-14 23:08:45 -03:00
commit 1e1753dff3
58 changed files with 18294 additions and 0 deletions

View 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>
)
}