import { decryptDataWithStoredPassword, encryptDataWithStoredPassword, getEncryptionPassword, MakeNewDate, } from "../helpers.ts"; import { Obligation } from "../services/financhalServices.ts"; import { useEffect, useRef, useState } from "preact/hooks"; import { PopAddObligationDialog } from "./dialogs/PopAddObligationDialog.tsx"; interface Props { obligations: Obligation[]; userId?: string; openModal?: boolean; } export default function ObligationList( { obligations, userId, openModal = false }: Props, ) { // Theme classes are defined in styles.css with @media (prefers-color-scheme: dark) const [popAddObligation, popupAddObligationDialog] = PopAddObligationDialog({ userId, defaultOpen: openModal, }); const [processedObligations, setProcessedObligations] = useState< Obligation[] >(obligations); const [isProcessing, setIsProcessing] = useState(false); const [error, setError] = useState(null); const [isOpeningModal, setIsOpeningModal] = useState(openModal); const processingRef = useRef(false); // Process obligations on component mount and when obligations or userId change useEffect(() => { setTimeout(() => { const lockRoute = localStorage.getItem("lock-route"); const url = new URL(globalThis.location.href); const currentRoute = url.pathname + url.search; if (lockRoute && lockRoute === currentRoute) { localStorage.removeItem("lock-route"); } }, 100); // Prevent concurrent processing if (processingRef.current) { return; } if (!userId) { setProcessedObligations(obligations); setError(null); return; } const processObligations = async () => { processingRef.current = true; setIsProcessing(true); setError(null); try { const encryptionPassword = getEncryptionPassword(userId); if (!encryptionPassword) { // No encryption key available, use obligations as-is setProcessedObligations(obligations); return; } const updatedObligations: Obligation[] = []; let hasEncryptionUpdates = false; for (const obligation of obligations) { const processedObligation = { ...obligation }; if (obligation.encrypted_data) { // Try to decrypt existing encrypted data try { const decryptedData = await decryptDataWithStoredPassword( userId, obligation.encrypted_data, ); if (decryptedData) { const parsed = JSON.parse(decryptedData); if (parsed.amount !== undefined) { processedObligation.amount = parsed.amount; } if (parsed.description !== undefined) { processedObligation.description = parsed.description; } } } catch (error) { console.error("Failed to decrypt obligation data:", error); setError("Failed to decrypt some obligation data"); } } else if ( obligation.amount !== null || obligation.description !== null ) { // No encrypted data but has plaintext data - encrypt it and clear plaintext try { const dataToEncrypt = { amount: obligation.amount, description: obligation.description, }; const encryptedData = await encryptDataWithStoredPassword( userId, JSON.stringify(dataToEncrypt), ); if (encryptedData) { // Update the obligation in the database const response = await fetch("/api/update/obligation", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ obligation_id: obligation.id, encrypted_data: encryptedData, description: null, // Clear plaintext description amount: null, // Clear plaintext amount }), }); if (response.ok) { // Update local state processedObligation.encrypted_data = encryptedData; processedObligation.description = obligation.description; // Keep for display processedObligation.amount = obligation.amount; // Keep for display hasEncryptionUpdates = true; } else { console.error("Failed to update obligation encryption"); setError("Failed to encrypt some obligation data"); } } } catch (error) { console.error("Failed to encrypt obligation data:", error); setError("Failed to encrypt some obligation data"); } } updatedObligations.push(processedObligation); } setProcessedObligations(updatedObligations); if (hasEncryptionUpdates) { console.log("Successfully encrypted plaintext obligation data"); } } catch (error) { console.error("Unexpected error during obligation processing:", error); setError("An unexpected error occurred while processing obligations"); setProcessedObligations(obligations); // Fallback to original data } finally { setIsProcessing(false); processingRef.current = false; } }; processObligations(); // Cleanup function to handle component unmount return () => { processingRef.current = false; }; }, []); return ( <> {popupAddObligationDialog}

Your Obligations

Back to Financial Calendar
); }