import { useEffect, useRef, useState } from "preact/hooks"; import { encryptDataWithStoredPassword, getEncryptionPassword, } from "../../helpers.ts"; interface AddObligationDialogProps { onClose: () => void; userId?: string; } type FieldErrorState = { description: boolean; amount: boolean; }; export default function AddObligationDialog( { onClose, userId }: AddObligationDialogProps, ) { // Theme classes are defined in styles.css with @media (prefers-color-scheme: dark) const formRef = useRef(null); const firstInputRef = useRef(null); const [loading, setLoading] = useState(false); const [fieldErrors, setFieldErrors] = useState({ description: false, amount: false, }); const today = new Date().toISOString().split("T")[0]; const addNewObligation = async () => { if (!formRef.current) return; // Collect form data const formData = new FormData(formRef.current); const description = formData.get("description")?.toString().trim() || ""; const amount = formData.get("amount")?.toString().trim() || ""; const frequency = formData.get("frequency")?.toString() || "Monthly"; const dueDate = formData.get("due_date")?.toString() || today; const sender = typeof location !== "undefined" ? location.pathname : "/"; const validation = { description: !description, amount: !amount, }; setFieldErrors(validation); if (validation.description || validation.amount) { return; } setLoading(true); localStorage.setItem( "lock-route", "/calendar/budget-obligations?modal=off", ); // Build submission data const submitData = new FormData(); submitData.set("sender", sender); submitData.set("frequency", frequency); submitData.set("due_date", dueDate); // Encrypt if password is available const encryptionPassword = userId ? getEncryptionPassword(userId) : null; if (encryptionPassword && (description || amount)) { try { const dataToEncrypt = { description, amount: parseFloat(amount) || 0, }; const encryptedData = await encryptDataWithStoredPassword( userId!, JSON.stringify(dataToEncrypt), ); if (encryptedData) { submitData.set("encrypted_data", encryptedData); // Send empty plaintext when encrypted submitData.set("description", ""); submitData.set("amount", "0"); } else { throw new Error("Encryption returned empty result"); } } catch (_err) { setLoading(false); return; } } else { submitData.set("description", description); submitData.set("amount", amount); submitData.set("encrypted_data", ""); } // Submit via fetch - let redirects follow automatically try { const response = await fetch("/api/create/obligation", { method: "POST", body: submitData, credentials: "same-origin", }); // Fetch followed the redirect, now navigate to final URL or sender if (response.ok) { globalThis.location.href = "/calendar/budget-obligations?modal=off"; } else { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || "Failed to create obligation"); } } catch (_err) { setLoading(false); } }; const clearFieldError = (field: keyof FieldErrorState) => { setFieldErrors((prev) => prev[field] ? { ...prev, [field]: false } : prev); }; const getInputErrorClass = (hasError: boolean) => hasError ? "border-red-500 ring-1 ring-red-300" : ""; useEffect(() => { firstInputRef.current?.focus(); }, []); return (

Add Obligation

clearFieldError("description")} />
Description is required.
clearFieldError("amount")} />
Amount is required.
); }