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:
@@ -6,7 +6,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { ChevronDown, LucideIcon } from 'lucide-react';
|
||||
import { ChevronDown, Lock, LucideIcon } from 'lucide-react';
|
||||
|
||||
interface SidebarSectionProps {
|
||||
title?: string;
|
||||
@@ -48,6 +48,7 @@ interface SidebarItemProps {
|
||||
disabled?: boolean;
|
||||
badge?: string | number;
|
||||
variant?: 'default' | 'settings';
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,6 +63,7 @@ export const SidebarItem: React.FC<SidebarItemProps> = ({
|
||||
disabled = false,
|
||||
badge,
|
||||
variant = 'default',
|
||||
locked = false,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const isActive = exact
|
||||
@@ -75,10 +77,14 @@ export const SidebarItem: React.FC<SidebarItemProps> = ({
|
||||
const colorClasses = variant === 'settings'
|
||||
? isActive
|
||||
? 'bg-brand-50 text-brand-700 dark:bg-brand-900/30 dark:text-brand-400'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-800'
|
||||
: locked
|
||||
? 'text-gray-400 hover:text-gray-500 hover:bg-gray-50 dark:text-gray-500 dark:hover:text-gray-400 dark:hover:bg-gray-800'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-800'
|
||||
: isActive
|
||||
? 'bg-white/10 text-white'
|
||||
: 'text-white/70 hover:text-white hover:bg-white/5';
|
||||
: locked
|
||||
? 'text-white/40 hover:text-white/60 hover:bg-white/5'
|
||||
: 'text-white/70 hover:text-white hover:bg-white/5';
|
||||
|
||||
const disabledClasses = variant === 'settings'
|
||||
? 'text-gray-300 dark:text-gray-600 cursor-not-allowed'
|
||||
@@ -101,7 +107,12 @@ export const SidebarItem: React.FC<SidebarItemProps> = ({
|
||||
return (
|
||||
<Link to={to} className={className} title={label}>
|
||||
<Icon size={20} className="shrink-0" />
|
||||
{!isCollapsed && <span className="flex-1">{label}</span>}
|
||||
{!isCollapsed && (
|
||||
<span className="flex-1 flex items-center gap-1.5">
|
||||
{label}
|
||||
{locked && <Lock size={12} className="opacity-60" />}
|
||||
</span>
|
||||
)}
|
||||
{badge && !isCollapsed && (
|
||||
<span className="px-2 py-0.5 text-xs rounded-full bg-brand-100 text-brand-700 dark:bg-brand-900/50 dark:text-brand-400">
|
||||
{badge}
|
||||
@@ -244,16 +255,18 @@ interface SettingsSidebarItemProps {
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
description?: string;
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings navigation item with optional description
|
||||
* Settings navigation item with optional description and lock indicator
|
||||
*/
|
||||
export const SettingsSidebarItem: React.FC<SettingsSidebarItemProps> = ({
|
||||
to,
|
||||
icon: Icon,
|
||||
label,
|
||||
description,
|
||||
locked = false,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const isActive = location.pathname === to || location.pathname.startsWith(to + '/');
|
||||
@@ -264,12 +277,19 @@ export const SettingsSidebarItem: React.FC<SettingsSidebarItemProps> = ({
|
||||
className={`flex items-start gap-2.5 px-4 py-1.5 text-sm rounded-lg transition-colors ${
|
||||
isActive
|
||||
? 'bg-brand-50 text-brand-700 dark:bg-brand-900/30 dark:text-brand-400'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-800'
|
||||
: locked
|
||||
? 'text-gray-400 hover:text-gray-500 hover:bg-gray-50 dark:text-gray-500 dark:hover:text-gray-400 dark:hover:bg-gray-800'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
<Icon size={16} className="shrink-0 mt-0.5" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="font-medium">{label}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-medium">{label}</span>
|
||||
{locked && (
|
||||
<Lock size={12} className="text-gray-400 dark:text-gray-500" />
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-500 truncate">
|
||||
{description}
|
||||
|
||||
Reference in New Issue
Block a user