"use client"
import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog"
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import { ArrowUpRightIcon, XIcon, Loader2, Trash2Icon, CheckIcon } from "lucide-react"
import type { z } from "zod"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { cn } from "@lib/utils"
import dynamic from "next/dynamic"
import { Title } from "./title"
import { Summary as DocumentSummary } from "./summary"
import { dmSansClassName } from "@/lib/fonts"
import { GraphListMemories, type MemoryEntry } from "./graph-list-memories"
import { YoutubeVideo } from "./content/yt-video"
import { TweetContent } from "./content/tweet"
import { isTwitterUrl } from "@/lib/url-helpers"
import { NotionDoc } from "./content/notion-doc"
import { TextEditor } from "../text-editor"
import { useState, useEffect, useCallback, useMemo } from "react"
import { motion, AnimatePresence } from "motion/react"
import { Button } from "@repo/ui/components/button"
import { useDocumentMutations } from "@/hooks/use-document-mutations"
import type { UseMutationResult } from "@tanstack/react-query"
import { toast } from "sonner"
// Dynamically importing to prevent DOMMatrix error
const PdfViewer = dynamic(
() => import("./content/pdf").then((mod) => ({ default: mod.PdfViewer })),
{
ssr: false,
loading: () => (
Loading PDF viewer...
),
},
) as typeof import("./content/pdf").PdfViewer
type DocumentsResponse = z.infer
type DocumentWithMemories = DocumentsResponse["documents"][0]
interface DocumentModalProps {
document: DocumentWithMemories | null
isOpen: boolean
onClose: () => void
}
interface DeleteButtonProps {
documentId: string | null | undefined
customId: string | null | undefined
deleteMutation: UseMutationResult<
unknown,
Error,
{ documentId: string },
unknown
>
}
function isTemporaryId(id: string | null | undefined): boolean {
if (!id) return false
return id.startsWith("temp-") || id.startsWith("temp-file-")
}
function DeleteButton({ documentId, customId, deleteMutation }: DeleteButtonProps) {
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const handleDelete = useCallback(() => {
const id = documentId ?? customId
if (!id) return
// Check both IDs to ensure we catch temporary documents regardless of which ID is used
if (isTemporaryId(documentId) || isTemporaryId(customId)) {
// this is when user added document immediately and trying to delete
toast.error("Cannot delete document", {
description: "This document is still being processed. Please wait.",
})
return
}
deleteMutation.mutate({ documentId: id as string })
}, [documentId, customId, deleteMutation])
return (
{!deleteConfirmOpen ? (
setDeleteConfirmOpen(true)}
tabIndex={-1}
className="bg-[#0D121A] w-7 h-7 flex items-center justify-center rounded-full transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-2 focus:outline-none cursor-pointer shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)]"
disabled={deleteMutation.isPending}
>
Delete document
) : (
)}
)
}
export function DocumentModal({
document: _document,
isOpen,
onClose,
}: DocumentModalProps) {
const { updateMutation, deleteMutation } = useDocumentMutations({ onClose })
const { initialEditorContent, initialEditorString } = useMemo(() => {
const content = _document?.content as string | null | undefined
return {
initialEditorContent: content ?? undefined,
initialEditorString: content ?? "",
}
}, [_document?.content])
const [draftContentString, setDraftContentString] =
useState(initialEditorString)
const [editorResetNonce, setEditorResetNonce] = useState(0)
const [lastSavedContent, setLastSavedContent] = useState(null)
const resetEditor = useCallback(() => {
setDraftContentString(initialEditorString)
setEditorResetNonce((n) => n + 1)
setLastSavedContent(null)
}, [initialEditorString])
useEffect(() => {
setDraftContentString(initialEditorString)
setEditorResetNonce((n) => n + 1)
setLastSavedContent(null)
}, [initialEditorString])
useEffect(() => {
if (!isOpen) {
resetEditor()
}
}, [isOpen, resetEditor])
const hasUnsavedChanges =
draftContentString !== initialEditorString &&
draftContentString !== lastSavedContent
const handleSave = useCallback(() => {
if (!_document?.id) return
updateMutation.mutate(
{ documentId: _document.id, content: draftContentString },
{ onSuccess: (_data, variables) => setLastSavedContent(variables.content) },
)
}, [_document?.id, draftContentString, updateMutation])
return (
)
}