Add VSCode task for automatic build on project open and update dependencies

Remove .vscode from .gitignore to allow for project-specific settings. Introduce a VSCode task to run `npm run dev` automatically when the project is opened. Update environment configuration files and dependencies to enhance support for the Node adapter.

Fixes #13
This commit is contained in:
GitHub Copilot
2025-05-01 21:05:24 +00:00
parent 8058df41fd
commit b51fe35a16
23 changed files with 342 additions and 401 deletions

View File

@@ -1,13 +1,13 @@
import React, { useState, useEffect } from "react";
import { useStore } from "@nanostores/react";
import type { Transaction } from "../types";
import React, { useState, useEffect } from 'react';
import { useStore } from '@nanostores/react';
import type { Transaction } from '../types';
// Import store atoms and actions
import {
currentAccountId as currentAccountIdStore,
transactionToEdit as transactionToEditStore,
cancelEditingTransaction,
transactionSaved,
} from "../stores/transactionStore";
} from '../stores/transactionStore';
// Remove props that now come from the store
interface AddTransactionFormProps {}
@@ -18,9 +18,9 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
const transactionToEdit = useStore(transactionToEditStore);
// --- State Variables ---
const [date, setDate] = useState("");
const [description, setDescription] = useState("");
const [amount, setAmount] = useState("");
const [date, setDate] = useState('');
const [description, setDescription] = useState('');
const [amount, setAmount] = useState('');
const [editingId, setEditingId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -32,7 +32,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
useEffect(() => {
// Only set default date if not editing
if (!transactionToEdit) {
setDate(new Date().toISOString().split("T")[0]);
setDate(new Date().toISOString().split('T')[0]);
}
}, [transactionToEdit]); // Rerun if edit mode changes
@@ -48,17 +48,14 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
// Directly format the date object (usually interpreted as UTC midnight)
// into the YYYY-MM-DD format required by the input.
// No timezone adjustment needed here.
setDate(dateObj.toISOString().split("T")[0]);
setDate(dateObj.toISOString().split('T')[0]);
} else {
console.warn(
"Invalid date received for editing:",
transactionToEdit.date
);
setDate(""); // Set to empty if invalid
console.warn('Invalid date received for editing:', transactionToEdit.date);
setDate(''); // Set to empty if invalid
}
} catch (e) {
console.error("Error parsing date for editing:", e);
setDate(""); // Set to empty on error
console.error('Error parsing date for editing:', e);
setDate(''); // Set to empty on error
}
setDescription(transactionToEdit.description);
setAmount(transactionToEdit.amount.toString());
@@ -75,9 +72,9 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
// --- Helper Functions ---
const resetForm = () => {
setEditingId(null);
setDate(new Date().toISOString().split("T")[0]);
setDescription("");
setAmount("");
setDate(new Date().toISOString().split('T')[0]);
setDescription('');
setAmount('');
setError(null);
// Don't reset isLoading here, it's handled in submit/cancel
};
@@ -85,28 +82,28 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
const validateForm = (): string[] => {
const errors: string[] = [];
if (!description || description.trim().length < 2) {
errors.push("Description must be at least 2 characters long");
errors.push('Description must be at least 2 characters long');
}
if (!amount) {
errors.push("Amount is required");
errors.push('Amount is required');
} else {
const amountNum = parseFloat(amount);
if (isNaN(amountNum)) {
errors.push("Amount must be a valid number");
errors.push('Amount must be a valid number');
} else if (amountNum === 0) {
errors.push("Amount cannot be zero");
errors.push('Amount cannot be zero');
}
}
if (!date) {
errors.push("Date is required");
errors.push('Date is required');
} else {
try {
const dateObj = new Date(date + "T00:00:00"); // Treat input as local date
const dateObj = new Date(date + 'T00:00:00'); // Treat input as local date
if (isNaN(dateObj.getTime())) {
errors.push("Invalid date format");
errors.push('Invalid date format');
}
} catch (e) {
errors.push("Invalid date format");
errors.push('Invalid date format');
}
}
return errors;
@@ -118,13 +115,13 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
setError(null);
if (isLoading || !currentAccountId) {
if (!currentAccountId) setError("No account selected.");
if (!currentAccountId) setError('No account selected.');
return;
}
const validationErrors = validateForm();
if (validationErrors.length > 0) {
setError(validationErrors.join(". "));
setError(validationErrors.join('. '));
return;
}
@@ -140,21 +137,17 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
amount: parseFloat(amount),
};
const method = editingId ? "PUT" : "POST";
const url = editingId
? `/api/transactions/${editingId}`
: "/api/transactions";
const method = editingId ? 'PUT' : 'POST';
const url = editingId ? `/api/transactions/${editingId}` : '/api/transactions';
const response = await fetch(url, {
method,
headers: { "Content-Type": "application/json" },
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(transactionData),
});
if (!response.ok) {
let errorMsg = `Failed to ${
isEditMode ? "update" : "create"
} transaction`;
let errorMsg = `Failed to ${isEditMode ? 'update' : 'create'} transaction`;
try {
const errorData = await response.json();
errorMsg = errorData.error || errorMsg;
@@ -169,9 +162,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
transactionSaved(savedTransaction); // Call store action instead of prop callback
resetForm(); // Reset form on success
} catch (err) {
setError(
err instanceof Error ? err.message : "An unexpected error occurred"
);
setError(err instanceof Error ? err.message : 'An unexpected error occurred');
} finally {
setIsLoading(false);
}
@@ -185,7 +176,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
// --- JSX ---
return (
<form id="add-transaction-form-react" onSubmit={handleSubmit} noValidate>
<h4>{isEditMode ? "Edit Transaction" : "New Transaction"}</h4>
<h4>{isEditMode ? 'Edit Transaction' : 'New Transaction'}</h4>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
@@ -228,29 +219,18 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
placeholder="e.g. -25.50 or 1200.00"
disabled={isLoading}
/>
<small className="help-text">
Use negative numbers for expenses (e.g., -50.00)
</small>
<small className="help-text">Use negative numbers for expenses (e.g., -50.00)</small>
</div>
<div className="button-group">
<button
type="submit"
className={`form-submit-btn ${isLoading ? "loading" : ""}`}
className={`form-submit-btn ${isLoading ? 'loading' : ''}`}
disabled={isLoading}
>
{isLoading
? "Saving..."
: isEditMode
? "Update Transaction"
: "Save Transaction"}
{isLoading ? 'Saving...' : isEditMode ? 'Update Transaction' : 'Save Transaction'}
</button>
{isEditMode && (
<button
type="button"
className="cancel-btn"
onClick={handleCancel}
disabled={isLoading}
>
<button type="button" className="cancel-btn" onClick={handleCancel} disabled={isLoading}>
Cancel
</button>
)}