feat: Reorganize settings sidebar and add plan-based feature locking
- Add locked state to Plugins sidebar item with plan feature check - Create Branding section in settings with Appearance, Email Templates, Custom Domains - Split Domains page into Booking (URLs, redirects) and Custom Domains (BYOD, purchase) - Add booking_return_url field to Tenant model for customer redirects - Update SidebarItem component to support locked prop with lock icon - Move Email Templates from main sidebar to Settings > Branding - Add communication credits hooks and payment form updates - Add timezone fields migration and various UI improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -19,14 +19,15 @@ import {
|
||||
Mail,
|
||||
Phone,
|
||||
CreditCard,
|
||||
Webhook,
|
||||
AlertTriangle,
|
||||
Calendar,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
SettingsSidebarSection,
|
||||
SettingsSidebarItem,
|
||||
} from '../components/navigation/SidebarComponents';
|
||||
import { Business, User } from '../types';
|
||||
import { Business, User, PlanPermissions } from '../types';
|
||||
import { usePlanFeatures, FeatureKey } from '../hooks/usePlanFeatures';
|
||||
|
||||
interface ParentContext {
|
||||
user: User;
|
||||
@@ -34,14 +35,34 @@ interface ParentContext {
|
||||
updateBusiness: (updates: Partial<Business>) => void;
|
||||
}
|
||||
|
||||
// Map settings pages to their required plan features
|
||||
const SETTINGS_PAGE_FEATURES: Record<string, FeatureKey> = {
|
||||
'/settings/branding': 'white_label',
|
||||
'/settings/custom-domains': 'custom_domain',
|
||||
'/settings/api': 'api_access',
|
||||
'/settings/authentication': 'custom_oauth',
|
||||
'/settings/sms-calling': 'sms_reminders',
|
||||
};
|
||||
|
||||
const SettingsLayout: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { canUse } = usePlanFeatures();
|
||||
|
||||
// Get context from parent route (BusinessLayout)
|
||||
const parentContext = useOutletContext<ParentContext>();
|
||||
|
||||
// Check if a feature is locked (returns true if locked)
|
||||
const isLocked = (feature: FeatureKey | undefined): boolean => {
|
||||
if (!feature) return false;
|
||||
return !canUse(feature);
|
||||
};
|
||||
|
||||
// Get the current page's feature requirement (if any)
|
||||
const currentPageFeature = SETTINGS_PAGE_FEATURES[location.pathname];
|
||||
const currentPageLocked = isLocked(currentPageFeature);
|
||||
|
||||
return (
|
||||
<div className="flex h-full bg-gray-50 dark:bg-gray-900">
|
||||
{/* Settings Sidebar */}
|
||||
@@ -74,33 +95,52 @@ const SettingsLayout: React.FC = () => {
|
||||
label={t('settings.general.title', 'General')}
|
||||
description={t('settings.general.description', 'Name, timezone, contact')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/branding"
|
||||
icon={Palette}
|
||||
label={t('settings.branding.title', 'Branding')}
|
||||
description={t('settings.branding.description', 'Logo, colors, appearance')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/resource-types"
|
||||
icon={Layers}
|
||||
label={t('settings.resourceTypes.title', 'Resource Types')}
|
||||
description={t('settings.resourceTypes.description', 'Staff, rooms, equipment')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/booking"
|
||||
icon={Calendar}
|
||||
label={t('settings.booking.title', 'Booking')}
|
||||
description={t('settings.booking.description', 'Booking URL, redirects')}
|
||||
/>
|
||||
</SettingsSidebarSection>
|
||||
|
||||
{/* Branding Section */}
|
||||
<SettingsSidebarSection title={t('settings.sections.branding', 'Branding')}>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/branding"
|
||||
icon={Palette}
|
||||
label={t('settings.appearance.title', 'Appearance')}
|
||||
description={t('settings.appearance.description', 'Logo, colors, theme')}
|
||||
locked={isLocked('white_label')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/email-templates"
|
||||
icon={Mail}
|
||||
label={t('settings.emailTemplates.title', 'Email Templates')}
|
||||
description={t('settings.emailTemplates.description', 'Customize email designs')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/custom-domains"
|
||||
icon={Globe}
|
||||
label={t('settings.customDomains.title', 'Custom Domains')}
|
||||
description={t('settings.customDomains.description', 'Use your own domain')}
|
||||
locked={isLocked('custom_domain')}
|
||||
/>
|
||||
</SettingsSidebarSection>
|
||||
|
||||
{/* Integrations Section */}
|
||||
<SettingsSidebarSection title={t('settings.sections.integrations', 'Integrations')}>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/domains"
|
||||
icon={Globe}
|
||||
label={t('settings.domains.title', 'Domains')}
|
||||
description={t('settings.domains.description', 'Custom domain setup')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/settings/api"
|
||||
icon={Key}
|
||||
label={t('settings.api.title', 'API & Webhooks')}
|
||||
description={t('settings.api.description', 'API tokens, webhooks')}
|
||||
locked={isLocked('api_access')}
|
||||
/>
|
||||
</SettingsSidebarSection>
|
||||
|
||||
@@ -111,6 +151,7 @@ const SettingsLayout: React.FC = () => {
|
||||
icon={Lock}
|
||||
label={t('settings.authentication.title', 'Authentication')}
|
||||
description={t('settings.authentication.description', 'OAuth, social login')}
|
||||
locked={isLocked('custom_oauth')}
|
||||
/>
|
||||
</SettingsSidebarSection>
|
||||
|
||||
@@ -127,6 +168,7 @@ const SettingsLayout: React.FC = () => {
|
||||
icon={Phone}
|
||||
label={t('settings.smsCalling.title', 'SMS & Calling')}
|
||||
description={t('settings.smsCalling.description', 'Credits, phone numbers')}
|
||||
locked={isLocked('sms_reminders')}
|
||||
/>
|
||||
</SettingsSidebarSection>
|
||||
|
||||
@@ -151,7 +193,11 @@ const SettingsLayout: React.FC = () => {
|
||||
{/* Content Area */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="max-w-4xl mx-auto p-8">
|
||||
<Outlet context={parentContext} />
|
||||
<Outlet context={{
|
||||
...parentContext,
|
||||
isFeatureLocked: currentPageLocked,
|
||||
lockedFeature: currentPageFeature,
|
||||
}} />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user