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:
122
frontend/src/utils/colorUtils.ts
Normal file
122
frontend/src/utils/colorUtils.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Color utility functions for generating brand color palettes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert hex color to HSL values
|
||||
*/
|
||||
export function hexToHSL(hex: string): { h: number; s: number; l: number } {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (!result) return { h: 0, s: 0, l: 0 };
|
||||
|
||||
const r = parseInt(result[1], 16) / 255;
|
||||
const g = parseInt(result[2], 16) / 255;
|
||||
const b = parseInt(result[3], 16) / 255;
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
const l = (max + min) / 2;
|
||||
|
||||
if (max !== min) {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
|
||||
switch (max) {
|
||||
case r:
|
||||
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
||||
break;
|
||||
case g:
|
||||
h = ((b - r) / d + 2) / 6;
|
||||
break;
|
||||
case b:
|
||||
h = ((r - g) / d + 4) / 6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { h: h * 360, s: s * 100, l: l * 100 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL values to hex color
|
||||
*/
|
||||
export function hslToHex(h: number, s: number, l: number): string {
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s;
|
||||
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
|
||||
const m = l - c / 2;
|
||||
|
||||
let r = 0, g = 0, b = 0;
|
||||
|
||||
if (h < 60) { r = c; g = x; b = 0; }
|
||||
else if (h < 120) { r = x; g = c; b = 0; }
|
||||
else if (h < 180) { r = 0; g = c; b = x; }
|
||||
else if (h < 240) { r = 0; g = x; b = c; }
|
||||
else if (h < 300) { r = x; g = 0; b = c; }
|
||||
else { r = c; g = 0; b = x; }
|
||||
|
||||
const toHex = (n: number) => Math.round((n + m) * 255).toString(16).padStart(2, '0');
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a color palette from a base color
|
||||
*/
|
||||
export function generateColorPalette(baseColor: string): Record<string, string> {
|
||||
const { h, s } = hexToHSL(baseColor);
|
||||
|
||||
return {
|
||||
50: hslToHex(h, Math.min(s, 30), 97),
|
||||
100: hslToHex(h, Math.min(s, 40), 94),
|
||||
200: hslToHex(h, Math.min(s, 50), 86),
|
||||
300: hslToHex(h, Math.min(s, 60), 74),
|
||||
400: hslToHex(h, Math.min(s, 70), 60),
|
||||
500: hslToHex(h, s, 50),
|
||||
600: baseColor, // Use the exact primary color for 600
|
||||
700: hslToHex(h, s, 40),
|
||||
800: hslToHex(h, s, 32),
|
||||
900: hslToHex(h, s, 24),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a color palette to CSS custom properties
|
||||
*/
|
||||
export function applyColorPalette(palette: Record<string, string>): void {
|
||||
const root = document.documentElement;
|
||||
Object.entries(palette).forEach(([shade, color]) => {
|
||||
root.style.setProperty(`--color-brand-${shade}`, color);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply primary and secondary colors including the secondary color variable
|
||||
*/
|
||||
export function applyBrandColors(primaryColor: string, secondaryColor?: string): void {
|
||||
const palette = generateColorPalette(primaryColor);
|
||||
applyColorPalette(palette);
|
||||
|
||||
// Set the secondary color variable (used for gradients)
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--color-brand-secondary', secondaryColor || primaryColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default brand color palette (blue)
|
||||
*/
|
||||
export const defaultColorPalette: Record<string, string> = {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
};
|
||||
Reference in New Issue
Block a user