From bc0c4c750cb212ed366f7b6b17d560af3b301a76 Mon Sep 17 00:00:00 2001 From: Meik Date: Wed, 5 Aug 2026 19:30:10 +0200 Subject: [PATCH] feat: combine mobile navigation and account menu Replace the mobile profile card with a compact page header, account menu, and contextual dashboard toolbar while preserving desktop behavior. --- .commitmessage | 6 +- src/App.js | 7 +- src/components/DashboardView.js | 21 +++- src/components/MobileConfigList.css | 77 +++++-------- src/components/NavigationTabs.css | 150 ++++++++++++++++++++++++++ src/components/NavigationTabs.js | 109 ++++++++----------- src/components/NavigationTabs.test.js | 28 +++++ 7 files changed, 276 insertions(+), 122 deletions(-) create mode 100644 src/components/NavigationTabs.css create mode 100644 src/components/NavigationTabs.test.js diff --git a/.commitmessage b/.commitmessage index f391d48..228bed0 100644 --- a/.commitmessage +++ b/.commitmessage @@ -1,4 +1,4 @@ -fix: clarify mobile slot card hierarchy +feat: combine mobile navigation and account menu -Prioritize store identity over booking controls and make card boundaries and -booking states easier to scan on mobile. +Replace the mobile profile card with a compact page header, account menu, and +contextual dashboard toolbar while preserving desktop behavior. diff --git a/src/App.js b/src/App.js index 9b5e493..e088dcf 100644 --- a/src/App.js +++ b/src/App.js @@ -868,7 +868,12 @@ function App() { <>
- + -

Foodsharing Pickup Manager

+

Foodsharing Pickup Manager

@@ -803,6 +803,25 @@ const DashboardView = ({
+
+ + + {notificationLoading && Lade...} +
+ {notificationPanelOpen && ( div { - display: grid; - gap: 0.65rem; + .dashboard-page-title { + display: none; } - .dashboard-session__identity { - display: flex; - align-items: baseline; - gap: 0.4rem; - min-width: 0; - } - - .dashboard-session__label, - .dashboard-session__name { - margin: 0; - } - - .dashboard-session__label { - flex: 0 0 auto; - color: #64748b; - font-size: 0.75rem; - letter-spacing: 0; - text-transform: none; - } - - .dashboard-session__name { - overflow: hidden; - color: #1f2937; - font-size: 0.95rem; - text-overflow: ellipsis; - white-space: nowrap; - } - - .dashboard-session__actions { + .dashboard-mobile-toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 0.45rem; align-items: center; + margin: 0.75rem 0 1rem; } - .dashboard-session__refresh, - .dashboard-session__logout, - .dashboard-session__notifications { + .dashboard-mobile-toolbar__refresh, + .dashboard-mobile-toolbar__notifications { box-sizing: border-box; min-height: 2.35rem; border-radius: 5px; font-size: 0.8125rem; } - .dashboard-session__refresh { + .dashboard-mobile-toolbar__refresh { width: 100%; border: 1px solid #2563eb; background: #2563eb; color: #fff; } - .dashboard-session__logout { - width: auto; - border: 1px solid #cbd5e1; - padding: 0.45rem 0.65rem; - background: #fff; - color: #475569; - } - - .dashboard-session__notifications { + .dashboard-mobile-toolbar__notifications { width: 2.35rem; height: 2.35rem; padding: 0; @@ -85,11 +47,22 @@ color: #475569; } - .dashboard-session__notifications svg { + .dashboard-mobile-toolbar__notifications svg { width: 1rem; height: 1rem; } + .dashboard-mobile-toolbar__notifications.is-active { + border-color: #2563eb; + background: #eff6ff; + color: #1d4ed8; + } + + .dashboard-mobile-toolbar__loading { + color: #64748b; + font-size: 0.75rem; + } + .mobile-config-list { display: block; padding-bottom: 5.5rem; diff --git a/src/components/NavigationTabs.css b/src/components/NavigationTabs.css new file mode 100644 index 0000000..7ce0651 --- /dev/null +++ b/src/components/NavigationTabs.css @@ -0,0 +1,150 @@ +.app-navigation { + margin-bottom: 1rem; +} + +.app-navigation__mobile-header, +.app-navigation__mobile-menu { + display: none; +} + +.app-navigation__desktop { + display: flex; + gap: 0.5rem; + align-items: center; + padding: 0.5rem 0.75rem; + border: 1px solid #dbe4ee; + border-radius: 6px; + background: #fff; +} + +.app-navigation__desktop-link { + border-radius: 4px; + padding: 0.45rem 0.65rem; + color: #475569; + font-size: 0.875rem; + font-weight: 600; + text-decoration: none; +} + +.app-navigation__desktop-link:hover, +.app-navigation__desktop-link.is-active { + background: #eff6ff; + color: #1d4ed8; +} + +@media (max-width: 767px) { + .app-navigation { + margin: 0 -0.25rem 0.75rem; + } + + .app-navigation__desktop { + display: none; + } + + .app-navigation__mobile-header { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 2.9rem; + padding: 0 0.25rem 0 0.5rem; + border-bottom: 1px solid #cbd5e1; + } + + .app-navigation__current-page { + overflow: hidden; + color: #1f2937; + font-size: 1rem; + font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; + } + + .app-navigation__menu-toggle { + display: grid; + flex: 0 0 auto; + width: 2.5rem; + height: 2.5rem; + place-items: center; + border: 1px solid #cbd5e1; + border-radius: 5px; + background: #fff; + color: #334155; + } + + .app-navigation__menu-toggle svg { + width: 1.2rem; + height: 1.2rem; + } + + .app-navigation__mobile-menu { + display: grid; + gap: 0.75rem; + margin-top: 0.6rem; + padding: 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 6px; + background: #fff; + } + + .app-navigation__mobile-links { + display: grid; + gap: 0.35rem; + } + + .app-navigation__mobile-link { + padding: 0.7rem 0.75rem; + border: 1px solid #dbe4ee; + border-radius: 5px; + color: #334155; + font-size: 0.9rem; + font-weight: 500; + text-decoration: none; + } + + .app-navigation__mobile-link.is-active { + border-color: #2563eb; + background: #eff6ff; + color: #1d4ed8; + font-weight: 600; + } + + .app-navigation__account { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid #dbe4ee; + } + + .app-navigation__account div { + display: grid; + min-width: 0; + gap: 0.1rem; + } + + .app-navigation__account span { + color: #64748b; + font-size: 0.75rem; + } + + .app-navigation__account strong { + overflow: hidden; + color: #1f2937; + font-size: 0.875rem; + font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; + } + + .app-navigation__account button { + flex: 0 0 auto; + min-height: 2.25rem; + border: 1px solid #cbd5e1; + border-radius: 5px; + padding: 0.4rem 0.65rem; + background: #fff; + color: #475569; + font-size: 0.8125rem; + } +} diff --git a/src/components/NavigationTabs.js b/src/components/NavigationTabs.js index c4910bf..bcfd1f6 100644 --- a/src/components/NavigationTabs.js +++ b/src/components/NavigationTabs.js @@ -1,20 +1,26 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Link, useLocation, useNavigate } from 'react-router-dom'; +import './NavigationTabs.css'; -const NavigationTabs = ({ isAdmin, onProtectedNavigate }) => { +const NavigationTabs = ({ isAdmin, onProtectedNavigate, profileName, onLogout }) => { const location = useLocation(); const navigate = useNavigate(); const [mobileOpen, setMobileOpen] = useState(false); - const tabs = [ - { to: '/', label: 'Slots buchen' }, - { to: '/store-watch', label: 'Betriebs-Monitoring' }, - { to: '/journal', label: 'Abhol-Journal' } - ]; - if (isAdmin) { - tabs.push({ to: '/debug', label: 'Debug' }); - tabs.push({ to: '/admin', label: 'Admin' }); - } + const tabs = useMemo(() => { + const items = [ + { to: '/', label: 'Slots buchen' }, + { to: '/store-watch', label: 'Betriebs-Monitoring' }, + { to: '/journal', label: 'Abhol-Journal' } + ]; + if (isAdmin) { + items.push({ to: '/debug', label: 'Debug' }); + items.push({ to: '/admin', label: 'Admin' }); + } + return items; + }, [isAdmin]); + + const activeTab = tabs.find((tab) => tab.to === location.pathname) || tabs[0]; const handleClick = (event, to) => { event.preventDefault(); @@ -34,50 +40,34 @@ const NavigationTabs = ({ isAdmin, onProtectedNavigate }) => { setMobileOpen(false); }, [location.pathname]); - const renderLink = (tab, className, options = {}) => { + const renderLink = (tab, className) => { const isActive = location.pathname === tab.to; - const combinedClassName = typeof className === 'function' ? className(isActive) : className; return ( handleClick(event, tab.to)} - className={combinedClassName} + className={`${className}${isActive ? ' is-active' : ''}`} aria-current={isActive ? 'page' : undefined} > - {tab.label} - {options.showUnderline ? ( - - ) : null} + {tab.label} ); }; return ( -