import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "./ui/dialog"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { Textarea } from "./ui/textarea"; import { Checkbox } from "./ui/checkbox"; import { useGrimoire } from "@/core/state"; import { toast } from "sonner"; import { saveSpellbook, markSpellbookPublished, } from "@/services/spellbook-storage"; import { PublishSpellbook } from "@/actions/publish-spellbook"; import { hub, publishEvent } from "@/services/hub"; import { createSpellbook } from "@/lib/spellbook-manager"; import { Loader2, Save, Send } from "lucide-react"; import type { SpellbookEvent } from "@/types/spell"; import { lastValueFrom } from "rxjs"; interface SaveSpellbookDialogProps { open: boolean; onOpenChange: (open: boolean) => void; existingSpellbook?: { slug: string; title: string; description?: string; workspaceIds?: string[]; localId?: string; pubkey?: string; }; } export function SaveSpellbookDialog({ open, onOpenChange, existingSpellbook, }: SaveSpellbookDialogProps) { const { state, loadSpellbook } = useGrimoire(); const isUpdateMode = !!existingSpellbook; const [title, setTitle] = useState(existingSpellbook?.title || ""); const [description, setDescription] = useState( existingSpellbook?.description || "", ); const [selectedWorkspaces, setSelectedWorkspaces] = useState( existingSpellbook?.workspaceIds || Object.keys(state.workspaces), ); const [isPublishing, setIsPublishing] = useState(false); const [isSaving, setIsSaving] = useState(false); // Update form when dialog opens with existing spellbook data useEffect(() => { if (open && existingSpellbook) { setTitle(existingSpellbook.title); setDescription(existingSpellbook.description || ""); setSelectedWorkspaces( existingSpellbook.workspaceIds || Object.keys(state.workspaces), ); } else if (open && !existingSpellbook) { // Reset form for new spellbook setTitle(""); setDescription(""); setSelectedWorkspaces(Object.keys(state.workspaces)); } }, [open, existingSpellbook, state.workspaces]); const handleSave = async (shouldPublish: boolean) => { if (!title.trim()) { toast.error("Please enter a title for your spellbook"); return; } if (selectedWorkspaces.length === 0) { toast.error("Please select at least one tab to include"); return; } setIsSaving(true); if (shouldPublish) setIsPublishing(true); try { // 1. Create content const encoded = createSpellbook({ state, title, description, workspaceIds: selectedWorkspaces, }); // 2. Determine slug (keep existing for updates, generate for new) const slug = isUpdateMode ? existingSpellbook.slug : title.toLowerCase().trim().replace(/\s+/g, "-"); // 3. Save locally (pass existing ID in update mode to prevent duplicates) const localSpellbook = await saveSpellbook({ id: isUpdateMode ? existingSpellbook.localId : undefined, slug, title, description, content: JSON.parse(encoded.eventProps.content), isPublished: false, }); // 4. Optionally publish if (shouldPublish) { const localId = existingSpellbook?.localId || localSpellbook.id; const event = await lastValueFrom( hub.exec(PublishSpellbook, { state, title, description, workspaceIds: selectedWorkspaces, content: localSpellbook.content, }), ); if (event) { await publishEvent(event); // Only mark as published AFTER successful relay publish await markSpellbookPublished(localId, event as SpellbookEvent); } toast.success( isUpdateMode ? "Spellbook updated and published to Nostr" : "Spellbook saved and published to Nostr", ); } else { toast.success( isUpdateMode ? "Spellbook updated locally" : "Spellbook saved locally", ); } // 5. Set as active spellbook with full source tracking const parsedSpellbook = { slug, title, description: description || undefined, content: localSpellbook.content, referencedSpells: [], event: localSpellbook.event as any, // Event might not exist for locally-only spellbooks // Enhanced source tracking: localId: localSpellbook.id, isPublished: shouldPublish || localSpellbook.isPublished, source: "local" as const, }; loadSpellbook(parsedSpellbook); onOpenChange(false); // Reset form only if creating new if (!isUpdateMode) { setTitle(""); setDescription(""); setSelectedWorkspaces(Object.keys(state.workspaces)); } } catch (error) { console.error("Failed to save spellbook:", error); toast.error( error instanceof Error ? error.message : "Failed to save spellbook", ); } finally { setIsSaving(false); setIsPublishing(false); } }; return ( {isUpdateMode ? "Update Spellbook" : "Save as Spellbook"} {isUpdateMode ? "Update the configuration of your spellbook." : "Save your current workspaces and window configuration."}
setTitle(e.target.value)} />