From e4668f81c56f65fb474c979d7fa6ed21ead2f3e7 Mon Sep 17 00:00:00 2001 From: poduck Date: Tue, 23 Dec 2025 22:18:02 -0500 Subject: [PATCH] Restructure navigation: move setup items to Settings with accordion menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move rarely-used setup items from main sidebar to Settings to keep daily-use features prominent: - Services → Settings > Business section - Locations → Settings > Business section - Site Builder → Settings > Branding section Settings sidebar changes: - Convert static sections to accordion (one open at a time) - Auto-expand section based on current URL - Preserve all permission checks for moved items Add redirects from old URLs to new locations for backwards compatibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/App.tsx | 31 +- frontend/src/components/Sidebar.tsx | 33 +- .../navigation/SidebarComponents.tsx | 49 ++ frontend/src/i18n/locales/en.json | 12 + frontend/src/layouts/SettingsLayout.tsx | 423 +++++++++++------- 5 files changed, 341 insertions(+), 207 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index af1cee92..b6cd23f3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -879,15 +879,10 @@ const AppContent: React.FC = () => { ) } /> + {/* Redirect old services path to new settings location */} - ) : ( - - ) - } + element={} /> { ) } /> + {/* Redirect old locations path to new settings location */} - ) : ( - - ) - } + element={} /> { ) } /> + {/* Redirect old site-editor path to new settings location */} - ) : ( - - ) - } + element={} /> { } /> } /> } /> + {/* Moved from main sidebar */} + } /> + } /> + } /> ) : ( } /> diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 708578fc..a42a8b5c 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -10,15 +10,12 @@ import { MessageSquare, LogOut, ClipboardList, - Briefcase, Ticket, HelpCircle, Clock, Plug, FileSignature, CalendarOff, - LayoutTemplate, - MapPin, Image, } from 'lucide-react'; import { Business, User } from '../types'; @@ -159,25 +156,14 @@ const Sidebar: React.FC = ({ business, user, isCollapsed, toggleCo {/* Manage Section - Show if user has any manage-related permission */} {(canViewManagementPages || - hasPermission('can_access_site_builder') || hasPermission('can_access_gallery') || hasPermission('can_access_customers') || - hasPermission('can_access_services') || hasPermission('can_access_resources') || hasPermission('can_access_staff') || hasPermission('can_access_contracts') || - hasPermission('can_access_time_blocks') || - hasPermission('can_access_locations') + hasPermission('can_access_time_blocks') ) && ( - {hasPermission('can_access_site_builder') && ( - - )} {hasPermission('can_access_gallery') && ( = ({ business, user, isCollapsed, toggleCo isCollapsed={isCollapsed} /> )} - {hasPermission('can_access_services') && ( - - )} {hasPermission('can_access_resources') && ( = ({ business, user, isCollapsed, toggleCo isCollapsed={isCollapsed} /> )} - {hasPermission('can_access_locations') && ( - - )} )} diff --git a/frontend/src/components/navigation/SidebarComponents.tsx b/frontend/src/components/navigation/SidebarComponents.tsx index a78c04a3..0c6a3ce7 100644 --- a/frontend/src/components/navigation/SidebarComponents.tsx +++ b/frontend/src/components/navigation/SidebarComponents.tsx @@ -256,6 +256,55 @@ export const SettingsSidebarSection: React.FC = ({ ); }; +interface SettingsAccordionSectionProps { + title: string; + sectionKey: string; + isOpen: boolean; + onToggle: (sectionKey: string) => void; + children: React.ReactNode; + hasVisibleItems?: boolean; +} + +/** + * Collapsible accordion section for settings sidebar + * Only one section can be open at a time (controlled by parent) + */ +export const SettingsAccordionSection: React.FC = ({ + title, + sectionKey, + isOpen, + onToggle, + children, + hasVisibleItems = true, +}) => { + // Don't render if no visible items + if (!hasVisibleItems) return null; + + return ( +
+ +
+
+ {children} +
+
+
+ ); +}; + interface SettingsSidebarItemProps { to: string; icon: LucideIcon; diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index d6699f6c..33840b8a 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2042,6 +2042,14 @@ "title": "Business Hours", "description": "Operating hours" }, + "services": { + "title": "Services", + "description": "Manage your services" + }, + "locations": { + "title": "Locations", + "description": "Business locations" + }, "appearance": { "title": "Appearance", "description": "Logo, colors, theme" @@ -2082,6 +2090,10 @@ "step2": "Copy the embed code and paste it into your website's HTML where you want the booking widget to appear.", "step3": "For platforms like WordPress, Squarespace, or Wix, look for an \"HTML\" or \"Embed\" block and paste the code there." }, + "siteBuilder": { + "title": "Site Builder", + "description": "Build your booking site" + }, "api": { "title": "API & Webhooks", "description": "API tokens, webhooks" diff --git a/frontend/src/layouts/SettingsLayout.tsx b/frontend/src/layouts/SettingsLayout.tsx index 9130f11e..09f4b8a4 100644 --- a/frontend/src/layouts/SettingsLayout.tsx +++ b/frontend/src/layouts/SettingsLayout.tsx @@ -5,7 +5,7 @@ * Used as a wrapper for all /settings/* routes. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Outlet, Link, useLocation, useNavigate, useOutletContext } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { @@ -24,9 +24,12 @@ import { Clock, Users, Code2, + Briefcase, + MapPin, + LayoutTemplate, } from 'lucide-react'; import { - SettingsSidebarSection, + SettingsAccordionSection, SettingsSidebarItem, } from '../components/navigation/SidebarComponents'; import UnfinishedBadge from '../components/ui/UnfinishedBadge'; @@ -46,6 +49,44 @@ const SETTINGS_PAGE_FEATURES: Record = { '/dashboard/settings/api': 'api_access', '/dashboard/settings/authentication': 'custom_oauth', '/dashboard/settings/sms-calling': 'sms_reminders', + '/dashboard/settings/locations': 'multi_location', +}; + +// Map URL paths to section keys for auto-expand +const URL_TO_SECTION: Record = { + '/dashboard/settings/general': 'business', + '/dashboard/settings/resource-types': 'business', + '/dashboard/settings/booking': 'business', + '/dashboard/settings/business-hours': 'business', + '/dashboard/settings/services': 'business', + '/dashboard/settings/locations': 'business', + '/dashboard/settings/branding': 'branding', + '/dashboard/settings/email-templates': 'branding', + '/dashboard/settings/custom-domains': 'branding', + '/dashboard/settings/embed-widget': 'branding', + '/dashboard/settings/site-builder': 'branding', + '/dashboard/settings/api': 'integrations', + '/dashboard/settings/staff-roles': 'access', + '/dashboard/settings/authentication': 'access', + '/dashboard/settings/email': 'communication', + '/dashboard/settings/sms-calling': 'communication', + '/dashboard/settings/billing': 'billing', + '/dashboard/settings/quota': 'billing', +}; + +// Helper to get section from URL +const getSectionFromUrl = (pathname: string): string => { + // Check for exact match first + if (URL_TO_SECTION[pathname]) { + return URL_TO_SECTION[pathname]; + } + // Check for prefix match (for nested routes) + for (const [path, section] of Object.entries(URL_TO_SECTION)) { + if (pathname.startsWith(path)) { + return section; + } + } + return 'business'; // Default to business section }; const SettingsLayout: React.FC = () => { @@ -59,6 +100,20 @@ const SettingsLayout: React.FC = () => { const { user } = parentContext || {}; const isOwner = user?.role === 'owner'; + // Accordion state - track which section is open + const [openSection, setOpenSection] = useState(() => getSectionFromUrl(location.pathname)); + + // Update open section when URL changes + useEffect(() => { + const section = getSectionFromUrl(location.pathname); + setOpenSection(section); + }, [location.pathname]); + + // Handle section toggle (only one open at a time) + const handleSectionToggle = (sectionKey: string) => { + setOpenSection(prev => prev === sectionKey ? '' : sectionKey); + }; + // Check if staff has access to a specific settings page const hasSettingsPermission = (permissionKey: string): boolean => { // Owners always have all permissions @@ -100,169 +155,229 @@ const SettingsLayout: React.FC = () => { {/* Navigation */} -