From 5143be1a672cf7dfb7f62ade6a3dbdabeccde3bd Mon Sep 17 00:00:00 2001 From: Brian Nelson Date: Mon, 7 Jul 2025 17:45:33 -0400 Subject: [PATCH] to do calendar view --- app/(auth)/layout.tsx | 20 ++++++++--- app/api/users/current-user/route.ts | 20 +++++++++++ app/layout.tsx | 10 ++++-- components/ToDoList.tsx | 20 +++++++---- components/app-sidebar.tsx | 11 +++--- components/auth/Provider.tsx | 11 ++++++ components/events/EventDashboard.tsx | 3 +- components/events/EventTaskCalendar.tsx | 35 ++++++++++++++++++ context/UserContext.tsx | 47 ++++++++++++++++++++++++ lib/queries.ts | 8 +++++ package-lock.json | 48 +++++++++++++++++++++++-- package.json | 4 +++ types.d.ts | 14 ++++++-- 13 files changed, 225 insertions(+), 26 deletions(-) create mode 100644 app/api/users/current-user/route.ts create mode 100644 components/auth/Provider.tsx create mode 100644 components/events/EventTaskCalendar.tsx create mode 100644 context/UserContext.tsx diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index 6043603..24dbfd7 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -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 ( - <> +
- +
) } diff --git a/app/api/users/current-user/route.ts b/app/api/users/current-user/route.ts new file mode 100644 index 0000000..34d1737 --- /dev/null +++ b/app/api/users/current-user/route.ts @@ -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); +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 639ad46..ee55994 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -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({ - {children} - + + + {children} + + + ); diff --git a/components/ToDoList.tsx b/components/ToDoList.tsx index 893e1d1..f8cb2e2 100644 --- a/components/ToDoList.tsx +++ b/components/ToDoList.tsx @@ -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) { -
- +
+
diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 3896bce..94e01dd 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -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) { - const session = useSession() - const user = session.data?.user + const { currentUser } = React.useContext(UserContext) return ( @@ -162,9 +159,9 @@ export function AppSidebar({ ...props }: React.ComponentProps) { {/* */} - {session && ( + {currentUser && ( - + )} diff --git a/components/auth/Provider.tsx b/components/auth/Provider.tsx new file mode 100644 index 0000000..5b90cf5 --- /dev/null +++ b/components/auth/Provider.tsx @@ -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 ( + + {children} + + ) +} diff --git a/components/events/EventDashboard.tsx b/components/events/EventDashboard.tsx index 7697779..f6b3049 100644 --- a/components/events/EventDashboard.tsx +++ b/components/events/EventDashboard.tsx @@ -22,12 +22,13 @@ export default function EventDashboard({ event }: Props) { } } + console.log(todos) + return (
- {/* */} !!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 ( + + ) +} \ No newline at end of file diff --git a/context/UserContext.tsx b/context/UserContext.tsx new file mode 100644 index 0000000..6183269 --- /dev/null +++ b/context/UserContext.tsx @@ -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({ + currentUser: null, + loading: true +}); + +export const UserContextProvider = ({ children }: { children: React.ReactNode }) => { + const { data: session, status } = useSession(); + const [currentUser, setCurrentUser] = useState(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 ( + + {children} + + ) +} diff --git a/lib/queries.ts b/lib/queries.ts index a3454a8..1069188 100644 --- a/lib/queries.ts +++ b/lib/queries.ts @@ -122,4 +122,12 @@ export const queries = { } }, + async fetchCurrentUser(id: string | null) { + if (!id) return + + return await prisma.user.findUnique({ + where: { id }, + }) + } + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4ca97f0..cc19298 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" }, diff --git a/package.json b/package.json index d5c64e8..8c47d79 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/types.d.ts b/types.d.ts index ea2e4bf..21a0668 100644 --- a/types.d.ts +++ b/types.d.ts @@ -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 { @@ -63,4 +63,12 @@ interface EventGuest { lName: string email?: string | null } +} + +type User = { + id: string + email: string + name?: string + username: string + role: 'COUPLE' | 'PLANNER' | 'GUEST' } \ No newline at end of file