baveshraam's picture
FIX: SurrealDB 2.0 migration syntax and Frontend/CORS link
f871fed
'use client'
import { Controller, useForm, useWatch } from 'react-hook-form'
import { useEffect, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { useCreateNote, useUpdateNote, useNote } from '@/lib/hooks/use-notes'
import { QUERY_KEYS } from '@/lib/api/query-client'
import { MarkdownEditor } from '@/components/ui/markdown-editor'
import { InlineEdit } from '@/components/common/InlineEdit'
import { cn } from "@/lib/utils";
const createNoteSchema = z.object({
title: z.string().optional(),
content: z.string().min(1, 'Content is required'),
})
type CreateNoteFormData = z.infer<typeof createNoteSchema>
interface NoteEditorDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
notebookId: string
note?: { id: string; title: string | null; content: string | null }
}
export function NoteEditorDialog({ open, onOpenChange, notebookId, note }: NoteEditorDialogProps) {
const createNote = useCreateNote()
const updateNote = useUpdateNote()
const queryClient = useQueryClient()
const isEditing = Boolean(note)
// Ensure note ID has 'note:' prefix for API calls
const noteIdWithPrefix = note?.id
? (note.id.includes(':') ? note.id : `note:${note.id}`)
: ''
const { data: fetchedNote, isLoading: noteLoading } = useNote(noteIdWithPrefix, { enabled: open && !!note?.id })
const isSaving = isEditing ? updateNote.isPending : createNote.isPending
const {
handleSubmit,
control,
formState: { errors },
reset,
setValue,
} = useForm<CreateNoteFormData>({
resolver: zodResolver(createNoteSchema),
defaultValues: {
title: '',
content: '',
},
})
const watchTitle = useWatch({ control, name: 'title' })
const [isEditorFullscreen, setIsEditorFullscreen] = useState(false)
useEffect(() => {
if (!open) {
reset({ title: '', content: '' })
return
}
const source = fetchedNote ?? note
const title = source?.title ?? ''
const content = source?.content ?? ''
reset({ title, content })
}, [open, note, fetchedNote, reset])
useEffect(() => {
if (!open) return
const observer = new MutationObserver(() => {
setIsEditorFullscreen(!!document.querySelector('.w-md-editor-fullscreen'))
})
observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['class'] })
return () => observer.disconnect()
}, [open])
const onSubmit = async (data: CreateNoteFormData) => {
if (note) {
await updateNote.mutateAsync({
id: noteIdWithPrefix,
data: {
title: data.title || undefined,
content: data.content,
},
})
// Only invalidate notebook-specific queries if we have a notebookId
if (notebookId) {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.notes(notebookId) })
}
} else {
// Creating a note requires a notebookId
if (!notebookId) {
console.error('Cannot create note without notebook_id')
return
}
await createNote.mutateAsync({
title: data.title || undefined,
content: data.content,
note_type: 'human',
notebook_id: notebookId,
})
}
reset()
onOpenChange(false)
}
const handleClose = () => {
reset()
setIsEditorFullscreen(false)
onOpenChange(false)
}
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className={cn(
"sm:max-w-3xl w-full max-h-[90vh] overflow-hidden p-0",
isEditorFullscreen && "!max-w-screen !max-h-screen border-none w-screen h-screen"
)}>
<DialogTitle className="sr-only">
{isEditing ? 'Edit note' : 'Create note'}
</DialogTitle>
<form onSubmit={handleSubmit(onSubmit)} className="flex h-full flex-col">
{isEditing && noteLoading ? (
<div className="flex-1 flex items-center justify-center py-10">
<span className="text-sm text-muted-foreground">Loading note…</span>
</div>
) : (
<>
<div className="border-b px-6 py-4">
<InlineEdit
value={watchTitle ?? ''}
onSave={(value) => setValue('title', value || '')}
placeholder="Add a title..."
emptyText="Untitled Note"
className="text-xl font-semibold"
inputClassName="text-xl font-semibold"
/>
</div>
<div className={cn(
"flex-1 overflow-y-auto",
!isEditorFullscreen && "px-6 py-4")
}>
<Controller
control={control}
name="content"
render={({ field }) => (
<MarkdownEditor
key={note?.id ?? 'new'}
value={field.value}
onChange={field.onChange}
height={420}
placeholder="Write your note content here..."
className={cn(
"w-full h-full min-h-[420px] [&_.w-md-editor]:!static [&_.w-md-editor]:!w-full [&_.w-md-editor]:!h-full",
!isEditorFullscreen && "rounded-md border"
)}
/>
)}
/>
{errors.content && (
<p className="text-sm text-red-600 mt-1">{errors.content.message}</p>
)}
</div>
</>
)}
<div className="border-t px-6 py-4 flex justify-end gap-2">
<Button type="button" variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button
type="submit"
disabled={isSaving || (isEditing && noteLoading)}
>
{isSaving
? isEditing ? 'Saving...' : 'Creating...'
: isEditing
? 'Save Note'
: 'Create Note'}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
)
}