This commit is contained in:
Ashwin 2025-12-28 17:22:29 +05:30
parent 60917e85c0
commit 26fe2eb52e
34 changed files with 2170 additions and 1544 deletions

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,9 @@
@custom-variant dark (&:is(.dark *));
/* ═══════════════════════════════════════════════════════════════════════════
OBSIDIAN LUXE - Premium Dark-First CRM Design System
A sophisticated aesthetic with vivid accents and glass-morphism effects
BOTTLECRM DESIGN SYSTEM - Orange Primary
A modern CRM design system with warm orange accents and professional styling
Based on DESIGN_SYSTEM.md v1.2
═══════════════════════════════════════════════════════════════════════════ */
:root {
@ -14,121 +15,249 @@
--radius: 0.625rem;
/* ─────────────────────────────────────────────────────────────────────────
LIGHT MODE - Clean, airy with subtle warmth
BRAND COLORS
───────────────────────────────────────────────────────────────────────── */
--color-brand-primary: #EA580C;
--color-brand-dark: #404346;
--color-brand-white: #FFFFFF;
/* ─────────────────────────────────────────────────────────────────────────
LIGHT MODE - Clean, professional with warm orange accents
───────────────────────────────────────────────────────────────────────── */
/* Core surfaces */
--background: oklch(0.985 0.002 250);
--background: oklch(0.985 0.002 60);
--foreground: oklch(0.15 0.02 260);
--card: oklch(1 0 0);
--card-foreground: oklch(0.15 0.02 260);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.15 0.02 260);
/* Primary - Deep teal */
--primary: oklch(0.55 0.15 195);
/* Primary - Orange */
--primary: oklch(0.58 0.2 30);
--primary-foreground: oklch(0.98 0 0);
/* Secondary */
--secondary: oklch(0.96 0.01 250);
--secondary: oklch(0.96 0.01 60);
--secondary-foreground: oklch(0.25 0.02 260);
/* Muted */
--muted: oklch(0.96 0.005 250);
--muted: oklch(0.96 0.005 60);
--muted-foreground: oklch(0.45 0.02 260);
/* Accent - Warm amber */
--accent: oklch(0.96 0.02 80);
--accent-foreground: oklch(0.25 0.05 80);
/* Accent - Light orange tint */
--accent: oklch(0.98 0.02 60);
--accent-foreground: oklch(0.25 0.05 30);
/* Destructive */
--destructive: oklch(0.55 0.22 25);
/* Borders & inputs */
--border: oklch(0.91 0.005 250);
--input: oklch(0.91 0.005 250);
--ring: oklch(0.55 0.15 195);
--border: oklch(0.91 0.005 60);
--input: oklch(0.91 0.005 60);
--ring: oklch(0.58 0.2 30);
/* Chart colors - Vibrant palette */
--chart-1: oklch(0.65 0.18 195);
--chart-2: oklch(0.7 0.16 145);
--chart-3: oklch(0.75 0.15 80);
--chart-4: oklch(0.6 0.2 280);
--chart-5: oklch(0.65 0.2 350);
/* Chart colors - Sequential (Orange-based) */
--chart-1: #EA580C;
--chart-2: #F97316;
--chart-3: #FB923C;
--chart-4: #FDBA74;
--chart-5: #FED7AA;
/* Sidebar - HubSpot Style Light */
/* Sidebar */
--sidebar: oklch(0.995 0 0);
--sidebar-foreground: oklch(0.25 0.02 250);
--sidebar-primary: oklch(0.62 0.2 25);
--sidebar-foreground: oklch(0.25 0.02 60);
--sidebar-primary: #EA580C;
--sidebar-primary-foreground: oklch(0.995 0 0);
--sidebar-accent: oklch(0.97 0.005 250);
--sidebar-accent-foreground: oklch(0.2 0.02 250);
--sidebar-border: oklch(0.94 0.005 250);
--sidebar-ring: oklch(0.62 0.2 25);
/* HubSpot Brand Colors */
--hubspot-orange: #ff7a59;
--hubspot-orange-hover: #ff5c35;
--hubspot-orange-light: #fff1ee;
--hubspot-navy: #2d3e50;
--hubspot-navy-light: #516f90;
--hubspot-teal: #00bda5;
--hubspot-teal-light: #e5f8f6;
--sidebar-accent: oklch(0.97 0.01 60);
--sidebar-accent-foreground: oklch(0.2 0.02 60);
--sidebar-border: oklch(0.94 0.005 60);
--sidebar-ring: #EA580C;
/* ─────────────────────────────────────────────────────────────────────────
SEMANTIC LAYERS
PRIMARY COLOR SCALE
───────────────────────────────────────────────────────────────────────── */
/* Background Layers */
--bg-base: #fafbfc;
--bg-subtle: #f4f6f8;
--bg-muted: #eef1f4;
--bg-emphasis: #e4e8ec;
/* Border Colors */
--border-default: #e2e6eb;
--border-strong: #c9d0d8;
--border-subtle: #f0f2f5;
/* Text Colors */
--text-primary: #0f172a;
--text-secondary: #475569;
--text-tertiary: #94a3b8;
--text-inverse: #ffffff;
/* Accent Colors - Teal/Cyan primary */
--accent-primary: #0891b2;
--accent-primary-hover: #0e7490;
--accent-primary-subtle: #ecfeff;
/* Secondary Accent - Warm Amber */
--accent-secondary: #f59e0b;
--accent-secondary-hover: #d97706;
--accent-secondary-subtle: #fffbeb;
/* Status Colors */
--status-success: #059669;
--status-success-bg: #ecfdf5;
--status-warning: #d97706;
--status-warning-bg: #fffbeb;
--status-danger: #dc2626;
--status-danger-bg: #fef2f2;
--status-info: #0891b2;
--status-info-bg: #ecfeff;
/* Stage Colors (Pipeline) - Refined palette */
--stage-prospecting: #64748b;
--stage-qualified: #0891b2;
--stage-proposal: #8b5cf6;
--stage-negotiation: #f59e0b;
--stage-won: #059669;
--stage-lost: #dc2626;
--color-primary-default: #EA580C;
--color-primary-hover: #C2410C;
--color-primary-active: #9A3412;
--color-primary-light: #FFF7ED;
/* ─────────────────────────────────────────────────────────────────────────
SPACING & LAYOUT
SEMANTIC COLORS
───────────────────────────────────────────────────────────────────────── */
/* Negative/Error */
--color-negative-default: #DC3545;
--color-negative-hover: #C82333;
--color-negative-light: #FDEAEA;
/* Warning */
--color-warning-default: #FFC107;
--color-warning-dark: #E0A800;
--color-warning-light: #FFF8E1;
/* Success */
--color-success-default: #28A745;
--color-success-light: #E8F5E9;
/* Info */
--color-info-default: #17A2B8;
--color-info-light: #E3F2FD;
/* ─────────────────────────────────────────────────────────────────────────
GREY SCALE
───────────────────────────────────────────────────────────────────────── */
--color-grey-50: #FAFAFA;
--color-grey-100: #F5F5F5;
--color-grey-200: #EEEEEE;
--color-grey-300: #E0E0E0;
--color-grey-400: #BDBDBD;
--color-grey-500: #9E9E9E;
--color-grey-600: #757575;
--color-grey-700: #616161;
--color-grey-800: #424242;
--color-grey-900: #212121;
/* ─────────────────────────────────────────────────────────────────────────
SURFACE COLORS
───────────────────────────────────────────────────────────────────────── */
--surface-default: #FFFFFF;
--surface-raised: #FFFFFF;
--surface-sunken: #F5F5F5;
--surface-overlay: #FFFFFF;
/* ─────────────────────────────────────────────────────────────────────────
TEXT COLORS
───────────────────────────────────────────────────────────────────────── */
--text-primary: #212121;
--text-secondary: #616161;
--text-tertiary: #9E9E9E;
--text-disabled: #BDBDBD;
--text-inverse: #FFFFFF;
--text-link: #EA580C;
--text-error: #DC3545;
/* ─────────────────────────────────────────────────────────────────────────
BORDER COLORS
───────────────────────────────────────────────────────────────────────── */
--border-default: #E0E0E0;
--border-strong: #BDBDBD;
--border-focus: #EA580C;
--border-error: #DC3545;
/* ─────────────────────────────────────────────────────────────────────────
PIPELINE STAGE COLORS
───────────────────────────────────────────────────────────────────────── */
--stage-new: #EA580C;
--stage-contacted: #F97316;
--stage-qualified: #3B82F6;
--stage-proposal: #8B5CF6;
--stage-negotiation: #F59E0B;
--stage-won: #22C55E;
--stage-lost: #EF4444;
/* Pipeline Stage Backgrounds */
--stage-new-bg: #FFF7ED;
--stage-contacted-bg: #FFEDD5;
--stage-qualified-bg: #EFF6FF;
--stage-proposal-bg: #F5F3FF;
--stage-negotiation-bg: #FFFBEB;
--stage-won-bg: #F0FDF4;
--stage-lost-bg: #FEF2F2;
/* ─────────────────────────────────────────────────────────────────────────
ACTIVITY TYPE COLORS
───────────────────────────────────────────────────────────────────────── */
--activity-call: #22C55E;
--activity-email: #3B82F6;
--activity-meeting: #8B5CF6;
--activity-task: #F59E0B;
--activity-note: #6B7280;
--activity-deadline: #EF4444;
/* ─────────────────────────────────────────────────────────────────────────
LEAD TEMPERATURE
───────────────────────────────────────────────────────────────────────── */
--lead-hot: #EF4444;
--lead-warm: #F59E0B;
--lead-cold: #6B7280;
/* Lead Temperature Backgrounds */
--lead-hot-bg: #FEF2F2;
--lead-warm-bg: #FFFBEB;
--lead-cold-bg: #F3F4F6;
/* ─────────────────────────────────────────────────────────────────────────
PRIORITY LEVELS
───────────────────────────────────────────────────────────────────────── */
--priority-urgent: #DC2626;
--priority-high: #F97316;
--priority-medium: #FBBF24;
--priority-low: #22C55E;
--priority-none: #9CA3AF;
/* ─────────────────────────────────────────────────────────────────────────
DEAL PROBABILITY
───────────────────────────────────────────────────────────────────────── */
--probability-high: #22C55E;
--probability-mid: #F59E0B;
--probability-low: #EF4444;
/* ─────────────────────────────────────────────────────────────────────────
TASK STATUS
───────────────────────────────────────────────────────────────────────── */
--task-overdue: #EF4444;
--task-due-today: #F59E0B;
--task-upcoming: #3B82F6;
--task-completed: #22C55E;
--task-cancelled: #9CA3AF;
/* Task Status Backgrounds */
--task-overdue-bg: #FEF2F2;
--task-due-today-bg: #FFFBEB;
--task-upcoming-bg: #EFF6FF;
--task-completed-bg: #F0FDF4;
--task-cancelled-bg: #F3F4F6;
/* ─────────────────────────────────────────────────────────────────────────
CHARTS - CATEGORICAL
───────────────────────────────────────────────────────────────────────── */
--cat-1: #EA580C;
--cat-2: #3B82F6;
--cat-3: #22C55E;
--cat-4: #8B5CF6;
--cat-5: #EC4899;
--cat-6: #14B8A6;
/* ─────────────────────────────────────────────────────────────────────────
PROGRESS BAR
───────────────────────────────────────────────────────────────────────── */
--progress-default: #EA580C;
--progress-success: #22C55E;
--progress-warning: #F59E0B;
--progress-danger: #EF4444;
--progress-bg: #E5E7EB;
/* ─────────────────────────────────────────────────────────────────────────
SPACING (8px Grid System)
───────────────────────────────────────────────────────────────────────── */
--space-0: 0px;
--space-25: 2px;
--space-50: 4px;
--space-100: 8px;
--space-150: 12px;
--space-200: 16px;
--space-250: 20px;
--space-300: 24px;
--space-400: 32px;
--space-500: 40px;
--space-600: 48px;
--space-700: 56px;
--space-800: 64px;
--space-900: 72px;
--space-1000: 80px;
/* Legacy spacing aliases */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
@ -139,39 +268,80 @@
--space-10: 40px;
--space-12: 48px;
/* Border Radius - Slightly larger for premium feel */
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: 20px;
/* ─────────────────────────────────────────────────────────────────────────
BORDER RADIUS
───────────────────────────────────────────────────────────────────────── */
--radius-none: 0px;
--radius-xs: 2px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-full: 9999px;
/* Z-Index Layers */
/* ─────────────────────────────────────────────────────────────────────────
SHADOWS
───────────────────────────────────────────────────────────────────────── */
--shadow-none: none;
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.08), 0 4px 6px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.10), 0 10px 10px rgba(0, 0, 0, 0.04);
/* Focus Ring */
--focus-ring: 0 0 0 2px var(--surface-default), 0 0 0 4px var(--color-primary-default);
/* ─────────────────────────────────────────────────────────────────────────
ICON SIZES
───────────────────────────────────────────────────────────────────────── */
--icon-xs: 12px;
--icon-sm: 16px;
--icon-md: 20px;
--icon-lg: 24px;
--icon-xl: 32px;
--icon-2xl: 48px;
/* ─────────────────────────────────────────────────────────────────────────
Z-INDEX LAYERS
───────────────────────────────────────────────────────────────────────── */
--z-base: 0;
--z-raised: 10;
--z-dropdown: 100;
--z-sticky: 200;
--z-modal: 300;
--z-overlay: 400;
--z-toast: 500;
--z-sidebar: 10;
--z-drawer-backdrop: 40;
--z-drawer: 50;
--z-dropdown: 60;
--z-modal: 70;
--z-toast: 80;
--z-tooltip: 90;
/* Animation Timing - Refined easing */
/* ─────────────────────────────────────────────────────────────────────────
TRANSITIONS
───────────────────────────────────────────────────────────────────────── */
--transition-fast: 100ms ease;
--transition-normal: 150ms ease;
--transition-slow: 300ms ease;
/* Animation timing functions */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);
/* Animation Durations */
/* Animation durations */
--duration-instant: 0ms;
--duration-fast: 120ms;
--duration-normal: 200ms;
--duration-slow: 350ms;
--duration-slower: 500ms;
/* Layout Dimensions */
/* ─────────────────────────────────────────────────────────────────────────
LAYOUT DIMENSIONS
───────────────────────────────────────────────────────────────────────── */
--sidebar-width: 260px;
--sidebar-width-collapsed: 72px;
--drawer-width: 520px;
@ -179,116 +349,213 @@
--content-padding: 28px;
--content-max-width: 1400px;
/* Typography */
--font-sans: 'Plus Jakarta Sans', 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-body: 'DM Sans', 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* Container Widths */
--container-sm: 640px;
--container-md: 768px;
--container-lg: 1024px;
--container-xl: 1280px;
/* Breakpoints */
--breakpoint-xs: 0px;
--breakpoint-sm: 480px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1440px;
/* ─────────────────────────────────────────────────────────────────────────
TYPOGRAPHY
───────────────────────────────────────────────────────────────────────── */
--font-family-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-family-mono: "Geist Mono", "SF Mono", "Fira Code", Consolas, monospace;
--font-sans: var(--font-family-sans);
--font-body: var(--font-family-sans);
--font-mono: var(--font-family-mono);
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Glass morphism properties */
--glass-bg: rgba(255, 255, 255, 0.8);
--glass-border: rgba(255, 255, 255, 0.3);
--glass-blur: 12px;
/* Legacy semantic aliases */
--bg-base: #FAFAFA;
--bg-subtle: #F5F5F5;
--bg-muted: #EEEEEE;
--bg-emphasis: #E0E0E0;
--accent-primary: #EA580C;
--accent-primary-hover: #C2410C;
--accent-primary-subtle: #FFF7ED;
--accent-secondary: #F59E0B;
--accent-secondary-hover: #D97706;
--accent-secondary-subtle: #FFFBEB;
--status-success: #28A745;
--status-success-bg: #E8F5E9;
--status-warning: #FFC107;
--status-warning-bg: #FFF8E1;
--status-danger: #DC3545;
--status-danger-bg: #FDEAEA;
--status-info: #17A2B8;
--status-info-bg: #E3F2FD;
}
/* ═══════════════════════════════════════════════════════════════════════════
DARK MODE - Obsidian Luxe
Deep, rich darks with vivid accent colors and subtle glow effects
DARK MODE
Deep, rich darks with vivid orange accent and subtle glow effects
═══════════════════════════════════════════════════════════════════════════ */
.dark {
/* Core surfaces - Deep obsidian with subtle blue undertone */
--background: oklch(0.12 0.015 260);
--foreground: oklch(0.95 0.005 250);
--card: oklch(0.16 0.015 260);
--card-foreground: oklch(0.95 0.005 250);
--popover: oklch(0.18 0.018 260);
--popover-foreground: oklch(0.95 0.005 250);
/* Core surfaces - Deep with subtle warmth */
--background: oklch(0.12 0.01 30);
--foreground: oklch(0.95 0.005 60);
--card: oklch(0.16 0.01 30);
--card-foreground: oklch(0.95 0.005 60);
--popover: oklch(0.18 0.012 30);
--popover-foreground: oklch(0.95 0.005 60);
/* Primary - Vivid cyan */
--primary: oklch(0.75 0.16 195);
--primary-foreground: oklch(0.12 0.015 260);
/* Primary - Vivid orange */
--primary: oklch(0.72 0.18 35);
--primary-foreground: oklch(0.12 0.01 30);
/* Secondary */
--secondary: oklch(0.22 0.015 260);
--secondary-foreground: oklch(0.92 0.005 250);
--secondary: oklch(0.22 0.01 30);
--secondary-foreground: oklch(0.92 0.005 60);
/* Muted */
--muted: oklch(0.22 0.012 260);
--muted-foreground: oklch(0.65 0.01 260);
--muted: oklch(0.22 0.008 30);
--muted-foreground: oklch(0.65 0.01 60);
/* Accent - Amber glow */
--accent: oklch(0.25 0.04 80);
--accent-foreground: oklch(0.85 0.12 80);
/* Accent - Warm orange glow */
--accent: oklch(0.25 0.04 35);
--accent-foreground: oklch(0.85 0.12 35);
/* Destructive */
--destructive: oklch(0.65 0.2 25);
/* Borders & inputs - Subtle with glow potential */
--border: oklch(0.28 0.015 260);
--input: oklch(0.24 0.015 260);
--ring: oklch(0.7 0.14 195);
/* Borders & inputs */
--border: oklch(0.28 0.01 30);
--input: oklch(0.24 0.01 30);
--ring: oklch(0.7 0.16 35);
/* Chart colors - Vibrant neon-ish */
--chart-1: oklch(0.75 0.18 195);
--chart-2: oklch(0.72 0.18 155);
--chart-3: oklch(0.78 0.15 85);
--chart-4: oklch(0.7 0.2 290);
--chart-5: oklch(0.72 0.2 10);
/* Chart colors - Vibrant */
--chart-1: #FB923C;
--chart-2: #FDBA74;
--chart-3: #FED7AA;
--chart-4: #FFEDD5;
--chart-5: #FFF7ED;
/* Sidebar - HubSpot Style Dark */
--sidebar: oklch(0.16 0.015 250);
--sidebar-foreground: oklch(0.92 0.005 250);
--sidebar-primary: oklch(0.7 0.18 25);
--sidebar-primary-foreground: oklch(0.12 0.015 260);
--sidebar-accent: oklch(0.22 0.02 250);
--sidebar-accent-foreground: oklch(0.92 0.005 250);
--sidebar-border: oklch(0.28 0.015 250);
--sidebar-ring: oklch(0.7 0.18 25);
/* Sidebar */
--sidebar: oklch(0.16 0.01 30);
--sidebar-foreground: oklch(0.92 0.005 60);
--sidebar-primary: #FB923C;
--sidebar-primary-foreground: oklch(0.12 0.01 30);
--sidebar-accent: oklch(0.22 0.015 30);
--sidebar-accent-foreground: oklch(0.92 0.005 60);
--sidebar-border: oklch(0.28 0.01 30);
--sidebar-ring: #FB923C;
/* Background Layers - Obsidian depth */
/* Primary color scale - Dark mode */
--color-primary-default: #FB923C;
--color-primary-hover: #FDBA74;
--color-primary-active: #F97316;
--color-primary-light: #431407;
/* Surface Colors - Dark mode */
--surface-default: #1E1E1E;
--surface-raised: #252525;
--surface-sunken: #141414;
--surface-overlay: #2D2D2D;
/* Text Colors - Dark mode */
--text-primary: #FFFFFF;
--text-secondary: #B0B0B0;
--text-tertiary: #757575;
--text-disabled: #525252;
--text-inverse: #212121;
--text-link: #FB923C;
--text-error: #F87171;
/* Border Colors - Dark mode */
--border-default: #3D3D3D;
--border-strong: #525252;
--border-focus: #FB923C;
--border-error: #F87171;
/* Background Layers - Dark */
--bg-base: #0c0d12;
--bg-subtle: #121318;
--bg-muted: #181a21;
--bg-emphasis: #1f222b;
/* Border Colors */
--border-default: #262a36;
--border-strong: #363b4a;
--border-subtle: #1c1f28;
/* Text Colors */
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-tertiary: #64748b;
--text-inverse: #0c0d12;
/* Accent Colors - Cyan glow */
--accent-primary: #22d3ee;
--accent-primary-hover: #06b6d4;
--accent-primary-subtle: rgba(34, 211, 238, 0.1);
/* Secondary Accent - Warm Amber */
--accent-secondary: #fbbf24;
--accent-secondary-hover: #f59e0b;
/* Accent Colors - Orange glow */
--accent-primary: #FB923C;
--accent-primary-hover: #FDBA74;
--accent-primary-subtle: rgba(251, 146, 60, 0.1);
--accent-secondary: #FBBF24;
--accent-secondary-hover: #F59E0B;
--accent-secondary-subtle: rgba(251, 191, 36, 0.1);
/* Status Colors - Vivid in dark */
--status-success: #34d399;
--status-success: #34D399;
--status-success-bg: rgba(52, 211, 153, 0.12);
--status-warning: #fbbf24;
--status-warning: #FBBF24;
--status-warning-bg: rgba(251, 191, 36, 0.12);
--status-danger: #f87171;
--status-danger: #F87171;
--status-danger-bg: rgba(248, 113, 113, 0.12);
--status-info: #22d3ee;
--status-info-bg: rgba(34, 211, 238, 0.12);
--status-info: #38BDF8;
--status-info-bg: rgba(56, 189, 248, 0.12);
/* Stage Colors - Neon variants */
--stage-prospecting: #94a3b8;
--stage-qualified: #22d3ee;
--stage-proposal: #a78bfa;
--stage-negotiation: #fbbf24;
--stage-won: #34d399;
--stage-lost: #f87171;
/* Semantic colors dark mode */
--color-negative-default: #F87171;
--color-negative-hover: #FCA5A5;
--color-negative-light: rgba(248, 113, 113, 0.15);
--color-warning-default: #FBBF24;
--color-warning-dark: #FCD34D;
--color-warning-light: rgba(251, 191, 36, 0.15);
--color-success-default: #34D399;
--color-success-light: rgba(52, 211, 153, 0.15);
--color-info-default: #38BDF8;
--color-info-light: rgba(56, 189, 248, 0.15);
/* Grey Scale - Dark mode */
--color-grey-50: #18181B;
--color-grey-100: #27272A;
--color-grey-200: #3F3F46;
--color-grey-300: #52525B;
--color-grey-400: #71717A;
--color-grey-500: #A1A1AA;
--color-grey-600: #D4D4D8;
--color-grey-700: #E4E4E7;
--color-grey-800: #F4F4F5;
--color-grey-900: #FAFAFA;
/* Stage Colors - Lighter variants for dark mode */
--stage-new-bg: rgba(234, 88, 12, 0.15);
--stage-contacted-bg: rgba(249, 115, 22, 0.15);
--stage-qualified-bg: rgba(59, 130, 246, 0.15);
--stage-proposal-bg: rgba(139, 92, 246, 0.15);
--stage-negotiation-bg: rgba(245, 158, 11, 0.15);
--stage-won-bg: rgba(34, 197, 94, 0.15);
--stage-lost-bg: rgba(239, 68, 68, 0.15);
/* Lead Temperature Backgrounds - Dark mode */
--lead-hot-bg: rgba(239, 68, 68, 0.15);
--lead-warm-bg: rgba(245, 158, 11, 0.15);
--lead-cold-bg: rgba(107, 114, 128, 0.15);
/* Task Status Backgrounds - Dark mode */
--task-overdue-bg: rgba(239, 68, 68, 0.15);
--task-due-today-bg: rgba(245, 158, 11, 0.15);
--task-upcoming-bg: rgba(59, 130, 246, 0.15);
--task-completed-bg: rgba(34, 197, 94, 0.15);
--task-cancelled-bg: rgba(156, 163, 175, 0.15);
/* Progress - Dark mode */
--progress-bg: #3F3F46;
/* Glass morphism - Dark variant */
--glass-bg: rgba(18, 19, 24, 0.8);
@ -301,10 +568,10 @@
═══════════════════════════════════════════════════════════════════════════ */
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-sm: var(--radius-sm);
--radius-md: var(--radius-md);
--radius-lg: var(--radius-lg);
--radius-xl: var(--radius-xl);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@ -382,7 +649,7 @@
background-clip: content-box;
}
/* Selection styling */
/* Selection styling - Orange */
::selection {
background: var(--accent-primary);
color: var(--text-inverse);
@ -402,7 +669,7 @@
border: 1px solid var(--glass-border);
}
/* Glow effects */
/* Glow effects - Orange accent */
.glow-sm {
box-shadow: 0 0 15px -3px var(--accent-primary);
}
@ -442,7 +709,7 @@
opacity: 0.04;
}
/* Gradient mesh background */
/* Gradient mesh background - Orange themed */
.gradient-mesh {
background:
radial-gradient(at 40% 20%, var(--accent-primary-subtle) 0px, transparent 50%),
@ -555,6 +822,41 @@
border-color: var(--accent-primary);
box-shadow: 0 0 30px -10px var(--accent-primary);
}
/* Pipeline stage badge utilities */
.badge-stage-new { background: var(--stage-new-bg); color: var(--stage-new); }
.badge-stage-contacted { background: var(--stage-contacted-bg); color: var(--stage-contacted); }
.badge-stage-qualified { background: var(--stage-qualified-bg); color: var(--stage-qualified); }
.badge-stage-proposal { background: var(--stage-proposal-bg); color: var(--stage-proposal); }
.badge-stage-negotiation { background: var(--stage-negotiation-bg); color: var(--stage-negotiation); }
.badge-stage-won { background: var(--stage-won-bg); color: var(--stage-won); }
.badge-stage-lost { background: var(--stage-lost-bg); color: var(--stage-lost); }
/* Lead temperature badge utilities */
.badge-lead-hot { background: var(--lead-hot-bg); color: var(--lead-hot); }
.badge-lead-warm { background: var(--lead-warm-bg); color: var(--lead-warm); }
.badge-lead-cold { background: var(--lead-cold-bg); color: var(--lead-cold); }
/* Priority badge utilities */
.badge-priority-urgent { background: rgba(220, 38, 38, 0.1); color: var(--priority-urgent); }
.badge-priority-high { background: rgba(249, 115, 22, 0.1); color: var(--priority-high); }
.badge-priority-medium { background: rgba(251, 191, 36, 0.1); color: var(--priority-medium); }
.badge-priority-low { background: rgba(34, 197, 94, 0.1); color: var(--priority-low); }
/* Task status badge utilities */
.badge-task-overdue { background: var(--task-overdue-bg); color: var(--task-overdue); }
.badge-task-due-today { background: var(--task-due-today-bg); color: var(--task-due-today); }
.badge-task-upcoming { background: var(--task-upcoming-bg); color: var(--task-upcoming); }
.badge-task-completed { background: var(--task-completed-bg); color: var(--task-completed); }
.badge-task-cancelled { background: var(--task-cancelled-bg); color: var(--task-cancelled); }
/* Activity type badge utilities */
.badge-activity-call { background: rgba(34, 197, 94, 0.1); color: var(--activity-call); }
.badge-activity-email { background: rgba(59, 130, 246, 0.1); color: var(--activity-email); }
.badge-activity-meeting { background: rgba(139, 92, 246, 0.1); color: var(--activity-meeting); }
.badge-activity-task { background: rgba(245, 158, 11, 0.1); color: var(--activity-task); }
.badge-activity-note { background: rgba(107, 114, 128, 0.1); color: var(--activity-note); }
.badge-activity-deadline { background: rgba(239, 68, 68, 0.1); color: var(--activity-deadline); }
}
/* ═══════════════════════════════════════════════════════════════════════════
@ -585,7 +887,7 @@ input, textarea, select {
}
/* ═══════════════════════════════════════════════════════════════════════════
HUBSPOT-STYLE SIDEBAR ENHANCEMENTS
SIDEBAR ENHANCEMENTS
═══════════════════════════════════════════════════════════════════════════ */
/* Sidebar base styling */
@ -593,12 +895,12 @@ input, textarea, select {
transition: width 0.2s ease, transform 0.2s ease;
}
/* HubSpot-style menu button refinements */
/* Menu button refinements */
[data-slot="sidebar-menu-button"] {
letter-spacing: -0.01em;
}
/* HubSpot-style active state with left accent bar */
/* Active state with left accent bar */
[data-slot="sidebar-menu-button"][data-active="true"] {
position: relative;
}
@ -612,7 +914,7 @@ input, textarea, select {
transform: none;
}
/* Group label styling - HubSpot uppercase sections */
/* Group label styling - uppercase sections */
[data-slot="sidebar-group-label"] {
font-size: 10px;
font-weight: 700;
@ -629,9 +931,9 @@ input, textarea, select {
margin-top: 4px;
}
/* Rail hover effect */
/* Rail hover effect - Orange accent */
[data-slot="sidebar-rail"]:hover {
background: linear-gradient(90deg, transparent, rgba(255, 122, 89, 0.1));
background: linear-gradient(90deg, transparent, rgba(234, 88, 12, 0.1));
}
/* Collapsed state icon centering */
@ -643,3 +945,35 @@ input, textarea, select {
[data-slot="sidebar-footer"] {
background: linear-gradient(180deg, transparent, var(--sidebar) 8px);
}
/* ═══════════════════════════════════════════════════════════════════════════
KANBAN BOARD STYLES
═══════════════════════════════════════════════════════════════════════════ */
/* Kanban column styles using pipeline colors */
.kanban-column-new { border-top: 3px solid var(--stage-new); background: var(--stage-new-bg); }
.kanban-column-contacted { border-top: 3px solid var(--stage-contacted); background: var(--stage-contacted-bg); }
.kanban-column-qualified { border-top: 3px solid var(--stage-qualified); background: var(--stage-qualified-bg); }
.kanban-column-proposal { border-top: 3px solid var(--stage-proposal); background: var(--stage-proposal-bg); }
.kanban-column-negotiation { border-top: 3px solid var(--stage-negotiation); background: var(--stage-negotiation-bg); }
.kanban-column-won { border-top: 3px solid var(--stage-won); background: var(--stage-won-bg); }
.kanban-column-lost { border-top: 3px solid var(--stage-lost); background: var(--stage-lost-bg); }
/* ═══════════════════════════════════════════════════════════════════════════
DATA TABLE STYLES
═══════════════════════════════════════════════════════════════════════════ */
/* Table row hover - subtle orange tint */
.data-table-row:hover {
background: var(--color-primary-light);
}
.dark .data-table-row:hover {
background: rgba(251, 146, 60, 0.05);
}
/* Selected row */
.data-table-row-selected {
background: var(--color-primary-light);
border-left: 3px solid var(--color-primary-default);
}

View file

@ -1,11 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<!-- Google Fonts - Plus Jakarta Sans, DM Sans, JetBrains Mono -->
<!-- Google Fonts - Geist (via Vercel CDN) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400;1,500&family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500;600&display=swap"
href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap"
rel="stylesheet"
/>
<!-- Google tag (gtag.js) -->

View file

@ -35,43 +35,44 @@
let showAll = $state(false);
// Using design system activity tokens
const actionConfig = /** @type {Record<string, { icon: typeof Activity, bg: string, text: string }>} */ ({
CREATE: {
icon: Plus,
bg: 'bg-emerald-500/10 dark:bg-emerald-500/20',
text: 'text-emerald-600 dark:text-emerald-400'
bg: 'bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/15',
text: 'text-[var(--color-success-default)]'
},
UPDATE: {
icon: Pencil,
bg: 'bg-cyan-500/10 dark:bg-cyan-500/20',
text: 'text-cyan-600 dark:text-cyan-400'
bg: 'bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15',
text: 'text-[var(--color-primary-default)]'
},
DELETE: {
icon: Trash2,
bg: 'bg-rose-500/10 dark:bg-rose-500/20',
text: 'text-rose-600 dark:text-rose-400'
bg: 'bg-[var(--color-negative-light)] dark:bg-[var(--color-negative-default)]/15',
text: 'text-[var(--color-negative-default)]'
},
VIEW: {
icon: Eye,
bg: 'bg-slate-500/10 dark:bg-slate-500/20',
text: 'text-slate-600 dark:text-slate-400'
bg: 'bg-[var(--surface-sunken)]',
text: 'text-[var(--text-secondary)]'
},
COMMENT: {
icon: MessageSquare,
bg: 'bg-violet-500/10 dark:bg-violet-500/20',
text: 'text-violet-600 dark:text-violet-400'
bg: 'bg-[var(--activity-note)]/10 dark:bg-[var(--activity-note)]/15',
text: 'text-[var(--activity-note)]'
},
ASSIGN: {
icon: UserPlus,
bg: 'bg-amber-500/10 dark:bg-amber-500/20',
text: 'text-amber-600 dark:text-amber-400'
bg: 'bg-[var(--activity-meeting)]/10 dark:bg-[var(--activity-meeting)]/15',
text: 'text-[var(--activity-meeting)]'
}
});
const defaultConfig = {
icon: Activity,
bg: 'bg-muted',
text: 'text-muted-foreground'
bg: 'bg-[var(--surface-sunken)]',
text: 'text-[var(--text-tertiary)]'
};
/**
@ -130,14 +131,14 @@
const groupedActivities = $derived(groupByDate(displayedActivities));
</script>
<div class="overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
<!-- Header -->
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
<div class="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
<!-- Header - Orange accent -->
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
<div class="flex items-center gap-3">
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500/10 to-purple-500/10 dark:from-violet-500/20 dark:to-purple-500/20">
<Activity class="size-4 text-violet-600 dark:text-violet-400" />
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
<Activity class="size-4 text-[var(--color-primary-default)]" />
</div>
<h3 class="text-foreground text-sm font-semibold tracking-tight">Recent Activity</h3>
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">Recent Activity</h3>
</div>
<Button variant="ghost" size="sm" href="/activities" class="gap-1 text-xs font-medium">
View all
@ -149,17 +150,17 @@
<div class="p-5">
{#if activities.length === 0}
<div class="flex flex-col items-center justify-center py-10 text-center">
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
<Clock class="text-muted-foreground/50 size-6" />
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
<Clock class="size-6 text-[var(--text-tertiary)]" />
</div>
<p class="text-muted-foreground text-sm font-medium">No recent activity</p>
<p class="text-muted-foreground/70 text-xs">Actions will appear here</p>
<p class="text-[var(--text-secondary)] text-sm font-medium">No recent activity</p>
<p class="text-[var(--text-tertiary)] text-xs">Actions will appear here</p>
</div>
{:else}
<div class="space-y-5">
{#each groupedActivities as group}
<div>
<p class="text-muted-foreground mb-3 text-[10px] font-semibold uppercase tracking-widest">
<p class="text-[var(--text-tertiary)] mb-3 text-[10px] font-semibold uppercase tracking-widest">
{group.category}
</p>
<div class="space-y-2">
@ -167,25 +168,25 @@
{@const config = actionConfig[activity.action || ''] || defaultConfig}
{@const Icon = config.icon}
<div
class="group -mx-2 flex items-start gap-3 rounded-lg px-2 py-2 transition-all duration-200 hover:bg-muted/30"
class="group -mx-2 flex items-start gap-3 rounded-[var(--radius-md)] px-2 py-2 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
>
<!-- Icon -->
<div
class="flex size-7 shrink-0 items-center justify-center rounded-lg transition-transform duration-200 group-hover:scale-105 {config.bg}"
class="flex size-7 shrink-0 items-center justify-center rounded-[var(--radius-md)] transition-transform duration-200 group-hover:scale-105 {config.bg}"
>
<Icon class="size-3.5 {config.text}" />
</div>
<!-- Content -->
<div class="min-w-0 flex-1">
<p class="text-foreground text-sm leading-snug">
<p class="text-[var(--text-primary)] text-sm leading-snug">
{activity.description ||
activity.message ||
`${activity.action} ${activity.entityType}: ${activity.entityName}`}
</p>
<div class="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
<div class="mt-1 flex items-center gap-1.5 text-xs text-[var(--text-secondary)]">
<span class="font-medium">{activity.user?.name || 'System'}</span>
<span class="text-muted-foreground/50">&middot;</span>
<span class="text-[var(--text-tertiary)]">&middot;</span>
<span>{formatTime(activity.timestamp || activity.createdAt)}</span>
</div>
</div>

View file

@ -16,8 +16,8 @@
{
href: '/tasks?filter=overdue',
icon: AlertCircle,
iconClass: 'text-rose-500 dark:text-rose-400',
bgClass: 'bg-rose-500/10 dark:bg-rose-500/15',
iconClass: 'text-[var(--task-overdue)]',
bgClass: 'bg-[var(--task-overdue-bg)] dark:bg-[var(--task-overdue)]/15',
count: overdueCount,
label: 'Overdue',
show: overdueCount > 0
@ -25,8 +25,8 @@
{
href: '/tasks?filter=today',
icon: Calendar,
iconClass: 'text-amber-500 dark:text-amber-400',
bgClass: 'bg-amber-500/10 dark:bg-amber-500/15',
iconClass: 'text-[var(--task-due-today)]',
bgClass: 'bg-[var(--task-due-today-bg)] dark:bg-[var(--task-due-today)]/15',
count: todayCount,
label: 'Due Today',
show: true
@ -34,8 +34,8 @@
{
href: '/leads?filter=followup_today',
icon: Phone,
iconClass: 'text-cyan-500 dark:text-cyan-400',
bgClass: 'bg-cyan-500/10 dark:bg-cyan-500/15',
iconClass: 'text-[var(--activity-call)]',
bgClass: 'bg-[var(--color-success-light)] dark:bg-[var(--activity-call)]/15',
count: followupsCount,
label: 'Follow-ups',
show: true
@ -43,8 +43,8 @@
{
href: '/leads?rating=HOT',
icon: Flame,
iconClass: 'text-orange-500 dark:text-orange-400',
bgClass: 'bg-orange-500/10 dark:bg-orange-500/15',
iconClass: 'text-[var(--lead-hot)]',
bgClass: 'bg-[var(--lead-hot-bg)] dark:bg-[var(--lead-hot)]/15',
count: hotLeadsCount,
label: 'Hot Leads',
show: hotLeadsCount > 0
@ -57,41 +57,41 @@
{#if visibleItems.length > 0}
<div
class="relative overflow-hidden rounded-xl border px-4 py-3 backdrop-blur-sm transition-all duration-300 sm:px-6
class="relative overflow-hidden rounded-[var(--radius-lg)] border px-4 py-3 backdrop-blur-sm transition-all duration-300 sm:px-6
{hasUrgentItems
? 'border-rose-300/50 bg-rose-50/50 dark:border-rose-500/20 dark:bg-rose-950/20'
: 'border-border/50 bg-card/50 dark:bg-card/30'}"
? 'border-[var(--task-overdue)]/30 bg-[var(--task-overdue-bg)] dark:border-[var(--task-overdue)]/20 dark:bg-[var(--task-overdue)]/10'
: 'border-[var(--border-default)] bg-[var(--surface-raised)]/50 dark:bg-[var(--surface-raised)]/30'}"
>
<!-- Subtle gradient overlay -->
<div class="pointer-events-none absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent dark:via-white/[0.02]"></div>
<div class="relative flex items-center gap-3 sm:gap-5">
<!-- Label with icon -->
<!-- Label with icon - Orange accent -->
<div class="hidden items-center gap-2 sm:flex">
<div class="flex size-7 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/10 to-teal-500/10 dark:from-cyan-500/20 dark:to-teal-500/20">
<Zap class="size-4 text-cyan-600 dark:text-cyan-400" />
<div class="flex size-7 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
<Zap class="size-4 text-[var(--color-primary-default)]" />
</div>
<span class="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
<span class="text-[var(--text-secondary)] text-xs font-semibold uppercase tracking-wider">
Today's Focus
</span>
</div>
<!-- Divider -->
<div class="hidden h-6 w-px bg-border/50 sm:block"></div>
<div class="hidden h-6 w-px bg-[var(--border-default)]/50 sm:block"></div>
<!-- Items -->
<div class="flex flex-1 items-center gap-1 overflow-x-auto sm:gap-2">
{#each visibleItems as item}
<a
href={item.href}
class="group flex items-center gap-2 rounded-lg px-3 py-2 transition-all duration-200 hover:bg-white/50 dark:hover:bg-white/5 sm:gap-2.5 sm:px-4"
class="group flex items-center gap-2 rounded-[var(--radius-md)] px-3 py-2 transition-all duration-200 hover:bg-white/50 dark:hover:bg-white/5 sm:gap-2.5 sm:px-4"
>
<div class="flex size-8 items-center justify-center rounded-lg transition-transform duration-200 group-hover:scale-110 {item.bgClass}">
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] transition-transform duration-200 group-hover:scale-110 {item.bgClass}">
<item.icon class="size-4 {item.iconClass}" />
</div>
<div class="flex items-center gap-2">
<span class="text-foreground text-base font-bold tabular-nums">{item.count}</span>
<span class="text-muted-foreground hidden text-xs font-medium sm:inline">{item.label}</span>
<span class="text-[var(--text-primary)] text-base font-bold tabular-nums">{item.count}</span>
<span class="text-[var(--text-secondary)] hidden text-xs font-medium sm:inline">{item.label}</span>
</div>
</a>
{/each}

View file

@ -22,24 +22,25 @@
/** @type {Props} */
let { leads = [] } = $props();
// Using design system lead temperature tokens
const ratingConfig = /** @type {Record<string, { bg: string, text: string, border: string, iconColor: string }>} */ ({
HOT: {
bg: 'bg-orange-500/10 dark:bg-orange-500/15',
text: 'text-orange-600 dark:text-orange-400',
border: 'border-orange-500/30 dark:border-orange-400/20',
iconColor: 'text-orange-500 dark:text-orange-400'
bg: 'bg-[var(--lead-hot-bg)] dark:bg-[var(--lead-hot)]/15',
text: 'text-[var(--lead-hot)]',
border: 'border-[var(--lead-hot)]/30',
iconColor: 'text-[var(--lead-hot)]'
},
WARM: {
bg: 'bg-amber-500/10 dark:bg-amber-500/15',
text: 'text-amber-600 dark:text-amber-400',
border: 'border-amber-500/30 dark:border-amber-400/20',
iconColor: 'text-amber-500 dark:text-amber-400'
bg: 'bg-[var(--lead-warm-bg)] dark:bg-[var(--lead-warm)]/15',
text: 'text-[var(--lead-warm)]',
border: 'border-[var(--lead-warm)]/30',
iconColor: 'text-[var(--lead-warm)]'
},
COLD: {
bg: 'bg-cyan-500/10 dark:bg-cyan-500/15',
text: 'text-cyan-600 dark:text-cyan-400',
border: 'border-cyan-500/30 dark:border-cyan-400/20',
iconColor: 'text-cyan-500 dark:text-cyan-400'
bg: 'bg-[var(--lead-cold-bg)] dark:bg-[var(--lead-cold)]/15',
text: 'text-[var(--lead-cold)]',
border: 'border-[var(--lead-cold)]/30',
iconColor: 'text-[var(--lead-cold)]'
}
});
@ -89,14 +90,14 @@
}
</script>
<div class="flex h-full flex-col overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
<!-- Header -->
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
<div class="flex h-full flex-col overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
<!-- Header - Orange accent for hot leads -->
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
<div class="flex items-center gap-3">
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500/10 to-red-500/10 dark:from-orange-500/20 dark:to-red-500/20">
<Flame class="size-4 text-orange-500 dark:text-orange-400" />
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--lead-hot-bg)] dark:bg-[var(--lead-hot)]/15">
<Flame class="size-4 text-[var(--lead-hot)]" />
</div>
<h3 class="text-foreground text-sm font-semibold tracking-tight">Hot Leads</h3>
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">Hot Leads</h3>
</div>
<Button variant="ghost" size="sm" href="/leads?rating=HOT" class="gap-1 text-xs font-medium">
View all
@ -108,26 +109,26 @@
<div class="flex-1 overflow-auto">
{#if leads.length === 0}
<div class="flex h-full flex-col items-center justify-center py-10 text-center">
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
<Sparkles class="text-muted-foreground/50 size-6" />
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
<Sparkles class="size-6 text-[var(--text-tertiary)]" />
</div>
<p class="text-muted-foreground text-sm font-medium">No hot leads</p>
<p class="text-muted-foreground/70 text-xs">Mark leads as "Hot" to see them here</p>
<p class="text-[var(--text-secondary)] text-sm font-medium">No hot leads</p>
<p class="text-[var(--text-tertiary)] text-xs">Mark leads as "Hot" to see them here</p>
</div>
{:else}
<div class="divide-y divide-border/30">
<div class="divide-y divide-[var(--border-default)]/30">
{#each leads as lead (lead.id)}
{@const config = ratingConfig[lead.rating || 'HOT'] || ratingConfig.HOT}
<a
href="/leads?view={lead.id}"
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-muted/30"
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
>
<!-- Lead info -->
<div class="min-w-0 flex-1">
<p class="text-foreground truncate text-sm font-medium transition-colors group-hover:text-primary">
<p class="text-[var(--text-primary)] truncate text-sm font-medium transition-colors group-hover:text-[var(--color-primary-default)]">
{getLeadName(lead)}
</p>
<p class="text-muted-foreground mt-0.5 truncate text-xs">
<p class="text-[var(--text-secondary)] mt-0.5 truncate text-xs">
{lead.company || 'No company'}
</p>
</div>
@ -143,10 +144,10 @@
<!-- Follow-up date -->
{#if lead.next_follow_up}
<div class="flex flex-shrink-0 items-center gap-1.5">
<Calendar class="text-muted-foreground size-3.5" />
<Calendar class="size-3.5 text-[var(--text-tertiary)]" />
<span
class="text-xs font-medium tabular-nums
{isOverdue(lead.next_follow_up) ? 'text-rose-500 dark:text-rose-400' : 'text-muted-foreground'}"
{isOverdue(lead.next_follow_up) ? 'text-[var(--task-overdue)]' : 'text-[var(--text-secondary)]'}"
>
{formatFollowUp(lead.next_follow_up)}
</span>
@ -158,7 +159,7 @@
<Button
variant="ghost"
size="icon"
class="size-7 rounded-lg hover:bg-cyan-500/10 hover:text-cyan-600 dark:hover:bg-cyan-500/20 dark:hover:text-cyan-400"
class="size-7 rounded-[var(--radius-md)] hover:bg-[var(--activity-call)]/10 hover:text-[var(--activity-call)]"
title="Call"
>
<Phone class="size-3.5" />
@ -166,7 +167,7 @@
<Button
variant="ghost"
size="icon"
class="size-7 rounded-lg hover:bg-violet-500/10 hover:text-violet-600 dark:hover:bg-violet-500/20 dark:hover:text-violet-400"
class="size-7 rounded-[var(--radius-md)] hover:bg-[var(--activity-email)]/10 hover:text-[var(--activity-email)]"
title="Email"
>
<Mail class="size-3.5" />

View file

@ -10,7 +10,7 @@
* @property {import('svelte').Snippet} [icon] - Icon snippet to display
* @property {number} [trend] - Percentage change (positive or negative)
* @property {string} [trendLabel] - Label for trend (e.g., "vs last month")
* @property {'cyan' | 'violet' | 'emerald' | 'amber' | 'rose'} [accentColor] - Accent color theme
* @property {'orange' | 'cyan' | 'violet' | 'emerald' | 'amber' | 'rose' | 'blue'} [accentColor] - Accent color theme
* @property {string} [class] - Additional classes
*/
@ -22,7 +22,7 @@
icon,
trend,
trendLabel,
accentColor = 'cyan',
accentColor = 'orange',
class: className,
...restProps
} = $props();
@ -30,8 +30,14 @@
const hasTrend = $derived(trend !== undefined && trend !== null);
const isPositive = $derived(trend !== undefined && trend >= 0);
// Color configurations for different accent colors
// Color configurations using design system tokens
const colorConfig = {
orange: {
iconBg: 'bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15',
iconColor: 'text-[var(--color-primary-default)] dark:text-[var(--color-primary-default)]',
borderGlow: 'hover:border-[var(--color-primary-default)]/30',
shadowGlow: 'dark:hover:shadow-[var(--color-primary-default)]/10'
},
cyan: {
iconBg: 'bg-cyan-500/10 dark:bg-cyan-500/20',
iconColor: 'text-cyan-600 dark:text-cyan-400',
@ -39,39 +45,45 @@
shadowGlow: 'dark:hover:shadow-cyan-500/10'
},
violet: {
iconBg: 'bg-violet-500/10 dark:bg-violet-500/20',
iconColor: 'text-violet-600 dark:text-violet-400',
borderGlow: 'hover:border-violet-500/30 dark:hover:border-violet-400/30',
shadowGlow: 'dark:hover:shadow-violet-500/10'
iconBg: 'bg-[var(--stage-proposal-bg)] dark:bg-[var(--stage-proposal)]/15',
iconColor: 'text-[var(--stage-proposal)] dark:text-[var(--stage-proposal)]',
borderGlow: 'hover:border-[var(--stage-proposal)]/30',
shadowGlow: 'dark:hover:shadow-[var(--stage-proposal)]/10'
},
blue: {
iconBg: 'bg-[var(--stage-qualified-bg)] dark:bg-[var(--stage-qualified)]/15',
iconColor: 'text-[var(--stage-qualified)] dark:text-[var(--stage-qualified)]',
borderGlow: 'hover:border-[var(--stage-qualified)]/30',
shadowGlow: 'dark:hover:shadow-[var(--stage-qualified)]/10'
},
emerald: {
iconBg: 'bg-emerald-500/10 dark:bg-emerald-500/20',
iconColor: 'text-emerald-600 dark:text-emerald-400',
borderGlow: 'hover:border-emerald-500/30 dark:hover:border-emerald-400/30',
shadowGlow: 'dark:hover:shadow-emerald-500/10'
iconBg: 'bg-[var(--color-success-light)] dark:bg-[var(--stage-won)]/15',
iconColor: 'text-[var(--stage-won)] dark:text-[var(--stage-won)]',
borderGlow: 'hover:border-[var(--stage-won)]/30',
shadowGlow: 'dark:hover:shadow-[var(--stage-won)]/10'
},
amber: {
iconBg: 'bg-amber-500/10 dark:bg-amber-500/20',
iconColor: 'text-amber-600 dark:text-amber-400',
borderGlow: 'hover:border-amber-500/30 dark:hover:border-amber-400/30',
shadowGlow: 'dark:hover:shadow-amber-500/10'
iconBg: 'bg-[var(--stage-negotiation-bg)] dark:bg-[var(--stage-negotiation)]/15',
iconColor: 'text-[var(--stage-negotiation)] dark:text-[var(--stage-negotiation)]',
borderGlow: 'hover:border-[var(--stage-negotiation)]/30',
shadowGlow: 'dark:hover:shadow-[var(--stage-negotiation)]/10'
},
rose: {
iconBg: 'bg-rose-500/10 dark:bg-rose-500/20',
iconColor: 'text-rose-600 dark:text-rose-400',
borderGlow: 'hover:border-rose-500/30 dark:hover:border-rose-400/30',
shadowGlow: 'dark:hover:shadow-rose-500/10'
iconBg: 'bg-[var(--stage-lost-bg)] dark:bg-[var(--stage-lost)]/15',
iconColor: 'text-[var(--stage-lost)] dark:text-[var(--stage-lost)]',
borderGlow: 'hover:border-[var(--stage-lost)]/30',
shadowGlow: 'dark:hover:shadow-[var(--stage-lost)]/10'
}
};
const colors = $derived(colorConfig[accentColor] || colorConfig.cyan);
const colors = $derived(colorConfig[accentColor] || colorConfig.orange);
</script>
<div
class={cn(
'group relative overflow-hidden rounded-xl border border-border/50 bg-card/80 p-5 backdrop-blur-sm transition-all duration-300',
'hover:-translate-y-0.5 hover:shadow-lg',
'dark:bg-card/50 dark:hover:shadow-xl',
'group relative overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] p-5 transition-all duration-300',
'hover:-translate-y-0.5 hover:shadow-[var(--shadow-md)]',
'dark:border-[var(--border-default)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm dark:hover:shadow-lg',
colors.borderGlow,
colors.shadowGlow,
className
@ -80,46 +92,46 @@
>
<!-- Subtle gradient overlay on hover -->
<div class="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<div class="absolute inset-0 bg-gradient-to-br from-white/5 via-transparent to-transparent dark:from-white/[0.02]"></div>
<div class="absolute inset-0 bg-gradient-to-br from-[var(--color-primary-default)]/5 via-transparent to-transparent dark:from-[var(--color-primary-default)]/[0.03]"></div>
</div>
<div class="relative flex items-start justify-between gap-4">
<div class="flex min-w-0 flex-col gap-1.5">
<!-- Label -->
<p class="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
<p class="text-[var(--text-secondary)] text-xs font-semibold uppercase tracking-wider">
{label}
</p>
<!-- Value -->
<p class="text-foreground truncate text-2xl font-bold tabular-nums tracking-tight lg:text-3xl">
<p class="text-[var(--text-primary)] truncate text-2xl font-bold tabular-nums tracking-tight lg:text-3xl">
{value}
</p>
<!-- Subtitle -->
{#if subtitle}
<p class="text-muted-foreground/70 text-[10px] font-medium">{subtitle}</p>
<p class="text-[var(--text-tertiary)] text-[10px] font-medium">{subtitle}</p>
{/if}
<!-- Trend indicator -->
{#if hasTrend}
<div class="mt-1 flex items-center gap-1.5">
{#if isPositive}
<div class="flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 dark:bg-emerald-500/20">
<TrendingUp class="size-3 text-emerald-600 dark:text-emerald-400" />
<span class="text-xs font-semibold text-emerald-600 dark:text-emerald-400">
<div class="flex items-center gap-1 rounded-full bg-[var(--color-success-light)] px-2 py-0.5 dark:bg-[var(--stage-won)]/15">
<TrendingUp class="size-3 text-[var(--stage-won)]" />
<span class="text-xs font-semibold text-[var(--stage-won)]">
+{trend}%
</span>
</div>
{:else}
<div class="flex items-center gap-1 rounded-full bg-rose-500/10 px-2 py-0.5 dark:bg-rose-500/20">
<TrendingDown class="size-3 text-rose-600 dark:text-rose-400" />
<span class="text-xs font-semibold text-rose-600 dark:text-rose-400">
<div class="flex items-center gap-1 rounded-full bg-[var(--stage-lost-bg)] px-2 py-0.5 dark:bg-[var(--stage-lost)]/15">
<TrendingDown class="size-3 text-[var(--stage-lost)]" />
<span class="text-xs font-semibold text-[var(--stage-lost)]">
{trend}%
</span>
</div>
{/if}
{#if trendLabel}
<span class="text-muted-foreground text-[10px]">{trendLabel}</span>
<span class="text-[var(--text-tertiary)] text-[10px]">{trendLabel}</span>
{/if}
</div>
{/if}
@ -129,7 +141,7 @@
{#if icon}
<div
class={cn(
'flex size-11 flex-shrink-0 items-center justify-center rounded-xl transition-transform duration-300 group-hover:scale-105',
'flex size-11 flex-shrink-0 items-center justify-center rounded-[var(--radius-lg)] transition-transform duration-300 group-hover:scale-105',
colors.iconBg
)}
>

View file

@ -19,55 +19,56 @@
/** @type {Props} */
let { pipelineData = {}, currency = 'USD' } = $props();
// Pipeline stages using design system tokens
const stages = [
{
id: 'PROSPECTING',
color: 'bg-slate-500',
textColor: 'text-slate-600 dark:text-slate-400',
bgColor: 'bg-slate-500/10 dark:bg-slate-500/15',
borderColor: 'border-slate-500/30 dark:border-slate-400/20',
glowColor: 'hover:shadow-slate-500/10'
color: 'bg-[var(--stage-new)]',
textColor: 'text-[var(--stage-new)]',
bgColor: 'bg-[var(--stage-new-bg)] dark:bg-[var(--stage-new)]/15',
borderColor: 'border-[var(--stage-new)]/30',
glowColor: 'hover:shadow-[var(--stage-new)]/10'
},
{
id: 'QUALIFICATION',
color: 'bg-cyan-500',
textColor: 'text-cyan-600 dark:text-cyan-400',
bgColor: 'bg-cyan-500/10 dark:bg-cyan-500/15',
borderColor: 'border-cyan-500/30 dark:border-cyan-400/20',
glowColor: 'hover:shadow-cyan-500/10 dark:hover:shadow-cyan-400/20'
color: 'bg-[var(--stage-qualified)]',
textColor: 'text-[var(--stage-qualified)]',
bgColor: 'bg-[var(--stage-qualified-bg)] dark:bg-[var(--stage-qualified)]/15',
borderColor: 'border-[var(--stage-qualified)]/30',
glowColor: 'hover:shadow-[var(--stage-qualified)]/10 dark:hover:shadow-[var(--stage-qualified)]/20'
},
{
id: 'PROPOSAL',
color: 'bg-violet-500',
textColor: 'text-violet-600 dark:text-violet-400',
bgColor: 'bg-violet-500/10 dark:bg-violet-500/15',
borderColor: 'border-violet-500/30 dark:border-violet-400/20',
glowColor: 'hover:shadow-violet-500/10 dark:hover:shadow-violet-400/20'
color: 'bg-[var(--stage-proposal)]',
textColor: 'text-[var(--stage-proposal)]',
bgColor: 'bg-[var(--stage-proposal-bg)] dark:bg-[var(--stage-proposal)]/15',
borderColor: 'border-[var(--stage-proposal)]/30',
glowColor: 'hover:shadow-[var(--stage-proposal)]/10 dark:hover:shadow-[var(--stage-proposal)]/20'
},
{
id: 'NEGOTIATION',
color: 'bg-amber-500',
textColor: 'text-amber-600 dark:text-amber-400',
bgColor: 'bg-amber-500/10 dark:bg-amber-500/15',
borderColor: 'border-amber-500/30 dark:border-amber-400/20',
glowColor: 'hover:shadow-amber-500/10 dark:hover:shadow-amber-400/20'
color: 'bg-[var(--stage-negotiation)]',
textColor: 'text-[var(--stage-negotiation)]',
bgColor: 'bg-[var(--stage-negotiation-bg)] dark:bg-[var(--stage-negotiation)]/15',
borderColor: 'border-[var(--stage-negotiation)]/30',
glowColor: 'hover:shadow-[var(--stage-negotiation)]/10 dark:hover:shadow-[var(--stage-negotiation)]/20'
},
{
id: 'CLOSED_WON',
color: 'bg-emerald-500',
textColor: 'text-emerald-600 dark:text-emerald-400',
bgColor: 'bg-emerald-500/10 dark:bg-emerald-500/15',
borderColor: 'border-emerald-500/30 dark:border-emerald-400/20',
glowColor: 'hover:shadow-emerald-500/10 dark:hover:shadow-emerald-400/20',
color: 'bg-[var(--stage-won)]',
textColor: 'text-[var(--stage-won)]',
bgColor: 'bg-[var(--stage-won-bg)] dark:bg-[var(--stage-won)]/15',
borderColor: 'border-[var(--stage-won)]/30',
glowColor: 'hover:shadow-[var(--stage-won)]/10 dark:hover:shadow-[var(--stage-won)]/20',
icon: Trophy
},
{
id: 'CLOSED_LOST',
color: 'bg-rose-500',
textColor: 'text-rose-600 dark:text-rose-400',
bgColor: 'bg-rose-500/10 dark:bg-rose-500/15',
borderColor: 'border-rose-500/30 dark:border-rose-400/20',
glowColor: 'hover:shadow-rose-500/10',
color: 'bg-[var(--stage-lost)]',
textColor: 'text-[var(--stage-lost)]',
bgColor: 'bg-[var(--stage-lost-bg)] dark:bg-[var(--stage-lost)]/15',
borderColor: 'border-[var(--stage-lost)]/30',
glowColor: 'hover:shadow-[var(--stage-lost)]/10',
icon: XCircle
}
];
@ -79,7 +80,7 @@
{@const data = pipelineData[stage.id] || { count: 0, value: 0, label: stage.id }}
<a
href="/opportunities?stage={stage.id}"
class="group relative min-w-[130px] flex-1 overflow-hidden rounded-xl border px-4 py-3.5 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg {stage.borderColor} {stage.bgColor} {stage.glowColor}"
class="group relative min-w-[130px] flex-1 overflow-hidden rounded-[var(--radius-lg)] border px-4 py-3.5 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg {stage.borderColor} {stage.bgColor} {stage.glowColor}"
>
<!-- Gradient overlay on hover -->
<div class="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/10 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:from-white/5"></div>
@ -89,7 +90,7 @@
<div class="flex items-center justify-between gap-2">
<div class="flex items-center gap-2">
<div class="size-2.5 rounded-full {stage.color} shadow-sm"></div>
<span class="text-foreground text-xs font-semibold tracking-tight">
<span class="text-[var(--text-primary)] text-xs font-semibold tracking-tight">
{data.label || stage.id.replace('_', ' ')}
</span>
</div>
@ -106,7 +107,7 @@
{#if stage.icon}
<stage.icon class="size-4 {stage.textColor}" />
{/if}
<p class="text-foreground text-lg font-bold tabular-nums tracking-tight">
<p class="text-[var(--text-primary)] text-lg font-bold tabular-nums tracking-tight">
{formatCurrency(data.value, currency, true)}
</p>
</div>
@ -116,16 +117,16 @@
<!-- Connector arrows between active stages -->
{#if index < 4}
<div class="flex-shrink-0 px-1">
<ChevronRight class="text-muted-foreground/30 size-5 transition-colors group-hover:text-muted-foreground/50" />
<ChevronRight class="size-5 text-[var(--text-tertiary)]/30 transition-colors group-hover:text-[var(--text-tertiary)]/50" />
</div>
{/if}
<!-- Divider before closed stages -->
{#if index === 3}
<div class="flex flex-shrink-0 flex-col items-center gap-1 px-2">
<div class="bg-border/50 h-4 w-px"></div>
<span class="text-muted-foreground/50 text-[8px] font-medium uppercase tracking-widest">Closed</span>
<div class="bg-border/50 h-4 w-px"></div>
<div class="h-4 w-px bg-[var(--border-default)]/50"></div>
<span class="text-[var(--text-tertiary)]/50 text-[8px] font-medium uppercase tracking-widest">Closed</span>
<div class="h-4 w-px bg-[var(--border-default)]/50"></div>
</div>
{/if}
{/each}

View file

@ -26,42 +26,43 @@
/** @type {Props} */
let { opportunities = [] } = $props();
// Using design system stage tokens
const stageConfig = /** @type {Record<string, { label: string, bg: string, text: string, border: string }>} */ ({
PROSPECTING: {
label: 'Prospecting',
bg: 'bg-slate-500/10 dark:bg-slate-500/15',
text: 'text-slate-600 dark:text-slate-400',
border: 'border-slate-500/30'
bg: 'bg-[var(--stage-new-bg)] dark:bg-[var(--stage-new)]/15',
text: 'text-[var(--stage-new)]',
border: 'border-[var(--stage-new)]/30'
},
QUALIFICATION: {
label: 'Qualification',
bg: 'bg-cyan-500/10 dark:bg-cyan-500/15',
text: 'text-cyan-600 dark:text-cyan-400',
border: 'border-cyan-500/30'
bg: 'bg-[var(--stage-qualified-bg)] dark:bg-[var(--stage-qualified)]/15',
text: 'text-[var(--stage-qualified)]',
border: 'border-[var(--stage-qualified)]/30'
},
PROPOSAL: {
label: 'Proposal',
bg: 'bg-violet-500/10 dark:bg-violet-500/15',
text: 'text-violet-600 dark:text-violet-400',
border: 'border-violet-500/30'
bg: 'bg-[var(--stage-proposal-bg)] dark:bg-[var(--stage-proposal)]/15',
text: 'text-[var(--stage-proposal)]',
border: 'border-[var(--stage-proposal)]/30'
},
NEGOTIATION: {
label: 'Negotiation',
bg: 'bg-amber-500/10 dark:bg-amber-500/15',
text: 'text-amber-600 dark:text-amber-400',
border: 'border-amber-500/30'
bg: 'bg-[var(--stage-negotiation-bg)] dark:bg-[var(--stage-negotiation)]/15',
text: 'text-[var(--stage-negotiation)]',
border: 'border-[var(--stage-negotiation)]/30'
},
CLOSED_WON: {
label: 'Won',
bg: 'bg-emerald-500/10 dark:bg-emerald-500/15',
text: 'text-emerald-600 dark:text-emerald-400',
border: 'border-emerald-500/30'
bg: 'bg-[var(--stage-won-bg)] dark:bg-[var(--stage-won)]/15',
text: 'text-[var(--stage-won)]',
border: 'border-[var(--stage-won)]/30'
},
CLOSED_LOST: {
label: 'Lost',
bg: 'bg-rose-500/10 dark:bg-rose-500/15',
text: 'text-rose-600 dark:text-rose-400',
border: 'border-rose-500/30'
bg: 'bg-[var(--stage-lost-bg)] dark:bg-[var(--stage-lost)]/15',
text: 'text-[var(--stage-lost)]',
border: 'border-[var(--stage-lost)]/30'
}
});
@ -97,14 +98,14 @@
);
</script>
<div class="flex h-full flex-col overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
<!-- Header -->
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
<div class="flex h-full flex-col overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
<!-- Header - Green/Success accent for opportunities -->
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
<div class="flex items-center gap-3">
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500/10 to-teal-500/10 dark:from-emerald-500/20 dark:to-teal-500/20">
<Sparkles class="size-4 text-emerald-600 dark:text-emerald-400" />
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/15">
<Sparkles class="size-4 text-[var(--color-success-default)]" />
</div>
<h3 class="text-foreground text-sm font-semibold tracking-tight">My Opportunities</h3>
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">My Opportunities</h3>
</div>
<Button variant="ghost" size="sm" href="/opportunities" class="gap-1 text-xs font-medium">
View all
@ -116,32 +117,32 @@
<div class="flex-1 overflow-auto">
{#if openOpportunities.length === 0}
<div class="flex h-full flex-col items-center justify-center py-10 text-center">
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
<Sparkles class="text-muted-foreground/50 size-6" />
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
<Sparkles class="size-6 text-[var(--text-tertiary)]" />
</div>
<p class="text-muted-foreground text-sm font-medium">No open opportunities</p>
<p class="text-muted-foreground/70 text-xs">Create one to start tracking</p>
<p class="text-[var(--text-secondary)] text-sm font-medium">No open opportunities</p>
<p class="text-[var(--text-tertiary)] text-xs">Create one to start tracking</p>
</div>
{:else}
<div class="divide-y divide-border/30">
<div class="divide-y divide-[var(--border-default)]/30">
{#each openOpportunities.slice(0, 5) as opp (opp.id)}
{@const daysUntilClose = getDaysUntilClose(opp.closed_on)}
{@const config = stageConfig[opp.stage] || stageConfig.PROSPECTING}
<a
href="/opportunities?view={opp.id}"
class="group block px-5 py-3.5 transition-all duration-200 hover:bg-muted/30"
class="group block px-5 py-3.5 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
>
<!-- Top row: Name and Amount -->
<div class="mb-2.5 flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<p class="text-foreground truncate text-sm font-medium transition-colors group-hover:text-primary">
<p class="text-[var(--text-primary)] truncate text-sm font-medium transition-colors group-hover:text-[var(--color-primary-default)]">
{opp.name}
</p>
<p class="text-muted-foreground mt-0.5 truncate text-xs">
<p class="text-[var(--text-secondary)] mt-0.5 truncate text-xs">
{opp.account?.name || 'No account'}
</p>
</div>
<span class="text-foreground flex-shrink-0 text-base font-bold tabular-nums tracking-tight">
<span class="text-[var(--text-primary)] flex-shrink-0 text-base font-bold tabular-nums tracking-tight">
{formatCurrency(opp.amount, opp.currency || 'USD')}
</span>
</div>
@ -158,7 +159,7 @@
<!-- Probability progress -->
<div class="flex flex-1 items-center gap-2">
<Progress value={opp.probability || 0} class="h-1.5 flex-1" />
<span class="text-muted-foreground w-8 text-right text-xs font-medium tabular-nums">
<span class="text-[var(--text-secondary)] w-8 text-right text-xs font-medium tabular-nums">
{opp.probability || 0}%
</span>
</div>
@ -166,14 +167,14 @@
<!-- Close date -->
{#if daysUntilClose !== null}
<div class="flex flex-shrink-0 items-center gap-1.5">
<Calendar class="text-muted-foreground size-3.5" />
<Calendar class="size-3.5 text-[var(--text-tertiary)]" />
<span
class="text-xs font-medium tabular-nums
{daysUntilClose < 0
? 'text-rose-500 dark:text-rose-400'
? 'text-[var(--task-overdue)]'
: daysUntilClose <= 7
? 'text-amber-500 dark:text-amber-400'
: 'text-muted-foreground'}"
? 'text-[var(--task-due-today)]'
: 'text-[var(--text-secondary)]'}"
>
{formatDaysUntilClose(daysUntilClose)}
</span>

View file

@ -18,31 +18,31 @@
/** @type {Props} */
let { pipelineData = {}, currency = 'USD' } = $props();
// Only show open stages in the chart (not closed)
// Only show open stages in the chart (not closed) - using design system tokens
const stages = [
{
id: 'PROSPECTING',
color: 'bg-slate-500',
bgLight: 'bg-slate-500/20',
text: 'text-slate-600 dark:text-slate-400'
color: 'bg-[var(--stage-new)]',
bgLight: 'bg-[var(--stage-new)]/20',
text: 'text-[var(--stage-new)]'
},
{
id: 'QUALIFICATION',
color: 'bg-cyan-500',
bgLight: 'bg-cyan-500/20',
text: 'text-cyan-600 dark:text-cyan-400'
color: 'bg-[var(--stage-qualified)]',
bgLight: 'bg-[var(--stage-qualified)]/20',
text: 'text-[var(--stage-qualified)]'
},
{
id: 'PROPOSAL',
color: 'bg-violet-500',
bgLight: 'bg-violet-500/20',
text: 'text-violet-600 dark:text-violet-400'
color: 'bg-[var(--stage-proposal)]',
bgLight: 'bg-[var(--stage-proposal)]/20',
text: 'text-[var(--stage-proposal)]'
},
{
id: 'NEGOTIATION',
color: 'bg-amber-500',
bgLight: 'bg-amber-500/20',
text: 'text-amber-600 dark:text-amber-400'
color: 'bg-[var(--stage-negotiation)]',
bgLight: 'bg-[var(--stage-negotiation)]/20',
text: 'text-[var(--stage-negotiation)]'
}
];
@ -50,13 +50,13 @@
const totalPipeline = $derived(stages.reduce((sum, s) => sum + (pipelineData[s.id]?.value || 0), 0));
</script>
<div class="overflow-hidden rounded-xl border border-border/50 bg-card/80 p-6 backdrop-blur-sm dark:bg-card/50">
<!-- Header -->
<div class="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] p-6 dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
<!-- Header - Orange accent -->
<div class="mb-6 flex items-center gap-3">
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/10 to-violet-500/10 dark:from-cyan-500/20 dark:to-violet-500/20">
<BarChart3 class="size-4 text-cyan-600 dark:text-cyan-400" />
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
<BarChart3 class="size-4 text-[var(--color-primary-default)]" />
</div>
<h3 class="text-foreground text-sm font-semibold tracking-tight">Pipeline by Stage</h3>
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">Pipeline by Stage</h3>
</div>
<!-- Bar chart -->
@ -69,9 +69,9 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="size-2.5 rounded-full {stage.color}"></div>
<span class="text-muted-foreground text-xs font-medium">{data.label || stage.id}</span>
<span class="text-[var(--text-secondary)] text-xs font-medium">{data.label || stage.id}</span>
</div>
<span class="text-foreground text-sm font-bold tabular-nums tracking-tight">
<span class="text-[var(--text-primary)] text-sm font-bold tabular-nums tracking-tight">
{formatCurrency(data.value, currency, true)}
</span>
</div>
@ -90,9 +90,9 @@
</div>
<!-- Summary -->
<div class="mt-6 flex items-center justify-between border-t border-border/50 pt-4">
<span class="text-muted-foreground text-xs font-medium">Total Open Pipeline</span>
<span class="text-foreground text-lg font-bold tabular-nums tracking-tight">
<div class="mt-6 flex items-center justify-between border-t border-[var(--border-default)]/50 pt-4">
<span class="text-[var(--text-secondary)] text-xs font-medium">Total Open Pipeline</span>
<span class="text-[var(--text-primary)] text-lg font-bold tabular-nums tracking-tight">
{formatCurrency(totalPipeline, currency, true)}
</span>
</div>

View file

@ -78,29 +78,36 @@
tasks.filter((t) => t.status !== 'Completed' && isOverdue(t.dueDate))
);
// Using design system priority tokens
const priorityConfig = /** @type {Record<string, { bg: string, text: string, border: string }>} */ ({
Urgent: {
bg: 'bg-[var(--priority-urgent-bg)] dark:bg-[var(--priority-urgent)]/15',
text: 'text-[var(--priority-urgent)]',
border: 'border-[var(--priority-urgent)]/30'
},
High: {
bg: 'bg-[var(--priority-high-bg)] dark:bg-[var(--priority-high)]/15',
text: 'text-[var(--priority-high)]',
border: 'border-[var(--priority-high)]/30'
},
Medium: {
bg: 'bg-[var(--priority-medium-bg)] dark:bg-[var(--priority-medium)]/15',
text: 'text-[var(--priority-medium)]',
border: 'border-[var(--priority-medium)]/30'
},
Low: {
bg: 'bg-[var(--priority-low-bg)] dark:bg-[var(--priority-low)]/15',
text: 'text-[var(--priority-low)]',
border: 'border-[var(--priority-low)]/30'
}
});
/**
* Get priority config
* @param {string} priority
*/
function getPriorityConfig(priority) {
const configs = /** @type {Record<string, { bg: string, text: string, border: string }>} */ ({
High: {
bg: 'bg-rose-500/10 dark:bg-rose-500/15',
text: 'text-rose-600 dark:text-rose-400',
border: 'border-rose-500/30 dark:border-rose-400/20'
},
Medium: {
bg: 'bg-amber-500/10 dark:bg-amber-500/15',
text: 'text-amber-600 dark:text-amber-400',
border: 'border-amber-500/30 dark:border-amber-400/20'
},
Low: {
bg: 'bg-slate-500/10 dark:bg-slate-500/15',
text: 'text-slate-600 dark:text-slate-400',
border: 'border-slate-500/30 dark:border-slate-400/20'
}
});
return configs[priority] || configs['Medium'];
return priorityConfig[priority] || priorityConfig['Medium'];
}
/**
@ -127,14 +134,14 @@
];
</script>
<div class="flex h-full flex-col overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
<!-- Header -->
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
<div class="flex h-full flex-col overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
<!-- Header - Orange accent -->
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
<div class="flex items-center gap-3">
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/10 to-teal-500/10 dark:from-cyan-500/20 dark:to-teal-500/20">
<ListTodo class="size-4 text-cyan-600 dark:text-cyan-400" />
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
<ListTodo class="size-4 text-[var(--color-primary-default)]" />
</div>
<h3 class="text-foreground text-sm font-semibold tracking-tight">My Tasks</h3>
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">My Tasks</h3>
</div>
<Button variant="ghost" size="sm" href="/tasks" class="gap-1 text-xs font-medium">
View all
@ -143,18 +150,18 @@
</div>
<!-- Filter tabs -->
<div class="flex gap-1 border-b border-border/50 px-4 py-2">
<div class="flex gap-1 border-b border-[var(--border-default)]/50 px-4 py-2">
{#each filterButtons as btn}
<Button
variant={filter === btn.id ? 'secondary' : 'ghost'}
size="sm"
class="h-7 px-3 text-xs font-medium transition-all duration-200
{filter === btn.id ? 'bg-secondary shadow-sm' : 'hover:bg-secondary/50'}"
{filter === btn.id ? 'bg-[var(--surface-sunken)] shadow-sm' : 'hover:bg-[var(--surface-sunken)]/50'}"
onclick={() => (filter = btn.id)}
>
{btn.label}
{#if btn.id === 'overdue' && overdueTasks.length > 0}
<Badge class="ml-1.5 h-4 min-w-4 bg-rose-500 px-1 text-[10px] font-bold text-white">
<Badge class="ml-1.5 h-4 min-w-4 bg-[var(--task-overdue)] px-1 text-[10px] font-bold text-white">
{overdueTasks.length}
</Badge>
{/if}
@ -166,24 +173,24 @@
<div class="flex-1 overflow-auto">
{#if filteredTasks().length === 0}
<div class="flex h-full flex-col items-center justify-center py-10 text-center">
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
<ListTodo class="text-muted-foreground/50 size-6" />
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
<ListTodo class="size-6 text-[var(--text-tertiary)]" />
</div>
<p class="text-muted-foreground text-sm font-medium">No tasks found</p>
<p class="text-muted-foreground/70 text-xs">All caught up!</p>
<p class="text-[var(--text-secondary)] text-sm font-medium">No tasks found</p>
<p class="text-[var(--text-tertiary)] text-xs">All caught up!</p>
</div>
{:else}
<div class="divide-y divide-border/30">
<div class="divide-y divide-[var(--border-default)]/30">
{#each filteredTasks() as task (task.id)}
{@const config = getPriorityConfig(task.priority)}
<a
href="/tasks?view={task.id}"
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-muted/30"
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
>
<!-- Status icon -->
<button class="text-muted-foreground flex-shrink-0 transition-colors hover:text-foreground">
<button class="flex-shrink-0 text-[var(--text-tertiary)] transition-colors hover:text-[var(--text-primary)]">
{#if task.status === 'Completed'}
<CheckCircle2 class="size-5 text-emerald-500 dark:text-emerald-400" />
<CheckCircle2 class="size-5 text-[var(--task-completed)]" />
{:else}
<Circle class="size-5" />
{/if}
@ -192,12 +199,12 @@
<!-- Task details -->
<div class="min-w-0 flex-1">
<p
class="text-foreground truncate text-sm font-medium transition-colors group-hover:text-primary
{task.status === 'Completed' ? 'text-muted-foreground line-through' : ''}"
class="text-[var(--text-primary)] truncate text-sm font-medium transition-colors group-hover:text-[var(--color-primary-default)]
{task.status === 'Completed' ? 'text-[var(--text-tertiary)] line-through' : ''}"
>
{task.subject}
</p>
<p class="text-muted-foreground mt-0.5 truncate text-xs">{task.status}</p>
<p class="text-[var(--text-secondary)] mt-0.5 truncate text-xs">{task.status}</p>
</div>
<!-- Priority badge -->
@ -212,7 +219,7 @@
{#if task.dueDate}
<span
class="flex-shrink-0 text-xs font-medium tabular-nums
{isOverdue(task.dueDate) ? 'text-rose-500 dark:text-rose-400' : 'text-muted-foreground'}"
{isOverdue(task.dueDate) ? 'text-[var(--task-overdue)]' : 'text-[var(--text-secondary)]'}"
>
{formatDate(task.dueDate)}
</span>

View file

@ -35,8 +35,6 @@
BarChart3,
Tag,
Sparkles,
Search,
Star,
Home
} from '@lucide/svelte';
@ -171,7 +169,7 @@
},
{
href: '/accounts',
label: 'Companies',
label: 'Accounts',
icon: Building,
type: 'link',
preload: 'off'
@ -248,7 +246,10 @@
};
</script>
<Sidebar.Root collapsible="icon" class="hubspot-sidebar border-r border-sidebar-border/60 bg-sidebar">
<Sidebar.Root
collapsible="icon"
class="hubspot-sidebar border-sidebar-border/60 bg-sidebar border-r"
>
<!-- Header with Logo - HubSpot Style -->
<Sidebar.Header class="px-3 py-4">
<Sidebar.Menu>
@ -258,13 +259,16 @@
<a href="/" {...props} class="flex items-center gap-3">
<!-- Logo Container -->
<div
class="flex size-9 items-center justify-center rounded-lg bg-gradient-to-br from-[#ff7a59] to-[#ff5c35] shadow-md shadow-[#ff7a59]/20 transition-transform duration-200 group-hover:scale-105"
class="flex size-9 items-center justify-center transition-transform duration-200 group-hover:scale-105"
>
<img src={imgLogo} alt="Logo" class="size-5" />
<img src={imgLogo} alt="Logo" class="size-8" />
</div>
<div class="grid flex-1 text-left leading-tight group-data-[collapsible=icon]:hidden">
<span class="truncate text-[15px] font-semibold tracking-tight text-sidebar-foreground">{org_name}</span>
<span class="truncate text-[11px] font-medium text-sidebar-foreground/50">
<span
class="text-sidebar-foreground truncate text-[15px] font-semibold tracking-tight"
>{org_name}</span
>
<span class="text-sidebar-foreground/50 truncate text-[11px] font-medium">
CRM Platform
</span>
</div>
@ -275,21 +279,12 @@
</Sidebar.Menu>
</Sidebar.Header>
<!-- Quick Search Bar - HubSpot Style -->
<div class="mx-3 mb-3 group-data-[collapsible=icon]:mx-2 group-data-[collapsible=icon]:hidden">
<button
class="flex h-9 w-full items-center gap-2 rounded-lg border border-sidebar-border/60 bg-sidebar-accent/30 px-3 text-sm text-sidebar-foreground/60 transition-all hover:border-sidebar-border hover:bg-sidebar-accent/50"
>
<Search class="size-4" />
<span class="flex-1 text-left">Search...</span>
<kbd class="hidden rounded bg-sidebar-accent px-1.5 py-0.5 text-[10px] font-medium text-sidebar-foreground/50 sm:inline-block">⌘K</kbd>
</button>
</div>
<Sidebar.Content class="px-2">
<!-- CRM Section -->
<Sidebar.Group>
<Sidebar.GroupLabel class="mb-1 px-3 text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-data-[collapsible=icon]:hidden">
<Sidebar.GroupLabel
class="text-sidebar-foreground/40 mb-1 px-3 text-[10px] font-bold tracking-widest uppercase group-data-[collapsible=icon]:hidden"
>
CRM
</Sidebar.GroupLabel>
<Sidebar.GroupContent>
@ -299,29 +294,30 @@
<Sidebar.MenuButton
isActive={currentPath === item.href}
tooltipContent={item.label}
class="hubspot-menu-item group/item relative h-9 rounded-lg px-3 transition-all duration-150
class="nav-item group/item relative h-9 rounded-lg px-2.5 transition-all duration-200
{currentPath === item.href
? 'bg-[#fff1ee] text-[#ff5c35] dark:bg-[#ff7a59]/10 dark:text-[#ff7a59]'
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
? 'nav-item-active bg-[var(--color-primary-default)]/[0.08] text-[var(--color-primary-default)] shadow-[inset_0_1px_2px_rgba(0,0,0,0.04)] dark:bg-[var(--color-primary-default)]/15 dark:shadow-none'
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground'}"
>
{#snippet child({ props })}
<a
href={item.href}
{...props}
data-sveltekit-preload-data={item.preload || 'hover'}
class="flex items-center gap-3"
class="flex items-center gap-2.5"
>
<!-- Active indicator bar - HubSpot style left border -->
{#if currentPath === item.href}
<div class="absolute left-0 top-1/2 h-5 w-[3px] -translate-y-1/2 rounded-r-full bg-[#ff7a59]"></div>
{/if}
<div class="flex size-5 items-center justify-center">
<div
class="flex size-6 items-center justify-center rounded-md transition-all duration-200
{currentPath === item.href
? 'bg-[var(--color-primary-default)] text-white shadow-sm shadow-[var(--color-primary-default)]/25'
: 'text-current group-hover/item:bg-sidebar-accent'}"
>
<item.icon
class="size-[18px] transition-colors duration-150"
strokeWidth={currentPath === item.href ? 2.25 : 1.75}
class="size-[15px] transition-transform duration-200 {currentPath === item.href ? 'scale-105' : 'group-hover/item:scale-105'}"
strokeWidth={2}
/>
</div>
<span class="text-[13px] font-medium">{item.label}</span>
<span class="text-[13px] {currentPath === item.href ? 'font-semibold' : 'font-medium'}">{item.label}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
@ -333,7 +329,9 @@
<!-- Sales Section -->
<Sidebar.Group class="mt-4">
<Sidebar.GroupLabel class="mb-1 px-3 text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-data-[collapsible=icon]:hidden">
<Sidebar.GroupLabel
class="text-sidebar-foreground/40 mb-1 px-3 text-[10px] font-bold tracking-widest uppercase group-data-[collapsible=icon]:hidden"
>
Sales
</Sidebar.GroupLabel>
<Sidebar.GroupContent>
@ -354,40 +352,48 @@
{...props}
isActive={hasActiveChild(item.children ?? [])}
tooltipContent={item.label}
class="hubspot-menu-item group/item h-9 rounded-lg px-3 transition-all duration-150
class="nav-item group/item h-9 rounded-lg px-2.5 transition-all duration-200
{hasActiveChild(item.children ?? [])
? 'text-[#ff5c35] dark:text-[#ff7a59]'
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
? 'text-[var(--color-primary-default)]'
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground'}"
>
<div class="flex w-full items-center gap-3">
<div class="flex size-5 items-center justify-center">
<item.icon class="size-[18px]" strokeWidth={1.75} />
<div class="flex w-full items-center gap-2.5">
<div
class="flex size-6 items-center justify-center rounded-md transition-all duration-200
{hasActiveChild(item.children ?? [])
? 'bg-[var(--color-primary-default)]/10 text-[var(--color-primary-default)]'
: 'text-current group-hover/item:bg-sidebar-accent'}"
>
<item.icon class="size-[15px]" strokeWidth={2} />
</div>
<span class="text-[13px] font-medium">{item.label}</span>
<span class="text-[13px] {hasActiveChild(item.children ?? []) ? 'font-semibold' : 'font-medium'}">{item.label}</span>
<ChevronDown
class="ml-auto size-4 text-sidebar-foreground/40 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180"
class="text-sidebar-foreground/40 ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180"
/>
</div>
</Sidebar.MenuButton>
{/snippet}
</Collapsible.Trigger>
<Collapsible.Content>
<Sidebar.MenuSub class="hubspot-submenu mt-1 border-l-2 border-sidebar-border/50 ml-[18px] pl-4 py-1">
<Sidebar.MenuSub
class="mt-1.5 ml-[11px] space-y-0.5 border-l border-sidebar-border/40 py-1 pl-3"
>
{#each item.children as navChild}
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton
isActive={currentPath === navChild.href}
class="hubspot-submenu-item group/subitem relative h-8 rounded-md px-2 transition-all duration-150
class="nav-subitem group/subitem relative h-7 rounded-md px-2 transition-all duration-200
{currentPath === navChild.href
? 'bg-[#fff1ee] text-[#ff5c35] dark:bg-[#ff7a59]/10 dark:text-[#ff7a59]'
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
? 'bg-[var(--color-primary-default)]/[0.08] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
: 'text-sidebar-foreground/60 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
>
{#snippet child({ props })}
<a href={navChild.href} {...props} class="flex items-center gap-2.5">
<div class="flex size-4 items-center justify-center">
<navChild.icon class="size-3.5" strokeWidth={1.75} />
</div>
<span class="text-[12px] font-medium">{navChild.label}</span>
<a href={navChild.href} {...props} class="flex items-center gap-2">
<navChild.icon
class="size-3.5 transition-colors duration-200"
strokeWidth={currentPath === navChild.href ? 2.25 : 1.75}
/>
<span class="text-[12px] {currentPath === navChild.href ? 'font-semibold' : 'font-medium'}">{navChild.label}</span>
</a>
{/snippet}
</Sidebar.MenuSubButton>
@ -405,7 +411,9 @@
<!-- Support Section -->
<Sidebar.Group class="mt-4">
<Sidebar.GroupLabel class="mb-1 px-3 text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-data-[collapsible=icon]:hidden">
<Sidebar.GroupLabel
class="text-sidebar-foreground/40 mb-1 px-3 text-[10px] font-bold tracking-widest uppercase group-data-[collapsible=icon]:hidden"
>
Support
</Sidebar.GroupLabel>
<Sidebar.GroupContent>
@ -415,28 +423,30 @@
<Sidebar.MenuButton
isActive={currentPath === item.href}
tooltipContent={item.label}
class="hubspot-menu-item group/item relative h-9 rounded-lg px-3 transition-all duration-150
class="nav-item group/item relative h-9 rounded-lg px-2.5 transition-all duration-200
{currentPath === item.href
? 'bg-[#fff1ee] text-[#ff5c35] dark:bg-[#ff7a59]/10 dark:text-[#ff7a59]'
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
? 'nav-item-active bg-[var(--color-primary-default)]/[0.08] text-[var(--color-primary-default)] shadow-[inset_0_1px_2px_rgba(0,0,0,0.04)] dark:bg-[var(--color-primary-default)]/15 dark:shadow-none'
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground'}"
>
{#snippet child({ props })}
<a
href={item.href}
{...props}
data-sveltekit-preload-data={item.preload || 'hover'}
class="flex items-center gap-3"
class="flex items-center gap-2.5"
>
{#if currentPath === item.href}
<div class="absolute left-0 top-1/2 h-5 w-[3px] -translate-y-1/2 rounded-r-full bg-[#ff7a59]"></div>
{/if}
<div class="flex size-5 items-center justify-center">
<div
class="flex size-6 items-center justify-center rounded-md transition-all duration-200
{currentPath === item.href
? 'bg-[var(--color-primary-default)] text-white shadow-sm shadow-[var(--color-primary-default)]/25'
: 'text-current group-hover/item:bg-sidebar-accent'}"
>
<item.icon
class="size-[18px] transition-colors duration-150"
strokeWidth={currentPath === item.href ? 2.25 : 1.75}
class="size-[15px] transition-transform duration-200 {currentPath === item.href ? 'scale-105' : 'group-hover/item:scale-105'}"
strokeWidth={2}
/>
</div>
<span class="text-[13px] font-medium">{item.label}</span>
<span class="text-[13px] {currentPath === item.href ? 'font-semibold' : 'font-medium'}">{item.label}</span>
</a>
{/snippet}
</Sidebar.MenuButton>
@ -447,14 +457,14 @@
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer class="border-t border-sidebar-border/60 px-2 py-3">
<Sidebar.Footer class="border-sidebar-border/60 border-t px-2 py-3">
<Sidebar.Menu class="space-y-1">
<!-- Collapse/Expand Toggle - HubSpot Style -->
<Sidebar.MenuItem class="group-data-[collapsible=icon]:hidden">
<Sidebar.MenuButton
onclick={() => sidebar.toggle()}
tooltipContent="Collapse sidebar"
class="h-8 rounded-lg px-3 text-sidebar-foreground/60 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-foreground"
class="text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-foreground h-8 rounded-lg px-3 transition-all duration-150"
>
{#snippet child({ props })}
<button {...props} class="flex w-full items-center gap-3">
@ -470,7 +480,7 @@
<Sidebar.MenuButton
onclick={() => sidebar.toggle()}
tooltipContent="Expand sidebar"
class="flex h-8 w-full items-center justify-center rounded-lg text-sidebar-foreground/60 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-foreground"
class="text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-foreground flex h-8 w-full items-center justify-center rounded-lg transition-all duration-150"
>
{#snippet child({ props })}
<button {...props} class="flex items-center justify-center">
@ -480,63 +490,6 @@
</Sidebar.MenuButton>
</Sidebar.MenuItem>
<!-- Theme Switcher -->
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton
{...props}
tooltipContent="Theme"
class="h-8 rounded-lg px-3 text-sidebar-foreground/60 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-foreground"
>
<div class="flex size-4 items-center justify-center">
{#if theme === 'dark'}
<Moon class="size-4 text-[#ff7a59]" strokeWidth={1.75} />
{:else if theme === 'light'}
<Sun class="size-4 text-amber-500" strokeWidth={1.75} />
{:else}
<Monitor class="size-4" strokeWidth={1.75} />
{/if}
</div>
<span class="text-[12px] font-medium">Theme</span>
<span
class="ml-auto rounded-md bg-sidebar-accent px-1.5 py-0.5 text-[10px] font-medium capitalize text-sidebar-foreground/50"
>
{theme}
</span>
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content side="top" align="start" class="w-44">
<DropdownMenu.Item inset={false} onclick={() => setTheme('light')} class="gap-2.5">
<Sun class="size-4 text-amber-500" />
<span>Light</span>
{#if theme === 'light'}
<div class="ml-auto size-2 rounded-full bg-[#ff7a59]"></div>
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item inset={false} onclick={() => setTheme('dark')} class="gap-2.5">
<Moon class="size-4 text-[#ff7a59]" />
<span>Dark</span>
{#if theme === 'dark'}
<div class="ml-auto size-2 rounded-full bg-[#ff7a59]"></div>
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item inset={false} onclick={() => setTheme('system')} class="gap-2.5">
<Monitor class="size-4 text-muted-foreground" />
<span>System</span>
{#if theme === 'system'}
<div class="ml-auto size-2 rounded-full bg-[#ff7a59]"></div>
{/if}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
<!-- Divider -->
<div class="mx-3 my-2 h-px bg-sidebar-border/50 group-data-[collapsible=icon]:hidden"></div>
<!-- User Menu - HubSpot Style -->
<Sidebar.MenuItem>
<DropdownMenu.Root>
@ -545,30 +498,36 @@
<Sidebar.MenuButton
size="lg"
{...props}
class="group/user h-12 rounded-lg px-2 transition-all duration-150 data-[state=open]:bg-sidebar-accent hover:bg-sidebar-accent"
class="group/user data-[state=open]:bg-sidebar-accent hover:bg-sidebar-accent h-12 rounded-lg px-2 transition-all duration-150"
>
<!-- Avatar with status -->
<div class="relative">
{#if user.profilePhoto && !profileImageError}
<img
class="size-8 rounded-full object-cover ring-2 ring-sidebar-border/50 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30"
class="ring-sidebar-border/50 size-8 rounded-full object-cover ring-2 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30"
src={user.profilePhoto}
alt="User avatar"
onerror={() => profileImageError = true}
onerror={() => (profileImageError = true)}
/>
{:else}
<div class="flex size-8 items-center justify-center rounded-full bg-gradient-to-br from-[#ff7a59] to-[#ff5c35] text-xs font-semibold text-white ring-2 ring-sidebar-border/50 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30">
<div
class="ring-sidebar-border/50 flex size-8 items-center justify-center rounded-full bg-gradient-to-br from-[#ff7a59] to-[#ff5c35] text-xs font-semibold text-white ring-2 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30"
>
{getInitials(user.name)}
</div>
{/if}
<!-- Online status indicator -->
<div class="absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-sidebar bg-emerald-500"></div>
<div
class="border-sidebar absolute -right-0.5 -bottom-0.5 size-2.5 rounded-full border-2 bg-emerald-500"
></div>
</div>
<div class="grid flex-1 text-left leading-tight">
<span class="truncate text-[13px] font-semibold text-sidebar-foreground">{user.name}</span>
<span class="truncate text-[11px] text-sidebar-foreground/50">{user.email}</span>
<span class="text-sidebar-foreground truncate text-[13px] font-semibold"
>{user.name}</span
>
<span class="text-sidebar-foreground/50 truncate text-[11px]">{user.email}</span>
</div>
<ChevronsUpDown class="ml-auto size-4 text-sidebar-foreground/40" />
<ChevronsUpDown class="text-sidebar-foreground/40 ml-auto size-4" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
@ -577,12 +536,18 @@
align="start"
class="w-[--bits-dropdown-menu-anchor-width] min-w-56"
>
<DropdownMenu.Label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
<DropdownMenu.Label
class="text-muted-foreground text-[10px] font-bold tracking-wider uppercase"
>
My Account
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item inset={false} onclick={() => navigateTo('/profile')} class="gap-2.5">
<DropdownMenu.Item
inset={false}
onclick={() => navigateTo('/profile')}
class="gap-2.5"
>
<User class="size-4" />
<span>Profile</span>
</DropdownMenu.Item>
@ -602,14 +567,47 @@
<Settings class="size-4" />
<span>Settings</span>
</DropdownMenu.Item>
<DropdownMenu.Item inset={false} onclick={() => navigateTo('/settings/tags')} class="gap-2.5">
<DropdownMenu.Item
inset={false}
onclick={() => navigateTo('/settings/tags')}
class="gap-2.5"
>
<Tag class="size-4" />
<span>Tags</span>
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Label
class="text-muted-foreground text-[10px] font-bold tracking-wider uppercase"
>
Theme
</DropdownMenu.Label>
<DropdownMenu.Group class="flex gap-1 px-2 py-1.5">
<button
onclick={() => setTheme('light')}
class="flex flex-1 flex-col items-center gap-1 rounded-md px-2 py-2 transition-colors {theme === 'light' ? 'bg-[#ff7a59]/10 text-[#ff7a59]' : 'hover:bg-sidebar-accent text-muted-foreground hover:text-foreground'}"
>
<Sun class="size-4" />
<span class="text-[10px] font-medium">Light</span>
</button>
<button
onclick={() => setTheme('dark')}
class="flex flex-1 flex-col items-center gap-1 rounded-md px-2 py-2 transition-colors {theme === 'dark' ? 'bg-[#ff7a59]/10 text-[#ff7a59]' : 'hover:bg-sidebar-accent text-muted-foreground hover:text-foreground'}"
>
<Moon class="size-4" />
<span class="text-[10px] font-medium">Dark</span>
</button>
<button
onclick={() => setTheme('system')}
class="flex flex-1 flex-col items-center gap-1 rounded-md px-2 py-2 transition-colors {theme === 'system' ? 'bg-[#ff7a59]/10 text-[#ff7a59]' : 'hover:bg-sidebar-accent text-muted-foreground hover:text-foreground'}"
>
<Monitor class="size-4" />
<span class="text-[10px] font-medium">System</span>
</button>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item
class="gap-2.5 text-destructive focus:bg-destructive/10 focus:text-destructive"
class="text-destructive focus:bg-destructive/10 focus:text-destructive gap-2.5"
inset={false}
onclick={() => navigateTo('/logout')}
>
@ -626,46 +624,17 @@
</Sidebar.Root>
<style>
/* HubSpot-style sidebar refinements */
/* Refined sidebar typography */
:global(.hubspot-sidebar) {
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-family:
'Geist',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
}
:global(.hubspot-sidebar [data-slot="sidebar-menu-button"]) {
:global(.hubspot-sidebar [data-slot='sidebar-menu-button']) {
font-weight: 500;
}
/* Smooth hover transition for menu items */
:global(.hubspot-menu-item) {
position: relative;
}
/* Active state animation */
:global(.hubspot-menu-item::before) {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 20px;
background: #ff7a59;
border-radius: 0 4px 4px 0;
transition: width 0.15s ease;
}
/* Submenu refinements */
:global(.hubspot-submenu) {
position: relative;
}
:global(.hubspot-submenu::before) {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(180deg, transparent, var(--sidebar-border), transparent);
}
</style>

View file

@ -18,7 +18,7 @@
*/
export function getOptionStyle(value, options) {
const option = options.find((o) => o.value === value);
return option?.color ?? 'bg-gray-100 text-gray-600';
return option?.color ?? 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
}
/**
@ -119,231 +119,239 @@ export function getVisibleColumnCount(columns) {
};
}
// Common status options for CRM entities
// Common status options for CRM entities - Using design system tokens
// Must match Django LEAD_STATUS: assigned, in process, converted, recycled, closed
export const leadStatusOptions = [
{
value: 'ASSIGNED',
label: 'Assigned',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
},
{
value: 'IN_PROCESS',
label: 'In Process',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15'
},
{
value: 'CONVERTED',
label: 'Converted',
color: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
color: 'bg-[var(--stage-won-bg)] text-[var(--stage-won)] dark:bg-[var(--stage-won)]/15'
},
{
value: 'RECYCLED',
label: 'Recycled',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
},
{
value: 'CLOSED',
label: 'Closed',
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}
];
// Lead temperature ratings - using design system tokens
export const leadRatingOptions = [
{
value: 'HOT',
label: 'Hot',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--lead-hot-bg)] text-[var(--lead-hot)] dark:bg-[var(--lead-hot)]/15'
},
{
value: 'WARM',
label: 'Warm',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--lead-warm-bg)] text-[var(--lead-warm)] dark:bg-[var(--lead-warm)]/15'
},
{
value: 'COLD',
label: 'Cold',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--lead-cold-bg)] text-[var(--lead-cold)] dark:bg-[var(--lead-cold)]/15'
}
];
// Priority options - using design system tokens
export const priorityOptions = [
{
value: 'URGENT',
label: 'Urgent',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--priority-urgent-bg)] text-[var(--priority-urgent)] dark:bg-[var(--priority-urgent)]/15'
},
{
value: 'HIGH',
label: 'High',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--priority-high-bg)] text-[var(--priority-high)] dark:bg-[var(--priority-high)]/15'
},
{
value: 'NORMAL',
label: 'Normal',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
},
{
value: 'MEDIUM',
label: 'Medium',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
},
{
value: 'LOW',
label: 'Low',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--priority-low-bg)] text-[var(--priority-low)] dark:bg-[var(--priority-low)]/15'
}
];
// Task status options - using design system tokens
export const taskStatusOptions = [
{
value: 'NOT STARTED',
label: 'Not Started',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--task-upcoming-bg)] text-[var(--task-upcoming)] dark:bg-[var(--task-upcoming)]/15'
},
{
value: 'IN PROGRESS',
label: 'In Progress',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--task-due-today-bg)] text-[var(--task-due-today)] dark:bg-[var(--task-due-today)]/15'
},
{
value: 'PENDING',
label: 'Pending',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
},
{
value: 'COMPLETED',
label: 'Completed',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--task-completed-bg)] text-[var(--task-completed)] dark:bg-[var(--task-completed)]/15'
},
{
value: 'DEFERRED',
label: 'Deferred',
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}
];
// Case status options - using design system tokens
export const caseStatusOptions = [
{
value: 'New',
label: 'New',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
},
{
value: 'Assigned',
label: 'Assigned',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
},
{
value: 'Pending',
label: 'Pending',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
},
{
value: 'Closed',
label: 'Closed',
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'Rejected',
label: 'Rejected',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
},
{
value: 'Duplicate',
label: 'Duplicate',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
}
];
// Case type options - using design system tokens
export const caseTypeOptions = [
{
value: 'Question',
label: 'Question',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
},
{
value: 'Incident',
label: 'Incident',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
},
{
value: 'Problem',
label: 'Problem',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
}
];
// Case priority options - using design system tokens
export const casePriorityOptions = [
{
value: 'Low',
label: 'Low',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--priority-low-bg)] text-[var(--priority-low)] dark:bg-[var(--priority-low)]/15'
},
{
value: 'Normal',
label: 'Normal',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
},
{
value: 'High',
label: 'High',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--priority-high-bg)] text-[var(--priority-high)] dark:bg-[var(--priority-high)]/15'
},
{
value: 'Urgent',
label: 'Urgent',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--priority-urgent-bg)] text-[var(--priority-urgent)] dark:bg-[var(--priority-urgent)]/15'
}
];
// Account status options - using design system tokens
export const accountStatusOptions = [
{
value: 'ACTIVE',
label: 'Active',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15'
},
{
value: 'INACTIVE',
label: 'Inactive',
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'PENDING',
label: 'Pending',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
}
];
// Opportunity stage options - using design system tokens
export const opportunityStageOptions = [
{
value: 'PROSPECTING',
label: 'Prospecting',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
},
{
value: 'QUALIFICATION',
label: 'Qualification',
color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
},
{
value: 'PROPOSAL',
label: 'Proposal',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--stage-proposal-bg)] text-[var(--stage-proposal)] dark:bg-[var(--stage-proposal)]/15'
},
{
value: 'NEGOTIATION',
label: 'Negotiation',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
},
{
value: 'CLOSED_WON',
label: 'Closed Won',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--stage-won-bg)] text-[var(--stage-won)] dark:bg-[var(--stage-won)]/15'
},
{
value: 'CLOSED_LOST',
label: 'Closed Lost',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
}
];

View file

@ -1,5 +1,5 @@
<script>
import { DollarSign, TrendingUp, Target, Percent, AlertCircle, Zap, Sparkles } from '@lucide/svelte';
import { DollarSign, TrendingUp, Target, Percent, AlertCircle, Sparkles } from '@lucide/svelte';
import {
KPICard,
FocusBar,
@ -46,26 +46,26 @@
</svelte:head>
<div class="relative min-h-screen">
<!-- Subtle gradient mesh background -->
<!-- Subtle gradient mesh background with orange tint -->
<div class="gradient-mesh noise-overlay pointer-events-none fixed inset-0 -z-10 opacity-50 dark:opacity-30"></div>
<div class="space-y-8 p-6 md:p-8">
<!-- Header Section with gradient text -->
<!-- Header Section with orange gradient accent -->
<header class="animate-in-up">
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-end">
<div class="space-y-2">
<div class="flex items-center gap-3">
<div class="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500 to-teal-600 shadow-lg dark:shadow-cyan-500/20">
<div class="flex size-10 items-center justify-center rounded-[var(--radius-lg)] bg-gradient-to-br from-[var(--color-primary-default)] to-[var(--stage-contacted)] shadow-lg dark:shadow-[var(--color-primary-default)]/20">
<Sparkles class="size-5 text-white" />
</div>
<div>
<p class="text-muted-foreground text-sm font-medium">{getGreeting()}</p>
<h1 class="text-foreground text-2xl font-bold tracking-tight md:text-3xl">
<p class="text-[var(--text-secondary)] text-sm font-medium">{getGreeting()}</p>
<h1 class="text-[var(--text-primary)] text-2xl font-bold tracking-tight md:text-3xl">
Dashboard
</h1>
</div>
</div>
<p class="text-muted-foreground max-w-md text-sm">
<p class="text-[var(--text-secondary)] max-w-md text-sm">
Here's what's happening with your CRM today. Stay on top of your pipeline and close more deals.
</p>
</div>
@ -74,14 +74,14 @@
{#if data.error}
<div
class="animate-in-up stagger-1 flex items-center gap-4 rounded-xl border border-red-200/50 bg-red-50/50 p-5 backdrop-blur-sm dark:border-red-800/30 dark:bg-red-900/10"
class="animate-in-up stagger-1 flex items-center gap-4 rounded-[var(--radius-lg)] border border-[var(--color-negative-default)]/20 bg-[var(--color-negative-light)] p-5 backdrop-blur-sm dark:border-[var(--color-negative-default)]/30 dark:bg-[var(--color-negative-default)]/10"
>
<div class="flex size-10 items-center justify-center rounded-lg bg-red-100 dark:bg-red-900/30">
<AlertCircle class="size-5 text-red-500 dark:text-red-400" />
<div class="flex size-10 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-negative-light)] dark:bg-[var(--color-negative-default)]/20">
<AlertCircle class="size-5 text-[var(--color-negative-default)]" />
</div>
<div>
<p class="text-sm font-medium text-red-700 dark:text-red-300">Error loading dashboard</p>
<p class="text-xs text-red-600/80 dark:text-red-400/80">{data.error}</p>
<p class="text-sm font-medium text-[var(--color-negative-default)]">Error loading dashboard</p>
<p class="text-xs text-[var(--color-negative-default)]/80">{data.error}</p>
</div>
</div>
{:else}
@ -96,15 +96,15 @@
</div>
<!-- Pipeline Overview - Full Width with glass effect -->
<div class="animate-in-up stagger-2 rounded-2xl border border-border/50 bg-card/80 p-6 shadow-sm backdrop-blur-sm dark:bg-card/50 dark:shadow-lg dark:shadow-black/10">
<div class="animate-in-up stagger-2 rounded-[var(--radius-xl)] border border-[var(--border-default)] bg-[var(--surface-raised)] p-6 shadow-[var(--shadow-sm)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm dark:shadow-lg dark:shadow-black/10">
<div class="mb-5 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="flex size-9 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500/10 to-purple-500/10 dark:from-violet-500/20 dark:to-purple-500/20">
<TrendingUp class="size-5 text-violet-600 dark:text-violet-400" />
<div class="flex size-9 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
<TrendingUp class="size-5 text-[var(--color-primary-default)]" />
</div>
<div>
<h2 class="text-foreground text-base font-semibold tracking-tight">Sales Pipeline</h2>
<p class="text-muted-foreground text-xs">{currencyNote}</p>
<h2 class="text-[var(--text-primary)] text-base font-semibold tracking-tight">Sales Pipeline</h2>
<p class="text-[var(--text-tertiary)] text-xs">{currencyNote}</p>
</div>
</div>
</div>
@ -117,7 +117,7 @@
label="Pipeline Value"
value={formatCurrency(revenueMetrics.pipeline_value || 0, orgCurrency, true)}
subtitle={currencyNote}
accentColor="cyan"
accentColor="orange"
>
{#snippet icon()}
<DollarSign class="size-5" />

View file

@ -98,7 +98,7 @@
const currencyOptions = CURRENCY_CODES.filter((c) => c.value).map((c) => ({
value: c.value,
label: c.label,
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}));
// Base drawer columns (using $derived for dynamic currency symbol)
@ -748,14 +748,14 @@
onclick={() => (statusChipFilter = 'ALL')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'ALL'
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
All
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{accounts.length}
</span>
@ -765,14 +765,14 @@
onclick={() => (statusChipFilter = 'active')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'active'
? 'bg-emerald-600 text-white dark:bg-emerald-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-success-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Active
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'active'
? 'bg-emerald-700 text-emerald-100 dark:bg-emerald-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-success-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{activeCount}
</span>
@ -782,14 +782,14 @@
onclick={() => (statusChipFilter = 'closed')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'closed'
? 'bg-gray-600 text-white dark:bg-gray-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--text-secondary)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Closed
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'closed'
? 'bg-gray-700 text-gray-200 dark:bg-gray-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--text-tertiary)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{closedCount}
</span>
@ -809,7 +809,7 @@
Filters
{#if activeFiltersCount > 0}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{activeFiltersCount}
</span>
@ -825,7 +825,7 @@
Columns
{#if visibleColumnCount < totalColumnCount}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{visibleColumnCount}/{totalColumnCount}
</span>
@ -892,8 +892,11 @@
<!-- Accounts Table -->
{#if filteredAccounts.length === 0}
<div class="flex flex-col items-center justify-center py-16 text-center">
<Building2 class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No accounts found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<Building2 class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No accounts found</h3>
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new account</p>
</div>
{:else}
<!-- Desktop Table using CrmTable -->
@ -906,42 +909,44 @@
>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<Building2 class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No accounts found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<Building2 class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No accounts found</h3>
</div>
{/snippet}
</CrmTable>
</div>
<!-- Mobile Card View -->
<div class="divide-y md:hidden dark:divide-gray-800">
<div class="divide-y divide-[var(--border-default)] md:hidden">
{#each filteredAccounts as account (account.id)}
<button
type="button"
class="flex w-full items-start gap-4 p-4 text-left transition-colors hover:bg-gray-50 dark:hover:bg-gray-800 {!account.isActive
class="flex w-full items-start gap-4 p-4 text-left transition-colors hover:bg-[var(--surface-sunken)] {!account.isActive
? 'opacity-60'
: ''}"
onclick={() => openAccount(account)}
>
<div
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 text-sm font-medium text-white"
class="flex size-12 shrink-0 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--color-primary-default)] text-sm font-medium text-white"
>
{getAccountInitials(account)}
</div>
<div class="min-w-0 flex-1">
<div class="flex items-start justify-between gap-2">
<div>
<p class="font-medium text-gray-900 dark:text-gray-100">{account.name}</p>
<p class="font-medium text-[var(--text-primary)]">{account.name}</p>
<div class="mt-1 flex items-center gap-1.5">
{#if account.isActive !== false}
<span
class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
class="inline-flex items-center rounded-full bg-[var(--color-success-light)] px-2.5 py-1 text-xs font-medium text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15"
>
Active
</span>
{:else}
<span
class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-1 text-xs font-medium text-gray-600 dark:bg-gray-800 dark:text-gray-400"
class="inline-flex items-center rounded-full bg-[var(--surface-sunken)] px-2.5 py-1 text-xs font-medium text-[var(--text-secondary)]"
>
Closed
</span>
@ -950,21 +955,21 @@
</div>
</div>
<div
class="mt-2 flex flex-wrap items-center gap-3 text-sm text-gray-500 dark:text-gray-400"
class="mt-2 flex flex-wrap items-center gap-3 text-sm text-[var(--text-secondary)]"
>
{#if account.industry}
<span>{account.industry}</span>
{/if}
<div class="flex items-center gap-1">
<Users class="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" />
<Users class="size-3.5 text-[var(--text-tertiary)]" />
<span>{account.contactCount || 0}</span>
</div>
<div class="flex items-center gap-1">
<Target class="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" />
<Target class="size-3.5 text-[var(--text-tertiary)]" />
<span>{account.opportunityCount || 0}</span>
</div>
<div class="flex items-center gap-1">
<Calendar class="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" />
<Calendar class="size-3.5 text-[var(--text-tertiary)]" />
<span>{formatRelativeDate(account.createdAt)}</span>
</div>
</div>
@ -1003,13 +1008,13 @@
<!-- Closed account warning -->
{#if isClosed && drawerMode !== 'create'}
<div
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/30"
class="mb-4 rounded-[var(--radius-lg)] border border-[var(--color-negative-light)] bg-[var(--color-negative-light)] p-3 dark:border-[var(--color-negative-default)]/30 dark:bg-[var(--color-negative-default)]/10"
>
<div class="flex gap-2">
<AlertTriangle class="mt-0.5 h-4 w-4 shrink-0 text-red-500 dark:text-red-400" />
<AlertTriangle class="mt-0.5 size-4 shrink-0 text-[var(--color-negative-default)]" />
<div>
<p class="text-sm font-medium text-red-700 dark:text-red-400">This account is closed</p>
<p class="mt-0.5 text-xs text-red-600 dark:text-red-500">
<p class="text-sm font-medium text-[var(--color-negative-default)]">This account is closed</p>
<p class="mt-0.5 text-xs text-[var(--color-negative-default)]/80">
Reopen the account to make changes
</p>
</div>
@ -1021,46 +1026,46 @@
{#if drawerMode !== 'create' && selectedAccount}
<div class="mb-4">
<p
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
>
Related
</p>
<div class="grid grid-cols-4 gap-2">
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
<Users class="h-3.5 w-3.5" />
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
<Users class="size-3.5" />
</div>
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
{selectedAccount.contactCount || 0}
</p>
<p class="text-[10px] text-gray-500 dark:text-gray-400">Contacts</p>
<p class="text-[10px] text-[var(--text-secondary)]">Contacts</p>
</div>
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
<Target class="h-3.5 w-3.5" />
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
<Target class="size-3.5" />
</div>
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
{selectedAccount.opportunityCount || 0}
</p>
<p class="text-[10px] text-gray-500 dark:text-gray-400">Opportunities</p>
<p class="text-[10px] text-[var(--text-secondary)]">Opportunities</p>
</div>
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
<AlertTriangle class="h-3.5 w-3.5" />
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
<AlertTriangle class="size-3.5" />
</div>
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
{selectedAccount.caseCount || 0}
</p>
<p class="text-[10px] text-gray-500 dark:text-gray-400">Cases</p>
<p class="text-[10px] text-[var(--text-secondary)]">Cases</p>
</div>
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
<CheckSquare class="h-3.5 w-3.5" />
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
<CheckSquare class="size-3.5" />
</div>
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
{selectedAccount.taskCount || 0}
</p>
<p class="text-[10px] text-gray-500 dark:text-gray-400">Tasks</p>
<p class="text-[10px] text-[var(--text-secondary)]">Tasks</p>
</div>
</div>
</div>
@ -1069,7 +1074,7 @@
{#if !isClosed}
<div class="mb-4">
<p
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
>
Quick Actions
</p>
@ -1099,20 +1104,20 @@
<!-- Metadata -->
<div>
<p
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
>
Details
</p>
<div class="grid grid-cols-2 gap-3 text-sm">
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Owner</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Owner</p>
<p class="font-medium text-[var(--text-primary)]">
{selectedAccount.owner?.name || 'Unassigned'}
</p>
</div>
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Created</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Created</p>
<p class="font-medium text-[var(--text-primary)]">
{formatRelativeDate(selectedAccount.createdAt)}
</p>
</div>
@ -1120,7 +1125,7 @@
</div>
<!-- Comments Section -->
<div class="mt-6 border-t pt-4 dark:border-gray-700">
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
<CommentSection
entityId={selectedAccount.id}
entityType="accounts"
@ -1142,22 +1147,22 @@
<Button variant="outline" onclick={closeDrawer} disabled={isSubmitting}>Cancel</Button>
<Button
variant="outline"
class="text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
class="text-[var(--color-success-default)] hover:text-[var(--color-success-dark)]"
onclick={handleReopen}
disabled={isSubmitting}
>
<Unlock class="mr-1.5 h-4 w-4" />
<Unlock class="mr-1.5 size-4" />
Reopen Account
</Button>
{:else}
<Button variant="outline" onclick={closeDrawer} disabled={isSubmitting}>Cancel</Button>
<Button
variant="ghost"
class="text-orange-600 hover:text-orange-700 dark:text-orange-400 dark:hover:text-orange-300"
class="text-[var(--color-primary-default)] hover:text-[var(--color-primary-dark)]"
onclick={handleClose}
disabled={isSubmitting}
>
<Lock class="mr-1.5 h-4 w-4" />
<Lock class="mr-1.5 size-4" />
Close Account
</Button>
<Button onclick={handleDrawerUpdate} disabled={isSubmitting || !drawerFormData.name?.trim()}>

View file

@ -253,11 +253,11 @@
// Account options for drawer select
const accountOptions = $derived([
{ value: '', label: 'None', color: 'bg-gray-100 text-gray-600' },
{ value: '', label: 'None', color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]' },
...accounts.map((/** @type {any} */ a) => ({
value: a.id,
label: a.name,
color: 'bg-blue-100 text-blue-700'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)]'
}))
]);
@ -801,14 +801,14 @@
onclick={() => (statusChipFilter = 'ALL')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'ALL'
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
All
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{casesData.length}
</span>
@ -818,14 +818,14 @@
onclick={() => (statusChipFilter = 'open')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'open'
? 'bg-blue-600 text-white dark:bg-blue-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--stage-qualified)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Open
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'open'
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-black/20 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{openCount}
</span>
@ -835,14 +835,14 @@
onclick={() => (statusChipFilter = 'closed')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'closed'
? 'bg-gray-600 text-white dark:bg-gray-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--text-secondary)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Closed
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'closed'
? 'bg-gray-700 text-gray-200 dark:bg-gray-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--text-tertiary)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{closedCount}
</span>
@ -886,7 +886,7 @@
Filters
{#if activeFiltersCount > 0}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{activeFiltersCount}
</span>
@ -901,7 +901,7 @@
Columns
{#if columnCounts.visible < columnCounts.total}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{columnCounts.visible}/{columnCounts.total}
</span>
@ -983,8 +983,11 @@
>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<Briefcase class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No cases found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<Briefcase class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No cases found</h3>
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new case</p>
</div>
{/snippet}
</CrmTable>

View file

@ -738,7 +738,7 @@
Filters
{#if activeFiltersCount > 0}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
>
{activeFiltersCount}
</span>
@ -754,7 +754,7 @@
Columns
{#if visibleColumns.length < columns.length}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
>
{visibleColumns.length}/{columns.length}
</span>
@ -819,8 +819,11 @@
<!-- Table -->
{#if filteredContacts.length === 0}
<div class="flex flex-col items-center justify-center py-16 text-center">
<User class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No contacts found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<User class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-lg font-medium text-[var(--text-primary)]">No contacts found</h3>
<p class="mt-1 text-sm text-[var(--text-secondary)]">Create a new contact to get started</p>
</div>
{:else}
<CrmTable
@ -832,8 +835,10 @@
>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<User class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No contacts found</h3>
<div class="mb-4 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
<User class="size-6 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-lg font-medium text-[var(--text-primary)]">No contacts found</h3>
</div>
{/snippet}
</CrmTable>
@ -870,20 +875,20 @@
{#if drawerMode !== 'create' && selectedContact}
<div>
<p
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
>
Details
</p>
<div class="grid grid-cols-2 gap-3 text-sm">
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Owner</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Owner</p>
<p class="font-medium text-[var(--text-primary)]">
{selectedContact.owner?.name || 'Unassigned'}
</p>
</div>
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Created</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Created</p>
<p class="font-medium text-[var(--text-primary)]">
{formatRelativeDate(selectedContact.createdAt)}
</p>
</div>
@ -891,7 +896,7 @@
</div>
<!-- Comments Section -->
<div class="mt-6 border-t pt-4 dark:border-gray-700">
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
<CommentSection
entityId={selectedContact.id}
entityType="contacts"

View file

@ -14,42 +14,42 @@
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
// Invoice status options
// Invoice status options - using design system tokens
const INVOICE_STATUSES = [
{
value: 'Draft',
label: 'Draft',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'Sent',
label: 'Sent',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15'
},
{
value: 'Viewed',
label: 'Viewed',
color: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300'
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
},
{
value: 'Partially_Paid',
label: 'Partially Paid',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300'
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
},
{
value: 'Paid',
label: 'Paid',
color: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'
color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15'
},
{
value: 'Overdue',
label: 'Overdue',
color: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'
color: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)] dark:bg-[var(--color-negative-default)]/15'
},
{
value: 'Cancelled',
label: 'Cancelled',
color: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
color: 'bg-[var(--surface-sunken)] text-[var(--text-tertiary)]'
}
];
@ -309,14 +309,14 @@
onclick={() => (statusChipFilter = chip.key)}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
chip.key
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
{chip.label}
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === chip.key
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{chipCounts[chip.key] || 0}
</span>
@ -336,7 +336,7 @@
<Filter class="size-4" />
Filters
{#if activeFiltersCount > 0}
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
{activeFiltersCount}
</span>
{/if}
@ -416,9 +416,11 @@
<CrmTable data={invoices} {columns} bind:visibleColumns onRowClick={handleRowClick}>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<span class="mb-4 text-6xl">📄</span>
<h3 class="text-foreground text-lg font-medium">No invoices yet</h3>
<p class="text-muted-foreground mb-4 text-sm">Create your first invoice to get started</p>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<span class="text-4xl">📄</span>
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No invoices yet</h3>
<p class="text-[var(--text-secondary)] mb-4 text-sm">Create your first invoice to get started</p>
<Button onclick={createNewInvoice} class="gap-2">
<Plus class="size-4" />
Create Invoice
@ -439,7 +441,7 @@
{:else if column.key === 'totalAmount' || column.key === 'amountDue'}
<span
class={column.key === 'amountDue' && parseFloat(row.amountDue || 0) > 0
? 'font-medium text-orange-600 dark:text-orange-400'
? 'font-medium text-[var(--color-primary-default)]'
: ''}
>
{formatCurrency(row[column.key], row.currency)}

View file

@ -53,16 +53,16 @@
{ value: 'CUSTOM', label: 'Custom' }
];
// Status colors
// Status colors - using design system tokens
const STATUS_COLORS = {
Draft: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300',
Sent: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300',
Viewed: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300',
Partially_Paid: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300',
Paid: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300',
Overdue: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',
Pending: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300',
Cancelled: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
Draft: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]',
Sent: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15',
Viewed: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15',
Partially_Paid: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15',
Paid: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15',
Overdue: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)] dark:bg-[var(--color-negative-default)]/15',
Pending: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15',
Cancelled: 'bg-[var(--surface-sunken)] text-[var(--text-tertiary)]'
};
// Status options for dropdown
@ -782,7 +782,7 @@
</div>
<div class="flex justify-between font-medium">
<span>Amount Due</span>
<span class="text-orange-600"
<span class="text-[var(--color-primary-default)]"
>{formatCurrency(Number(invoice.amountDue), invoice.currency)}</span
>
</div>

View file

@ -17,14 +17,14 @@
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
// Estimate status options
// Estimate status options - using design system tokens
const ESTIMATE_STATUSES = [
{ value: 'Draft', label: 'Draft', color: 'gray' },
{ value: 'Sent', label: 'Sent', color: 'blue' },
{ value: 'Viewed', label: 'Viewed', color: 'indigo' },
{ value: 'Accepted', label: 'Accepted', color: 'green' },
{ value: 'Declined', label: 'Declined', color: 'red' },
{ value: 'Expired', label: 'Expired', color: 'orange' }
{ value: 'Draft', label: 'Draft', color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]' },
{ value: 'Sent', label: 'Sent', color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15' },
{ value: 'Viewed', label: 'Viewed', color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15' },
{ value: 'Accepted', label: 'Accepted', color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15' },
{ value: 'Declined', label: 'Declined', color: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)] dark:bg-[var(--color-negative-default)]/15' },
{ value: 'Expired', label: 'Expired', color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15' }
];
/**
@ -451,17 +451,10 @@
window.open(`/api/invoices/estimates/${selectedEstimate.id}/pdf/`, '_blank');
}
// Status badge color
// Status badge color - using design system tokens
function getStatusColor(status) {
const colors = {
Draft: 'bg-gray-100 text-gray-700',
Sent: 'bg-blue-100 text-blue-700',
Viewed: 'bg-indigo-100 text-indigo-700',
Accepted: 'bg-green-100 text-green-700',
Declined: 'bg-red-100 text-red-700',
Expired: 'bg-orange-100 text-orange-700'
};
return colors[status] || 'bg-gray-100 text-gray-700';
const statusObj = ESTIMATE_STATUSES.find((s) => s.value === status);
return statusObj?.color || 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
}
// Check if estimate is expired
@ -685,14 +678,14 @@
onclick={() => (statusChipFilter = chip.key)}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
chip.key
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
{chip.label}
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === chip.key
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{chipCounts[chip.key] || 0}
</span>
@ -743,7 +736,7 @@
</svg>
Filters
{#if activeFiltersCount() > 0}
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
{activeFiltersCount()}
</span>
{/if}
@ -849,9 +842,11 @@
<CrmTable data={estimates} {columns} bind:visibleColumns onRowClick={handleRowClick}>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<span class="mb-4 text-4xl">📋</span>
<h3 class="text-foreground text-lg font-medium">No estimates yet</h3>
<p class="text-muted-foreground text-sm">Create your first estimate to get started</p>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<span class="text-4xl">📋</span>
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No estimates yet</h3>
<p class="text-[var(--text-secondary)] text-sm">Create your first estimate to get started</p>
</div>
{/snippet}
{#snippet cellContent(row, column)}

View file

@ -411,7 +411,7 @@
</svg>
Filters
{#if activeFiltersCount() > 0}
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
{activeFiltersCount()}
</span>
{/if}
@ -509,9 +509,11 @@
<CrmTable data={products} {columns} bind:visibleColumns onRowClick={handleRowClick}>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<span class="mb-4 text-4xl">📦</span>
<h3 class="text-foreground text-lg font-medium">No products yet</h3>
<p class="text-muted-foreground text-sm">Create your first product to use in invoices</p>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<span class="text-4xl">📦</span>
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No products yet</h3>
<p class="text-[var(--text-secondary)] text-sm">Create your first product to use in invoices</p>
</div>
{/snippet}
{#snippet cellContent(row, column)}

View file

@ -17,15 +17,15 @@
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
// Frequency options
// Frequency options - using design system tokens
const FREQUENCIES = [
{ value: 'WEEKLY', label: 'Weekly', color: 'blue' },
{ value: 'BIWEEKLY', label: 'Bi-weekly', color: 'cyan' },
{ value: 'MONTHLY', label: 'Monthly', color: 'green' },
{ value: 'QUARTERLY', label: 'Quarterly', color: 'yellow' },
{ value: 'SEMI_ANNUALLY', label: 'Semi-annually', color: 'orange' },
{ value: 'YEARLY', label: 'Yearly', color: 'purple' },
{ value: 'CUSTOM', label: 'Custom', color: 'gray' }
{ value: 'WEEKLY', label: 'Weekly', color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)]' },
{ value: 'BIWEEKLY', label: 'Bi-weekly', color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)]' },
{ value: 'MONTHLY', label: 'Monthly', color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)]' },
{ value: 'QUARTERLY', label: 'Quarterly', color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)]' },
{ value: 'SEMI_ANNUALLY', label: 'Semi-annually', color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)]' },
{ value: 'YEARLY', label: 'Yearly', color: 'bg-[var(--stage-proposal-bg)] text-[var(--stage-proposal)]' },
{ value: 'CUSTOM', label: 'Custom', color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]' }
];
// Status options
@ -592,7 +592,7 @@
</svg>
Filters
{#if activeFiltersCount() > 0}
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
{activeFiltersCount()}
</span>
{/if}
@ -688,9 +688,11 @@
<CrmTable data={recurringInvoices} {columns} bind:visibleColumns onRowClick={handleRowClick}>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<span class="mb-4 text-4xl">🔄</span>
<h3 class="text-foreground text-lg font-medium">No recurring invoices yet</h3>
<p class="text-muted-foreground text-sm">Create your first recurring invoice template</p>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<span class="text-4xl">🔄</span>
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No recurring invoices yet</h3>
<p class="text-[var(--text-secondary)] text-sm">Create your first recurring invoice template</p>
</div>
{/snippet}
{#snippet cellContent(row, column)}

View file

@ -33,17 +33,18 @@
await goto(url.toString(), { replaceState: true, invalidateAll: true });
}
// Status colors - using design system tokens
function getStatusColor(status) {
const colors = {
Draft: 'bg-gray-500',
Sent: 'bg-blue-500',
Viewed: 'bg-indigo-500',
Partially_Paid: 'bg-yellow-500',
Paid: 'bg-green-500',
Overdue: 'bg-red-500',
Cancelled: 'bg-gray-400'
Draft: 'bg-[var(--text-tertiary)]',
Sent: 'bg-[var(--stage-contacted)]',
Viewed: 'bg-[var(--stage-qualified)]',
Partially_Paid: 'bg-[var(--stage-negotiation)]',
Paid: 'bg-[var(--color-success-default)]',
Overdue: 'bg-[var(--color-negative-default)]',
Cancelled: 'bg-[var(--text-tertiary)]'
};
return colors[status] || 'bg-gray-500';
return colors[status] || 'bg-[var(--text-tertiary)]';
}
</script>
@ -74,15 +75,15 @@
<!-- Summary Cards -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<!-- Total Invoiced -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Total Invoiced</p>
<p class="mt-1 text-2xl font-bold text-gray-900">
<p class="text-sm text-[var(--text-secondary)]">Total Invoiced</p>
<p class="mt-1 text-2xl font-bold text-[var(--text-primary)]">
{formatCurrency(Number(dashboard.summary?.total_invoiced || 0), 'USD')}
</p>
</div>
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-primary-light)]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -93,7 +94,7 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-blue-600"
class="text-[var(--color-primary-default)]"
>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
@ -105,15 +106,15 @@
</div>
<!-- Total Paid -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Total Collected</p>
<p class="mt-1 text-2xl font-bold text-green-600">
<p class="text-sm text-[var(--text-secondary)]">Total Collected</p>
<p class="mt-1 text-2xl font-bold text-[var(--color-success-default)]">
{formatCurrency(Number(dashboard.summary?.total_paid || 0), 'USD')}
</p>
</div>
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-success-light)]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -124,7 +125,7 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-green-600"
class="text-[var(--color-success-default)]"
>
<line x1="12" y1="1" x2="12" y2="23" />
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
@ -134,15 +135,15 @@
</div>
<!-- Outstanding -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Outstanding</p>
<p class="mt-1 text-2xl font-bold text-orange-600">
<p class="text-sm text-[var(--text-secondary)]">Outstanding</p>
<p class="mt-1 text-2xl font-bold text-[var(--color-primary-default)]">
{formatCurrency(Number(dashboard.summary?.total_due || 0), 'USD')}
</p>
</div>
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-orange-100">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-primary-light)]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -153,7 +154,7 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-orange-600"
class="text-[var(--color-primary-default)]"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
@ -163,15 +164,15 @@
</div>
<!-- Overdue -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500">Overdue ({dashboard.overdue?.count || 0})</p>
<p class="mt-1 text-2xl font-bold text-red-600">
<p class="text-sm text-[var(--text-secondary)]">Overdue ({dashboard.overdue?.count || 0})</p>
<p class="mt-1 text-2xl font-bold text-[var(--color-negative-default)]">
{formatCurrency(Number(dashboard.overdue?.amount || 0), 'USD')}
</p>
</div>
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-negative-light)]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -182,7 +183,7 @@
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-red-600"
class="text-[var(--color-negative-default)]"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
@ -198,26 +199,26 @@
<!-- Recent Activity & Status -->
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<!-- Recent Activity -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<h3 class="mb-4 text-lg font-semibold text-gray-900">Last 30 Days</h3>
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<h3 class="mb-4 text-lg font-semibold text-[var(--text-primary)]">Last 30 Days</h3>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-600">Revenue Collected</span>
<span class="font-medium text-green-600">
<span class="text-[var(--text-secondary)]">Revenue Collected</span>
<span class="font-medium text-[var(--color-success-default)]">
{formatCurrency(Number(dashboard.recent_activity?.revenue_30d || 0), 'USD')}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600">Invoices Created</span>
<span class="font-medium">{dashboard.recent_activity?.invoices_created_30d || 0}</span>
<span class="text-[var(--text-secondary)]">Invoices Created</span>
<span class="font-medium text-[var(--text-primary)]">{dashboard.recent_activity?.invoices_created_30d || 0}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600">Invoices Paid</span>
<span class="font-medium">{dashboard.recent_activity?.invoices_paid_30d || 0}</span>
<span class="text-[var(--text-secondary)]">Invoices Paid</span>
<span class="font-medium text-[var(--text-primary)]">{dashboard.recent_activity?.invoices_paid_30d || 0}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600">Total Invoiced</span>
<span class="font-medium">
<span class="text-[var(--text-secondary)]">Total Invoiced</span>
<span class="font-medium text-[var(--text-primary)]">
{formatCurrency(Number(dashboard.recent_activity?.invoiced_30d || 0), 'USD')}
</span>
</div>
@ -225,14 +226,14 @@
</div>
<!-- Status Breakdown -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<h3 class="mb-4 text-lg font-semibold text-gray-900">Invoice Status</h3>
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<h3 class="mb-4 text-lg font-semibold text-[var(--text-primary)]">Invoice Status</h3>
<div class="space-y-3">
{#each Object.entries(dashboard.status_counts || {}) as [status, count]}
<div class="flex items-center gap-3">
<div class="h-3 w-3 rounded-full {getStatusColor(status)}"></div>
<span class="flex-1 text-gray-600">{status.replace('_', ' ')}</span>
<span class="font-medium">{count}</span>
<span class="flex-1 text-[var(--text-secondary)]">{status.replace('_', ' ')}</span>
<span class="font-medium text-[var(--text-primary)]">{count}</span>
</div>
{/each}
</div>
@ -240,9 +241,9 @@
</div>
<!-- Revenue Chart Section -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<div class="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<h3 class="text-lg font-semibold text-gray-900">Revenue Over Time</h3>
<h3 class="text-lg font-semibold text-[var(--text-primary)]">Revenue Over Time</h3>
<div class="flex flex-wrap gap-3">
<input
type="date"
@ -252,7 +253,7 @@
...filters,
startDate: /** @type {HTMLInputElement} */ (e.target).value
})}
class="rounded-lg border px-3 py-1.5 text-sm"
class="rounded-lg border border-[var(--border-default)] bg-[var(--surface-default)] px-3 py-1.5 text-sm text-[var(--text-primary)]"
/>
<input
type="date"
@ -262,7 +263,7 @@
...filters,
endDate: /** @type {HTMLInputElement} */ (e.target).value
})}
class="rounded-lg border px-3 py-1.5 text-sm"
class="rounded-lg border border-[var(--border-default)] bg-[var(--surface-default)] px-3 py-1.5 text-sm text-[var(--text-primary)]"
/>
<select
value={filters.groupBy}
@ -271,7 +272,7 @@
...filters,
groupBy: /** @type {HTMLSelectElement} */ (e.target).value
})}
class="rounded-lg border px-3 py-1.5 text-sm"
class="rounded-lg border border-[var(--border-default)] bg-[var(--surface-default)] px-3 py-1.5 text-sm text-[var(--text-primary)]"
>
{#each GROUP_BY_OPTIONS as option}
<option value={option.value}>{option.label}</option>
@ -284,28 +285,28 @@
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b">
<th class="py-2 text-left font-medium text-gray-500">Period</th>
<th class="py-2 text-right font-medium text-gray-500">Invoices</th>
<th class="py-2 text-right font-medium text-gray-500">Revenue</th>
<tr class="border-b border-[var(--border-default)]">
<th class="py-2 text-left font-medium text-[var(--text-secondary)]">Period</th>
<th class="py-2 text-right font-medium text-[var(--text-secondary)]">Invoices</th>
<th class="py-2 text-right font-medium text-[var(--text-secondary)]">Revenue</th>
</tr>
</thead>
<tbody>
{#each revenue.data as item}
<tr class="border-b last:border-b-0">
<td class="py-3 text-gray-700">{formatDate(item.period)}</td>
<td class="py-3 text-right text-gray-600">{item.count}</td>
<td class="py-3 text-right font-medium text-green-600">
<tr class="border-b border-[var(--border-default)] last:border-b-0">
<td class="py-3 text-[var(--text-primary)]">{formatDate(item.period)}</td>
<td class="py-3 text-right text-[var(--text-secondary)]">{item.count}</td>
<td class="py-3 text-right font-medium text-[var(--color-success-default)]">
{formatCurrency(Number(item.revenue), 'USD')}
</td>
</tr>
{/each}
</tbody>
<tfoot>
<tr class="border-t bg-gray-50">
<td class="py-3 font-medium">Total</td>
<td class="py-3 text-right font-medium">{revenue.total?.count || 0}</td>
<td class="py-3 text-right font-bold text-green-600">
<tr class="border-t border-[var(--border-default)] bg-[var(--surface-sunken)]">
<td class="py-3 font-medium text-[var(--text-primary)]">Total</td>
<td class="py-3 text-right font-medium text-[var(--text-primary)]">{revenue.total?.count || 0}</td>
<td class="py-3 text-right font-bold text-[var(--color-success-default)]">
{formatCurrency(Number(revenue.total?.revenue || 0), 'USD')}
</td>
</tr>
@ -313,88 +314,88 @@
</table>
</div>
{:else}
<div class="py-8 text-center text-gray-500">No revenue data for the selected period</div>
<div class="py-8 text-center text-[var(--text-secondary)]">No revenue data for the selected period</div>
{/if}
</div>
<!-- Aging Report -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<h3 class="mb-6 text-lg font-semibold text-gray-900">Accounts Receivable Aging</h3>
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<h3 class="mb-6 text-lg font-semibold text-[var(--text-primary)]">Accounts Receivable Aging</h3>
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-5">
<!-- Current -->
<div class="rounded-lg bg-green-50 p-4">
<p class="text-sm font-medium text-green-700">Current</p>
<p class="mt-1 text-xl font-bold text-green-800">
<div class="rounded-lg bg-[var(--color-success-light)] p-4">
<p class="text-sm font-medium text-[var(--color-success-default)]">Current</p>
<p class="mt-1 text-xl font-bold text-[var(--color-success-default)]">
{formatCurrency(Number(aging.current?.amount || 0), 'USD')}
</p>
<p class="text-sm text-green-600">{aging.current?.count || 0} invoices</p>
<p class="text-sm text-[var(--color-success-default)]/80">{aging.current?.count || 0} invoices</p>
</div>
<!-- 1-30 Days -->
<div class="rounded-lg bg-yellow-50 p-4">
<p class="text-sm font-medium text-yellow-700">1-30 Days</p>
<p class="mt-1 text-xl font-bold text-yellow-800">
<div class="rounded-lg bg-[var(--stage-negotiation-bg)] p-4">
<p class="text-sm font-medium text-[var(--stage-negotiation)]">1-30 Days</p>
<p class="mt-1 text-xl font-bold text-[var(--stage-negotiation)]">
{formatCurrency(Number(aging['1_30_days']?.amount || 0), 'USD')}
</p>
<p class="text-sm text-yellow-600">{aging['1_30_days']?.count || 0} invoices</p>
<p class="text-sm text-[var(--stage-negotiation)]/80">{aging['1_30_days']?.count || 0} invoices</p>
</div>
<!-- 31-60 Days -->
<div class="rounded-lg bg-orange-50 p-4">
<p class="text-sm font-medium text-orange-700">31-60 Days</p>
<p class="mt-1 text-xl font-bold text-orange-800">
<div class="rounded-lg bg-[var(--color-primary-light)] p-4">
<p class="text-sm font-medium text-[var(--color-primary-default)]">31-60 Days</p>
<p class="mt-1 text-xl font-bold text-[var(--color-primary-default)]">
{formatCurrency(Number(aging['31_60_days']?.amount || 0), 'USD')}
</p>
<p class="text-sm text-orange-600">{aging['31_60_days']?.count || 0} invoices</p>
<p class="text-sm text-[var(--color-primary-default)]/80">{aging['31_60_days']?.count || 0} invoices</p>
</div>
<!-- 61-90 Days -->
<div class="rounded-lg bg-red-50 p-4">
<p class="text-sm font-medium text-red-700">61-90 Days</p>
<p class="mt-1 text-xl font-bold text-red-800">
<div class="rounded-lg bg-[var(--color-negative-light)] p-4">
<p class="text-sm font-medium text-[var(--color-negative-default)]">61-90 Days</p>
<p class="mt-1 text-xl font-bold text-[var(--color-negative-default)]">
{formatCurrency(Number(aging['61_90_days']?.amount || 0), 'USD')}
</p>
<p class="text-sm text-red-600">{aging['61_90_days']?.count || 0} invoices</p>
<p class="text-sm text-[var(--color-negative-default)]/80">{aging['61_90_days']?.count || 0} invoices</p>
</div>
<!-- Over 90 Days -->
<div class="rounded-lg bg-red-100 p-4">
<p class="text-sm font-medium text-red-800">Over 90 Days</p>
<p class="mt-1 text-xl font-bold text-red-900">
<div class="rounded-lg bg-[var(--color-negative-default)]/20 p-4">
<p class="text-sm font-medium text-[var(--color-negative-default)]">Over 90 Days</p>
<p class="mt-1 text-xl font-bold text-[var(--color-negative-default)]">
{formatCurrency(Number(aging['over_90_days']?.amount || 0), 'USD')}
</p>
<p class="text-sm text-red-700">{aging['over_90_days']?.count || 0} invoices</p>
<p class="text-sm text-[var(--color-negative-default)]/80">{aging['over_90_days']?.count || 0} invoices</p>
</div>
</div>
<!-- Total Outstanding -->
<div class="flex items-center justify-between border-t pt-4">
<span class="font-medium text-gray-600">Total Outstanding</span>
<div class="flex items-center justify-between border-t border-[var(--border-default)] pt-4">
<span class="font-medium text-[var(--text-secondary)]">Total Outstanding</span>
<div class="text-right">
<p class="text-2xl font-bold text-gray-900">
<p class="text-2xl font-bold text-[var(--text-primary)]">
{formatCurrency(Number(aging.total?.amount || 0), 'USD')}
</p>
<p class="text-sm text-gray-500">{aging.total?.count || 0} invoices</p>
<p class="text-sm text-[var(--text-secondary)]">{aging.total?.count || 0} invoices</p>
</div>
</div>
</div>
<!-- Estimates Summary -->
<div class="rounded-xl border bg-white p-6 shadow-sm">
<h3 class="mb-4 text-lg font-semibold text-gray-900">Estimates Overview</h3>
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<h3 class="mb-4 text-lg font-semibold text-[var(--text-primary)]">Estimates Overview</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div class="rounded-lg bg-blue-50 p-4 text-center">
<p class="text-3xl font-bold text-blue-600">{dashboard.estimates?.pending || 0}</p>
<p class="mt-1 text-sm text-blue-700">Pending</p>
<div class="rounded-lg bg-[var(--color-primary-light)] p-4 text-center">
<p class="text-3xl font-bold text-[var(--color-primary-default)]">{dashboard.estimates?.pending || 0}</p>
<p class="mt-1 text-sm text-[var(--color-primary-default)]">Pending</p>
</div>
<div class="rounded-lg bg-green-50 p-4 text-center">
<p class="text-3xl font-bold text-green-600">{dashboard.estimates?.accepted || 0}</p>
<p class="mt-1 text-sm text-green-700">Accepted</p>
<div class="rounded-lg bg-[var(--color-success-light)] p-4 text-center">
<p class="text-3xl font-bold text-[var(--color-success-default)]">{dashboard.estimates?.accepted || 0}</p>
<p class="mt-1 text-sm text-[var(--color-success-default)]">Accepted</p>
</div>
<div class="rounded-lg bg-red-50 p-4 text-center">
<p class="text-3xl font-bold text-red-600">{dashboard.estimates?.declined || 0}</p>
<p class="mt-1 text-sm text-red-700">Declined</p>
<div class="rounded-lg bg-[var(--color-negative-light)] p-4 text-center">
<p class="text-3xl font-bold text-[var(--color-negative-default)]">{dashboard.estimates?.declined || 0}</p>
<p class="mt-1 text-sm text-[var(--color-negative-default)]">Declined</p>
</div>
</div>
</div>

View file

@ -355,7 +355,7 @@
</svg>
Filters
{#if activeFiltersCount() > 0}
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
{activeFiltersCount()}
</span>
{/if}
@ -400,26 +400,28 @@
<CrmTable data={templates} {columns} bind:visibleColumns onRowClick={handleRowClick}>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<span class="mb-4 text-4xl">🎨</span>
<h3 class="text-foreground text-lg font-medium">No templates yet</h3>
<p class="text-muted-foreground text-sm">Create your first invoice template</p>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<span class="text-4xl">🎨</span>
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No templates yet</h3>
<p class="text-[var(--text-secondary)] text-sm">Create your first invoice template</p>
</div>
{/snippet}
{#snippet cellContent(row, column)}
{#if column.key === 'isDefault'}
{#if row.isDefault}
<span
class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700"
class="inline-flex items-center rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
Default
</span>
{:else}
<span class="text-gray-400">-</span>
<span class="text-[var(--text-tertiary)]">-</span>
{/if}
{:else if column.key === 'primaryColor'}
<div class="flex items-center gap-2">
<div class="h-5 w-5 rounded border" style="background-color: {row.primaryColor}"></div>
<span class="text-xs text-gray-500">{row.primaryColor}</span>
<div class="h-5 w-5 rounded border border-[var(--border-default)]" style="background-color: {row.primaryColor}"></div>
<span class="text-xs text-[var(--text-secondary)]">{row.primaryColor}</span>
</div>
{:else}
{row[column.key] || '-'}
@ -479,7 +481,7 @@
></div>
</div>
{#if selectedTemplate.footerText}
<p class="text-xs text-gray-500 italic">{selectedTemplate.footerText}</p>
<p class="text-xs text-[var(--text-secondary)] italic">{selectedTemplate.footerText}</p>
{/if}
</div>
</div>

View file

@ -184,79 +184,79 @@
];
let visibleColumns = $state([...DEFAULT_VISIBLE_COLUMNS]);
// Source options for leads
// Source options for leads - using design system tokens
const sourceOptions = [
{
value: 'call',
label: 'Call',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--activity-call)]/10 text-[var(--activity-call)] dark:bg-[var(--activity-call)]/15'
},
{
value: 'email',
label: 'Email',
color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
color: 'bg-[var(--activity-email)]/10 text-[var(--activity-email)] dark:bg-[var(--activity-email)]/15'
},
{
value: 'existing customer',
label: 'Existing Customer',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15'
},
{
value: 'partner',
label: 'Partner',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
},
{
value: 'public relations',
label: 'Public Relations',
color: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-400'
color: 'bg-[var(--activity-meeting)]/10 text-[var(--activity-meeting)] dark:bg-[var(--activity-meeting)]/15'
},
{
value: 'campaign',
label: 'Campaign',
color: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400'
color: 'bg-[var(--stage-qualified)]/10 text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
},
{
value: 'other',
label: 'Other',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] dark:bg-[var(--surface-sunken)]'
}
];
// Salutation options
// Salutation options - using design system tokens
const salutationOptions = [
{
value: 'Mr',
label: 'Mr',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'Mrs',
label: 'Mrs',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'Ms',
label: 'Ms',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'Dr',
label: 'Dr',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
},
{
value: 'Prof',
label: 'Prof',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}
];
// Currency options for select
// Currency options for select - using design system tokens
const currencyOptions = CURRENCY_CODES.filter((c) => c.value).map((c) => ({
value: c.value,
label: c.label,
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}));
// Full drawer columns for NotionDrawer (all lead fields)
@ -1443,23 +1443,23 @@
<!-- View Toggle -->
<ViewToggle view={viewMode} onchange={setViewMode} />
<div class="bg-border mx-1 h-6 w-px"></div>
<div class="mx-1 h-6 w-px bg-[var(--border-default)]"></div>
<!-- Status Filter Chips -->
<!-- Status Filter Chips - Using design system tokens -->
<div class="flex gap-1">
<button
type="button"
onclick={() => (statusChipFilter = 'ALL')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'ALL'
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--text-primary)] text-[var(--surface-default)]'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
All
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--text-secondary)] text-[var(--surface-default)]'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{totalLeadCount}
</span>
@ -1469,14 +1469,14 @@
onclick={() => (statusChipFilter = 'open')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'open'
? 'bg-blue-600 text-white dark:bg-blue-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Open
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'open'
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-hover)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{openCount}
</span>
@ -1486,21 +1486,21 @@
onclick={() => (statusChipFilter = 'lost')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'lost'
? 'bg-red-600 text-white dark:bg-red-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--stage-lost)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Lost
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'lost'
? 'bg-red-700 text-red-100 dark:bg-red-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--stage-lost)]/80 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{lostCount}
</span>
</button>
</div>
<div class="bg-border mx-1 h-6 w-px"></div>
<div class="mx-1 h-6 w-px bg-[var(--border-default)]"></div>
<!-- Filter Toggle Button -->
<Button
@ -1513,7 +1513,7 @@
Filters
{#if activeFiltersCount > 0}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
>
{activeFiltersCount}
</span>
@ -1530,7 +1530,7 @@
Columns
{#if visibleColumns.length < columns.length}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
>
{visibleColumns.length}/{columns.length}
</span>
@ -1620,8 +1620,11 @@
<!-- Table View -->
{#if filteredLeads.length === 0}
<div class="flex flex-col items-center justify-center py-16 text-center">
<User class="mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">No leads found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<User class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-lg font-medium text-[var(--text-primary)]">No leads found</h3>
<p class="mt-1 text-sm text-[var(--text-secondary)]">Create a new lead to get started</p>
</div>
{:else}
<!-- Desktop Table using CrmTable -->
@ -1635,29 +1638,31 @@
>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<User class="mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">No leads found</h3>
<div class="mb-4 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
<User class="size-6 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-lg font-medium text-[var(--text-primary)]">No leads found</h3>
</div>
{/snippet}
</CrmTable>
</div>
<!-- Mobile Card View -->
<div class="divide-y md:hidden dark:divide-gray-800">
<div class="divide-y divide-[var(--border-default)]/50 md:hidden">
{#each filteredLeads as lead (lead.id)}
<button
type="button"
class="flex w-full items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-gray-50/30 dark:hover:bg-gray-800/30"
class="flex w-full items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
onclick={() => openLead(lead)}
>
<div class="min-w-0 flex-1">
<div class="flex items-start justify-between gap-2">
<div>
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
<p class="text-sm font-medium text-[var(--text-primary)]">
{getFullName(lead)}
</p>
{#if lead.company}
<p class="text-sm text-gray-500 dark:text-gray-400">
<p class="text-sm text-[var(--text-secondary)]">
{typeof lead.company === 'object' ? lead.company.name : lead.company}
</p>
{/if}
@ -1672,7 +1677,7 @@
</span>
</div>
<div
class="mt-2 flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-gray-400"
class="mt-2 flex flex-wrap items-center gap-3 text-xs text-[var(--text-secondary)]"
>
{#if lead.rating}
<span
@ -1694,7 +1699,7 @@
<button
type="button"
onclick={openCreate}
class="flex w-full items-center gap-2 px-4 py-3 text-sm text-gray-500 transition-colors hover:bg-gray-50 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-300"
class="flex w-full items-center gap-2 px-4 py-3 text-sm text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-raised)] hover:text-[var(--text-primary)]"
>
<Plus class="h-4 w-4" />
New

View file

@ -60,59 +60,59 @@
let accountName = $state('');
let accountIdFromUrl = $state('');
// Stage options with colors
// Stage options with colors - using design system tokens
const stageOptions = [
{
value: 'PROSPECTING',
label: 'Prospecting',
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
},
{
value: 'QUALIFICATION',
label: 'Qualification',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
},
{
value: 'PROPOSAL',
label: 'Proposal',
color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
color: 'bg-[var(--stage-proposal-bg)] text-[var(--stage-proposal)] dark:bg-[var(--stage-proposal)]/15'
},
{
value: 'NEGOTIATION',
label: 'Negotiation',
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
},
{
value: 'CLOSED_WON',
label: 'Won',
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
color: 'bg-[var(--stage-won-bg)] text-[var(--stage-won)] dark:bg-[var(--stage-won)]/15'
},
{
value: 'CLOSED_LOST',
label: 'Lost',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
}
];
// Type options
// Type options - using design system tokens
const typeOptions = OPPORTUNITY_TYPES.map((t) => ({
value: t.value,
label: t.label,
color: 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}));
// Source options
// Source options - using design system tokens
const sourceOptions = OPPORTUNITY_SOURCES.map((s) => ({
value: s.value,
label: s.label,
color: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400'
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
}));
// Currency options
// Currency options - using design system tokens
const currencyOptions = CURRENCY_CODES.filter((c) => c.value).map((c) => ({
value: c.value,
label: c.label,
color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
}));
/**
@ -1098,14 +1098,14 @@
onclick={() => (statusChipFilter = 'ALL')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'ALL'
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
All
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{opportunities.length}
</span>
@ -1115,14 +1115,14 @@
onclick={() => (statusChipFilter = 'open')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'open'
? 'bg-blue-600 text-white dark:bg-blue-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--stage-qualified)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Open
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'open'
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-black/20 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{openCount}
</span>
@ -1132,14 +1132,14 @@
onclick={() => (statusChipFilter = 'won')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'won'
? 'bg-emerald-600 text-white dark:bg-emerald-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--stage-won)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Won
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'won'
? 'bg-emerald-700 text-emerald-100 dark:bg-emerald-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-black/20 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{wonCount}
</span>
@ -1149,14 +1149,14 @@
onclick={() => (statusChipFilter = 'lost')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'lost'
? 'bg-red-600 text-white dark:bg-red-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--stage-lost)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Lost
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'lost'
? 'bg-red-700 text-red-100 dark:bg-red-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-black/20 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{lostCount}
</span>
@ -1176,7 +1176,7 @@
Filters
{#if activeFiltersCount > 0}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{activeFiltersCount}
</span>
@ -1192,7 +1192,7 @@
Columns
{#if visibleColumns.length < columns.length}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{visibleColumns.length}/{columns.length}
</span>
@ -1261,8 +1261,11 @@
<!-- Table View -->
{#if filteredOpportunities.length === 0}
<div class="flex flex-col items-center justify-center py-16 text-center">
<Target class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No opportunities found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<Target class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No opportunities found</h3>
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new opportunity</p>
</div>
{:else}
<CrmTable
@ -1274,8 +1277,10 @@
>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<Target class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No opportunities found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<Target class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No opportunities found</h3>
</div>
{/snippet}
</CrmTable>
@ -1311,37 +1316,37 @@
{#if drawerMode !== 'create' && selectedRow}
<div class="mb-4">
<p
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
>
Details
</p>
<div class="grid grid-cols-2 gap-3 text-sm">
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Account</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Account</p>
<p class="font-medium text-[var(--text-primary)]">
{selectedRow.account?.name || '-'}
</p>
</div>
{#if selectedRow.createdAt}
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Created</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Created</p>
<p class="font-medium text-[var(--text-primary)]">
{formatRelativeDate(selectedRow.createdAt)}
</p>
</div>
{/if}
{#if isClosed && selectedRow.closedBy}
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Closed By</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Closed By</p>
<p class="font-medium text-[var(--text-primary)]">
{selectedRow.closedBy.name || '-'}
</p>
</div>
{/if}
{#if isClosed && selectedRow.closedOn}
<div>
<p class="text-xs text-gray-400 dark:text-gray-500">Closed On</p>
<p class="font-medium text-gray-900 dark:text-gray-100">
<p class="text-xs text-[var(--text-tertiary)]">Closed On</p>
<p class="font-medium text-[var(--text-primary)]">
{formatDate(selectedRow.closedOn)}
</p>
</div>
@ -1350,16 +1355,16 @@
</div>
<!-- Products Section -->
<div class="mt-6 border-t pt-4 dark:border-gray-700">
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
<div class="mb-3 flex items-center justify-between">
<p
class="flex items-center gap-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
class="flex items-center gap-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
>
<Package class="h-4 w-4" />
<Package class="size-4" />
Products
</p>
{#if hasLineItems}
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
<span class="text-sm font-medium text-[var(--text-primary)]">
Total: {formatCurrency(
selectedRow.lineItemsTotal || 0,
selectedRow.currency || 'USD'
@ -1373,13 +1378,13 @@
<div class="mb-4 space-y-2">
{#each lineItems as item (item.id)}
<div
class="flex items-center justify-between rounded-lg border bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-800"
class="flex items-center justify-between rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-sunken)] px-3 py-2"
>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900 dark:text-gray-100">
<p class="truncate text-sm font-medium text-[var(--text-primary)]">
{item.productName || item.name || 'Custom Item'}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-[var(--text-secondary)]">
{item.quantity} x {formatCurrency(
item.unitPrice,
selectedRow.currency || 'USD'
@ -1390,12 +1395,12 @@
{#if !isClosed}
<button
type="button"
class="ml-2 rounded p-1 text-gray-400 hover:bg-gray-200 hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-700 dark:hover:text-red-400"
class="ml-2 rounded-[var(--radius-md)] p-1 text-[var(--text-tertiary)] hover:bg-[var(--surface-raised)] hover:text-[var(--color-negative-default)] disabled:cursor-not-allowed disabled:opacity-50"
onclick={() => deleteLineItem(item.id)}
disabled={isDeletingLineItem === item.id}
>
{#if isDeletingLineItem === item.id}
<svg class="h-4 w-4 animate-spin" viewBox="0 0 24 24">
<svg class="size-4 animate-spin" viewBox="0 0 24 24">
<circle
class="opacity-25"
cx="12"
@ -1412,7 +1417,7 @@
></path>
</svg>
{:else}
<Trash2 class="h-4 w-4" />
<Trash2 class="size-4" />
{/if}
</button>
{/if}
@ -1420,15 +1425,15 @@
{/each}
</div>
{:else}
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">No products added yet.</p>
<p class="mb-4 text-sm text-[var(--text-secondary)]">No products added yet.</p>
{/if}
<!-- Add product form (only if not closed) -->
{#if !isClosed}
<div
class="space-y-3 rounded-lg border bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800/50"
class="space-y-3 rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-sunken)] p-3"
>
<p class="text-xs font-medium text-gray-600 dark:text-gray-400">Add Product</p>
<p class="text-xs font-medium text-[var(--text-secondary)]">Add Product</p>
<div class="grid grid-cols-1 gap-2">
<Select.Root
type="single"
@ -1446,7 +1451,7 @@
'Select product...'}
</Select.Trigger>
<Select.Content class="max-h-60">
<div class="sticky top-0 bg-white p-2 dark:bg-gray-800">
<div class="sticky top-0 bg-[var(--surface-raised)] p-2">
<Input
type="text"
placeholder="Search products..."
@ -1500,7 +1505,7 @@
</div>
<!-- Comments Section -->
<div class="mt-6 border-t pt-4 dark:border-gray-700">
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
<CommentSection
entityId={selectedRow.id}
entityType="opportunity"
@ -1521,20 +1526,20 @@
{#if !isClosed}
<Button
variant="outline"
class="text-green-600 hover:bg-green-50 hover:text-green-700 dark:text-green-400 dark:hover:bg-green-900/30 dark:hover:text-green-300"
class="text-[var(--stage-won)] hover:bg-[var(--stage-won-bg)] hover:text-[var(--stage-won)]"
onclick={handleMarkWon}
disabled={isLoading}
>
<Trophy class="mr-1.5 h-4 w-4" />
<Trophy class="mr-1.5 size-4" />
Mark Won
</Button>
<Button
variant="outline"
class="text-red-600 hover:bg-red-50 hover:text-red-700 dark:text-red-400 dark:hover:bg-red-900/30 dark:hover:text-red-300"
class="text-[var(--stage-lost)] hover:bg-[var(--stage-lost-bg)] hover:text-[var(--stage-lost)]"
onclick={handleMarkLost}
disabled={isLoading}
>
<XCircle class="mr-1.5 h-4 w-4" />
<XCircle class="mr-1.5 size-4" />
Mark Lost
</Button>
{/if}
@ -1545,10 +1550,10 @@
<Button
{...props}
variant="outline"
class="text-blue-600 hover:bg-blue-50 hover:text-blue-700 dark:text-blue-400 dark:hover:bg-blue-900/30 dark:hover:text-blue-300"
class="text-[var(--color-primary-default)] hover:bg-[var(--color-primary-light)] hover:text-[var(--color-primary-default)]"
disabled={isLoading}
>
<Receipt class="mr-1.5 h-4 w-4" />
<Receipt class="mr-1.5 size-4" />
Create Invoice
</Button>
{/snippet}

View file

@ -103,14 +103,14 @@
<div class="flex-1 space-y-6 p-4 md:p-6">
<!-- Success/Error Messages -->
{#if form?.success}
<Card.Root class="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-900/20">
<Card.Root class="border-[var(--color-success-default)]/20 bg-[var(--color-success-light)]">
<Card.Content class="flex items-center gap-3 p-4">
<div
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-green-100 dark:bg-green-800"
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/20"
>
<Check class="h-4 w-4 text-green-600 dark:text-green-300" />
<Check class="h-4 w-4 text-[var(--color-success-default)]" />
</div>
<p class="text-sm font-medium text-green-800 dark:text-green-200">
<p class="text-sm font-medium text-[var(--color-success-default)]">
{form.message}
</p>
</Card.Content>
@ -118,14 +118,14 @@
{/if}
{#if form?.error}
<Card.Root class="border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/20">
<Card.Root class="border-[var(--color-negative-default)]/20 bg-[var(--color-negative-light)]">
<Card.Content class="flex items-center gap-3 p-4">
<div
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-800"
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-negative-light)] dark:bg-[var(--color-negative-default)]/20"
>
<X class="h-4 w-4 text-red-600 dark:text-red-300" />
<X class="h-4 w-4 text-[var(--color-negative-default)]" />
</div>
<p class="text-sm font-medium text-red-800 dark:text-red-200">
<p class="text-sm font-medium text-[var(--color-negative-default)]">
{form.error}
</p>
</Card.Content>
@ -146,7 +146,7 @@
class=""
/>
{/if}
<Avatar.Fallback class="bg-gradient-to-br from-blue-500 to-purple-600 text-white">
<Avatar.Fallback class="bg-[var(--color-primary-default)] text-white">
{getInitials(data.user.name)}
</Avatar.Fallback>
</Avatar.Root>
@ -315,7 +315,7 @@
<div class="bg-muted/30 flex items-center justify-between rounded-lg border p-4">
<div class="flex items-center gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-purple-600"
class="flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--color-primary-default)]"
>
<Building2 class="h-5 w-5 text-white" />
</div>

View file

@ -157,7 +157,7 @@
Create and manage tags to organize contacts, companies, deals, and tickets.
</p>
</div>
<Button onclick={openCreate} class="bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0">
<Button onclick={openCreate} class="bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0">
<Plus class="mr-2 h-4 w-4" />
Create tag
</Button>
@ -217,7 +217,7 @@
<p class="mt-1 max-w-sm text-sm text-muted-foreground">
Tags help you organize and filter your CRM records. Create your first tag to get started.
</p>
<Button onclick={openCreate} class="mt-4 bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0">
<Button onclick={openCreate} class="mt-4 bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0">
<Plus class="mr-2 h-4 w-4" />
Create tag
</Button>
@ -457,7 +457,7 @@
<Button
type="submit"
disabled={!formName.trim() || isLoading}
class="bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0"
class="bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0"
>
{isLoading ? 'Creating...' : 'Create tag'}
</Button>
@ -540,7 +540,7 @@
<Button
type="submit"
disabled={!formName.trim() || isLoading}
class="bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0"
class="bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0"
>
{isLoading ? 'Saving...' : 'Save'}
</Button>

View file

@ -56,22 +56,22 @@
let accountNameFromUrl = $state('');
let accountIdFromUrl = $state('');
// Status and priority options with colors
// Status and priority options with colors - using design system tokens
const statusOptions = [
{
value: 'New',
label: 'New',
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
color: 'bg-[var(--task-upcoming-bg)] text-[var(--task-upcoming)] dark:bg-[var(--task-upcoming)]/15'
},
{
value: 'In Progress',
label: 'In Progress',
color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
color: 'bg-[var(--task-due-today-bg)] text-[var(--task-due-today)] dark:bg-[var(--task-due-today)]/15'
},
{
value: 'Completed',
label: 'Completed',
color: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
color: 'bg-[var(--task-completed-bg)] text-[var(--task-completed)] dark:bg-[var(--task-completed)]/15'
}
];
@ -79,17 +79,17 @@
{
value: 'High',
label: 'High',
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
color: 'bg-[var(--priority-high-bg)] text-[var(--priority-high)] dark:bg-[var(--priority-high)]/15'
},
{
value: 'Medium',
label: 'Medium',
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
},
{
value: 'Low',
label: 'Low',
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
color: 'bg-[var(--priority-low-bg)] text-[var(--priority-low)] dark:bg-[var(--priority-low)]/15'
}
];
@ -550,7 +550,7 @@
*/
function getOptionStyle(value, options) {
const option = options.find((o) => o.value === value);
return option?.color ?? 'bg-gray-100 text-gray-600';
return option?.color ?? 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
}
/**
@ -1088,14 +1088,14 @@
onclick={() => (statusChipFilter = 'ALL')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'ALL'
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--color-primary-default)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
All
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-[var(--color-primary-dark)] text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{tasks.length}
</span>
@ -1105,14 +1105,14 @@
onclick={() => (statusChipFilter = 'active')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'active'
? 'bg-blue-600 text-white dark:bg-blue-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--task-due-today)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Active
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'active'
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-black/20 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{activeTaskCount}
</span>
@ -1122,14 +1122,14 @@
onclick={() => (statusChipFilter = 'completed')}
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
'completed'
? 'bg-emerald-600 text-white dark:bg-emerald-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
? 'bg-[var(--task-completed)] text-white'
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
>
Completed
<span
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'completed'
? 'bg-emerald-700 text-emerald-100 dark:bg-emerald-600'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
? 'bg-black/20 text-white/90'
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
>
{completedCount}
</span>
@ -1180,7 +1180,7 @@
Filters
{#if activeFiltersCount > 0}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{activeFiltersCount}
</span>
@ -1196,7 +1196,7 @@
Columns
{#if columnCounts.visible < columnCounts.total}
<span
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
>
{columnCounts.visible}/{columnCounts.total}
</span>
@ -1272,8 +1272,11 @@
>
{#snippet emptyState()}
<div class="flex flex-col items-center justify-center py-16 text-center">
<CheckSquare class="text-muted-foreground/50 mb-4 h-12 w-12" />
<h3 class="text-foreground text-lg font-medium">No tasks found</h3>
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
<CheckSquare class="size-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-[var(--text-primary)] text-lg font-medium">No tasks found</h3>
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new task</p>
</div>
{/snippet}
</CrmTable>
@ -1516,23 +1519,23 @@
<!-- Task metadata (only for existing tasks) -->
{#if selectedTask && !isCreateMode}
<div class="space-y-2 text-sm">
<div class="mb-3 text-[13px] font-medium text-gray-500 dark:text-gray-400">Details</div>
<div class="mb-3 text-[13px] font-medium text-[var(--text-tertiary)]">Details</div>
{#if selectedTask.createdBy}
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-400">
<User class="h-3.5 w-3.5" />
<div class="flex items-center gap-2 text-[var(--text-secondary)]">
<User class="size-3.5" />
<span>Created by {selectedTask.createdBy.name}</span>
</div>
{/if}
{#if selectedTask.createdAt}
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-400">
<Clock class="h-3.5 w-3.5" />
<div class="flex items-center gap-2 text-[var(--text-secondary)]">
<Clock class="size-3.5" />
<span>Created {formatRelativeDate(selectedTask.createdAt)}</span>
</div>
{/if}
</div>
<!-- Comments Section -->
<div class="mt-6 border-t pt-4 dark:border-gray-700">
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
<CommentSection
entityId={selectedTask.id}
entityType="tasks"

View file

@ -259,9 +259,9 @@
<Card.Header class="pb-4">
<div class="flex items-center gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-green-100 dark:bg-green-900/30"
class="flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/15"
>
<Plus class="h-5 w-5 text-green-600 dark:text-green-400" />
<Plus class="h-5 w-5 text-[var(--color-success-default)]" />
</div>
<div>
<Card.Title class="">Add New Member</Card.Title>
@ -312,9 +312,9 @@
<Card.Header class="pb-4">
<div class="flex items-center gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/30"
class="flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15"
>
<Users class="h-5 w-5 text-blue-600 dark:text-blue-400" />
<Users class="h-5 w-5 text-[var(--color-primary-default)]" />
</div>
<div>
<Card.Title class="">Team Members</Card.Title>
@ -345,7 +345,7 @@
<Avatar.Image class="" src={user.avatar} alt={user.name} />
{/if}
<Avatar.Fallback
class="bg-gradient-to-br from-blue-500 to-purple-600 text-sm text-white"
class="bg-[var(--color-primary-default)] text-sm text-white"
>
{getInitials(user.name)}
</Avatar.Fallback>
@ -432,7 +432,7 @@
<Button
variant="ghost"
size="icon"
class="h-8 w-8 text-green-600 hover:bg-green-100"
class="h-8 w-8 text-[var(--color-success-default)] hover:bg-[var(--color-success-light)]"
onclick={() => handleActivateUser(user.id)}
title="Activate user"
>
@ -514,9 +514,9 @@
<Card.Root class="py-12">
<Card.Content class="text-center">
<div
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-purple-100 dark:bg-purple-900/30"
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15"
>
<UsersRound class="h-8 w-8 text-purple-600 dark:text-purple-400" />
<UsersRound class="h-8 w-8 text-[var(--color-primary-default)]" />
</div>
<h3 class="mb-2 text-lg font-semibold">No teams yet</h3>
<p class="text-muted-foreground mx-auto mb-6 max-w-sm text-sm">

View file

@ -15,17 +15,17 @@
<title>Select Organization | BottleCRM</title>
</svelte:head>
<div class="flex min-h-screen flex-col bg-slate-50">
<div class="flex min-h-screen flex-col bg-[var(--surface-sunken)]">
<!-- Header -->
<header class="border-b border-slate-200 bg-white">
<header class="border-b border-[var(--border-default)] bg-[var(--surface-default)]">
<div class="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
<div class="flex items-center gap-3">
<img src={imgLogo} alt="BottleCRM" class="h-8 w-auto" />
<span class="text-lg font-semibold text-slate-900">BottleCRM</span>
<span class="text-lg font-semibold text-[var(--text-primary)]">BottleCRM</span>
</div>
<a
href="/logout"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-raised)] hover:text-[var(--text-primary)]"
>
<LogOut class="h-4 w-4" />
<span class="hidden sm:inline">Sign out</span>
@ -38,8 +38,8 @@
<div class="w-full max-w-2xl">
<!-- Page Header -->
<div class="mb-8 text-center">
<h1 class="text-2xl font-bold text-slate-900">Select an organization</h1>
<p class="mt-2 text-slate-600">Choose which organization you'd like to work in</p>
<h1 class="text-2xl font-bold text-[var(--text-primary)]">Select an organization</h1>
<p class="mt-2 text-[var(--text-secondary)]">Choose which organization you'd like to work in</p>
</div>
<!-- Organizations List -->
@ -64,20 +64,20 @@
<button
type="submit"
disabled={loading}
class="group w-full rounded-xl border border-slate-200 bg-white p-5 text-left shadow-sm transition-all hover:border-slate-300 hover:shadow-md focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none disabled:opacity-60"
class="group w-full rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-5 text-left shadow-sm transition-all hover:border-[var(--border-strong)] hover:shadow-md focus:ring-2 focus:ring-[var(--color-primary-default)] focus:ring-offset-2 focus:outline-none disabled:opacity-60"
>
<div class="flex items-center gap-4">
<!-- Org Icon -->
<div
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-blue-50 text-blue-600 transition-colors group-hover:bg-blue-100"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-[var(--color-primary-light)] text-[var(--color-primary-default)] transition-colors group-hover:bg-[var(--color-primary-default)]/20"
>
<Building2 class="h-6 w-6" />
</div>
<!-- Org Info -->
<div class="min-w-0 flex-1">
<h3 class="truncate font-semibold text-slate-900">{org.name}</h3>
<div class="mt-1 flex items-center gap-3 text-sm text-slate-500">
<h3 class="truncate font-semibold text-[var(--text-primary)]">{org.name}</h3>
<div class="mt-1 flex items-center gap-3 text-sm text-[var(--text-secondary)]">
<span class="inline-flex items-center gap-1 capitalize">
<Users class="h-3.5 w-3.5" />
{org.role?.toLowerCase() || 'Member'}
@ -89,11 +89,11 @@
<div class="shrink-0">
{#if loading && selectedOrgId === org.id}
<div
class="h-5 w-5 animate-spin rounded-full border-2 border-slate-200 border-t-blue-600"
class="h-5 w-5 animate-spin rounded-full border-2 border-[var(--border-default)] border-t-[var(--color-primary-default)]"
></div>
{:else}
<ChevronRight
class="h-5 w-5 text-slate-400 transition-transform group-hover:translate-x-0.5 group-hover:text-slate-600"
class="h-5 w-5 text-[var(--text-tertiary)] transition-transform group-hover:translate-x-0.5 group-hover:text-[var(--text-secondary)]"
/>
{/if}
</div>
@ -107,7 +107,7 @@
<div class="mt-6">
<a
href="/org/new"
class="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-slate-300 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-600 transition-all hover:border-blue-400 hover:bg-blue-50 hover:text-blue-600"
class="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-[var(--border-default)] bg-[var(--surface-sunken)] px-5 py-4 text-sm font-medium text-[var(--text-secondary)] transition-all hover:border-[var(--color-primary-default)] hover:bg-[var(--color-primary-light)] hover:text-[var(--color-primary-default)]"
>
<Plus class="h-4 w-4" />
Create new organization
@ -115,19 +115,19 @@
</div>
{:else}
<!-- Empty State -->
<div class="rounded-xl border border-slate-200 bg-white p-12 text-center shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-12 text-center shadow-sm">
<div
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-slate-100"
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--surface-sunken)]"
>
<Building2 class="h-8 w-8 text-slate-400" />
<Building2 class="h-8 w-8 text-[var(--text-tertiary)]" />
</div>
<h3 class="text-lg font-semibold text-slate-900">No organizations yet</h3>
<p class="mt-2 text-slate-600">
<h3 class="text-lg font-semibold text-[var(--text-primary)]">No organizations yet</h3>
<p class="mt-2 text-[var(--text-secondary)]">
Create your first organization to get started with BottleCRM
</p>
<a
href="/org/new"
class="mt-6 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700"
class="mt-6 inline-flex items-center gap-2 rounded-lg bg-[var(--color-primary-default)] px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-[var(--color-primary-dark)]"
>
<Plus class="h-4 w-4" />
Create organization
@ -136,7 +136,7 @@
{/if}
<!-- Trust Signal -->
<div class="mt-8 flex items-center justify-center gap-2 text-sm text-slate-500">
<div class="mt-8 flex items-center justify-center gap-2 text-sm text-[var(--text-secondary)]">
<Shield class="h-4 w-4" />
<span>Your data stays private and secure</span>
</div>

View file

@ -24,17 +24,17 @@
<title>Create Organization | BottleCRM</title>
</svelte:head>
<div class="flex min-h-screen flex-col bg-slate-50">
<div class="flex min-h-screen flex-col bg-[var(--surface-sunken)]">
<!-- Header -->
<header class="border-b border-slate-200 bg-white">
<header class="border-b border-[var(--border-default)] bg-[var(--surface-default)]">
<div class="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
<div class="flex items-center gap-3">
<img src={imgLogo} alt="BottleCRM" class="h-8 w-auto" />
<span class="text-lg font-semibold text-slate-900">BottleCRM</span>
<span class="text-lg font-semibold text-[var(--text-primary)]">BottleCRM</span>
</div>
<a
href="/org"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-raised)] hover:text-[var(--text-primary)]"
>
<ArrowLeft class="h-4 w-4" />
<span>Back</span>
@ -48,16 +48,16 @@
<!-- Page Header -->
<div class="mb-8 text-center">
<div
class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-50"
class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-primary-light)]"
>
<Sparkles class="h-6 w-6 text-blue-600" />
<Sparkles class="h-6 w-6 text-[var(--color-primary-default)]" />
</div>
<h1 class="text-2xl font-bold text-slate-900">Create organization</h1>
<p class="mt-2 text-slate-600">Set up a new workspace for your team</p>
<h1 class="text-2xl font-bold text-[var(--text-primary)]">Create organization</h1>
<p class="mt-2 text-[var(--text-secondary)]">Set up a new workspace for your team</p>
</div>
<!-- Form Card -->
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
<form
action="/org/new"
method="POST"
@ -72,7 +72,7 @@
>
<!-- Organization Name Field -->
<div class="space-y-2">
<label for="org_name" class="block text-sm font-medium text-slate-700">
<label for="org_name" class="block text-sm font-medium text-[var(--text-secondary)]">
Organization name
</label>
<input
@ -81,30 +81,30 @@
name="org_name"
required
disabled={isSubmitting || !!form?.data}
class="w-full rounded-lg border border-slate-300 px-4 py-2.5 text-slate-900 placeholder-slate-400 transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none disabled:bg-slate-50 disabled:text-slate-500"
class="w-full rounded-lg border border-[var(--border-default)] px-4 py-2.5 text-[var(--text-primary)] placeholder-[var(--text-tertiary)] transition-colors focus:border-[var(--color-primary-default)] focus:ring-2 focus:ring-[var(--color-primary-default)]/20 focus:outline-none disabled:bg-[var(--surface-sunken)] disabled:text-[var(--text-secondary)]"
placeholder="e.g. Acme Inc."
/>
<p class="text-xs text-slate-500">This will be your workspace name in BottleCRM</p>
<p class="text-xs text-[var(--text-secondary)]">This will be your workspace name in BottleCRM</p>
</div>
<!-- Error Message -->
{#if form?.error}
<div class="flex items-start gap-3 rounded-lg border border-red-200 bg-red-50 p-4">
<AlertCircle class="h-5 w-5 shrink-0 text-red-500" />
<div class="flex items-start gap-3 rounded-lg border border-[var(--color-negative-default)]/20 bg-[var(--color-negative-light)] p-4">
<AlertCircle class="h-5 w-5 shrink-0 text-[var(--color-negative-default)]" />
<div>
<p class="text-sm font-medium text-red-800">Unable to create organization</p>
<p class="mt-1 text-sm text-red-600">{form.error.name || 'Please try again'}</p>
<p class="text-sm font-medium text-[var(--color-negative-default)]">Unable to create organization</p>
<p class="mt-1 text-sm text-[var(--color-negative-default)]/80">{form.error.name || 'Please try again'}</p>
</div>
</div>
{/if}
<!-- Success Message -->
{#if form?.data}
<div class="flex items-start gap-3 rounded-lg border border-green-200 bg-green-50 p-4">
<Check class="h-5 w-5 shrink-0 text-green-500" />
<div class="flex items-start gap-3 rounded-lg border border-[var(--color-success-default)]/20 bg-[var(--color-success-light)] p-4">
<Check class="h-5 w-5 shrink-0 text-[var(--color-success-default)]" />
<div>
<p class="text-sm font-medium text-green-800">Organization created!</p>
<p class="mt-1 text-sm text-green-600">Redirecting to your organizations...</p>
<p class="text-sm font-medium text-[var(--color-success-default)]">Organization created!</p>
<p class="mt-1 text-sm text-[var(--color-success-default)]/80">Redirecting to your organizations...</p>
</div>
</div>
{/if}
@ -113,7 +113,7 @@
<button
type="submit"
disabled={isSubmitting || !!form?.data}
class="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
class="flex w-full items-center justify-center gap-2 rounded-lg bg-[var(--color-primary-default)] px-4 py-2.5 font-medium text-white transition-colors hover:bg-[var(--color-primary-dark)] focus:ring-2 focus:ring-[var(--color-primary-default)] focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
>
{#if isSubmitting}
<div
@ -132,7 +132,7 @@
</div>
<!-- Help Text -->
<p class="mt-6 text-center text-sm text-slate-500">
<p class="mt-6 text-center text-sm text-[var(--text-secondary)]">
You can invite team members after creating your organization
</p>
</div>

View file

@ -18,16 +18,17 @@
let isSubmitting = $state(false);
// Status colors - using design system tokens
function getStatusColor(status) {
const colors = {
Draft: 'bg-gray-100 text-gray-700',
Sent: 'bg-blue-100 text-blue-700',
Viewed: 'bg-indigo-100 text-indigo-700',
Accepted: 'bg-green-100 text-green-700',
Declined: 'bg-red-100 text-red-700',
Expired: 'bg-orange-100 text-orange-700'
Draft: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]',
Sent: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)]',
Viewed: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)]',
Accepted: 'bg-[var(--color-success-light)] text-[var(--color-success-default)]',
Declined: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)]',
Expired: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)]'
};
return colors[status] || 'bg-gray-100 text-gray-700';
return colors[status] || 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
}
function isExpired() {

View file

@ -14,17 +14,18 @@
const secondaryColor = $derived(template?.secondaryColor || '#1E40AF');
const footerText = $derived(template?.footerText || '');
// Status colors - using design system tokens
function getStatusColor(status) {
const colors = {
Draft: 'bg-gray-100 text-gray-700',
Sent: 'bg-blue-100 text-blue-700',
Viewed: 'bg-indigo-100 text-indigo-700',
Partially_Paid: 'bg-yellow-100 text-yellow-700',
Paid: 'bg-green-100 text-green-700',
Overdue: 'bg-red-100 text-red-700',
Cancelled: 'bg-gray-200 text-gray-500'
Draft: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]',
Sent: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)]',
Viewed: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)]',
Partially_Paid: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)]',
Paid: 'bg-[var(--color-success-light)] text-[var(--color-success-default)]',
Overdue: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)]',
Cancelled: 'bg-[var(--surface-sunken)] text-[var(--text-tertiary)]'
};
return colors[status] || 'bg-gray-100 text-gray-700';
return colors[status] || 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
}
function downloadPDF() {