@nextsparkjs/theme-crm 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CRM_PLAN.md +343 -0
  2. package/about.md +122 -0
  3. package/config/app.config.ts +185 -0
  4. package/config/billing.config.ts +187 -0
  5. package/config/dashboard.config.ts +372 -0
  6. package/config/dev.config.ts +55 -0
  7. package/config/features.config.ts +336 -0
  8. package/config/flows.config.ts +511 -0
  9. package/config/permissions.config.ts +297 -0
  10. package/config/theme.config.ts +111 -0
  11. package/entities/activities/activities.config.ts +61 -0
  12. package/entities/activities/activities.fields.ts +362 -0
  13. package/entities/activities/activities.service.ts +503 -0
  14. package/entities/activities/activities.types.ts +117 -0
  15. package/entities/activities/messages/en.json +123 -0
  16. package/entities/activities/messages/es.json +123 -0
  17. package/entities/activities/migrations/020_activities_table.sql +123 -0
  18. package/entities/activities/migrations/021_activities_metas.sql +114 -0
  19. package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
  20. package/entities/campaigns/campaigns.config.ts +61 -0
  21. package/entities/campaigns/campaigns.fields.ts +413 -0
  22. package/entities/campaigns/campaigns.service.ts +426 -0
  23. package/entities/campaigns/campaigns.types.ts +124 -0
  24. package/entities/campaigns/messages/en.json +145 -0
  25. package/entities/campaigns/messages/es.json +145 -0
  26. package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
  27. package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
  28. package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
  29. package/entities/companies/companies.config.ts +61 -0
  30. package/entities/companies/companies.fields.ts +429 -0
  31. package/entities/companies/companies.service.ts +566 -0
  32. package/entities/companies/companies.types.ts +125 -0
  33. package/entities/companies/messages/en.json +146 -0
  34. package/entities/companies/messages/es.json +146 -0
  35. package/entities/companies/migrations/001_companies_table.sql +150 -0
  36. package/entities/companies/migrations/002_companies_metas.sql +114 -0
  37. package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
  38. package/entities/contacts/contacts.config.ts +61 -0
  39. package/entities/contacts/contacts.fields.ts +359 -0
  40. package/entities/contacts/contacts.service.ts +509 -0
  41. package/entities/contacts/contacts.types.ts +108 -0
  42. package/entities/contacts/messages/en.json +117 -0
  43. package/entities/contacts/messages/es.json +117 -0
  44. package/entities/contacts/migrations/001_contacts_table.sql +134 -0
  45. package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
  46. package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
  47. package/entities/leads/leads.config.ts +61 -0
  48. package/entities/leads/leads.fields.ts +336 -0
  49. package/entities/leads/leads.service.ts +496 -0
  50. package/entities/leads/leads.types.ts +114 -0
  51. package/entities/leads/messages/en.json +132 -0
  52. package/entities/leads/messages/es.json +132 -0
  53. package/entities/leads/migrations/001_leads_table.sql +150 -0
  54. package/entities/leads/migrations/002_leads_metas.sql +120 -0
  55. package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
  56. package/entities/notes/messages/en.json +114 -0
  57. package/entities/notes/messages/es.json +114 -0
  58. package/entities/notes/migrations/020_notes_table.sql +118 -0
  59. package/entities/notes/migrations/021_notes_metas.sql +114 -0
  60. package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
  61. package/entities/notes/notes.config.ts +61 -0
  62. package/entities/notes/notes.fields.ts +283 -0
  63. package/entities/notes/notes.service.ts +320 -0
  64. package/entities/notes/notes.types.ts +102 -0
  65. package/entities/opportunities/messages/en.json +107 -0
  66. package/entities/opportunities/messages/es.json +107 -0
  67. package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
  68. package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
  69. package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
  70. package/entities/opportunities/opportunities.config.ts +61 -0
  71. package/entities/opportunities/opportunities.fields.ts +416 -0
  72. package/entities/opportunities/opportunities.service.ts +525 -0
  73. package/entities/opportunities/opportunities.types.ts +135 -0
  74. package/entities/pipelines/messages/en.json +115 -0
  75. package/entities/pipelines/messages/es.json +115 -0
  76. package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
  77. package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
  78. package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
  79. package/entities/pipelines/pipelines.config.ts +62 -0
  80. package/entities/pipelines/pipelines.fields.ts +193 -0
  81. package/entities/pipelines/pipelines.service.ts +383 -0
  82. package/entities/pipelines/pipelines.types.ts +78 -0
  83. package/entities/products/messages/en.json +135 -0
  84. package/entities/products/messages/es.json +135 -0
  85. package/entities/products/migrations/001_products_table.sql +117 -0
  86. package/entities/products/migrations/002_products_metas.sql +114 -0
  87. package/entities/products/migrations/003_products_sample_data.sql +247 -0
  88. package/entities/products/products.config.ts +62 -0
  89. package/entities/products/products.fields.ts +361 -0
  90. package/entities/products/products.service.ts +437 -0
  91. package/entities/products/products.types.ts +125 -0
  92. package/lib/crm-constants.ts +77 -0
  93. package/lib/crm-utils.ts +185 -0
  94. package/lib/selectors.ts +333 -0
  95. package/messages/en.json +131 -0
  96. package/messages/es.json +131 -0
  97. package/migrations/999_theme_sample_data.sql +473 -0
  98. package/package.json +18 -0
  99. package/pendings.md +205 -0
  100. package/permissions-matrix.md +216 -0
  101. package/styles/components.css +414 -0
  102. package/styles/crm-theme.css +358 -0
  103. package/styles/globals.css +576 -0
  104. package/styles/variables.css +111 -0
  105. package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
  106. package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
  107. package/templates/dashboard/(main)/activities/page.tsx +297 -0
  108. package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
  109. package/templates/dashboard/(main)/companies/page.tsx +296 -0
  110. package/templates/dashboard/(main)/contacts/page.tsx +347 -0
  111. package/templates/dashboard/(main)/layout.tsx +98 -0
  112. package/templates/dashboard/(main)/leads/page.tsx +335 -0
  113. package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
  114. package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
  115. package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
  116. package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
  117. package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
  118. package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
  119. package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
  120. package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
  121. package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
  122. package/templates/dashboard/(main)/products/create/page.tsx +96 -0
  123. package/templates/dashboard/(main)/products/page.tsx +308 -0
  124. package/templates/shared/ActionButtons.tsx +41 -0
  125. package/templates/shared/CRMDashboard.tsx +519 -0
  126. package/templates/shared/CRMDataTable.tsx +441 -0
  127. package/templates/shared/CRMMetricCard.tsx +76 -0
  128. package/templates/shared/CRMMobileNav.tsx +172 -0
  129. package/templates/shared/CRMSidebar.tsx +346 -0
  130. package/templates/shared/CRMTopBar.tsx +265 -0
  131. package/templates/shared/DealCard.tsx +123 -0
  132. package/templates/shared/EntityCard.tsx +58 -0
  133. package/templates/shared/OpportunityForm.tsx +649 -0
  134. package/templates/shared/PipelineForm.tsx +367 -0
  135. package/templates/shared/PipelineKanban.tsx +194 -0
  136. package/templates/shared/QuickFilters.tsx +47 -0
  137. package/templates/shared/StageColumn.tsx +175 -0
  138. package/templates/shared/StageSelect.tsx +177 -0
  139. package/templates/shared/StagesRepeater.tsx +317 -0
  140. package/templates/shared/index.ts +9 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * CRM Mobile Navigation Component
3
+ * Bottom navigation for mobile devices
4
+ */
5
+
6
+ 'use client'
7
+
8
+ import Link from 'next/link'
9
+ import { usePathname } from 'next/navigation'
10
+ import { useState } from 'react'
11
+ import { cn } from '@nextsparkjs/core/lib/utils'
12
+ import { TeamSwitcherCompact } from '@nextsparkjs/core/components/teams/TeamSwitcherCompact'
13
+ import { useTranslations } from 'next-intl'
14
+ import {
15
+ LayoutDashboard,
16
+ Users,
17
+ UserPlus,
18
+ Building2,
19
+ Layers,
20
+ MoreHorizontal,
21
+ X,
22
+ Package,
23
+ Megaphone,
24
+ Calendar,
25
+ Settings
26
+ } from 'lucide-react'
27
+ import { Sheet, SheetContent, SheetTrigger } from '@nextsparkjs/core/components/ui/sheet'
28
+
29
+ interface NavItem {
30
+ href: string
31
+ icon: React.ElementType
32
+ labelKey: string
33
+ }
34
+
35
+ const mainNavItems: NavItem[] = [
36
+ { href: '/dashboard', icon: LayoutDashboard, labelKey: 'common.mobileNav.home' },
37
+ { href: '/dashboard/leads', icon: UserPlus, labelKey: 'crm.navigation.leads' },
38
+ { href: '/dashboard/contacts', icon: Users, labelKey: 'crm.navigation.contacts' },
39
+ { href: '/dashboard/pipelines', icon: Layers, labelKey: 'pipelines.entity.plural' },
40
+ ]
41
+
42
+ const moreNavItems: NavItem[] = [
43
+ { href: '/dashboard/companies', icon: Building2, labelKey: 'crm.navigation.companies' },
44
+ { href: '/dashboard/products', icon: Package, labelKey: 'products.entity.plural' },
45
+ { href: '/dashboard/campaigns', icon: Megaphone, labelKey: 'crm.navigation.campaigns' },
46
+ { href: '/dashboard/activities', icon: Calendar, labelKey: 'crm.navigation.activities' },
47
+ { href: '/dashboard/settings', icon: Settings, labelKey: 'crm.navigation.settings' },
48
+ ]
49
+
50
+ export function CRMMobileNav() {
51
+ const pathname = usePathname()
52
+ const t = useTranslations()
53
+ const [moreOpen, setMoreOpen] = useState(false)
54
+
55
+ const isActive = (href: string) => {
56
+ if (href === '/dashboard') {
57
+ return pathname === '/dashboard'
58
+ }
59
+ return pathname.startsWith(href)
60
+ }
61
+
62
+ const isMoreActive = moreNavItems.some(item => isActive(item.href))
63
+
64
+ return (
65
+ <>
66
+ {/* Mobile Top Bar */}
67
+ <header className="lg:hidden fixed top-0 left-0 right-0 z-50 h-14 bg-background/95 backdrop-blur-md border-b border-border" data-cy="crm-mobile-topbar">
68
+ <div className="flex items-center justify-between h-full px-4">
69
+ <Link href="/dashboard" className="flex items-center gap-2">
70
+ <div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
71
+ <Layers className="w-4 h-4 text-primary-foreground" />
72
+ </div>
73
+ <span className="font-bold text-lg">CRM</span>
74
+ </Link>
75
+
76
+ <TeamSwitcherCompact />
77
+ </div>
78
+ </header>
79
+
80
+ {/* Mobile Bottom Navigation */}
81
+ <nav className="lg:hidden fixed bottom-0 left-0 right-0 z-50 h-20 bg-background/95 backdrop-blur-md border-t border-border safe-area-inset-bottom" data-cy="crm-mobile-nav">
82
+ <div className="flex items-center justify-around h-full px-2">
83
+ {mainNavItems.map((item) => {
84
+ const Icon = item.icon
85
+ const active = isActive(item.href)
86
+
87
+ return (
88
+ <Link
89
+ key={item.href}
90
+ href={item.href}
91
+ className={cn(
92
+ 'flex flex-col items-center justify-center gap-1 min-w-[4rem] py-2 rounded-xl transition-colors',
93
+ active
94
+ ? 'text-primary'
95
+ : 'text-muted-foreground hover:text-foreground'
96
+ )}
97
+ data-cy={`crm-mobile-nav-${item.href.split('/').pop() || 'home'}`}
98
+ >
99
+ <div className={cn(
100
+ 'flex items-center justify-center w-10 h-7 rounded-lg transition-colors',
101
+ active && 'bg-primary/10'
102
+ )}>
103
+ <Icon className="w-5 h-5" />
104
+ </div>
105
+ <span className="text-[11px] font-medium">{t(item.labelKey)}</span>
106
+ </Link>
107
+ )
108
+ })}
109
+
110
+ {/* More Button */}
111
+ <Sheet open={moreOpen} onOpenChange={setMoreOpen}>
112
+ <SheetTrigger asChild>
113
+ <button
114
+ className={cn(
115
+ 'flex flex-col items-center justify-center gap-1 min-w-[4rem] py-2 rounded-xl transition-colors',
116
+ isMoreActive
117
+ ? 'text-primary'
118
+ : 'text-muted-foreground hover:text-foreground'
119
+ )}
120
+ data-cy="crm-mobile-nav-more"
121
+ >
122
+ <div className={cn(
123
+ 'flex items-center justify-center w-10 h-7 rounded-lg transition-colors',
124
+ isMoreActive && 'bg-primary/10'
125
+ )}>
126
+ <MoreHorizontal className="w-5 h-5" />
127
+ </div>
128
+ <span className="text-[11px] font-medium">{t('common.mobileNav.more')}</span>
129
+ </button>
130
+ </SheetTrigger>
131
+ <SheetContent side="bottom" className="h-auto rounded-t-3xl pb-8" data-cy="crm-mobile-more-sheet">
132
+ <div className="flex justify-center mb-4">
133
+ <div className="w-10 h-1 rounded-full bg-muted" />
134
+ </div>
135
+ <div className="grid grid-cols-4 gap-4">
136
+ {moreNavItems.map((item) => {
137
+ const Icon = item.icon
138
+ const active = isActive(item.href)
139
+
140
+ return (
141
+ <Link
142
+ key={item.href}
143
+ href={item.href}
144
+ onClick={() => setMoreOpen(false)}
145
+ className={cn(
146
+ 'flex flex-col items-center justify-center gap-2 p-3 rounded-xl transition-colors',
147
+ active
148
+ ? 'bg-primary/10 text-primary'
149
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
150
+ )}
151
+ data-cy={`crm-mobile-more-${item.href.split('/').pop()}`}
152
+ >
153
+ <div className={cn(
154
+ 'w-12 h-12 rounded-xl flex items-center justify-center',
155
+ active ? 'bg-primary text-primary-foreground' : 'bg-muted'
156
+ )}>
157
+ <Icon className="w-6 h-6" />
158
+ </div>
159
+ <span className="text-xs font-medium">{t(item.labelKey)}</span>
160
+ </Link>
161
+ )
162
+ })}
163
+ </div>
164
+ </SheetContent>
165
+ </Sheet>
166
+ </div>
167
+ </nav>
168
+ </>
169
+ )
170
+ }
171
+
172
+ export default CRMMobileNav
@@ -0,0 +1,346 @@
1
+ /**
2
+ * CRM Sidebar Component
3
+ * Professional sidebar with smooth expand-on-hover animation
4
+ */
5
+
6
+ 'use client'
7
+
8
+ import Link from 'next/link'
9
+ import { usePathname } from 'next/navigation'
10
+ import { useState, useCallback } from 'react'
11
+ import { cn } from '@nextsparkjs/core/lib/utils'
12
+ import { TeamSwitcherCompact } from '@nextsparkjs/core/components/teams/TeamSwitcherCompact'
13
+ import { useAuth } from '@nextsparkjs/core/hooks/useAuth'
14
+ import Image from 'next/image'
15
+ import {
16
+ LayoutDashboard,
17
+ Users,
18
+ UserPlus,
19
+ Building2,
20
+ Target,
21
+ Layers,
22
+ Package,
23
+ Megaphone,
24
+ Calendar,
25
+ Settings,
26
+ ChevronLeft,
27
+ ChevronRight,
28
+ LogOut,
29
+ Sparkles
30
+ } from 'lucide-react'
31
+
32
+ interface NavItem {
33
+ href: string
34
+ icon: React.ElementType
35
+ label: string
36
+ badge?: number
37
+ color?: string
38
+ }
39
+
40
+ const mainNavItems: NavItem[] = [
41
+ { href: '/dashboard', icon: LayoutDashboard, label: 'Dashboard', color: 'text-primary' },
42
+ { href: '/dashboard/leads', icon: UserPlus, label: 'Leads', color: 'text-amber-500' },
43
+ { href: '/dashboard/contacts', icon: Users, label: 'Contacts', color: 'text-emerald-500' },
44
+ { href: '/dashboard/companies', icon: Building2, label: 'Companies', color: 'text-blue-500' },
45
+ { href: '/dashboard/pipelines', icon: Layers, label: 'Pipelines', color: 'text-violet-500' },
46
+ { href: '/dashboard/products', icon: Package, label: 'Products', color: 'text-rose-500' },
47
+ { href: '/dashboard/campaigns', icon: Megaphone, label: 'Campaigns', color: 'text-orange-500' },
48
+ { href: '/dashboard/activities', icon: Calendar, label: 'Activities', color: 'text-cyan-500' },
49
+ ]
50
+
51
+ const bottomNavItems: NavItem[] = [
52
+ { href: '/dashboard/settings', icon: Settings, label: 'Settings' },
53
+ ]
54
+
55
+ // Tooltip component
56
+ function Tooltip({ children, content, side = 'right', show = true }: {
57
+ children: React.ReactNode
58
+ content: string
59
+ side?: 'right' | 'bottom'
60
+ show?: boolean
61
+ }) {
62
+ const [isVisible, setIsVisible] = useState(false)
63
+
64
+ return (
65
+ <div
66
+ className="relative"
67
+ onMouseEnter={() => setIsVisible(true)}
68
+ onMouseLeave={() => setIsVisible(false)}
69
+ >
70
+ {children}
71
+ {show && isVisible && (
72
+ <div
73
+ className={cn(
74
+ 'absolute z-[60] px-3 py-1.5 text-sm font-medium text-white bg-foreground rounded-lg whitespace-nowrap shadow-lg',
75
+ 'animate-in fade-in-0 zoom-in-95 duration-100',
76
+ side === 'right' && 'left-full ml-3 top-1/2 -translate-y-1/2',
77
+ side === 'bottom' && 'top-full mt-2 left-1/2 -translate-x-1/2'
78
+ )}
79
+ >
80
+ {content}
81
+ {/* Arrow */}
82
+ <div
83
+ className={cn(
84
+ 'absolute w-2 h-2 bg-foreground rotate-45',
85
+ side === 'right' && 'left-0 top-1/2 -translate-y-1/2 -translate-x-1',
86
+ side === 'bottom' && 'top-0 left-1/2 -translate-x-1/2 -translate-y-1'
87
+ )}
88
+ />
89
+ </div>
90
+ )}
91
+ </div>
92
+ )
93
+ }
94
+
95
+ // Nav item component
96
+ function NavItemButton({ item, isActive, expanded }: {
97
+ item: NavItem
98
+ isActive: boolean
99
+ expanded: boolean
100
+ }) {
101
+ const Icon = item.icon
102
+ const slug = item.href.split('/').pop() || 'dashboard'
103
+
104
+ const button = (
105
+ <Link
106
+ href={item.href}
107
+ className={cn(
108
+ 'group/item flex items-center gap-3 rounded-xl transition-all duration-200 relative overflow-hidden',
109
+ expanded ? 'px-3 py-2.5' : 'p-2.5 justify-center',
110
+ isActive
111
+ ? 'bg-primary text-primary-foreground shadow-lg shadow-primary/25'
112
+ : 'text-muted-foreground hover:bg-muted/80 hover:text-foreground'
113
+ )}
114
+ data-cy={`crm-sidebar-nav-${slug}`}
115
+ >
116
+ {/* Gradient overlay for active state */}
117
+ {isActive && (
118
+ <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent" />
119
+ )}
120
+
121
+ <Icon className={cn(
122
+ 'shrink-0 w-5 h-5 relative z-10 transition-transform duration-200',
123
+ !isActive && 'group-hover/item:scale-110',
124
+ !isActive && item.color
125
+ )} />
126
+
127
+ {expanded && (
128
+ <span className="text-sm font-medium truncate relative z-10">{item.label}</span>
129
+ )}
130
+
131
+ {expanded && item.badge && item.badge > 0 && (
132
+ <span className="ml-auto px-2 py-0.5 text-xs font-semibold bg-white/20 text-white rounded-full relative z-10">
133
+ {item.badge}
134
+ </span>
135
+ )}
136
+ </Link>
137
+ )
138
+
139
+ if (expanded) {
140
+ return button
141
+ }
142
+
143
+ return (
144
+ <Tooltip content={item.label} show={!expanded}>
145
+ {button}
146
+ </Tooltip>
147
+ )
148
+ }
149
+
150
+ export function CRMSidebar() {
151
+ const pathname = usePathname()
152
+ const { user, signOut } = useAuth()
153
+ const [expanded, setExpanded] = useState(false)
154
+
155
+ const isActive = (href: string) => {
156
+ if (href === '/dashboard') {
157
+ return pathname === '/dashboard'
158
+ }
159
+ return pathname.startsWith(href)
160
+ }
161
+
162
+ const getUserInitials = useCallback(() => {
163
+ if (!user) return 'U'
164
+ if (user.firstName && user.lastName) {
165
+ return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase()
166
+ }
167
+ if (user.firstName) {
168
+ return user.firstName.slice(0, 2).toUpperCase()
169
+ }
170
+ return user.email?.slice(0, 2).toUpperCase() || 'U'
171
+ }, [user])
172
+
173
+ const handleSignOut = useCallback(async () => {
174
+ try {
175
+ await signOut()
176
+ } catch (error) {
177
+ console.error('Sign out failed:', error)
178
+ }
179
+ }, [signOut])
180
+
181
+ return (
182
+ <aside
183
+ data-cy="crm-sidebar"
184
+ className={cn(
185
+ 'hidden lg:flex flex-col fixed left-0 top-0 h-screen z-50 transition-all duration-300 ease-out',
186
+ 'bg-gradient-to-b from-card via-card to-card/95 border-r border-border/50',
187
+ 'shadow-[2px_0_20px_-5px_rgba(0,0,0,0.1)]',
188
+ expanded ? 'w-64' : 'w-16'
189
+ )}
190
+ onMouseEnter={() => setExpanded(true)}
191
+ onMouseLeave={() => setExpanded(false)}
192
+ style={{
193
+ // CSS variable for layout
194
+ ['--crm-sidebar-width' as any]: expanded ? '16rem' : '4rem'
195
+ }}
196
+ >
197
+ {/* Header with Logo */}
198
+ <div className={cn(
199
+ 'flex items-center h-16 border-b border-border/50',
200
+ expanded ? 'px-4 justify-between' : 'justify-center'
201
+ )}>
202
+ <Link href="/dashboard" className="flex items-center gap-3 group" data-cy="crm-sidebar-logo">
203
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary to-primary/80 flex items-center justify-center shrink-0 shadow-lg shadow-primary/20 transition-transform group-hover:scale-105">
204
+ <Sparkles className="w-5 h-5 text-primary-foreground" />
205
+ </div>
206
+ {expanded && (
207
+ <div className="animate-in fade-in slide-in-from-left-2 duration-200">
208
+ <span className="font-bold text-lg text-foreground tracking-tight">SalesHub</span>
209
+ <span className="text-[10px] text-muted-foreground block -mt-0.5 uppercase tracking-widest">CRM Pro</span>
210
+ </div>
211
+ )}
212
+ </Link>
213
+ {expanded && (
214
+ <button
215
+ onClick={(e) => {
216
+ e.stopPropagation()
217
+ setExpanded(false)
218
+ }}
219
+ className="p-1.5 rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-all hover:scale-105"
220
+ data-cy="crm-sidebar-collapse-btn"
221
+ >
222
+ <ChevronLeft className="w-4 h-4" />
223
+ </button>
224
+ )}
225
+ </div>
226
+
227
+ {/* Main Navigation */}
228
+ <nav className={cn('flex-1 overflow-y-auto py-4 scrollbar-thin', expanded ? 'px-3' : 'px-2')}>
229
+ {expanded && (
230
+ <p className="px-3 text-[11px] font-semibold text-muted-foreground uppercase tracking-widest mb-3 animate-in fade-in duration-200">
231
+ Navigation
232
+ </p>
233
+ )}
234
+ <div className="space-y-1">
235
+ {mainNavItems.map((item, index) => (
236
+ <div
237
+ key={item.href}
238
+ style={{ animationDelay: `${index * 30}ms` }}
239
+ className={expanded ? 'animate-in fade-in slide-in-from-left-2' : ''}
240
+ >
241
+ <NavItemButton
242
+ item={item}
243
+ isActive={isActive(item.href)}
244
+ expanded={expanded}
245
+ />
246
+ </div>
247
+ ))}
248
+ </div>
249
+ </nav>
250
+
251
+ {/* Bottom Section */}
252
+ <div className={cn('border-t border-border/50 py-3', expanded ? 'px-3' : 'px-2')}>
253
+ {/* Settings */}
254
+ <div className="space-y-1 mb-3">
255
+ {bottomNavItems.map((item) => (
256
+ <NavItemButton
257
+ key={item.href}
258
+ item={item}
259
+ isActive={isActive(item.href)}
260
+ expanded={expanded}
261
+ />
262
+ ))}
263
+ </div>
264
+
265
+ {/* Team Switcher */}
266
+ {expanded && (
267
+ <div className="mb-3 animate-in fade-in slide-in-from-left-2 duration-200">
268
+ <TeamSwitcherCompact />
269
+ </div>
270
+ )}
271
+
272
+ {/* User Profile Section */}
273
+ <div className={cn(
274
+ 'rounded-xl transition-all duration-200',
275
+ expanded ? 'bg-muted/50 p-3' : 'flex justify-center'
276
+ )}>
277
+ {expanded ? (
278
+ <div className="flex items-center gap-3 animate-in fade-in slide-in-from-left-2 duration-200">
279
+ {user?.image ? (
280
+ <Image
281
+ src={user.image}
282
+ alt=""
283
+ width={36}
284
+ height={36}
285
+ className="w-9 h-9 rounded-full object-cover ring-2 ring-background"
286
+ data-cy="crm-sidebar-user-avatar"
287
+ />
288
+ ) : (
289
+ <div className="w-9 h-9 rounded-full bg-gradient-to-br from-primary to-primary/80 flex items-center justify-center text-xs font-bold text-primary-foreground ring-2 ring-background" data-cy="crm-sidebar-user-avatar">
290
+ {getUserInitials()}
291
+ </div>
292
+ )}
293
+ <div className="flex-1 min-w-0">
294
+ <p className="text-sm font-medium text-foreground truncate" data-cy="crm-sidebar-user-name">
295
+ {user?.firstName || 'User'}
296
+ </p>
297
+ <p className="text-xs text-muted-foreground truncate" data-cy="crm-sidebar-user-email">
298
+ {user?.email}
299
+ </p>
300
+ </div>
301
+ <Tooltip content="Sign out" side="bottom">
302
+ <button
303
+ onClick={handleSignOut}
304
+ className="p-1.5 rounded-lg text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors"
305
+ data-cy="crm-sidebar-signout-btn"
306
+ >
307
+ <LogOut className="w-4 h-4" />
308
+ </button>
309
+ </Tooltip>
310
+ </div>
311
+ ) : (
312
+ <Tooltip content={user?.firstName || 'Profile'}>
313
+ <Link
314
+ href="/dashboard/settings/profile"
315
+ className="block"
316
+ >
317
+ {user?.image ? (
318
+ <Image
319
+ src={user.image}
320
+ alt=""
321
+ width={36}
322
+ height={36}
323
+ className="w-9 h-9 rounded-full object-cover ring-2 ring-border hover:ring-primary transition-all"
324
+ data-cy="crm-sidebar-user-avatar"
325
+ />
326
+ ) : (
327
+ <div className="w-9 h-9 rounded-full bg-gradient-to-br from-primary to-primary/80 flex items-center justify-center text-xs font-bold text-primary-foreground ring-2 ring-border hover:ring-primary transition-all" data-cy="crm-sidebar-user-avatar">
328
+ {getUserInitials()}
329
+ </div>
330
+ )}
331
+ </Link>
332
+ </Tooltip>
333
+ )}
334
+ </div>
335
+ </div>
336
+
337
+ {/* Hover indicator edge */}
338
+ <div className={cn(
339
+ 'absolute right-0 top-0 bottom-0 w-1 bg-gradient-to-b from-primary/0 via-primary/50 to-primary/0 opacity-0 transition-opacity duration-300',
340
+ expanded && 'opacity-100'
341
+ )} />
342
+ </aside>
343
+ )
344
+ }
345
+
346
+ export default CRMSidebar