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 ? (
+
+
+
+ )
+}
diff --git a/components/icons/InfoIcon.tsx b/components/icons/InfoIcon.tsx
new file mode 100644
index 0000000..d84dd6a
--- /dev/null
+++ b/components/icons/InfoIcon.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export default function InfoIcon() {
+ return (
+
+ )
+}
diff --git a/lib/mutations.ts b/lib/mutations.ts
index 208e231..f4f41da 100644
--- a/lib/mutations.ts
+++ b/lib/mutations.ts
@@ -60,7 +60,7 @@ export const mutations = {
async updateEvent(
eventId: string,
- data: Partial<{ name: string; date: string; location: string }>
+ data: Partial<{ name: string; date: string; location: string; notes?: string; }>
) {
const { date, ...rest } = data;
@@ -74,8 +74,8 @@ export const mutations = {
const event = await prisma.event.update({
where: { id: eventId },
data: {
- ...rest,
- ...(parsedDate ? { date: parsedDate } : {}),
+ ...rest,
+ ...(parsedDate ? { date: parsedDate } : {}),
},
});
diff --git a/lib/queries.ts b/lib/queries.ts
index 98a5d3d..3736585 100644
--- a/lib/queries.ts
+++ b/lib/queries.ts
@@ -69,7 +69,7 @@ export const queries = {
eventGuests: {
include: {
guestBookEntry: true,
- }
+ },
},
}
})
diff --git a/package.json b/package.json
index ec38fad..160d70f 100644
--- a/package.json
+++ b/package.json
@@ -21,11 +21,14 @@
"nodemailer": "^7.0.3",
"prisma": "^6.10.1",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-markdown": "^10.1.0",
+ "remark-gfm": "^4.0.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
+ "@tailwindcss/typography": "^0.5.16",
"@types/bcrypt": "^5.0.2",
"@types/node": "^20",
"@types/react": "^19",
diff --git a/prisma/migrations/20250629122552_add_notes_to_events/migration.sql b/prisma/migrations/20250629122552_add_notes_to_events/migration.sql
new file mode 100644
index 0000000..99c1342
--- /dev/null
+++ b/prisma/migrations/20250629122552_add_notes_to_events/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Event" ADD COLUMN "notes" TEXT;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 998b0c4..c88a15b 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -33,6 +33,7 @@ model Event {
creatorId String
guests Guest[]
eventGuests EventGuest[]
+ notes String?
createdAt DateTime @default(now())
}