mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 21:51:14 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
117
src-old/components/layout/AdminHeader.tsx
Normal file
117
src-old/components/layout/AdminHeader.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Shield, ArrowLeft, Settings, Menu } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RefreshButton } from '@/components/ui/refresh-button';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { ThemeToggle } from '@/components/theme/ThemeToggle';
|
||||
import { AuthButtons } from '@/components/auth/AuthButtons';
|
||||
import { NotificationCenter } from '@/components/notifications/NotificationCenter';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
|
||||
export function AdminHeader({ onRefresh, isRefreshing }: { onRefresh?: () => void; isRefreshing?: boolean }) {
|
||||
const { permissions } = useUserRole();
|
||||
const { user } = useAuth();
|
||||
const location = useLocation();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const isSettingsPage = location.pathname === '/admin/settings';
|
||||
const backLink = isSettingsPage ? '/admin' : '/';
|
||||
const backText = isSettingsPage ? 'Back to Admin' : 'Back to ThrillWiki';
|
||||
const pageTitle = isSettingsPage ? 'Admin Settings' : 'Admin Dashboard';
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container flex h-16 items-center justify-between px-4">
|
||||
{/* Left Section - Navigation */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link to={backLink} className="flex items-center gap-2">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">{backText}</span>
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<div className="h-6 w-px bg-border hidden sm:block" />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-6 h-6 text-primary" />
|
||||
<h1 className="text-lg font-semibold">
|
||||
<span className="sm:hidden">Admin</span>
|
||||
<span className="hidden sm:inline">{pageTitle}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Section - Admin actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Mobile Menu */}
|
||||
<Sheet>
|
||||
<SheetTrigger asChild className="md:hidden">
|
||||
<Button variant="ghost" size="icon">
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right" className="w-[300px] sm:w-[400px]">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Admin Menu</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex flex-col gap-4 mt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">Theme</span>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<RefreshButton
|
||||
onRefresh={onRefresh!}
|
||||
isLoading={isRefreshing}
|
||||
variant="ghost"
|
||||
className="justify-start w-full"
|
||||
/>
|
||||
{permissions?.role_level === 'superuser' && !isSettingsPage && (
|
||||
<Button variant="ghost" asChild className="justify-start">
|
||||
<Link to="/admin/settings">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Settings
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* Desktop Actions */}
|
||||
{onRefresh && (
|
||||
<RefreshButton
|
||||
onRefresh={onRefresh}
|
||||
isLoading={isRefreshing}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="hidden md:flex"
|
||||
/>
|
||||
)}
|
||||
{permissions?.role_level === 'superuser' && !isSettingsPage && (
|
||||
<Button variant="ghost" size="sm" asChild className="hidden md:flex">
|
||||
<Link to="/admin/settings">
|
||||
<Settings className="w-4 h-4" />
|
||||
<span className="hidden sm:ml-2 sm:inline">Settings</span>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<div className="hidden md:block">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
{user && <NotificationCenter />}
|
||||
<AuthButtons />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
58
src-old/components/layout/AdminLayout.tsx
Normal file
58
src-old/components/layout/AdminLayout.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { SidebarProvider } from '@/components/ui/sidebar';
|
||||
import { AdminSidebar } from './AdminSidebar';
|
||||
import { AdminTopBar } from './AdminTopBar';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { useSessionMonitor } from '@/hooks/useSessionMonitor';
|
||||
|
||||
interface AdminLayoutProps {
|
||||
children: ReactNode;
|
||||
onRefresh?: () => void;
|
||||
refreshMode?: 'auto' | 'manual';
|
||||
pollInterval?: number;
|
||||
lastUpdated?: Date;
|
||||
isRefreshing?: boolean;
|
||||
}
|
||||
|
||||
export function AdminLayout({
|
||||
children,
|
||||
onRefresh,
|
||||
refreshMode,
|
||||
pollInterval,
|
||||
lastUpdated,
|
||||
isRefreshing
|
||||
}: AdminLayoutProps) {
|
||||
const { aalWarning } = useSessionMonitor();
|
||||
|
||||
return (
|
||||
<SidebarProvider defaultOpen={true}>
|
||||
<div className="flex min-h-screen w-full">
|
||||
<AdminSidebar />
|
||||
<main className="flex-1 flex flex-col">
|
||||
<AdminTopBar
|
||||
onRefresh={onRefresh}
|
||||
refreshMode={refreshMode}
|
||||
pollInterval={pollInterval}
|
||||
lastUpdated={lastUpdated}
|
||||
isRefreshing={isRefreshing}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto bg-muted/30">
|
||||
<div className="container mx-auto px-6 py-8 max-w-7xl">
|
||||
{aalWarning && (
|
||||
<Alert variant="destructive" className="mb-6">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>Session Verification Required</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your session requires re-verification. You will be redirected to verify your identity in 30 seconds.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
154
src-old/components/layout/AdminSidebar.tsx
Normal file
154
src-old/components/layout/AdminSidebar.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft, ScrollText, BookOpen, Inbox, Mail, AlertTriangle } from 'lucide-react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { useSidebar } from '@/hooks/useSidebar';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '@/components/ui/sidebar';
|
||||
|
||||
export function AdminSidebar() {
|
||||
const { state } = useSidebar();
|
||||
const { permissions } = useUserRole();
|
||||
const isSuperuser = permissions?.role_level === 'superuser';
|
||||
const isAdmin = permissions?.role_level === 'admin' || isSuperuser;
|
||||
const collapsed = state === 'collapsed';
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
url: '/admin',
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
title: 'Moderation',
|
||||
url: '/admin/moderation',
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: 'Reports',
|
||||
url: '/admin/reports',
|
||||
icon: Flag,
|
||||
},
|
||||
{
|
||||
title: 'Inbox',
|
||||
url: '/admin/contact',
|
||||
icon: Inbox,
|
||||
},
|
||||
{
|
||||
title: 'System Log',
|
||||
url: '/admin/system-log',
|
||||
icon: ScrollText,
|
||||
},
|
||||
{
|
||||
title: 'Error Monitoring',
|
||||
url: '/admin/error-monitoring',
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
{
|
||||
title: 'Users',
|
||||
url: '/admin/users',
|
||||
icon: Users,
|
||||
},
|
||||
...(isAdmin ? [{
|
||||
title: 'Blog',
|
||||
url: '/admin/blog',
|
||||
icon: BookOpen,
|
||||
}] : []),
|
||||
...(isSuperuser ? [{
|
||||
title: 'Settings',
|
||||
url: '/admin/settings',
|
||||
icon: Settings,
|
||||
}, {
|
||||
title: 'Email Settings',
|
||||
url: '/admin/email-settings',
|
||||
icon: Mail,
|
||||
}] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarHeader className="border-b border-border/40 px-4 py-4">
|
||||
<div className="flex items-center gap-2 min-h-[32px]">
|
||||
<div className="flex items-center justify-center flex-shrink-0">
|
||||
<img
|
||||
src="https://cdn.thrillwiki.com/images/5d06b122-a3a3-47bc-6176-f93ad8f0ce00/favicon512"
|
||||
alt="ThrillWiki"
|
||||
width="32"
|
||||
height="32"
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
draggable="false"
|
||||
className={`
|
||||
object-contain
|
||||
transition-all duration-200 ease-in-out
|
||||
${collapsed ? 'w-6 h-6' : 'w-8 h-8'}
|
||||
`}
|
||||
onError={(e) => {
|
||||
const img = e.target as HTMLImageElement;
|
||||
if (!img.src.includes('favicon128')) {
|
||||
img.src = 'https://cdn.thrillwiki.com/images/5d06b122-a3a3-47bc-6176-f93ad8f0ce00/favicon128';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="flex flex-col overflow-hidden">
|
||||
<span className="text-sm font-semibold truncate">ThrillWiki</span>
|
||||
<span className="text-xs text-muted-foreground truncate">Admin Panel</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{navItems.map((item) => (
|
||||
<SidebarMenuItem key={item.url}>
|
||||
<SidebarMenuButton asChild tooltip={collapsed ? item.title : undefined}>
|
||||
<NavLink
|
||||
to={item.url}
|
||||
end={item.url === '/admin'}
|
||||
className={({ isActive }) =>
|
||||
isActive
|
||||
? 'bg-sidebar-accent text-sidebar-accent-foreground font-medium'
|
||||
: 'hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground'
|
||||
}
|
||||
>
|
||||
<item.icon className="w-4 h-4" />
|
||||
{!collapsed && <span>{item.title}</span>}
|
||||
</NavLink>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter className="border-t border-border/40">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip={collapsed ? 'Back to ThrillWiki' : undefined}>
|
||||
<NavLink to="/" className="hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
{!collapsed && <span>Back to ThrillWiki</span>}
|
||||
</NavLink>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
67
src-old/components/layout/AdminTopBar.tsx
Normal file
67
src-old/components/layout/AdminTopBar.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { RefreshButton } from '@/components/ui/refresh-button';
|
||||
import { ThemeToggle } from '@/components/theme/ThemeToggle';
|
||||
import { AuthButtons } from '@/components/auth/AuthButtons';
|
||||
import { NotificationCenter } from '@/components/notifications/NotificationCenter';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
|
||||
interface AdminTopBarProps {
|
||||
onRefresh?: () => void;
|
||||
refreshMode?: 'auto' | 'manual';
|
||||
pollInterval?: number;
|
||||
lastUpdated?: Date;
|
||||
isRefreshing?: boolean;
|
||||
}
|
||||
|
||||
export function AdminTopBar({
|
||||
onRefresh,
|
||||
refreshMode,
|
||||
pollInterval,
|
||||
lastUpdated,
|
||||
isRefreshing
|
||||
}: AdminTopBarProps) {
|
||||
const { user } = useAuth();
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="flex h-14 items-center justify-between px-4 gap-4">
|
||||
{/* Left Section */}
|
||||
<div className="flex items-center gap-3">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
|
||||
{refreshMode && (
|
||||
<div className="hidden sm:flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<RefreshCw className="w-3 h-3" />
|
||||
{refreshMode === 'auto' ? (
|
||||
<span>Auto: {pollInterval ? pollInterval / 1000 : 30}s</span>
|
||||
) : (
|
||||
<span>Manual</span>
|
||||
)}
|
||||
{lastUpdated && (
|
||||
<span className="hidden md:inline">
|
||||
• {lastUpdated.toLocaleTimeString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Section */}
|
||||
<div className="flex items-center gap-2">
|
||||
{onRefresh && (
|
||||
<RefreshButton
|
||||
onRefresh={onRefresh}
|
||||
isLoading={isRefreshing}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
<ThemeToggle />
|
||||
{user && <NotificationCenter />}
|
||||
<AuthButtons />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
47
src-old/components/layout/Footer.tsx
Normal file
47
src-old/components/layout/Footer.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-border bg-background py-4">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-2 text-xs text-muted-foreground">
|
||||
<div>
|
||||
© {new Date().getFullYear()} ThrillWiki. All rights reserved.
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
to="/contact"
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
<Link
|
||||
to="/terms"
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>
|
||||
<Link
|
||||
to="/privacy"
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
<Link
|
||||
to="/submission-guidelines"
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
Submission Guidelines
|
||||
</Link>
|
||||
<Link
|
||||
to="/blog"
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
256
src-old/components/layout/Header.tsx
Normal file
256
src-old/components/layout/Header.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
import { useState } from 'react';
|
||||
import { Search, Menu, Sparkles, MapPin, Star, ChevronDown, Building, Users, Crown, Palette, Shield, FerrisWheel, Factory } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { SearchDropdown } from '@/components/search/SearchDropdown';
|
||||
import { MobileSearch } from '@/components/search/MobileSearch';
|
||||
import { AuthButtons } from '@/components/auth/AuthButtons';
|
||||
import { ThemeToggle } from '@/components/theme/ThemeToggle';
|
||||
import { NotificationCenter } from '@/components/notifications/NotificationCenter';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
} from "@/components/ui/navigation-menu";
|
||||
|
||||
export function Header() {
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const { user } = useAuth();
|
||||
const { isModerator, loading: rolesLoading } = useUserRole();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="sticky top-0 z-40 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container flex h-14 md:h-16 items-center justify-between gap-2 md:gap-4 px-3 md:px-4">
|
||||
{/* Mobile: Menu + Logo */}
|
||||
<div className="flex items-center gap-2 md:gap-6 flex-1 md:flex-initial min-w-0">
|
||||
{/* Mobile Menu */}
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="md:hidden h-9 w-9">
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-[280px] sm:w-[320px]">
|
||||
<nav className="flex flex-col gap-1 mt-8">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2 px-3">
|
||||
Explore
|
||||
</h3>
|
||||
</div>
|
||||
<Link
|
||||
to="/rides"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Rides
|
||||
</Link>
|
||||
<Link
|
||||
to="/parks"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Parks
|
||||
</Link>
|
||||
<Link
|
||||
to="/manufacturers"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Manufacturers
|
||||
</Link>
|
||||
<Link
|
||||
to="/designers"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Designers
|
||||
</Link>
|
||||
<Link
|
||||
to="/operators"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Operators
|
||||
</Link>
|
||||
<Link
|
||||
to="/owners"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Property Owners
|
||||
</Link>
|
||||
{!rolesLoading && isModerator() && (
|
||||
<>
|
||||
<div className="my-2 border-t border-border" />
|
||||
<Link
|
||||
to="/admin"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Admin
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<div className="my-2 border-t border-border" />
|
||||
<div className="px-3 py-2.5 flex items-center justify-between">
|
||||
<span className="text-base font-medium">Theme</span>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center hover:opacity-80 transition-opacity flex-shrink-0">
|
||||
<span className="font-bold text-base sm:text-lg md:text-xl bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent whitespace-nowrap">
|
||||
ThrillWiki
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center space-x-1">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger className="h-9">Explore</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid min-w-[320px] max-w-[500px] w-fit gap-3 p-4">
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/rides"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Rides</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
Discover exciting rides and attractions
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/parks"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Parks</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
Browse theme parks around the world
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/manufacturers"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Manufacturers</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
Explore ride manufacturers
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/designers"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Designers</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
View ride designers
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/operators"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Operators</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
Find park operators
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/owners"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Property Owners</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
View property owners
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
|
||||
{!rolesLoading && isModerator() && (
|
||||
<Button variant="ghost" size="sm" className="h-9" asChild>
|
||||
<Link to="/admin">Admin</Link>
|
||||
</Button>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Right side: Search, Theme, Auth */}
|
||||
<div className="flex items-center gap-1.5 md:gap-2 flex-shrink-0">
|
||||
{/* Desktop Search */}
|
||||
<div className="hidden lg:block lg:w-64 xl:w-80 min-w-0">
|
||||
<SearchDropdown />
|
||||
</div>
|
||||
|
||||
{/* Mobile Search Button */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="lg:hidden h-9 w-9 min-w-[36px]"
|
||||
onClick={() => setMobileSearchOpen(true)}
|
||||
>
|
||||
<Search className="h-5 w-5" />
|
||||
<span className="sr-only">Search</span>
|
||||
</Button>
|
||||
|
||||
<div className="hidden md:block">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
{user && <NotificationCenter />}
|
||||
<AuthButtons />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Mobile Search Modal */}
|
||||
<MobileSearch open={mobileSearchOpen} onOpenChange={setMobileSearchOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
61
src-old/components/layout/ResilienceProvider.tsx
Normal file
61
src-old/components/layout/ResilienceProvider.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { NetworkErrorBanner } from '@/components/error/NetworkErrorBanner';
|
||||
import { SubmissionQueueIndicator } from '@/components/submission/SubmissionQueueIndicator';
|
||||
import { useNetworkStatus } from '@/hooks/useNetworkStatus';
|
||||
import { useSubmissionQueue } from '@/hooks/useSubmissionQueue';
|
||||
|
||||
interface ResilienceProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* ResilienceProvider wraps the app with network error handling
|
||||
* and submission queue management UI
|
||||
*/
|
||||
export function ResilienceProvider({ children }: ResilienceProviderProps) {
|
||||
const { isOnline } = useNetworkStatus();
|
||||
const {
|
||||
queuedItems,
|
||||
lastSyncTime,
|
||||
nextRetryTime,
|
||||
retryItem,
|
||||
retryAll,
|
||||
removeItem,
|
||||
clearQueue,
|
||||
} = useSubmissionQueue({
|
||||
autoRetry: true,
|
||||
retryDelayMs: 5000,
|
||||
maxRetries: 3,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Network Error Banner - Shows at top when offline or errors present */}
|
||||
<NetworkErrorBanner
|
||||
isOffline={!isOnline}
|
||||
pendingCount={queuedItems.length}
|
||||
onRetryNow={retryAll}
|
||||
estimatedRetryTime={nextRetryTime || undefined}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="min-h-screen">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* Floating Queue Indicator - Shows in bottom right */}
|
||||
{queuedItems.length > 0 && (
|
||||
<div className="fixed bottom-6 right-6 z-40">
|
||||
<SubmissionQueueIndicator
|
||||
queuedItems={queuedItems}
|
||||
lastSyncTime={lastSyncTime || undefined}
|
||||
onRetryItem={retryItem}
|
||||
onRetryAll={retryAll}
|
||||
onRemoveItem={removeItem}
|
||||
onClearQueue={clearQueue}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user