Tonight
This commit is contained in:
parent
28323f145e
commit
94f9c0d594
76 changed files with 3238 additions and 1230 deletions
|
|
@ -28,6 +28,7 @@ interface MobileMenuProps {
|
|||
onClose: () => void;
|
||||
onShare: () => void;
|
||||
copied: boolean;
|
||||
sharing: boolean;
|
||||
}
|
||||
|
||||
export default function MobileMenu({
|
||||
|
|
@ -46,6 +47,7 @@ export default function MobileMenu({
|
|||
onClose,
|
||||
onShare,
|
||||
copied,
|
||||
sharing,
|
||||
}: MobileMenuProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
|
|
@ -53,7 +55,7 @@ export default function MobileMenu({
|
|||
<a
|
||||
key={page}
|
||||
href={PAGE_PATHS[page]}
|
||||
className={`block w-full text-left px-4 py-3 text-base font-medium rounded ${
|
||||
className={`block w-full cursor-pointer text-left px-3 py-2 text-sm font-medium rounded ${
|
||||
activePage === page
|
||||
? 'bg-navy-700 text-white'
|
||||
: 'text-warm-300 hover:bg-navy-800 hover:text-white'
|
||||
|
|
@ -69,23 +71,95 @@ export default function MobileMenu({
|
|||
</a>
|
||||
);
|
||||
|
||||
const dashboardActionClass =
|
||||
'w-full flex cursor-pointer items-center justify-center gap-2 px-3 py-2 rounded bg-navy-800 text-sm font-semibold text-white border border-navy-700 shadow-sm hover:bg-navy-700 disabled:cursor-wait disabled:opacity-50 transition-colors';
|
||||
|
||||
const dashboardSavedItem = user && (
|
||||
<a
|
||||
href={PAGE_PATHS.saved}
|
||||
className={dashboardActionClass}
|
||||
onClick={(e) => {
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
onPageChange('saved');
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('header.saved')}
|
||||
</a>
|
||||
);
|
||||
|
||||
const dashboardActions = activePage === 'dashboard' && (
|
||||
<div className="px-2 py-2 border-b border-navy-700">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
onShare();
|
||||
onClose();
|
||||
}}
|
||||
disabled={sharing}
|
||||
className={dashboardActionClass}
|
||||
>
|
||||
{sharing ? (
|
||||
<SpinnerIcon className="w-4 h-4 animate-spin" />
|
||||
) : copied ? (
|
||||
<CheckIcon className="w-4 h-4" />
|
||||
) : (
|
||||
<ClipboardIcon className="w-4 h-4" />
|
||||
)}
|
||||
{sharing ? t('header.sharing') : copied ? t('common.copied') : t('common.share')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onExport?.();
|
||||
onClose();
|
||||
}}
|
||||
disabled={!onExport || exporting}
|
||||
className={dashboardActionClass}
|
||||
>
|
||||
<DownloadIcon className="w-4 h-4" />
|
||||
{exporting ? t('header.exporting') : t('header.exportLabel')}
|
||||
</button>
|
||||
{onSaveSearch && (
|
||||
<button
|
||||
onClick={() => {
|
||||
onSaveSearch();
|
||||
onClose();
|
||||
}}
|
||||
disabled={savingSearch}
|
||||
className={dashboardActionClass}
|
||||
>
|
||||
{savingSearch ? (
|
||||
<SpinnerIcon className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<BookmarkIcon className="w-4 h-4" />
|
||||
)}
|
||||
{t('common.save')}
|
||||
</button>
|
||||
)}
|
||||
{dashboardSavedItem}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div className="fixed inset-0 bg-black/50 z-[70]" onClick={onClose} />
|
||||
{/* Menu panel */}
|
||||
<div className="fixed top-0 right-0 bottom-0 w-64 bg-navy-900 z-[80] flex flex-col shadow-xl">
|
||||
<div className="flex items-center justify-between px-4 h-12 border-b border-navy-700">
|
||||
<div className="mobile-menu-panel fixed top-0 right-0 bottom-0 w-64 bg-navy-900 text-white z-[80] flex flex-col shadow-xl">
|
||||
<div className="flex items-center justify-between px-3 h-11 border-b border-navy-700">
|
||||
<span className="font-semibold">{t('mobileMenu.menu')}</span>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex items-center justify-center w-10 h-10 -mr-2 rounded hover:bg-navy-800 transition-colors"
|
||||
className="flex cursor-pointer items-center justify-center w-10 h-10 -mr-2 rounded hover:bg-navy-800 transition-colors"
|
||||
aria-label={t('header.closeMenu')}
|
||||
>
|
||||
<CloseIcon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<nav className="flex-1 flex flex-col gap-1 p-3 overflow-y-auto">
|
||||
{dashboardActions}
|
||||
<nav className="flex-1 flex flex-col gap-0.5 p-2 overflow-y-auto">
|
||||
{mobileNavItem('home', t('mobileMenu.home'))}
|
||||
{mobileNavItem('dashboard', t('header.dashboard'))}
|
||||
{mobileNavItem('learn', t('header.learn'))}
|
||||
|
|
@ -94,69 +168,24 @@ export default function MobileMenu({
|
|||
mobileNavItem('pricing', t('header.pricing'))}
|
||||
{user && mobileNavItem('invites', t('header.inviteFriends'))}
|
||||
{user && mobileNavItem('account', t('userMenu.account'))}
|
||||
|
||||
{/* Dashboard actions */}
|
||||
{activePage === 'dashboard' && (
|
||||
<div className="mt-3 pt-3 border-t border-navy-700 flex flex-col gap-1">
|
||||
<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 ? t('common.copied') : t('common.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 ? t('header.exporting') : t('header.exportLabel')}
|
||||
</button>
|
||||
{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" />
|
||||
)}
|
||||
{t('common.save')}
|
||||
</button>
|
||||
)}
|
||||
{user && mobileNavItem('saved', t('header.saved'))}
|
||||
</div>
|
||||
)}
|
||||
{activePage !== 'dashboard' && user && mobileNavItem('saved', t('header.saved'))}
|
||||
</nav>
|
||||
|
||||
{/* Theme toggle + Auth section at bottom */}
|
||||
<div className="p-3 border-t border-navy-700 flex flex-col gap-3">
|
||||
<div className="p-2 border-t border-navy-700 flex flex-col gap-2">
|
||||
{/* 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"
|
||||
className="w-full flex cursor-pointer items-center gap-2 px-3 py-2 text-sm 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 === 'light' ? t('userMenu.themeLight') : t('userMenu.themeDark')}</span>
|
||||
</button>
|
||||
|
||||
{/* Language selector */}
|
||||
<div className="flex max-w-full gap-1 overflow-x-auto overflow-y-hidden px-4 pb-1 scrollbar-hide">
|
||||
<div className="flex max-w-full gap-1 overflow-x-auto overflow-y-hidden px-3 pb-1 scrollbar-hide">
|
||||
{SUPPORTED_LANGUAGES.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
|
|
@ -165,7 +194,7 @@ export default function MobileMenu({
|
|||
localStorage.setItem('language', lang.code);
|
||||
void changeAppLanguage(lang.code);
|
||||
}}
|
||||
className={`flex-none min-w-[2.75rem] flex items-center justify-center gap-1.5 px-2 py-2 rounded text-sm ${
|
||||
className={`flex-none min-w-[2.5rem] flex cursor-pointer items-center justify-center gap-1.5 px-2 py-1.5 rounded text-sm ${
|
||||
i18n.language === lang.code
|
||||
? 'bg-navy-700 text-white font-medium'
|
||||
: 'text-warm-400 hover:bg-navy-800 hover:text-white'
|
||||
|
|
@ -180,14 +209,14 @@ export default function MobileMenu({
|
|||
{/* Auth buttons */}
|
||||
<div>
|
||||
{user ? (
|
||||
<div className="flex items-center justify-between gap-3 px-4 py-2">
|
||||
<div className="flex items-center justify-between gap-2 px-3 py-1.5">
|
||||
<span className="text-sm text-warm-300 truncate">{user.email}</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLogout();
|
||||
onClose();
|
||||
}}
|
||||
className="shrink-0 text-sm text-warm-400 hover:text-white"
|
||||
className="shrink-0 cursor-pointer text-sm text-warm-400 hover:text-white"
|
||||
>
|
||||
{t('userMenu.logOut')}
|
||||
</button>
|
||||
|
|
@ -199,7 +228,7 @@ export default function MobileMenu({
|
|||
onLoginClick();
|
||||
onClose();
|
||||
}}
|
||||
className="flex-1 px-3 py-2.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm text-center"
|
||||
className="flex-1 cursor-pointer px-3 py-2 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm text-center"
|
||||
>
|
||||
{t('header.logIn')}
|
||||
</button>
|
||||
|
|
@ -208,7 +237,7 @@ export default function MobileMenu({
|
|||
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"
|
||||
className="flex-1 cursor-pointer px-3 py-2 rounded bg-teal-600 hover:bg-teal-700 transition-colors text-sm font-medium text-center"
|
||||
>
|
||||
{t('header.createAccount')}
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue