Consolidate white_label to remove_branding and add embed widget
- Rename white_label feature to remove_branding across frontend/backend - Update billing catalog, plan features, and permission checks - Add dark mode support to Recharts tooltips with useDarkMode hook - Create embeddable booking widget with EmbedBooking page - Add EmbedWidgetSettings for generating embed code - Fix Appearance settings page permission check - Update test files for new feature naming - Add notes field to User model 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
Calendar,
|
||||
Clock,
|
||||
Users,
|
||||
Code2,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
SettingsSidebarSection,
|
||||
@@ -40,7 +41,7 @@ interface ParentContext {
|
||||
|
||||
// Map settings pages to their required plan features
|
||||
const SETTINGS_PAGE_FEATURES: Record<string, FeatureKey> = {
|
||||
'/dashboard/settings/branding': 'white_label',
|
||||
'/dashboard/settings/branding': 'remove_branding',
|
||||
'/dashboard/settings/custom-domains': 'custom_domain',
|
||||
'/dashboard/settings/api': 'api_access',
|
||||
'/dashboard/settings/authentication': 'custom_oauth',
|
||||
@@ -125,7 +126,7 @@ const SettingsLayout: React.FC = () => {
|
||||
icon={Palette}
|
||||
label={t('settings.appearance.title', 'Appearance')}
|
||||
description={t('settings.appearance.description', 'Logo, colors, theme')}
|
||||
locked={isLocked('white_label')}
|
||||
locked={isLocked('remove_branding')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/dashboard/settings/email-templates"
|
||||
@@ -140,6 +141,12 @@ const SettingsLayout: React.FC = () => {
|
||||
description={t('settings.customDomains.description', 'Use your own domain')}
|
||||
locked={isLocked('custom_domain')}
|
||||
/>
|
||||
<SettingsSidebarItem
|
||||
to="/dashboard/settings/embed-widget"
|
||||
icon={Code2}
|
||||
label={t('settings.embedWidget.title', 'Embed Widget')}
|
||||
description={t('settings.embedWidget.sidebarDescription', 'Add booking to your site')}
|
||||
/>
|
||||
</SettingsSidebarSection>
|
||||
|
||||
{/* Integrations Section */}
|
||||
|
||||
@@ -372,7 +372,7 @@ describe('SettingsLayout', () => {
|
||||
// Reset mock for locked feature tests
|
||||
mockCanUse.mockImplementation((feature: string) => {
|
||||
// Lock specific features
|
||||
if (feature === 'white_label') return false;
|
||||
if (feature === 'remove_branding') return false;
|
||||
if (feature === 'custom_domain') return false;
|
||||
if (feature === 'api_access') return false;
|
||||
if (feature === 'custom_oauth') return false;
|
||||
@@ -381,7 +381,7 @@ describe('SettingsLayout', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shows lock icon for Appearance link when white_label is locked', () => {
|
||||
it('shows lock icon for Appearance link when remove_branding is locked', () => {
|
||||
renderWithRouter();
|
||||
const appearanceLink = screen.getByRole('link', { name: /Appearance/i });
|
||||
const lockIcons = within(appearanceLink).queryAllByTestId('lock-icon');
|
||||
@@ -461,7 +461,7 @@ describe('SettingsLayout', () => {
|
||||
|
||||
it('passes isFeatureLocked to child routes when feature is locked', () => {
|
||||
mockCanUse.mockImplementation((feature: string) => {
|
||||
return feature !== 'white_label';
|
||||
return feature !== 'remove_branding';
|
||||
});
|
||||
|
||||
const ChildComponent = () => {
|
||||
@@ -485,7 +485,7 @@ describe('SettingsLayout', () => {
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('is-locked')).toHaveTextContent('true');
|
||||
expect(screen.getByTestId('locked-feature')).toHaveTextContent('white_label');
|
||||
expect(screen.getByTestId('locked-feature')).toHaveTextContent('remove_branding');
|
||||
});
|
||||
|
||||
it('passes isFeatureLocked as false when feature is unlocked', () => {
|
||||
|
||||
Reference in New Issue
Block a user