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

157 lines
4.5 KiB
TypeScript

import { useCallback, useRef, useState } from "preact/hooks";
import {
encryptDataWithStoredPassword,
getEncryptionPassword,
} from "../helpers.ts";
interface AddIncomeModalProps {
userId?: string;
}
export default function AddIncomeModal(
{ userId }: AddIncomeModalProps,
) {
const formRef = useRef<HTMLFormElement>(null);
const encryptedDataRef = useRef<HTMLInputElement>(null);
const sourceRef = useRef<HTMLInputElement>(null);
const amountRef = useRef<HTMLInputElement>(null);
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 source = sourceRef.current?.value || "";
const amount = amountRef.current?.value || "";
// Check if encryption is available and encrypt the sensitive fields
if (userId && (source || amount)) {
const encryptionPassword = getEncryptionPassword(userId);
if (encryptionPassword) {
try {
const dataToEncrypt = {
source: source,
amount: parseFloat(amount) || 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 income data:", error);
}
}
}
}, [userId]);
const handleAddClick = async () => {
setLoading(true);
// Encrypt data before submission
await handleEncryptData();
// Get current values from the form inputs
const source = sourceRef.current?.value || "";
const amount = amountRef.current?.value || "";
// Clear plaintext fields if encryption was successful
if (userId && (source || amount)) {
const encryptionPassword = getEncryptionPassword(userId);
if (encryptionPassword && encryptedDataRef.current?.value) {
if (sourceRef.current) sourceRef.current.value = "";
if (amountRef.current) amountRef.current.value = "";
}
}
// Submit the form using formRef
setTimeout(() => {
if (formRef.current) {
formRef.current.submit();
}
}, 100);
};
return (
<div class="bg-white rounded-lg p-8 shadow-lg max-w-md w-full">
<h3 class="text-xl font-bold mb-4">Add Income</h3>
<form
ref={formRef}
method="POST"
action="/api/create/income"
class="flex flex-col gap-4"
>
<input
type="hidden"
name="sender"
value="/calendar/every-income?modal=off"
/>
<input
ref={encryptedDataRef}
type="hidden"
name="encrypted_data"
value=""
/>
<input
ref={sourceRef}
class="border rounded px-3 py-2"
placeholder="Source"
name="source"
required
/>
<input
ref={amountRef}
class="border rounded px-3 py-2"
placeholder="Amount"
type="number"
step="0.01"
name="amount"
required
/>
<select
class="w-full border p-2 rounded"
name="frequency"
required
defaultValue="Monthly"
>
<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="border rounded px-3 py-2"
placeholder="Received On (YYYY-MM-DD)"
type="date"
name="received_date"
required
defaultValue={today}
/>
</form>
<div class="flex gap-2 justify-end mt-4">
<a
href="/calendar/every-income"
class="px-4 py-2 rounded bg-gray-300 hover:bg-gray-400 text-center inline-block"
>
Cancel
</a>
<button
type="button"
onClick={handleAddClick}
class="px-4 py-2 rounded bg-green-600 text-white hover:bg-green-700"
disabled={loading}
>
{loading ? "Adding..." : "Add"}
</button>
</div>
</div>
);
}