@nextsparkjs/theme-productivity 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 (57) hide show
  1. package/README.md +76 -0
  2. package/about.md +123 -0
  3. package/components/CardDetailModal.tsx +318 -0
  4. package/components/KanbanBoard.tsx +612 -0
  5. package/components/KanbanCard.tsx +218 -0
  6. package/components/KanbanColumn.tsx +264 -0
  7. package/components/SortableList.tsx +46 -0
  8. package/components/index.ts +4 -0
  9. package/config/app.config.ts +172 -0
  10. package/config/billing.config.ts +187 -0
  11. package/config/dashboard.config.ts +357 -0
  12. package/config/dev.config.ts +55 -0
  13. package/config/features.config.ts +256 -0
  14. package/config/flows.config.ts +484 -0
  15. package/config/permissions.config.ts +167 -0
  16. package/config/theme.config.ts +106 -0
  17. package/entities/boards/boards.config.ts +61 -0
  18. package/entities/boards/boards.fields.ts +154 -0
  19. package/entities/boards/boards.service.ts +256 -0
  20. package/entities/boards/boards.types.ts +57 -0
  21. package/entities/boards/messages/en.json +80 -0
  22. package/entities/boards/messages/es.json +80 -0
  23. package/entities/boards/migrations/001_boards_table.sql +83 -0
  24. package/entities/cards/cards.config.ts +61 -0
  25. package/entities/cards/cards.fields.ts +242 -0
  26. package/entities/cards/cards.service.ts +336 -0
  27. package/entities/cards/cards.types.ts +79 -0
  28. package/entities/cards/messages/en.json +114 -0
  29. package/entities/cards/messages/es.json +114 -0
  30. package/entities/cards/migrations/020_cards_table.sql +92 -0
  31. package/entities/lists/lists.config.ts +61 -0
  32. package/entities/lists/lists.fields.ts +105 -0
  33. package/entities/lists/lists.service.ts +252 -0
  34. package/entities/lists/lists.types.ts +55 -0
  35. package/entities/lists/messages/en.json +60 -0
  36. package/entities/lists/messages/es.json +60 -0
  37. package/entities/lists/migrations/010_lists_table.sql +79 -0
  38. package/lib/selectors.ts +206 -0
  39. package/messages/en.json +79 -0
  40. package/messages/es.json +79 -0
  41. package/migrations/999_theme_sample_data.sql +922 -0
  42. package/migrations/999a_initial_sample_data.sql +377 -0
  43. package/migrations/999b_abundant_sample_data.sql +346 -0
  44. package/package.json +17 -0
  45. package/permissions-matrix.md +122 -0
  46. package/styles/components.css +460 -0
  47. package/styles/globals.css +560 -0
  48. package/templates/dashboard/(main)/boards/[id]/[cardId]/page.tsx +238 -0
  49. package/templates/dashboard/(main)/boards/[id]/edit/page.tsx +390 -0
  50. package/templates/dashboard/(main)/boards/[id]/page.tsx +236 -0
  51. package/templates/dashboard/(main)/boards/create/page.tsx +236 -0
  52. package/templates/dashboard/(main)/boards/page.tsx +335 -0
  53. package/templates/dashboard/(main)/layout.tsx +32 -0
  54. package/templates/dashboard/(main)/page.tsx +592 -0
  55. package/templates/shared/ProductivityMobileNav.tsx +410 -0
  56. package/templates/shared/ProductivitySidebar.tsx +538 -0
  57. package/templates/shared/ProductivityTopBar.tsx +317 -0
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Productivity TopBar Component
3
+ * Professional top navigation bar for Productivity dashboard
4
+ */
5
+
6
+ 'use client'
7
+
8
+ import Link from 'next/link'
9
+ import Image from 'next/image'
10
+ import { usePathname, useParams } from 'next/navigation'
11
+ import { useAuth } from '@nextsparkjs/core/hooks/useAuth'
12
+ import { Button } from '@nextsparkjs/core/components/ui/button'
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuItem,
17
+ DropdownMenuLabel,
18
+ DropdownMenuSeparator,
19
+ DropdownMenuTrigger,
20
+ } from '@nextsparkjs/core/components/ui/dropdown-menu'
21
+ import {
22
+ Bell,
23
+ Search,
24
+ Plus,
25
+ User,
26
+ Settings,
27
+ CreditCard,
28
+ LogOut,
29
+ Sun,
30
+ Moon,
31
+ HelpCircle,
32
+ ChevronDown,
33
+ Home,
34
+ ChevronRight,
35
+ Kanban
36
+ } from 'lucide-react'
37
+ import { useState, useCallback, useEffect } from 'react'
38
+ import { cn } from '@nextsparkjs/core/lib/utils'
39
+ import { useTheme } from 'next-themes'
40
+
41
+ interface Board {
42
+ id: string
43
+ name: string
44
+ color: string
45
+ }
46
+
47
+ export function ProductivityTopBar() {
48
+ const { user, signOut, isLoading } = useAuth()
49
+ const pathname = usePathname()
50
+ const params = useParams()
51
+ const { theme, setTheme } = useTheme()
52
+ const [searchQuery, setSearchQuery] = useState('')
53
+ const [showSearch, setShowSearch] = useState(false)
54
+ const [currentBoard, setCurrentBoard] = useState<Board | null>(null)
55
+
56
+ const boardId = params?.id as string
57
+
58
+ // Fetch current board name if on a board page
59
+ useEffect(() => {
60
+ const fetchBoard = async () => {
61
+ if (boardId) {
62
+ try {
63
+ const response = await fetch(`/api/v1/boards/${boardId}`)
64
+ if (response.ok) {
65
+ const data = await response.json()
66
+ setCurrentBoard(data.data)
67
+ }
68
+ } catch (error) {
69
+ console.error('Failed to fetch board:', error)
70
+ }
71
+ } else {
72
+ setCurrentBoard(null)
73
+ }
74
+ }
75
+
76
+ fetchBoard()
77
+ }, [boardId])
78
+
79
+ // Generate user initials
80
+ const getUserInitials = (user: { firstName?: string; lastName?: string; email: string }) => {
81
+ if (user.firstName && user.lastName) {
82
+ return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase()
83
+ }
84
+ if (user.firstName) {
85
+ return user.firstName.slice(0, 2).toUpperCase()
86
+ }
87
+ return user.email.slice(0, 2).toUpperCase()
88
+ }
89
+
90
+ const handleSignOut = useCallback(async () => {
91
+ try {
92
+ await signOut()
93
+ } catch (error) {
94
+ console.error('Sign out failed:', error)
95
+ }
96
+ }, [signOut])
97
+
98
+ // Get breadcrumb items
99
+ const getBreadcrumb = () => {
100
+ const items: { label: string; href?: string; icon?: React.ElementType }[] = [
101
+ { label: 'Home', href: '/dashboard', icon: Home }
102
+ ]
103
+
104
+ if (pathname.includes('/boards')) {
105
+ items.push({ label: 'Boards', href: '/dashboard/boards' })
106
+
107
+ if (currentBoard) {
108
+ items.push({ label: currentBoard.name })
109
+ } else if (pathname.includes('/create')) {
110
+ items.push({ label: 'New Board' })
111
+ }
112
+ } else if (pathname.includes('/settings')) {
113
+ items.push({ label: 'Settings', href: '/dashboard/settings' })
114
+ }
115
+
116
+ return items
117
+ }
118
+
119
+ const breadcrumb = getBreadcrumb()
120
+
121
+ return (
122
+ <header
123
+ className={cn(
124
+ 'hidden lg:block bg-background/80 backdrop-blur-md border-b border-border/50 fixed top-0 right-0 z-40 transition-all duration-300 ease-out'
125
+ )}
126
+ style={{ left: 'var(--productivity-sidebar-width, 4rem)' }}
127
+ >
128
+ <div className="h-16 px-6 flex items-center justify-between gap-4">
129
+ {/* Left side: Breadcrumb */}
130
+ <div className="flex items-center gap-2 min-w-0">
131
+ {breadcrumb.map((item, index) => (
132
+ <div key={index} className="flex items-center gap-2">
133
+ {index > 0 && (
134
+ <ChevronRight className="w-4 h-4 text-muted-foreground" />
135
+ )}
136
+ {item.href ? (
137
+ <Link
138
+ href={item.href}
139
+ className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
140
+ >
141
+ {item.icon && <item.icon className="w-4 h-4" />}
142
+ {item.label}
143
+ </Link>
144
+ ) : (
145
+ <span className="flex items-center gap-1.5 text-sm font-medium text-foreground truncate">
146
+ {currentBoard && (
147
+ <div
148
+ className={cn(
149
+ 'w-3 h-3 rounded-sm',
150
+ `bg-[var(--board-${currentBoard.color})]`
151
+ )}
152
+ style={{
153
+ backgroundColor: `var(--board-${currentBoard.color}, var(--board-blue))`
154
+ }}
155
+ />
156
+ )}
157
+ {item.label}
158
+ </span>
159
+ )}
160
+ </div>
161
+ ))}
162
+ </div>
163
+
164
+ {/* Center: Search */}
165
+ <div className="hidden md:flex flex-1 max-w-md mx-4">
166
+ <div className="relative w-full">
167
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
168
+ <input
169
+ type="text"
170
+ placeholder="Search cards..."
171
+ value={searchQuery}
172
+ onChange={(e) => setSearchQuery(e.target.value)}
173
+ className="w-full pl-10 pr-4 py-2 bg-muted/50 border border-border rounded-lg text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all"
174
+ />
175
+ {searchQuery && (
176
+ <kbd className="absolute right-3 top-1/2 -translate-y-1/2 hidden sm:inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
177
+ ESC
178
+ </kbd>
179
+ )}
180
+ </div>
181
+ </div>
182
+
183
+ {/* Right side: Actions */}
184
+ <div className="flex items-center gap-2">
185
+ {/* Mobile search toggle */}
186
+ <Button
187
+ variant="ghost"
188
+ size="icon"
189
+ className="md:hidden h-9 w-9"
190
+ onClick={() => setShowSearch(!showSearch)}
191
+ >
192
+ <Search className="h-4 w-4" />
193
+ </Button>
194
+
195
+ {/* Quick Create Card */}
196
+ {currentBoard && (
197
+ <Button size="sm" className="gap-2 h-9">
198
+ <Plus className="h-4 w-4" />
199
+ <span className="hidden sm:inline">Add Card</span>
200
+ </Button>
201
+ )}
202
+
203
+ {/* Notifications */}
204
+ <Button variant="ghost" size="icon" className="h-9 w-9 relative">
205
+ <Bell className="h-4 w-4" />
206
+ <span className="absolute top-1.5 right-1.5 w-2 h-2 bg-destructive rounded-full" />
207
+ </Button>
208
+
209
+ {/* Help */}
210
+ <Button variant="ghost" size="icon" className="h-9 w-9 hidden sm:flex">
211
+ <HelpCircle className="h-4 w-4" />
212
+ </Button>
213
+
214
+ {/* Theme Toggle */}
215
+ <Button
216
+ variant="ghost"
217
+ size="icon"
218
+ className="h-9 w-9"
219
+ onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
220
+ >
221
+ <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
222
+ <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
223
+ </Button>
224
+
225
+ {/* User Menu */}
226
+ {isLoading ? (
227
+ <div className="h-9 w-9 bg-muted animate-pulse rounded-full" />
228
+ ) : user ? (
229
+ <DropdownMenu>
230
+ <DropdownMenuTrigger asChild>
231
+ <Button variant="ghost" className="h-9 gap-2 pl-2 pr-3">
232
+ {user.image ? (
233
+ <Image
234
+ src={user.image}
235
+ alt=""
236
+ width={28}
237
+ height={28}
238
+ className="h-7 w-7 rounded-full object-cover"
239
+ />
240
+ ) : (
241
+ <div className="h-7 w-7 rounded-full bg-primary flex items-center justify-center text-xs font-medium text-primary-foreground">
242
+ {getUserInitials(user)}
243
+ </div>
244
+ )}
245
+ <ChevronDown className="h-3 w-3 opacity-50 hidden sm:block" />
246
+ </Button>
247
+ </DropdownMenuTrigger>
248
+ <DropdownMenuContent align="end" className="w-56">
249
+ <DropdownMenuLabel>
250
+ <div className="flex flex-col space-y-1">
251
+ <p className="text-sm font-medium">{user.firstName || 'User'}</p>
252
+ <p className="text-xs text-muted-foreground">{user.email}</p>
253
+ </div>
254
+ </DropdownMenuLabel>
255
+ <DropdownMenuSeparator />
256
+ <DropdownMenuItem asChild>
257
+ <Link href="/dashboard/settings/profile" className="flex items-center">
258
+ <User className="mr-2 h-4 w-4" />
259
+ Profile
260
+ </Link>
261
+ </DropdownMenuItem>
262
+ <DropdownMenuItem asChild>
263
+ <Link href="/dashboard/settings" className="flex items-center">
264
+ <Settings className="mr-2 h-4 w-4" />
265
+ Settings
266
+ </Link>
267
+ </DropdownMenuItem>
268
+ <DropdownMenuItem asChild>
269
+ <Link href="/dashboard/settings/billing" className="flex items-center">
270
+ <CreditCard className="mr-2 h-4 w-4" />
271
+ Billing
272
+ </Link>
273
+ </DropdownMenuItem>
274
+ <DropdownMenuSeparator />
275
+ <DropdownMenuItem
276
+ onClick={handleSignOut}
277
+ className="text-destructive focus:text-destructive"
278
+ >
279
+ <LogOut className="mr-2 h-4 w-4" />
280
+ Sign out
281
+ </DropdownMenuItem>
282
+ </DropdownMenuContent>
283
+ </DropdownMenu>
284
+ ) : (
285
+ <div className="flex items-center gap-2">
286
+ <Button variant="ghost" size="sm" asChild>
287
+ <Link href="/login">Sign in</Link>
288
+ </Button>
289
+ <Button size="sm" asChild>
290
+ <Link href="/signup">Sign up</Link>
291
+ </Button>
292
+ </div>
293
+ )}
294
+ </div>
295
+ </div>
296
+
297
+ {/* Mobile search bar */}
298
+ {showSearch && (
299
+ <div className="md:hidden px-4 pb-4 animate-in slide-in-from-top-2">
300
+ <div className="relative">
301
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
302
+ <input
303
+ type="text"
304
+ placeholder="Search cards..."
305
+ value={searchQuery}
306
+ onChange={(e) => setSearchQuery(e.target.value)}
307
+ className="w-full pl-10 pr-4 py-2 bg-muted/50 border border-border rounded-lg text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
308
+ autoFocus
309
+ />
310
+ </div>
311
+ </div>
312
+ )}
313
+ </header>
314
+ )
315
+ }
316
+
317
+ export default ProductivityTopBar