diff --git a/README.md b/README.md index f172af4..a2415da 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ My goal for this project is to be an all-in-one self hosted event planner for ma - Event type - Details - Location + - [x] Markdown supported notes - [x] Guest book (contact information) - [x] Ability to switch between table or card view - [x] Add Guests to events diff --git a/app/(auth)/events/[eventId]/page.tsx b/app/(auth)/events/[eventId]/page.tsx index afa29f5..272452c 100644 --- a/app/(auth)/events/[eventId]/page.tsx +++ b/app/(auth)/events/[eventId]/page.tsx @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import EventInfoDisplay from '@/components/EventInfoDisplay' -import HeadingWithEdit from '@/components/HeadingWithEdit' import { queries } from '@/lib/queries' import React from 'react' diff --git a/app/api/events/[eventId]/route.ts b/app/api/events/[eventId]/route.ts index 647dcb0..cf85bda 100644 --- a/app/api/events/[eventId]/route.ts +++ b/app/api/events/[eventId]/route.ts @@ -13,7 +13,12 @@ export async function PATCH(req: NextRequest, { params }: { params: { eventId: s const body = await req.json(); try { - const updated = await mutations.updateEvent(eventId, body); + const updated = await mutations.updateEvent(eventId, { + name: body.name, + date: body.date, + location: body.location, + notes: body.notes, + }); return NextResponse.json(updated); } catch (error) { console.error('[PATCH EVENT]', error); diff --git a/app/globals.css b/app/globals.css index f51b3e3..edbd5e6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@plugin "@tailwindcss/typography"; @theme { --color-background: #fff5eb; @@ -75,4 +76,36 @@ .btn-primary{ @apply bg-brand-primary text-brand-background border-0 hover:bg-brand-primary-500 transition-colors duration-300 -} \ No newline at end of file +} + +.prose h1 { + @apply text-2xl font-bold m-0 p-0 +} + +.prose h2 { + @apply text-xl font-bold m-0 p-0 +} + +.prose p { + @apply p-0 m-0 +} + +.prose a { + @apply text-brand-primary-500 +} + +.input { + @apply block w-full px-4 py-2 rounded-md text-sm text-gray-900 bg-white border border-gray-300 shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-brand-primary-500 focus:border-brand-primary-500; +} + +.input-bordered { + @apply border border-gray-300; +} + +.textarea { + @apply block w-full px-4 py-2 rounded-md text-sm text-gray-900 bg-white border border-gray-300 shadow-sm placeholder-gray-400 resize-y min-h-[100px] focus:outline-none focus:ring-2 focus:ring-brand-primary-500 focus:border-brand-primary-500; +} + +.textarea-bordered { + @apply border border-gray-300; +} diff --git a/bun.lockb b/bun.lockb index c8b4c5d..c59dfc8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/EventInfoDisplay.tsx b/components/EventInfoDisplay.tsx index 07afae5..9adb34f 100644 --- a/components/EventInfoDisplay.tsx +++ b/components/EventInfoDisplay.tsx @@ -4,6 +4,7 @@ import React, { useState } from 'react' import AddGuestFromGuestBook from './AddGuestFromGuestBook' +import EventNotesEditor from './EventNotesEditor' interface Creator { id: string @@ -21,6 +22,7 @@ interface EventData { createdAt: string creator: Creator guests: any[] + notes?: string eventGuests: any[] } @@ -167,7 +169,7 @@ export default function EventInfoDisplay({ event }: Props) {

{formatDate(event.createdAt)}

- {error &&

{error}

} + {error &&

{error}

} {isEditing && (
)} +
+ +
diff --git a/components/EventNotesEditor.tsx b/components/EventNotesEditor.tsx new file mode 100644 index 0000000..562fe6f --- /dev/null +++ b/components/EventNotesEditor.tsx @@ -0,0 +1,103 @@ +'use client' +import { useSession } from 'next-auth/react' +import React, { useEffect, useRef, useState } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import InfoIcon from './icons/InfoIcon' + +interface Props { + eventId: string + initialNotes: string + canEdit: boolean +} + +export default function EventNotesEditor({ eventId, initialNotes, canEdit }: Props) { + const [notes, setNotes] = useState(initialNotes || ''); + const [isEditing, setIsEditing] = useState(false) + const [saving, setSaving] = useState(false); + const textareaRef = useRef(null) + const { data: session } = useSession() + + const isAuthorized = + session?.user?.role === 'COUPLE' || session?.user?.role === 'PLANNER' + + useEffect(() => { + if (isEditing && textareaRef.current) { + textareaRef.current.focus() + } + }, [isEditing]) + + async function saveNotes() { + setSaving(true) + try { + const res = await fetch(`/api/events/${eventId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ notes }), + }) + + if (!res.ok) throw new Error('Failed to save notes') + setIsEditing(false) + } catch (err) { + console.error(err) + } finally { + setSaving(false) + } + } + + if (!canEdit) { + return ( +
+

Event Notes

+
+ {notes || '_No notes yet._'} +
+
+ ) + } + + function handleBlur() { + if (notes.trim() !== initialNotes?.trim()) { + saveNotes() + } else { + setIsEditing(false) + } + } + + return ( +
+

+ Notes +

+
{ + if (isAuthorized) setIsEditing(true) + }} + > + {isEditing ? ( +