"use client" import { useState, useEffect, useMemo, useCallback } from "react" import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" import { FileTextIcon, GlobeIcon, ZapIcon, ChevronsUpDownIcon, FolderIcon, Loader2, } from "lucide-react" import { Button } from "@ui/components/button" import { ConnectContent } from "./connections" import { NoteContent } from "./note" import { LinkContent, type LinkData } from "./link" import { FileContent, type FileData } from "./file" import { useProject } from "@/stores" import { $fetch } from "@lib/api" import { DEFAULT_PROJECT_ID } from "@repo/lib/constants" import type { Project } from "@repo/lib/types" import { useQuery } from "@tanstack/react-query" import { motion } from "motion/react" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@repo/ui/components/dropdown-menu" import { toast } from "sonner" import { useDocumentMutations } from "../../../hooks/use-document-mutations" import { useCustomer } from "autumn-js/react" import { useMemoriesUsage } from "@/hooks/use-memories-usage" type TabType = "note" | "link" | "file" | "connect" interface AddDocumentModalProps { isOpen: boolean onClose: () => void defaultTab?: TabType } export function AddDocumentModal({ isOpen, onClose, defaultTab, }: AddDocumentModalProps) { return ( !open && onClose()}> Add Document
) } const tabs = [ { id: "note" as const, icon: FileTextIcon, title: "Write a note", description: "Save your thoughts, notes and summaries, as memories", }, { id: "link" as const, icon: GlobeIcon, title: "Save a link", description: "Add any webpage into your searchable knowledge base", }, { id: "file" as const, icon: FileTextIcon, title: "Upload a file", description: "Turn any image, PDF or document into contextual memories", }, { id: "connect" as const, icon: ZapIcon, title: "Connect knowledge bases", description: "Sync with Google Drive, Notion and OneDrive and import data", isPro: true, }, ] export function AddDocument({ defaultTab, onClose, isOpen, }: { defaultTab?: TabType onClose: () => void isOpen?: boolean }) { const [activeTab, setActiveTab] = useState(defaultTab ?? "note") const { selectedProject: globalSelectedProject } = useProject() const [localSelectedProject, setLocalSelectedProject] = useState( globalSelectedProject, ) const [isProjectSelectorOpen, setIsProjectSelectorOpen] = useState(false) // Form data state for button click handling const [noteContent, setNoteContent] = useState("") const [linkData, setLinkData] = useState({ url: "", title: "", description: "", }) const [fileData, setFileData] = useState({ file: null, title: "", description: "", }) const { noteMutation, linkMutation, fileMutation } = useDocumentMutations({ onClose, }) const autumn = useCustomer() const { memoriesUsed, memoriesLimit, hasProProduct, isLoading: isLoadingMemories, usagePercent, } = useMemoriesUsage(autumn) useEffect(() => { setLocalSelectedProject(globalSelectedProject) }, [globalSelectedProject]) const { data: projects = [], isLoading: isLoadingProjects } = useQuery({ queryKey: ["projects"], queryFn: async () => { const response = await $fetch("@get/projects") if (response.error) { throw new Error(response.error?.message || "Failed to load projects") } return response.data?.projects || [] }, staleTime: 30 * 1000, }) const projectName = useMemo(() => { if (localSelectedProject === DEFAULT_PROJECT_ID) return "Default Project" const found = projects.find( (p: Project) => p.containerTag === localSelectedProject, ) return found?.name ?? localSelectedProject }, [projects, localSelectedProject]) const handleProjectSelect = (containerTag: string) => { setLocalSelectedProject(containerTag) setIsProjectSelectorOpen(false) } useEffect(() => { if (defaultTab) { setActiveTab(defaultTab) } }, [defaultTab]) // Submit handlers const handleNoteSubmit = useCallback( (content: string) => { if (!content.trim()) { toast.error("Please enter some content") return } noteMutation.mutate({ content, project: localSelectedProject }) }, [noteMutation, localSelectedProject], ) const handleLinkSubmit = useCallback( (data: LinkData) => { if (!data.url.trim()) { toast.error("Please enter a URL") return } linkMutation.mutate({ url: data.url, project: localSelectedProject }) }, [linkMutation, localSelectedProject], ) const handleFileSubmit = useCallback( (data: { file: File; title: string; description: string }) => { if (!data.file) { toast.error("Please select a file") return } fileMutation.mutate({ file: data.file, title: data.title || undefined, description: data.description || undefined, project: localSelectedProject, }) }, [fileMutation, localSelectedProject], ) // Data change handlers const handleNoteContentChange = useCallback((content: string) => { setNoteContent(content) }, []) const handleLinkDataChange = useCallback((data: LinkData) => { setLinkData(data) }, []) const handleFileDataChange = useCallback((data: FileData) => { setFileData(data) }, []) // Button click handler const handleButtonClick = () => { if (activeTab === "note") { handleNoteSubmit(noteContent) } else if (activeTab === "link") { handleLinkSubmit(linkData) } else if (activeTab === "file") { if (fileData.file) { handleFileSubmit( fileData as { file: File; title: string; description: string }, ) } else { toast.error("Please select a file") } } } const isSubmitting = noteMutation.isPending || linkMutation.isPending || fileMutation.isPending return (
{tabs.map((tab) => ( setActiveTab(tab.id)} icon={tab.icon} title={tab.title} description={tab.description} isPro={tab.isPro} /> ))}
Memories {isLoadingMemories ? "…" : hasProProduct ? "Unlimited" : `${memoriesUsed}/${memoriesLimit}`}
{!hasProProduct && (
)}
{activeTab === "note" && ( )} {activeTab === "link" && ( )} {activeTab === "file" && ( )} {activeTab === "connect" && ( )}
handleProjectSelect(DEFAULT_PROJECT_ID)} className={cn( "flex items-center gap-2 px-3 py-2 rounded-[8px] cursor-pointer", localSelectedProject === DEFAULT_PROJECT_ID ? "bg-[#4BA0FA]/20 text-white" : "text-[#737373] hover:bg-[#14161A] hover:text-white", )} > Default Project {projects .filter((p: Project) => p.containerTag !== DEFAULT_PROJECT_ID) .map((project: Project) => ( handleProjectSelect(project.containerTag)} className={cn( "flex items-center gap-2 px-3 py-2 rounded-[8px] cursor-pointer", localSelectedProject === project.containerTag ? "bg-[#4BA0FA]/20 text-white" : "text-[#737373] hover:bg-[#14161A] hover:text-white", )} > {project.name} ))}
{activeTab !== "connect" && ( )}
) } function TabButton({ active, onClick, icon: Icon, title, description, isPro, }: { active: boolean onClick: () => void icon: React.ComponentType<{ className?: string }> title: string description: string isPro?: boolean }) { return ( ) }