to do calendar view
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
20
app/api/users/current-user/route.ts
Normal file
20
app/api/users/current-user/route.ts
Normal 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);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
11
components/auth/Provider.tsx
Normal file
11
components/auth/Provider.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
35
components/events/EventTaskCalendar.tsx
Normal file
35
components/events/EventTaskCalendar.tsx
Normal 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
47
context/UserContext.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
48
package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
14
types.d.ts
vendored
@@ -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'
|
||||
}
|
||||
Reference in New Issue
Block a user