diff --git a/bun.lock b/bun.lock
index 08e6405..bfecab2 100644
--- a/bun.lock
+++ b/bun.lock
@@ -13,6 +13,7 @@
"@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
"dotenv": "^17.2.1",
"drizzle-orm": "^0.44.4",
"ical.js": "^2.2.1",
@@ -25,6 +26,7 @@
"pg": "^8.16.3",
"postgres": "^3.4.7",
"react": "19.1.0",
+ "react-day-picker": "^9.9.0",
"react-dom": "19.1.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
@@ -56,6 +58,8 @@
"@auth/drizzle-adapter": ["@auth/drizzle-adapter@1.10.0", "", { "dependencies": { "@auth/core": "0.40.0" } }, "sha512-3MKsdAINTfvV4QKev8PFMNG93HJEUHh9sggDXnmUmriFogRf8qLvgqnPsTlfUyWcLwTzzrrYjeu8CGM+4IxHwQ=="],
+ "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
+
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],
@@ -508,6 +512,10 @@
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
+ "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
+
+ "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="],
+
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@@ -898,6 +906,8 @@
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
+ "react-day-picker": ["react-day-picker@9.9.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-NtkJbuX6cl/VaGNb3sVVhmMA6LSMnL5G3xNL+61IyoZj0mUZFWTg4hmj7PHjIQ8MXN9dHWhUHFoJWG6y60DKSg=="],
+
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
diff --git a/package.json b/package.json
index 687ed12..8fdd932 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
"dotenv": "^17.2.1",
"drizzle-orm": "^0.44.4",
"ical.js": "^2.2.1",
@@ -30,6 +31,7 @@
"pg": "^8.16.3",
"postgres": "^3.4.7",
"react": "19.1.0",
+ "react-day-picker": "^9.9.0",
"react-dom": "19.1.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1"
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 965dbe8..4803dc7 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -5,7 +5,10 @@ import { nanoid } from 'nanoid'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
-import { Card } from '@/components/ui/card'
+import { Card, CardHeader, CardContent } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+import { LucideMapPin, Clock, MoreHorizontal, Calendar1Icon } from 'lucide-react'
+import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuItem } from '@/components/ui/dropdown-menu'
import { RecurrencePicker } from '@/components/recurrence-picker'
import { IcsFilePicker } from '@/components/ics-file-picker'
@@ -16,6 +19,111 @@ import { Textarea } from '@/components/ui/textarea'
import { useSession } from 'next-auth/react'
import { toast } from 'sonner'
+// Individual event card component
+const EventCard = ({ event, onEdit, onDelete }: { event: CalendarEvent, onEdit: (event: CalendarEvent) => void, onDelete: (eventId: string) => void }) => {
+ const formatDateTime = (dateStr: string, allDay: boolean | undefined) => {
+ return allDay
+ ? new Date(dateStr).toLocaleDateString()
+ : new Date(dateStr).toLocaleString()
+ }
+
+ const handleEdit = () => {
+ onEdit({
+ id: event.id,
+ title: event.title,
+ description: event.description || '',
+ location: event.location || '',
+ url: event.url || '',
+ start: event.start,
+ end: event.end || '',
+ allDay: event.allDay || false
+ })
+ }
+
+ return (
+
+
+
+
+
+ {event.title}
+
+ {event.recurrenceRule && (
+
+ Repeats: {event.recurrenceRule}
+
+ )}
+ {event.description && (
+
+ {event.description}
+
+ )}
+
+
+
+
+
+
+
+ Edit event
+
+ onDelete(event.id)}
+ className="text-destructive"
+ >
+ Delete event
+
+
+
+
+
+
+
+
+
+
+ {formatDateTime(event.start, event.allDay)}
+
+
+ {event.location && (
+
+
+ {event.location}
+
+ )}
+
+
+
+ )
+}
+
+const EventsList = ({ events, onEdit, onDelete }: { events: CalendarEvent[], onEdit: (event: CalendarEvent) => void, onDelete: (eventId: string) => void }) => {
+ if (events.length === 0) {
+
+ return (
+
+
No events yet
+
Create your first event to get started
+
)
+ }
+
+ return (
+
+ {events.map(event => (
+
+ ))}
+
+ )
+}
+
export default function HomePage() {
const [events, setEvents] = useState([])
const [dialogOpen, setDialogOpen] = useState(false)
@@ -321,41 +429,24 @@ export default function HomePage() {
{/* Event List */}
- {events.length === 0 && No events yet
}
-
-
+ {events.length === 0 && (
+ <>
+ >)}
+ {
+ setTitle(eventData.title)
+ setDescription(eventData.description || "")
+ setLocation(eventData.location || "")
+ setUrl(eventData.url || "")
+ setStart(eventData.start)
+ setEnd(eventData.end || "")
+ setAllDay(eventData.allDay || false)
+ setEditingId(eventData.id)
+ setDialogOpen(true)
+ }}
+ onDelete={handleDelete}
+ />
{/* Add/Edit Dialog */}