added markdown notes for events

This commit is contained in:
2025-06-29 10:07:01 -04:00
parent 28efa115ad
commit c7c121e23d
13 changed files with 174 additions and 9 deletions

View File

@@ -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) {
<p>{formatDate(event.createdAt)}</p>
</div>
</div>
{error && <p className="text-red-500 text-sm">{error}</p>}
{error && <p className="text-red-500 text-sm">{error}</p>}
{isEditing && (
<div className="text-right">
<button
@@ -179,6 +181,13 @@ export default function EventInfoDisplay({ event }: Props) {
</button>
</div>
)}
<div className='col-span-6'>
<EventNotesEditor
eventId={event.id}
initialNotes={event.notes || ''}
canEdit={['COUPLE', 'PLANNER'].includes(event.creator.role)}
/>
</div>
</div>
<div className='col-span-3'>
<div className='flex justify-between items-center'>

View File

@@ -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<HTMLTextAreaElement>(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 (
<div className="p-4 rounded shadow">
<h3 className="font-semibold text-lg mb-2">Event Notes</h3>
<div className='prose prose-sm dark:prose-invertse'>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{notes || '_No notes yet._'}</ReactMarkdown>
</div>
</div>
)
}
function handleBlur() {
if (notes.trim() !== initialNotes?.trim()) {
saveNotes()
} else {
setIsEditing(false)
}
}
return (
<div>
<p className='text-sm font-semibold mb-1 flex gap-1 items-center'>
Notes
</p>
<div
className="prose prose-brand rounded-lg textarea-bordered p-4 w-full min-h-[120px] cursor-text"
onClick={() => {
if (isAuthorized) setIsEditing(true)
}}
>
{isEditing ? (
<textarea
ref={textareaRef}
value={notes}
onChange={(e) => setNotes(e.target.value)}
onBlur={handleBlur}
rows={6}
className="textarea textarea-bordered w-full resize-none"
/>
) : notes.trim() ? (
<ReactMarkdown remarkPlugins={[remarkGfm]}>{notes}</ReactMarkdown>
) : (
<p className="text-gray-500 italic textarea-bordered rounded-lg min-h-32 p-4">Click to add notes...</p>
)}
{saving && <p className="text-xs text-gray-400">Saving...</p>}
</div>
<div className='text-xs font-normal'>
<a href={'https://www.markdownguide.org/cheat-sheet/'} target='_blank' className='flex items-center underline'>
Supports Markdown
<InfoIcon />
</a>
</div>
</div>
)
}

View File

@@ -0,0 +1,9 @@
import React from 'react'
export default function InfoIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-4">
<path strokeLinecap="round" strokeLinejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
)
}