161 lines
4.7 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|