integratedhub/islands/AddObligationModal.tsx
2026-01-20 09:02:26 -06:00

161 lines
4.7 KiB
TypeScript

import { useCallback, useRef, useState } from "preact/hooks";
import {
encryptDataWithStoredPassword,
getEncryptionPassword,
} from "../helpers.ts";
interface AddObligationModalProps {
userId?: string;
}
export default function AddObligationModal(
{ userId }: AddObligationModalProps,
) {
const formRef = useRef<HTMLFormElement>(null);
const encryptedDataRef = useRef<HTMLInputElement>(null);
const descriptionRef = useRef<HTMLInputElement>(null);
const amountRef = useRef<HTMLInputElement>(null);
// Removed duplicate useState for description/amount - use refs only for form values
// State only for UI feedback, not form data
const [error, _setError] = useState("");
const [loading, setLoading] = useState(false);
const today = new Date().toISOString().split("T")[0];
// Only encrypt on submit, not on every keystroke - prevents V8 overload
const handleEncryptData = useCallback(async () => {
// Get current values from the form inputs
const desc = descriptionRef.current?.value || "";
const amt = amountRef.current?.value || "";
// Check if encryption is available and encrypt the sensitive fields
if (userId && (desc || amt)) {
const encryptionPassword = getEncryptionPassword(userId);
if (encryptionPassword) {
try {
const dataToEncrypt = {
description: desc,
amount: parseFloat(amt) || 0,
};
const encryptedData = await encryptDataWithStoredPassword(
userId,
JSON.stringify(dataToEncrypt),
);
if (encryptedData && encryptedDataRef.current) {
// Set encrypted data
encryptedDataRef.current.value = encryptedData;
}
} catch (error) {
console.error("Failed to encrypt obligation data:", error);
}
}
}
}, [userId]);
const handleSubmit = async () => {
setLoading(true);
// Encrypt data before submission
await handleEncryptData();
// Get current values from the form inputs
const desc = descriptionRef.current?.value || "";
const amt = amountRef.current?.value || "";
// Clear plaintext fields if encryption was successful
if (userId && (desc || amt)) {
const encryptionPassword = getEncryptionPassword(userId);
if (encryptionPassword && encryptedDataRef.current?.value) {
if (descriptionRef.current) descriptionRef.current.value = "";
if (amountRef.current) amountRef.current.value = "";
}
}
// Submit the form using formRef
setTimeout(() => {
if (formRef.current) {
formRef.current.submit();
}
}, 100);
};
return (
<>
<form
ref={formRef}
class="bg-white p-6 rounded shadow-lg w-full max-w-md space-y-4"
method="POST"
action="/api/create/obligation"
>
<h2 class="text-xl font-bold mb-2">Add Obligation</h2>
{error && <div class="text-red-500 text-sm">{error}</div>}
<input
type="hidden"
name="sender"
value="/calendar/budget-obligations?modal=off"
/>
<input
ref={encryptedDataRef}
type="hidden"
name="encrypted_data"
value=""
/>
<input
ref={descriptionRef}
class="w-full border p-2 rounded"
name="description"
placeholder="Description"
required
/>
<input
ref={amountRef}
class="w-full border p-2 rounded"
name="amount"
placeholder="Amount"
type="number"
step="0.01"
required
/>
<select
class="w-full border p-2 rounded"
name="frequency"
defaultValue="Monthly"
>
<option value="One-time">One-time</option>
<option value="Biweekly">Biweekly</option>
<option value="Weekly">Weekly</option>
<option value="Monthly">Monthly</option>
<option value="Quarterly">Quarterly</option>
<option value="Yearly">Yearly</option>
</select>
<input
class="w-full border p-2 rounded"
name="due_date"
type="date"
defaultValue={today}
required
/>
</form>
<div class="flex justify-end space-x-2">
<a
href="/calendar/budget-obligations"
class="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300 text-center inline-block"
>
Cancel
</a>
<button
type="button"
onClick={handleSubmit}
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
disabled={loading}
>
{loading ? "Adding..." : "Add"}
</button>
</div>
</>
);
}