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 (
+
+
+
+
+ );
+};
+
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 */}
-