Aethel Bot OSS repository! aethel.xyz
bot fun ai discord discord-bot aethel
at dev 10 kB view raw
1import { ReactNode, useState } from 'react'; 2import { Link, useLocation } from 'react-router-dom'; 3import { LayoutDashboard, CheckSquare, Key, Bell, Menu, X, LogOut, User } from 'lucide-react'; 4import { useAuthStore } from '../stores/authStore'; 5import { toast } from 'sonner'; 6import ThemeToggle from './ThemeToggle'; 7 8interface LayoutProps { 9 children: ReactNode; 10} 11 12const Layout = ({ children }: LayoutProps) => { 13 const [mobileMenuOpen, setMobileMenuOpen] = useState(false); 14 const location = useLocation(); 15 const { user, logout } = useAuthStore(); 16 17 const navigation = [ 18 { name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard }, 19 { name: 'Todos', href: '/todos', icon: CheckSquare }, 20 { name: 'Reminders', href: '/reminders', icon: Bell }, 21 { name: 'API Keys', href: '/api-keys', icon: Key }, 22 ]; 23 24 const handleLogout = () => { 25 logout(); 26 toast.success('Logged out successfully'); 27 }; 28 29 return ( 30 <div className="min-h-screen relative overflow-hidden bg-gradient-to-b from-white to-gray-50 dark:from-gray-900 dark:to-black text-gray-900 dark:text-gray-100 font-inter transition-colors duration-300"> 31 <div className="pointer-events-none absolute -top-32 -right-32 h-96 w-96 rounded-full blur-xl md:blur-3xl opacity-30 bg-gradient-to-tr from-pink-400 to-purple-500 dark:opacity-20" /> 32 <div className="pointer-events-none absolute -bottom-32 -left-32 h-[28rem] w-[28rem] rounded-full blur-xl md:blur-3xl opacity-30 bg-gradient-to-tr from-indigo-400 to-sky-500 dark:opacity-20" /> 33 34 <div 35 className={`fixed inset-0 z-50 lg:hidden transition-opacity duration-200 ${ 36 mobileMenuOpen ? 'opacity-100' : 'opacity-0 pointer-events-none' 37 }`} 38 > 39 <div 40 className="fixed inset-0 bg-black/70" 41 onClick={() => setMobileMenuOpen(false)} 42 /> 43 <div 44 className={`fixed inset-y-0 left-0 flex w-72 flex-col bg-white dark:bg-gray-800 backdrop-blur-md shadow-xl transform transition-transform duration-200 ease-out ${ 45 mobileMenuOpen ? 'translate-x-0' : '-translate-x-full' 46 }`} 47 > 48 <div className="flex h-16 items-center justify-between px-6 border-b border-gray-200 dark:border-gray-700"> 49 <div className="flex items-center space-x-3"> 50 <img 51 className="w-8 h-8 rounded-lg object-contain" 52 src="/bot_icon.png" 53 alt="Aethel Bot" 54 style={{ imageRendering: 'crisp-edges' }} 55 /> 56 <h1 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Aethel</h1> 57 </div> 58 <div className="flex items-center space-x-2"> 59 <ThemeToggle className="shadow-md" /> 60 <button 61 onClick={() => setMobileMenuOpen(false)} 62 className="p-2 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors duration-150" 63 > 64 <X className="h-5 w-5" /> 65 </button> 66 </div> 67 </div> 68 <nav className="flex-1 px-4 py-6"> 69 <div className="space-y-2"> 70 {navigation.map((item) => { 71 const Icon = item.icon; 72 const isActive = location.pathname === item.href; 73 return ( 74 <Link 75 key={item.name} 76 to={item.href} 77 onClick={() => setMobileMenuOpen(false)} 78 className={`group flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-150 ${ 79 isActive 80 ? 'bg-blue-600 text-white' 81 : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100' 82 }`} 83 > 84 <Icon 85 className={`mr-3 h-5 w-5 flex-shrink-0 ${ 86 isActive 87 ? 'text-white' 88 : 'text-gray-500 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-100' 89 }`} 90 /> 91 {item.name} 92 </Link> 93 ); 94 })} 95 </div> 96 </nav> 97 98 <div className="border-t border-gray-200 dark:border-gray-700 p-4"> 99 <div className="flex items-center mb-4 p-3 rounded-xl"> 100 <div className="flex-shrink-0"> 101 {user?.avatar ? ( 102 <img 103 className="h-10 w-10 rounded-full" 104 src={`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`} 105 alt={user.username} 106 /> 107 ) : ( 108 <div className="h-10 w-10 rounded-full bg-blue-600 flex items-center justify-center"> 109 <User className="h-5 w-5 text-white" /> 110 </div> 111 )} 112 </div> 113 <div className="ml-3 flex-1 min-w-0"> 114 <p className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"> 115 {user?.discriminator && user.discriminator !== '0' 116 ? `${user.username}#${user.discriminator}` 117 : user?.username} 118 </p> 119 <p className="text-xs text-gray-500 dark:text-gray-400 truncate">{user?.email}</p> 120 </div> 121 </div> 122 <button 123 onClick={handleLogout} 124 className="flex w-full items-center px-4 py-3 text-sm font-medium text-gray-600 dark:text-gray-300 rounded-xl hover:bg-red-600 hover:text-white transition-all duration-150 group" 125 > 126 <LogOut className="mr-3 h-4 w-4 flex-shrink-0" /> 127 Logout 128 </button> 129 </div> 130 </div> 131 </div> 132 133 <header className="fixed top-4 left-8 right-8 lg:left-16 lg:right-16 xl:left-32 xl:right-32 z-40 bg-white dark:bg-gray-800 backdrop-blur-xl border border-gray-200 dark:border-gray-700 shadow-2xl rounded-2xl transition-colors duration-300"> 134 <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> 135 <div className="flex h-16 items-center justify-between"> 136 <div className="flex items-center space-x-4"> 137 <button 138 type="button" 139 className="p-2 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors duration-150 lg:hidden" 140 onClick={() => setMobileMenuOpen(true)} 141 > 142 <Menu className="h-5 w-5" /> 143 </button> 144 <div className="flex items-center space-x-3"> 145 <img 146 className="w-8 h-8 rounded-lg object-contain" 147 src="/bot_icon.png" 148 alt="Aethel Bot" 149 style={{ imageRendering: 'crisp-edges' }} 150 /> 151 <h1 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Aethel</h1> 152 </div> 153 </div> 154 155 <nav className="hidden lg:flex lg:space-x-2"> 156 {navigation.map((item) => { 157 const Icon = item.icon; 158 const isActive = location.pathname === item.href; 159 return ( 160 <Link 161 key={item.name} 162 to={item.href} 163 className={`group flex items-center px-4 py-2 text-sm font-medium rounded-full transition-all duration-150 ${ 164 isActive 165 ? 'bg-blue-600 text-white' 166 : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100' 167 }`} 168 > 169 <Icon 170 className={`mr-2 h-4 w-4 flex-shrink-0 ${ 171 isActive 172 ? 'text-white' 173 : 'text-gray-500 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-100' 174 }`} 175 /> 176 {item.name} 177 </Link> 178 ); 179 })} 180 </nav> 181 182 <div className="flex items-center space-x-4"> 183 <div className="hidden lg:flex items-center space-x-3 px-3 py-2 rounded-xl"> 184 <div className="flex-shrink-0"> 185 {user?.avatar ? ( 186 <img 187 className="h-8 w-8 rounded-full" 188 src={`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`} 189 alt={user.username} 190 /> 191 ) : ( 192 <div className="h-8 w-8 rounded-full bg-blue-600 flex items-center justify-center"> 193 <User className="h-4 w-4 text-white" /> 194 </div> 195 )} 196 </div> 197 <div className="hidden xl:block"> 198 <p className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"> 199 {user?.discriminator && user.discriminator !== '0' 200 ? `${user.username}#${user.discriminator}` 201 : user?.username} 202 </p> 203 </div> 204 </div> 205 <ThemeToggle /> 206 <button 207 onClick={handleLogout} 208 className="hidden lg:flex items-center px-4 py-2 text-sm font-medium text-gray-600 dark:text-gray-300 rounded-full hover:bg-red-600 hover:text-white transition-all duration-150" 209 > 210 <LogOut className="h-4 w-4" /> 211 </button> 212 </div> 213 </div> 214 </div> 215 </header> 216 217 <main className="flex-1 pt-24"> 218 <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8">{children}</div> 219 </main> 220 </div> 221 ); 222}; 223 224export default Layout;