@moontra/moonui-pro 2.0.22 → 2.0.23

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 (96) hide show
  1. package/package.json +2 -1
  2. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  3. package/src/__tests__/use-local-storage.test.tsx +174 -0
  4. package/src/__tests__/use-pro-access.test.tsx +183 -0
  5. package/src/components/advanced-chart/advanced-chart.test.tsx +281 -0
  6. package/src/components/advanced-chart/index.tsx +412 -0
  7. package/src/components/advanced-forms/index.tsx +431 -0
  8. package/src/components/animated-button/index.tsx +202 -0
  9. package/src/components/calendar/event-dialog.tsx +372 -0
  10. package/src/components/calendar/index.tsx +531 -0
  11. package/src/components/color-picker/index.tsx +434 -0
  12. package/src/components/dashboard/index.tsx +334 -0
  13. package/src/components/data-table/data-table.test.tsx +187 -0
  14. package/src/components/data-table/index.tsx +368 -0
  15. package/src/components/draggable-list/index.tsx +100 -0
  16. package/src/components/enhanced/button.tsx +360 -0
  17. package/src/components/enhanced/card.tsx +272 -0
  18. package/src/components/enhanced/dialog.tsx +248 -0
  19. package/src/components/enhanced/index.ts +3 -0
  20. package/src/components/error-boundary/index.tsx +111 -0
  21. package/src/components/file-upload/file-upload.test.tsx +242 -0
  22. package/src/components/file-upload/index.tsx +362 -0
  23. package/src/components/floating-action-button/index.tsx +209 -0
  24. package/src/components/github-stars/index.tsx +414 -0
  25. package/src/components/health-check/index.tsx +441 -0
  26. package/src/components/hover-card-3d/index.tsx +170 -0
  27. package/src/components/index.ts +76 -0
  28. package/src/components/kanban/index.tsx +436 -0
  29. package/src/components/lazy-component/index.tsx +342 -0
  30. package/src/components/magnetic-button/index.tsx +170 -0
  31. package/src/components/memory-efficient-data/index.tsx +352 -0
  32. package/src/components/optimized-image/index.tsx +427 -0
  33. package/src/components/performance-debugger/index.tsx +591 -0
  34. package/src/components/performance-monitor/index.tsx +775 -0
  35. package/src/components/pinch-zoom/index.tsx +172 -0
  36. package/src/components/rich-text-editor/index-old-backup.tsx +443 -0
  37. package/src/components/rich-text-editor/index.tsx +1537 -0
  38. package/src/components/rich-text-editor/slash-commands-extension.ts +220 -0
  39. package/src/components/rich-text-editor/slash-commands.css +35 -0
  40. package/src/components/rich-text-editor/table-styles.css +65 -0
  41. package/src/components/spotlight-card/index.tsx +194 -0
  42. package/src/components/swipeable-card/index.tsx +100 -0
  43. package/src/components/timeline/index.tsx +333 -0
  44. package/src/components/ui/animated-button.tsx +185 -0
  45. package/src/components/ui/avatar.tsx +135 -0
  46. package/src/components/ui/badge.tsx +225 -0
  47. package/src/components/ui/button.tsx +221 -0
  48. package/src/components/ui/card.tsx +141 -0
  49. package/src/components/ui/checkbox.tsx +256 -0
  50. package/src/components/ui/color-picker.tsx +95 -0
  51. package/src/components/ui/dialog.tsx +332 -0
  52. package/src/components/ui/dropdown-menu.tsx +200 -0
  53. package/src/components/ui/hover-card-3d.tsx +103 -0
  54. package/src/components/ui/index.ts +33 -0
  55. package/src/components/ui/input.tsx +219 -0
  56. package/src/components/ui/label.tsx +26 -0
  57. package/src/components/ui/magnetic-button.tsx +129 -0
  58. package/src/components/ui/popover.tsx +183 -0
  59. package/src/components/ui/select.tsx +273 -0
  60. package/src/components/ui/separator.tsx +140 -0
  61. package/src/components/ui/slider.tsx +351 -0
  62. package/src/components/ui/spotlight-card.tsx +119 -0
  63. package/src/components/ui/switch.tsx +83 -0
  64. package/src/components/ui/tabs.tsx +195 -0
  65. package/src/components/ui/textarea.tsx +25 -0
  66. package/src/components/ui/toast.tsx +313 -0
  67. package/src/components/ui/tooltip.tsx +152 -0
  68. package/src/components/virtual-list/index.tsx +369 -0
  69. package/src/hooks/use-chart.ts +205 -0
  70. package/src/hooks/use-data-table.ts +182 -0
  71. package/src/hooks/use-docs-pro-access.ts +13 -0
  72. package/src/hooks/use-license-check.ts +65 -0
  73. package/src/hooks/use-subscription.ts +19 -0
  74. package/src/index.ts +11 -0
  75. package/src/lib/micro-interactions.ts +255 -0
  76. package/src/lib/utils.ts +6 -0
  77. package/src/patterns/login-form/index.tsx +276 -0
  78. package/src/patterns/login-form/types.ts +67 -0
  79. package/src/setupTests.ts +41 -0
  80. package/src/styles/design-system.css +365 -0
  81. package/src/styles/index.css +4 -0
  82. package/src/styles/tailwind.css +6 -0
  83. package/src/styles/tokens.css +453 -0
  84. package/src/types/moonui.d.ts +22 -0
  85. package/src/use-intersection-observer.tsx +154 -0
  86. package/src/use-local-storage.tsx +71 -0
  87. package/src/use-paddle.ts +138 -0
  88. package/src/use-performance-optimizer.ts +379 -0
  89. package/src/use-pro-access.ts +141 -0
  90. package/src/use-scroll-animation.ts +221 -0
  91. package/src/use-subscription.ts +37 -0
  92. package/src/use-toast.ts +32 -0
  93. package/src/utils/chart-helpers.ts +257 -0
  94. package/src/utils/cn.ts +69 -0
  95. package/src/utils/data-processing.ts +151 -0
  96. package/src/utils/license-validator.tsx +183 -0
@@ -0,0 +1,531 @@
1
+ "use client"
2
+
3
+ import React from 'react'
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
5
+ import { Button } from '../ui/button'
6
+ import { Badge } from '../ui/badge'
7
+ import {
8
+ ChevronLeft,
9
+ ChevronRight,
10
+ Calendar as CalendarIcon,
11
+ Plus,
12
+ Edit,
13
+ Trash2,
14
+ Clock,
15
+ MapPin,
16
+ User,
17
+ Lock,
18
+ Sparkles
19
+ } from 'lucide-react'
20
+ import { cn } from '../../lib/utils'
21
+ import { EventDialog } from './event-dialog'
22
+
23
+ export interface CalendarEvent {
24
+ id: string
25
+ title: string
26
+ description?: string
27
+ date: Date
28
+ startTime?: string
29
+ endTime?: string
30
+ location?: string
31
+ attendees?: string[]
32
+ color?: string
33
+ type?: 'meeting' | 'task' | 'reminder' | 'event'
34
+ }
35
+
36
+ export interface CalendarProps {
37
+ events?: CalendarEvent[]
38
+ onEventClick?: (event: CalendarEvent) => void
39
+ onEventAdd?: (eventData: Omit<CalendarEvent, 'id'> & { id?: string }) => void
40
+ onEventEdit?: (eventData: Omit<CalendarEvent, 'id'> & { id: string }) => void
41
+ onEventDelete?: (eventId: string) => void
42
+ className?: string
43
+ showWeekends?: boolean
44
+ showEventDetails?: boolean
45
+ disabled?: boolean
46
+ minDate?: Date
47
+ maxDate?: Date
48
+ highlightToday?: boolean
49
+ height?: number
50
+ // For NPM package usage - allows external control of pro access
51
+ showProUpgrade?: boolean
52
+ }
53
+
54
+ const DAYS_OF_WEEK = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
55
+ const MONTHS = [
56
+ 'January', 'February', 'March', 'April', 'May', 'June',
57
+ 'July', 'August', 'September', 'October', 'November', 'December'
58
+ ]
59
+
60
+ const EVENT_COLORS = {
61
+ meeting: 'bg-blue-500',
62
+ task: 'bg-green-500',
63
+ reminder: 'bg-yellow-500',
64
+ event: 'bg-purple-500'
65
+ }
66
+
67
+ export function Calendar({
68
+ events = [],
69
+ onEventClick,
70
+ onEventAdd,
71
+ onEventEdit,
72
+ onEventDelete,
73
+ className,
74
+ showWeekends = true,
75
+ showEventDetails = true,
76
+ disabled = false,
77
+ minDate,
78
+ maxDate,
79
+ highlightToday = true,
80
+ height,
81
+ showProUpgrade = false
82
+ }: CalendarProps) {
83
+ // For NPM package usage, show upgrade prompt if specified
84
+ if (showProUpgrade) {
85
+ return (
86
+ <Card className={cn("w-full", className)}>
87
+ <CardContent className="py-12 text-center">
88
+ <div className="max-w-md mx-auto space-y-4">
89
+ <div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
90
+ <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
91
+ </div>
92
+ <div>
93
+ <h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
94
+ <p className="text-muted-foreground text-sm mb-4">
95
+ Calendar is available exclusively to MoonUI Pro subscribers.
96
+ </p>
97
+ <div className="flex gap-3 justify-center">
98
+ <a href="/pricing">
99
+ <Button size="sm">
100
+ <Sparkles className="mr-2 h-4 w-4" />
101
+ Upgrade to Pro
102
+ </Button>
103
+ </a>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </CardContent>
108
+ </Card>
109
+ )
110
+ }
111
+
112
+ const [currentDate, setCurrentDate] = React.useState(new Date())
113
+ const [selectedDate, setSelectedDate] = React.useState<Date | null>(null)
114
+ const [view, setView] = React.useState<'month' | 'week' | 'day'>('month')
115
+ const [eventDialogOpen, setEventDialogOpen] = React.useState(false)
116
+ const [eventDialogMode, setEventDialogMode] = React.useState<'create' | 'edit'>('create')
117
+ const [selectedEvent, setSelectedEvent] = React.useState<CalendarEvent | null>(null)
118
+ const [draggedEvent, setDraggedEvent] = React.useState<CalendarEvent | null>(null)
119
+ const [dragTargetDate, setDragTargetDate] = React.useState<Date | null>(null)
120
+
121
+ const today = new Date()
122
+ const currentMonth = currentDate.getMonth()
123
+ const currentYear = currentDate.getFullYear()
124
+
125
+ const firstDayOfMonth = new Date(currentYear, currentMonth, 1)
126
+ const lastDayOfMonth = new Date(currentYear, currentMonth + 1, 0)
127
+ const startDate = new Date(firstDayOfMonth)
128
+ startDate.setDate(startDate.getDate() - startDate.getDay())
129
+
130
+ const endDate = new Date(lastDayOfMonth)
131
+ endDate.setDate(endDate.getDate() + (6 - endDate.getDay()))
132
+
133
+ const calendarDays = []
134
+ const currentDateIterator = new Date(startDate)
135
+
136
+ while (currentDateIterator <= endDate) {
137
+ calendarDays.push(new Date(currentDateIterator))
138
+ currentDateIterator.setDate(currentDateIterator.getDate() + 1)
139
+ }
140
+
141
+ const getEventsForDate = (date: Date) => {
142
+ return events.filter(event => {
143
+ const eventDate = new Date(event.date)
144
+ return eventDate.toDateString() === date.toDateString()
145
+ })
146
+ }
147
+
148
+ const isToday = (date: Date) => {
149
+ return date.toDateString() === today.toDateString()
150
+ }
151
+
152
+ const isCurrentMonth = (date: Date) => {
153
+ return date.getMonth() === currentMonth
154
+ }
155
+
156
+ const isSelected = (date: Date) => {
157
+ return selectedDate && date.toDateString() === selectedDate.toDateString()
158
+ }
159
+
160
+ const isDisabled = (date: Date) => {
161
+ if (disabled) return true
162
+ if (minDate && date < minDate) return true
163
+ if (maxDate && date > maxDate) return true
164
+ return false
165
+ }
166
+
167
+ const navigateMonth = (direction: 'prev' | 'next') => {
168
+ const newDate = new Date(currentDate)
169
+ if (direction === 'prev') {
170
+ newDate.setMonth(currentMonth - 1)
171
+ } else {
172
+ newDate.setMonth(currentMonth + 1)
173
+ }
174
+ setCurrentDate(newDate)
175
+ }
176
+
177
+ const handleDateClick = (date: Date) => {
178
+ if (isDisabled(date)) return
179
+
180
+ setSelectedDate(date)
181
+ const dateEvents = getEventsForDate(date)
182
+
183
+ if (dateEvents.length === 0) {
184
+ // Open dialog for creating new event
185
+ setEventDialogMode('create')
186
+ setSelectedEvent(null)
187
+ setEventDialogOpen(true)
188
+ } else if (dateEvents.length === 1) {
189
+ onEventClick?.(dateEvents[0])
190
+ }
191
+ }
192
+
193
+ const handleEventClick = (event: CalendarEvent, e: React.MouseEvent) => {
194
+ e.stopPropagation()
195
+ onEventClick?.(event)
196
+ }
197
+
198
+ const handleEventDragStart = (event: CalendarEvent, e: React.DragEvent) => {
199
+ setDraggedEvent(event)
200
+ e.dataTransfer.effectAllowed = 'move'
201
+ e.dataTransfer.setData('text/plain', event.id)
202
+
203
+ // Add visual feedback
204
+ const target = e.target as HTMLElement
205
+ target.style.opacity = '0.5'
206
+ }
207
+
208
+ const handleEventDragEnd = (e: React.DragEvent) => {
209
+ setDraggedEvent(null)
210
+ setDragTargetDate(null)
211
+
212
+ // Remove visual feedback
213
+ const target = e.target as HTMLElement
214
+ target.style.opacity = '1'
215
+ }
216
+
217
+ const handleDateDragOver = (date: Date, e: React.DragEvent) => {
218
+ if (isDisabled(date) || !draggedEvent) return
219
+ e.preventDefault()
220
+ e.dataTransfer.dropEffect = 'move'
221
+ setDragTargetDate(date)
222
+ }
223
+
224
+ const handleDateDrop = (date: Date, e: React.DragEvent) => {
225
+ e.preventDefault()
226
+
227
+ if (!draggedEvent || isDisabled(date)) return
228
+
229
+ // Only move if it's a different date
230
+ if (draggedEvent.date.toDateString() !== date.toDateString()) {
231
+ const updatedEvent = {
232
+ ...draggedEvent,
233
+ date: date
234
+ }
235
+
236
+ onEventEdit?.(updatedEvent)
237
+ }
238
+
239
+ setDraggedEvent(null)
240
+ setDragTargetDate(null)
241
+ }
242
+
243
+ const handleEventEdit = (event: CalendarEvent, e: React.MouseEvent) => {
244
+ e.stopPropagation()
245
+ setEventDialogMode('edit')
246
+ setSelectedEvent(event)
247
+ setEventDialogOpen(true)
248
+ }
249
+
250
+ const handleEventDelete = (event: CalendarEvent, e: React.MouseEvent) => {
251
+ e.stopPropagation()
252
+ onEventDelete?.(event.id)
253
+ }
254
+
255
+ const handleEventSave = (eventData: Omit<CalendarEvent, 'id'> & { id?: string }) => {
256
+ if (eventDialogMode === 'create') {
257
+ onEventAdd?.(eventData)
258
+ } else if (eventDialogMode === 'edit' && eventData.id) {
259
+ onEventEdit?.(eventData as Omit<CalendarEvent, 'id'> & { id: string })
260
+ }
261
+ }
262
+
263
+ const handleEventDialogDelete = (eventId: string) => {
264
+ onEventDelete?.(eventId)
265
+ }
266
+
267
+ const goToToday = () => {
268
+ setCurrentDate(new Date())
269
+ setSelectedDate(new Date())
270
+ }
271
+
272
+ const filteredDays = showWeekends ? calendarDays : calendarDays.filter(day => {
273
+ const dayOfWeek = day.getDay()
274
+ return dayOfWeek !== 0 && dayOfWeek !== 6
275
+ })
276
+
277
+ const visibleDaysOfWeek = showWeekends ? DAYS_OF_WEEK : DAYS_OF_WEEK.slice(1, 6)
278
+
279
+ return (
280
+ <>
281
+ <Card className={cn("w-full max-w-full overflow-hidden", className)} style={{ height: height ? `${height}px` : undefined }}>
282
+ <CardHeader>
283
+ <div className="flex items-center justify-between">
284
+ <div>
285
+ <CardTitle className="flex items-center gap-2">
286
+ <CalendarIcon className="h-5 w-5" />
287
+ Calendar
288
+ </CardTitle>
289
+ <CardDescription>
290
+ {MONTHS[currentMonth]} {currentYear}
291
+ </CardDescription>
292
+ </div>
293
+ <div className="flex items-center gap-2">
294
+ <Button variant="outline" size="sm" onClick={goToToday}>
295
+ Today
296
+ </Button>
297
+ <Button
298
+ variant="outline"
299
+ size="sm"
300
+ onClick={() => navigateMonth('prev')}
301
+ disabled={disabled}
302
+ >
303
+ <ChevronLeft className="h-4 w-4" />
304
+ </Button>
305
+ <Button
306
+ variant="outline"
307
+ size="sm"
308
+ onClick={() => navigateMonth('next')}
309
+ disabled={disabled}
310
+ >
311
+ <ChevronRight className="h-4 w-4" />
312
+ </Button>
313
+ </div>
314
+ </div>
315
+ </CardHeader>
316
+ <CardContent className="overflow-x-auto">
317
+ <div className="space-y-4">
318
+ {/* Calendar Grid */}
319
+ <div className="grid grid-cols-7 gap-1 w-full" style={{ minWidth: "500px" }}>
320
+ {/* Day Headers */}
321
+ {visibleDaysOfWeek.map((day) => (
322
+ <div key={day} className="p-1 text-center text-xs font-medium text-muted-foreground min-w-[70px]">
323
+ {day}
324
+ </div>
325
+ ))}
326
+
327
+ {/* Calendar Days */}
328
+ {filteredDays.map((date, index) => {
329
+ const dayEvents = getEventsForDate(date)
330
+ const isCurrentMonthDate = isCurrentMonth(date)
331
+ const isTodayDate = isToday(date)
332
+ const isSelectedDate = isSelected(date)
333
+ const isDisabledDate = isDisabled(date)
334
+
335
+ return (
336
+ <div
337
+ key={index}
338
+ className={cn(
339
+ "min-h-[80px] min-w-[70px] p-1 border border-border/50 cursor-pointer hover:bg-muted/50 transition-colors text-xs flex flex-col",
340
+ !isCurrentMonthDate && "text-muted-foreground bg-muted/20",
341
+ isTodayDate && highlightToday && "bg-primary/10 border-primary/50",
342
+ isSelectedDate && "bg-primary/20 border-primary",
343
+ isDisabledDate && "cursor-not-allowed opacity-50",
344
+ dragTargetDate && dragTargetDate.toDateString() === date.toDateString() && "bg-primary/30 border-primary"
345
+ )}
346
+ onClick={() => handleDateClick(date)}
347
+ onDragOver={(e) => handleDateDragOver(date, e)}
348
+ onDrop={(e) => handleDateDrop(date, e)}
349
+ >
350
+ <div className="flex items-center justify-between mb-1">
351
+ <span className={cn(
352
+ "text-sm font-medium",
353
+ isTodayDate && "text-primary font-bold"
354
+ )}>
355
+ {date.getDate()}
356
+ </span>
357
+ {dayEvents.length > 0 && (
358
+ <Badge variant="secondary" className="text-xs px-1">
359
+ {dayEvents.length}
360
+ </Badge>
361
+ )}
362
+ </div>
363
+
364
+ {/* Events */}
365
+ <div className="flex-1 space-y-1 overflow-hidden">
366
+ {dayEvents.slice(0, 3).map((event) => (
367
+ <div
368
+ key={event.id}
369
+ className={cn(
370
+ "text-xs p-1 mb-1 rounded text-white cursor-move group relative select-none block w-full truncate",
371
+ event.color || EVENT_COLORS[event.type || 'event']
372
+ )}
373
+ draggable={!disabled}
374
+ onClick={(e) => handleEventClick(event, e)}
375
+ onDragStart={(e) => handleEventDragStart(event, e)}
376
+ onDragEnd={handleEventDragEnd}
377
+ style={{
378
+ backgroundColor: event.color || undefined
379
+ }}
380
+ >
381
+ <div className="flex items-center justify-between">
382
+ <span className="truncate flex-1">{event.title}</span>
383
+ {showEventDetails && (
384
+ <div className="hidden group-hover:flex items-center gap-1 ml-1">
385
+ <Button
386
+ variant="ghost"
387
+ size="sm"
388
+ className="h-4 w-4 p-0 text-white/80 hover:text-white"
389
+ onClick={(e) => handleEventEdit(event, e)}
390
+ >
391
+ <Edit className="h-3 w-3" />
392
+ </Button>
393
+ <Button
394
+ variant="ghost"
395
+ size="sm"
396
+ className="h-4 w-4 p-0 text-white/80 hover:text-white"
397
+ onClick={(e) => handleEventDelete(event, e)}
398
+ >
399
+ <Trash2 className="h-3 w-3" />
400
+ </Button>
401
+ </div>
402
+ )}
403
+ </div>
404
+ {event.startTime && (
405
+ <div className="flex items-center gap-1 mt-1">
406
+ <Clock className="h-3 w-3" />
407
+ <span>{event.startTime}</span>
408
+ </div>
409
+ )}
410
+ </div>
411
+ ))}
412
+ {dayEvents.length > 3 && (
413
+ <div className="text-xs text-muted-foreground text-center">
414
+ +{dayEvents.length - 3} more
415
+ </div>
416
+ )}
417
+ </div>
418
+ </div>
419
+ )
420
+ })}
421
+ </div>
422
+
423
+ {/* Selected Date Details */}
424
+ {selectedDate && (
425
+ <div className="border rounded-lg p-4 bg-muted/50">
426
+ <h4 className="font-medium mb-2">
427
+ {selectedDate.toLocaleDateString('en-US', {
428
+ weekday: 'long',
429
+ year: 'numeric',
430
+ month: 'long',
431
+ day: 'numeric'
432
+ })}
433
+ </h4>
434
+
435
+ <div className="space-y-2">
436
+ {getEventsForDate(selectedDate).map((event) => (
437
+ <div key={event.id} className="flex items-start gap-3 p-2 border rounded">
438
+ <div className={cn(
439
+ "w-3 h-3 rounded-full mt-1 flex-shrink-0",
440
+ event.color || EVENT_COLORS[event.type || 'event']
441
+ )} />
442
+ <div className="flex-1">
443
+ <div className="flex items-center justify-between">
444
+ <h5 className="font-medium">{event.title}</h5>
445
+ <div className="flex items-center gap-1">
446
+ <Button
447
+ variant="ghost"
448
+ size="sm"
449
+ onClick={(e) => handleEventEdit(event, e)}
450
+ >
451
+ <Edit className="h-4 w-4" />
452
+ </Button>
453
+ <Button
454
+ variant="ghost"
455
+ size="sm"
456
+ onClick={(e) => handleEventDelete(event, e)}
457
+ >
458
+ <Trash2 className="h-4 w-4" />
459
+ </Button>
460
+ </div>
461
+ </div>
462
+ {event.description && (
463
+ <p className="text-sm text-muted-foreground mt-1">
464
+ {event.description}
465
+ </p>
466
+ )}
467
+ <div className="flex items-center gap-4 text-xs text-muted-foreground mt-2">
468
+ {event.startTime && (
469
+ <div className="flex items-center gap-1">
470
+ <Clock className="h-3 w-3" />
471
+ <span>{event.startTime} - {event.endTime}</span>
472
+ </div>
473
+ )}
474
+ {event.location && (
475
+ <div className="flex items-center gap-1">
476
+ <MapPin className="h-3 w-3" />
477
+ <span>{event.location}</span>
478
+ </div>
479
+ )}
480
+ {event.attendees && event.attendees.length > 0 && (
481
+ <div className="flex items-center gap-1">
482
+ <User className="h-3 w-3" />
483
+ <span>{event.attendees.length} attendees</span>
484
+ </div>
485
+ )}
486
+ </div>
487
+ </div>
488
+ </div>
489
+ ))}
490
+
491
+ {getEventsForDate(selectedDate).length === 0 && (
492
+ <div className="text-center py-4">
493
+ <p className="text-sm text-muted-foreground mb-2">
494
+ No events scheduled for this date
495
+ </p>
496
+ <Button
497
+ variant="outline"
498
+ size="sm"
499
+ onClick={() => {
500
+ setEventDialogMode('create')
501
+ setSelectedEvent(null)
502
+ setEventDialogOpen(true)
503
+ }}
504
+ >
505
+ <Plus className="h-4 w-4 mr-1" />
506
+ Add Event
507
+ </Button>
508
+ </div>
509
+ )}
510
+ </div>
511
+ </div>
512
+ )}
513
+ </div>
514
+ </CardContent>
515
+ </Card>
516
+
517
+ {/* Event Dialog */}
518
+ <EventDialog
519
+ open={eventDialogOpen}
520
+ onOpenChange={setEventDialogOpen}
521
+ event={selectedEvent}
522
+ selectedDate={selectedDate}
523
+ onSave={handleEventSave}
524
+ onDelete={handleEventDialogDelete}
525
+ mode={eventDialogMode}
526
+ />
527
+ </>
528
+ )
529
+ }
530
+
531
+ export default Calendar