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:
poduck
2025-12-03 01:35:59 -05:00
parent ef58e9fc94
commit 5cef01ad0d
25 changed files with 2220 additions and 330 deletions

View File

@@ -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}