mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
0d7238300e
- BottomNav respects dark mode via settingsStore (matching Navbar pattern) - Currency/Timezone widgets always visible on mobile regardless of desktop setting - Hero trip stats simplified to 3 columns (Days, Places, Buddies) — removed Starts date - Atlas page uses h-screen overflow-hidden to prevent double scrollbar
165 lines
6.5 KiB
TypeScript
165 lines
6.5 KiB
TypeScript
import { useState } from 'react'
|
|
import { NavLink, useNavigate } from 'react-router-dom'
|
|
import { useAddonStore } from '../../store/addonStore'
|
|
import { useAuthStore } from '../../store/authStore'
|
|
import { useSettingsStore } from '../../store/settingsStore'
|
|
import { useTranslation } from '../../i18n'
|
|
import { Plane, CalendarDays, Globe, Compass, User, Settings, Shield, LogOut, X } from 'lucide-react'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
|
|
const BASE_ITEMS: { to: string; label: string; icon: LucideIcon; addonId?: string }[] = [
|
|
{ to: '/trips', label: 'Trips', icon: Plane },
|
|
]
|
|
|
|
const ADDON_NAV: Record<string, { to: string; label: string; icon: LucideIcon }> = {
|
|
vacay: { to: '/vacay', label: 'Vacay', icon: CalendarDays },
|
|
atlas: { to: '/atlas', label: 'Atlas', icon: Globe },
|
|
journey: { to: '/journey', label: 'Journey', icon: Compass },
|
|
}
|
|
|
|
export default function BottomNav() {
|
|
const { t } = useTranslation()
|
|
const darkMode = useSettingsStore(s => s.settings.dark_mode)
|
|
const dark = darkMode === true || darkMode === 'dark' || (darkMode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
const addons = useAddonStore(s => s.addons)
|
|
const globalAddons = addons.filter(a => a.type === 'global' && a.enabled)
|
|
const [showProfile, setShowProfile] = useState(false)
|
|
|
|
const items = [...BASE_ITEMS]
|
|
for (const addon of globalAddons) {
|
|
const nav = ADDON_NAV[addon.id]
|
|
if (nav) items.push(nav)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<nav
|
|
className="md:hidden sticky bottom-0 border-t border-zinc-200 dark:border-zinc-800 flex justify-around items-start pt-3 z-50 mt-auto flex-shrink-0"
|
|
style={{
|
|
height: 'calc(84px + env(safe-area-inset-bottom, 0px))',
|
|
paddingBottom: 'env(safe-area-inset-bottom, 0px)',
|
|
background: dark ? 'rgba(9,9,11,0.96)' : 'rgba(255,255,255,0.96)',
|
|
backdropFilter: 'blur(20px)',
|
|
WebkitBackdropFilter: 'blur(20px)',
|
|
}}
|
|
>
|
|
{items.map(({ to, label, icon: Icon }) => (
|
|
<NavLink
|
|
key={to}
|
|
to={to}
|
|
className={({ isActive }) =>
|
|
`flex flex-col items-center gap-1 px-3 py-1 min-w-[60px] ${
|
|
isActive ? 'text-zinc-900 dark:text-white' : 'text-zinc-400 dark:text-zinc-500'
|
|
}`
|
|
}
|
|
>
|
|
<Icon size={22} strokeWidth={2} />
|
|
<span className="text-[10px] font-medium">{label}</span>
|
|
</NavLink>
|
|
))}
|
|
<button
|
|
onClick={() => setShowProfile(true)}
|
|
className="flex flex-col items-center gap-1 px-3 py-1 min-w-[60px] text-zinc-400 dark:text-zinc-500"
|
|
>
|
|
<User size={22} strokeWidth={2} />
|
|
<span className="text-[10px] font-medium">{t("nav.profile")}</span>
|
|
</button>
|
|
</nav>
|
|
|
|
{showProfile && <ProfileSheet onClose={() => setShowProfile(false)} />}
|
|
</>
|
|
)
|
|
}
|
|
|
|
function ProfileSheet({ onClose }: { onClose: () => void }) {
|
|
const { t } = useTranslation()
|
|
const { user, logout } = useAuthStore()
|
|
const navigate = useNavigate()
|
|
|
|
const handleNav = (path: string) => {
|
|
onClose()
|
|
navigate(path)
|
|
}
|
|
|
|
const handleLogout = () => {
|
|
onClose()
|
|
logout()
|
|
navigate('/login')
|
|
}
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[300] md:hidden" onClick={onClose}>
|
|
{/* Backdrop */}
|
|
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
|
|
|
|
{/* Sheet */}
|
|
<div
|
|
className="absolute bottom-0 left-0 right-0 bg-white dark:bg-zinc-900 rounded-t-2xl overflow-hidden"
|
|
style={{ animation: 'slideUp 0.25s ease-out', paddingBottom: 'env(safe-area-inset-bottom, 0px)' }}
|
|
onClick={e => e.stopPropagation()}
|
|
>
|
|
{/* Handle */}
|
|
<div className="flex justify-center pt-3 pb-2">
|
|
<div className="w-10 h-1 rounded-full bg-zinc-300 dark:bg-zinc-700" />
|
|
</div>
|
|
|
|
{/* User info */}
|
|
<div className="px-6 pb-4 pt-1">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-11 h-11 rounded-full bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 flex items-center justify-center text-[16px] font-bold">
|
|
{(user?.username || '?')[0].toUpperCase()}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-[15px] font-semibold text-zinc-900 dark:text-white">{user?.username}</p>
|
|
<p className="text-[12px] text-zinc-500 truncate">{user?.email}</p>
|
|
</div>
|
|
{user?.role === 'admin' && (
|
|
<span className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 text-[10px] font-semibold text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
|
|
<Shield size={10} /> Admin
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="h-px bg-zinc-100 dark:bg-zinc-800 mx-4" />
|
|
|
|
{/* Links */}
|
|
<div className="py-2 px-2">
|
|
<button
|
|
onClick={() => handleNav('/settings')}
|
|
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left hover:bg-zinc-50 dark:hover:bg-zinc-800 active:bg-zinc-100 dark:active:bg-zinc-800 transition-colors"
|
|
>
|
|
<Settings size={18} className="text-zinc-500" />
|
|
<span className="text-[14px] font-medium text-zinc-900 dark:text-white">{t("nav.bottomSettings")}</span>
|
|
</button>
|
|
|
|
{user?.role === 'admin' && (
|
|
<button
|
|
onClick={() => handleNav('/admin')}
|
|
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left hover:bg-zinc-50 dark:hover:bg-zinc-800 active:bg-zinc-100 dark:active:bg-zinc-800 transition-colors"
|
|
>
|
|
<Shield size={18} className="text-zinc-500" />
|
|
<span className="text-[14px] font-medium text-zinc-900 dark:text-white">{t("nav.bottomAdmin")}</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="h-px bg-zinc-100 dark:bg-zinc-800 mx-4" />
|
|
|
|
{/* Logout */}
|
|
<div className="py-2 px-2">
|
|
<button
|
|
onClick={handleLogout}
|
|
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left hover:bg-red-50 dark:hover:bg-red-900/20 active:bg-red-100 transition-colors"
|
|
>
|
|
<LogOut size={18} className="text-red-500" />
|
|
<span className="text-[14px] font-medium text-red-600 dark:text-red-400">{t("nav.bottomLogout")}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="h-4" />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|