perfect-postcode/frontend/src/components/ui/MobileMenu.tsx
2026-02-09 19:26:54 +00:00

187 lines
6.3 KiB
TypeScript

import type { Page } from './Header';
import type { AuthUser } from '../../hooks/useAuth';
import { DownloadIcon } from './icons/DownloadIcon';
import { BookmarkIcon } from './icons/BookmarkIcon';
import { CheckIcon } from './icons/CheckIcon';
import { ClipboardIcon } from './icons/ClipboardIcon';
import { CloseIcon } from './icons/CloseIcon';
import { SunIcon } from './icons/SunIcon';
import { MoonIcon } from './icons/MoonIcon';
import { SpinnerIcon } from './icons/SpinnerIcon';
interface MobileMenuProps {
activePage: Page;
onPageChange: (page: Page) => void;
theme: 'light' | 'dark';
onToggleTheme: () => void;
onExport: (() => void) | null;
exporting: boolean;
onSaveSearch: (() => void) | null;
savingSearch: boolean;
user: AuthUser | null;
onLoginClick: () => void;
onRegisterClick: () => void;
onLogout: () => void;
onClose: () => void;
onShare: () => void;
copied: boolean;
}
export default function MobileMenu({
activePage,
onPageChange,
theme,
onToggleTheme,
onExport,
exporting,
onSaveSearch,
savingSearch,
user,
onLoginClick,
onRegisterClick,
onLogout,
onClose,
onShare,
copied,
}: MobileMenuProps) {
const mobileNavItem = (page: Page, label: string) => (
<button
key={page}
className={`w-full text-left px-4 py-3 text-base font-medium rounded ${
activePage === page
? 'bg-navy-700 text-white'
: 'text-warm-300 hover:bg-navy-800 hover:text-white'
}`}
onClick={() => {
onPageChange(page);
onClose();
}}
>
{label}
</button>
);
return (
<>
{/* Backdrop */}
<div className="fixed inset-0 bg-black/50 z-40" onClick={onClose} />
{/* Menu panel */}
<div className="fixed top-0 right-0 bottom-0 w-64 bg-navy-900 z-50 flex flex-col shadow-xl">
<div className="flex items-center justify-between px-4 h-12 border-b border-navy-700">
<span className="font-semibold">Menu</span>
<button
onClick={onClose}
className="flex items-center justify-center w-10 h-10 -mr-2 rounded hover:bg-navy-800 transition-colors"
aria-label="Close menu"
>
<CloseIcon className="w-5 h-5" />
</button>
</div>
<nav className="flex-1 flex flex-col gap-1 p-3 overflow-y-auto">
{mobileNavItem('dashboard', 'Dashboard')}
{user && mobileNavItem('saved-searches', 'Saved')}
{mobileNavItem('data-sources', 'Data Sources')}
{mobileNavItem('faq', 'FAQ')}
{mobileNavItem('pricing', 'Pricing')}
{/* Dashboard actions */}
{activePage === 'dashboard' && (
<div className="mt-3 pt-3 border-t border-navy-700 flex flex-col gap-1">
{onSaveSearch && (
<button
onClick={() => {
onSaveSearch();
onClose();
}}
disabled={savingSearch}
className="w-full flex items-center gap-2 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded disabled:opacity-50"
>
{savingSearch ? (
<SpinnerIcon className="w-5 h-5 animate-spin" />
) : (
<BookmarkIcon className="w-5 h-5" />
)}
Save
</button>
)}
<button
onClick={() => {
onShare();
onClose();
}}
className="w-full flex items-center gap-2 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded"
>
{copied ? <CheckIcon className="w-5 h-5" /> : <ClipboardIcon className="w-5 h-5" />}
{copied ? 'Copied!' : 'Share'}
</button>
<button
onClick={() => {
onExport?.();
onClose();
}}
disabled={!onExport || exporting}
className="w-full flex items-center gap-2 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded disabled:opacity-50"
>
<DownloadIcon className="w-5 h-5" />
{exporting ? 'Exporting...' : 'Export'}
</button>
</div>
)}
</nav>
{/* Theme toggle + Auth section at bottom */}
<div className="p-3 border-t border-navy-700 flex flex-col gap-3">
{/* Theme toggle */}
<button
onClick={() => {
onToggleTheme();
}}
className="w-full flex items-center gap-3 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded transition-colors"
>
{theme === 'light' ? <SunIcon className="w-5 h-5" /> : <MoonIcon className="w-5 h-5" />}
<span>Theme: {theme === 'light' ? 'Light' : 'Dark'}</span>
</button>
{/* Auth buttons */}
<div>
{user ? (
<div className="flex items-center justify-between px-4 py-2">
<span className="text-sm text-warm-300 truncate">{user.email}</span>
<button
onClick={() => {
onLogout();
onClose();
}}
className="text-sm text-warm-400 hover:text-white"
>
Log out
</button>
</div>
) : (
<div className="flex gap-2">
<button
onClick={() => {
onLoginClick();
onClose();
}}
className="flex-1 px-3 py-2.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm text-center"
>
Log in
</button>
<button
onClick={() => {
onRegisterClick();
onClose();
}}
className="flex-1 px-3 py-2.5 rounded bg-teal-600 hover:bg-teal-700 transition-colors text-sm font-medium text-center"
>
Register
</button>
</div>
)}
</div>
</div>
</div>
</>
);
}