to do calendar view

This commit is contained in:
2025-07-07 17:45:33 -04:00
parent de40c78f47
commit 5143be1a67
13 changed files with 225 additions and 26 deletions

View File

@@ -1,16 +1,28 @@
'use client'
import { SessionProvider } from 'next-auth/react'
import { ReactNode } from 'react'
import DashboardNavbar from '@/components/DashboardNavbar'
import { ReactNode, useContext } from 'react'
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'
import { AppSidebar } from '@/components/app-sidebar'
import { SiteHeader } from '@/components/site-header'
import { UserContext } from '@/context/UserContext'
import { redirect } from 'next/navigation'
export default function AuthLayout({ children }: { children: ReactNode }) {
const { currentUser, loading } = useContext(UserContext)
if (loading) {
return <>Loading...</>
}
if (!currentUser) {
redirect('/login')
}
console.log(currentUser)
return (
<>
<main>
<SessionProvider>
<SidebarProvider
style={
@@ -33,6 +45,6 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
</SidebarInset>
</SidebarProvider>
</SessionProvider>
</>
</main>
)
}

View File

@@ -0,0 +1,20 @@
import { getServerSession } from "next-auth";
import { authOptions } from "../../auth/[...nextauth]/route";
import { NextResponse } from "next/server";
import { queries } from "@/lib/queries";
export async function GET() {
const session = await getServerSession(authOptions);
if (!session?.user.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const user = await queries.fetchCurrentUser(session.user.id)
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
return NextResponse.json(user);
}

View File

@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import "./globals.css";
import { Toaster } from "@/components/ui/sonner";
import Provider from "@/components/auth/Provider";
import { UserContextProvider } from "@/context/UserContext";
export const metadata: Metadata = {
title: "Wedding Planner",
@@ -18,8 +20,12 @@ export default async function RootLayout({
<body
className="bg-brand-background text-brand-text"
>
{children}
<Toaster />
<Provider>
<UserContextProvider>
{children}
</UserContextProvider>
<Toaster />
</Provider>
</body>
</html>
);

View File

@@ -2,13 +2,17 @@
import React, { useState } from 'react'
import { Card, CardContent } from './ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'
import EventTaskCalendar from './events/EventTaskCalendar'
interface Todo {
id: string
name: string
complete: boolean
dueDate?: string | null
notes?: string | null
id: string
name: string
complete: boolean
dueDate?: string | null
notes?: string | null
eventId: string
createdAt: string
updatedAt: string
}
interface Props {
@@ -195,8 +199,10 @@ export default function ToDoList({ eventId, initialTodos, onUpdate }: Props) {
</div>
</TabsContent>
<TabsContent value='calendar'>
<div className="grid grid-cols-7 gap-1 text-xs">
<div className=''>
<EventTaskCalendar
todos={todos}
/>
</div>
</TabsContent>
</Tabs>

View File

@@ -19,9 +19,7 @@ import {
IconUsers,
} from "@tabler/icons-react"
import { NavDocuments } from "@/components/nav-documents"
import { NavMain } from "@/components/nav-main"
import { NavSecondary } from "@/components/nav-secondary"
import { NavUser } from "@/components/nav-user"
import {
Sidebar,
@@ -32,8 +30,8 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
import { useSession } from "next-auth/react"
import Link from "next/link"
import { UserContext } from "@/context/UserContext"
const data = {
navMain: [
@@ -138,8 +136,7 @@ const data = {
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const session = useSession()
const user = session.data?.user
const { currentUser } = React.useContext(UserContext)
return (
<Sidebar collapsible="offcanvas" {...props}>
<SidebarHeader>
@@ -162,9 +159,9 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
{/* <NavDocuments items={data.documents} />
<NavSecondary items={data.navSecondary} className="mt-auto" /> */}
</SidebarContent>
{session && (
{currentUser && (
<SidebarFooter>
<NavUser user={user} />
<NavUser user={currentUser} />
</SidebarFooter>
)}

View File

@@ -0,0 +1,11 @@
'use client'
import { SessionProvider } from 'next-auth/react'
import React from 'react'
export default function Provider({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
{children}
</SessionProvider>
)
}

View File

@@ -22,12 +22,13 @@ export default function EventDashboard({ event }: Props) {
}
}
console.log(todos)
return (
<div className='grid grid-cols-1 lg:grid-cols-3 gap-6'>
<EventInfo event={event} />
<div className='lg:col-span-2 space-y-4'>
<EventRsvpTracking eventGuests={event.eventGuests} />
{/* <EventToDoList tasks={event.todos} /> */}
<ToDoList
eventId={event.id}
initialTodos={todos}

View File

@@ -0,0 +1,35 @@
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid' // a plugin!
import type { EventInput } from '@fullcalendar/core'
interface EventTodo {
id: string
name: string
complete: boolean
dueDate?: string | null
notes?: string | null
eventId: string
createdAt: string
updatedAt: string
}
export default function EventTaskCalendar({ todos }: { todos: EventTodo[] }) {
const calendarEvents: EventInput[] = todos
.filter(todo => !!todo.dueDate)
.map(todo => ({
id: todo.id,
title: todo.name,
start: todo.dueDate as string,
backgroundColor: todo.complete ? '#9ae6b4' : '#fbd38d',
borderColor: todo.complete ? '#38a169' : '#dd6b20',
allDay: true,
}))
return (
<FullCalendar
plugins={[ dayGridPlugin ]}
initialView="dayGridMonth"
height={650}
events={calendarEvents}
/>
)
}

47
context/UserContext.tsx Normal file
View File

@@ -0,0 +1,47 @@
'use client'
import { useSession } from 'next-auth/react'
import React, { createContext, useEffect, useState } from 'react'
type UserContextType = {
currentUser: User | null
loading: boolean
}
export const UserContext = createContext<UserContextType>({
currentUser: null,
loading: true
});
export const UserContextProvider = ({ children }: { children: React.ReactNode }) => {
const { data: session, status } = useSession();
const [currentUser, setCurrentUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
try {
const res = await fetch('api/users/current-user')
if (!res.ok) throw new Error('User not found')
const data: User = await res.json()
setCurrentUser(data)
} catch (err) {
console.error('Failed to fetch current user:', err)
} finally {
setLoading(false)
}
}
if (status === 'authenticated' && session?.user?.id) {
fetchUser()
} else if (status === 'unauthenticated') {
setLoading(false)
}
}, [session?.user.id, status])
return (
<UserContext.Provider value={{ currentUser, loading }}>
{children}
</UserContext.Provider>
)
}

View File

@@ -122,4 +122,12 @@ export const queries = {
}
},
async fetchCurrentUser(id: string | null) {
if (!id) return
return await prisma.user.findUnique({
where: { id },
})
}
}

48
package-lock.json generated
View File

@@ -12,8 +12,12 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fullcalendar/core": "^6.1.18",
"@fullcalendar/daygrid": "^6.1.18",
"@fullcalendar/react": "^6.1.18",
"@headlessui/react": "^2.2.4",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^6.11.1",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14",
@@ -411,6 +415,45 @@
"version": "0.2.9",
"license": "MIT"
},
"node_modules/@fullcalendar/core": {
"version": "6.1.18",
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.18.tgz",
"integrity": "sha512-cD7XtZIZZ87Cg2+itnpsONCsZ89VIfLLDZ22pQX4IQVWlpYUB3bcCf878DhWkqyEen6dhi5ePtBoqYgm5K+0fQ==",
"license": "MIT",
"dependencies": {
"preact": "~10.12.1"
}
},
"node_modules/@fullcalendar/core/node_modules/preact": {
"version": "10.12.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/@fullcalendar/daygrid": {
"version": "6.1.18",
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.18.tgz",
"integrity": "sha512-s452Zle1SdMEzZDw+pDczm8m3JLIZzS9ANMThXTnqeqJewW1gqNFYas18aHypJSgF9Fh9rDJjTSUw04BpXB/Mg==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.18"
}
},
"node_modules/@fullcalendar/react": {
"version": "6.1.18",
"resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.18.tgz",
"integrity": "sha512-Jwvb+T+/1yGZKe+UYXn0id022HJm0Fq2X/PGFvVh/QRYAI/6xPMRvJrwercBkToxf6LjqYXrDO+/NhRN6IDlmg==",
"license": "MIT",
"peerDependencies": {
"@fullcalendar/core": "~6.1.18",
"react": "^16.7.0 || ^17 || ^18 || ^19",
"react-dom": "^16.7.0 || ^17 || ^18 || ^19"
}
},
"node_modules/@headlessui/react": {
"version": "2.2.4",
"license": "MIT",
@@ -758,10 +801,11 @@
}
},
"node_modules/@prisma/client": {
"version": "6.10.1",
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.11.1.tgz",
"integrity": "sha512-5CLFh8QP6KxRm83pJ84jaVCeSVPQr8k0L2SEtOJHwdkS57/VQDcI/wQpGmdyOZi+D9gdNabdo8tj1Uk+w+upsQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=18.18"
},

View File

@@ -15,8 +15,12 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fullcalendar/core": "^6.1.18",
"@fullcalendar/daygrid": "^6.1.18",
"@fullcalendar/react": "^6.1.18",
"@headlessui/react": "^2.2.4",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^6.11.1",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14",

14
types.d.ts vendored
View File

@@ -34,10 +34,10 @@ interface Todo {
name: string
complete: boolean
dueDate?: string | null
createdAt: string
updatedAt?: string
dueDate?: string | null
notes?: string | null
eventId: string
createdAt: string
updatedAt: string
}
interface EventData {
@@ -64,3 +64,11 @@ interface EventGuest {
email?: string | null
}
}
type User = {
id: string
email: string
name?: string
username: string
role: 'COUPLE' | 'PLANNER' | 'GUEST'
}