feat: enhance module resolution and improve API error handling

This commit is contained in:
Peter Wood
2025-05-17 21:45:43 -04:00
parent cf99168f8f
commit 9382e41a55
8 changed files with 96 additions and 51 deletions

View File

@@ -1,7 +1,7 @@
import { useStore } from '@nanostores/react';
import { currentAccountId as currentAccountIdStore, refreshKey } from '@stores/transactionStore';
import type { Account } from '@types';
import { formatCurrency } from '@utils/formatters';
import { formatCurrency } from '@utils';
import { useEffect, useState } from 'react';
export default function AccountSummary() {

View File

@@ -1,19 +1,22 @@
---
import type { Account } from '@types';
import type { Account, Transaction } from "@types";
import TransactionTable from "./TransactionTable.astro";
interface Props {
account: Account;
transactions: Transaction[];
}
const { account } = Astro.props;
const { account, transactions } = Astro.props;
---
<main class="main-content">
<header class="main-header">
<h1>
Transactions for <span id="current-account-name"
>{account.name} (***{account.accountNumber.slice(-3)})</span>
>{account.name} (***{account.accountNumber.slice(-3)})</span
>
</h1>
</header>
<TransactionTable client:load />
<TransactionTable client:load transactions={transactions} />
</main>

View File

@@ -1,9 +1,11 @@
---
import type { Account } from '@types';
import type { Account } from "@types";
import AddTransactionForm from "./AddTransactionForm";
import AccountSummary from "./AccountSummary";
interface Props {
accounts: Account[];
initialAccount: Account;
accounts: Account[];
initialAccount: Account;
}
const { accounts, initialAccount } = Astro.props;

View File

@@ -1,6 +1,6 @@
---
interface Props {
title: string;
export interface Props {
title: string;
}
// TODO: Accessibility Improvements

View File

@@ -2,11 +2,13 @@ import { AccountStatus, AccountType, accountService } from '@data/db.service';
import type { Account } from '@types';
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
export const GET: APIRoute = async ({ params, request }) => {
try {
console.log('GET /api/accounts - Fetching all accounts');
const accounts = await accountService.getAll();
console.log('GET /api/accounts - Found accounts:', accounts?.length ?? 0);
return new Response(JSON.stringify(accounts), {
return new Response(JSON.stringify(accounts || []), {
status: 200,
headers: {
'Content-Type': 'application/json',
@@ -14,12 +16,19 @@ export const GET: APIRoute = async () => {
});
} catch (error) {
console.error('Error fetching accounts:', error);
return new Response(JSON.stringify({ error: 'Failed to fetch accounts' }), {
status: 500,
headers: {
'Content-Type': 'application/json',
// Always return a proper JSON response, even in error cases
return new Response(
JSON.stringify({
error: 'Failed to fetch accounts',
details: error instanceof Error ? error.message : 'Unknown error',
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
},
});
);
}
};

View File

@@ -1,36 +1,52 @@
---
import type { Account, Transaction } from '@types';
import BaseLayout from "@layouts/BaseLayout.astro";
import MainContent from "@components/MainContent.astro";
import Sidebar from "@components/Sidebar.astro";
import AddTransactionForm from "@components/AddTransactionForm";
import type { Account, Transaction } from "@types";
// Get the base URL from the incoming request
const baseUrl = new URL(Astro.request.url).origin;
// Fetch accounts from API using absolute URL constructed from the request
const accountsResponse = await fetch(`${baseUrl}/api/accounts`);
const accounts: Account[] = await accountsResponse.json();
// Create an instance of Astro's built-in fetch which handles SSR correctly
let accounts: Account[] = [];
try {
const accountsResponse = await fetch(new URL("/api/accounts", Astro.url));
if (!accountsResponse.ok) {
const error = await accountsResponse.text();
console.error("Failed to fetch accounts:", error);
// Continue with empty accounts array
} else {
accounts = await accountsResponse.json();
}
} catch (error) {
console.error("Error fetching accounts:", error);
// Continue with empty accounts array
}
// Initialize with first account or empty account if none exist
const initialAccount: Account = accounts[0] || {
id: '',
name: 'No accounts available',
accountNumber: '000000',
balance: 0,
bankName: '',
id: "",
name: "No accounts available",
accountNumber: "000000",
balance: 0,
bankName: "",
};
// Fetch initial transactions if we have an account, using absolute URL
// Fetch initial transactions if we have an account
let initialTransactions: Transaction[] = [];
if (initialAccount.id) {
const transactionsResponse = await fetch(
`${baseUrl}/api/accounts/${initialAccount.id}/transactions`,
);
initialTransactions = await transactionsResponse.json();
const transactionsResponse = await fetch(
new URL(`/api/accounts/${initialAccount.id}/transactions`, Astro.url),
);
initialTransactions = await transactionsResponse.json();
}
---
<BaseLayout title="Bank Transactions Dashboard">
<div class="dashboard-layout">
<Sidebar accounts={accounts} initialAccount={initialAccount} />
<MainContent account={initialAccount} />
<MainContent
account={initialAccount}
transactions={initialTransactions}
/>
</div>
</BaseLayout>
@@ -56,20 +72,24 @@ if (initialAccount.id) {
);
// --- DOM Elements ---
const accountSelect = document.getElementById("account-select");
const accountSelect = document.getElementById(
"account-select",
) as HTMLSelectElement | null;
const currentAccountNameSpan = document.getElementById(
"current-account-name",
);
) as HTMLSpanElement | null;
const addTransactionSection = document.getElementById(
"add-transaction-section",
);
const toggleAddTxnBtn = document.getElementById("toggle-add-txn-btn");
) as HTMLElement | null;
const toggleAddTxnBtn = document.getElementById(
"toggle-add-txn-btn",
) as HTMLButtonElement | null;
console.log("Initial setup - Account:", initialAccountData);
console.log("Initial setup - Transactions:", initialTransactionsData);
// --- Helper Functions ---
async function fetchAccountDetails(accountId) {
async function fetchAccountDetails(accountId: string) {
console.log("Fetching details for account:", accountId);
try {
const response = await fetch(`/api/accounts/${accountId}`);
@@ -83,7 +103,7 @@ if (initialAccount.id) {
}
// --- Update UI Function ---
async function updateUIForAccount(accountId) {
async function updateUIForAccount(accountId: string) {
console.log("Updating UI for account:", accountId);
// Update the store with the current account ID
@@ -118,7 +138,7 @@ if (initialAccount.id) {
}
// --- Transaction Actions ---
async function handleEditTransaction(txnId) {
async function handleEditTransaction(txnId: string) {
console.log("Edit transaction requested:", txnId);
try {
const accountId = currentAccountId.get();
@@ -133,7 +153,9 @@ if (initialAccount.id) {
if (!response.ok)
throw new Error("Failed to fetch transactions for edit");
const transactions = await response.json();
const transaction = transactions.find((t) => t.id === txnId);
const transaction = transactions.find(
(t: { id: string }) => t.id === txnId,
);
if (!transaction) {
throw new Error("Transaction not found for editing");
@@ -164,18 +186,18 @@ if (initialAccount.id) {
// --- Event Listeners ---
if (accountSelect) {
accountSelect.addEventListener("change", (event) => {
const target = event.target;
if (target && target.value) {
accountSelect.addEventListener("change", (event: Event) => {
const target = event.target as HTMLSelectElement;
if (target?.value) {
updateUIForAccount(target.value);
}
});
}
document.addEventListener("click", (event) => {
const target = event.target;
if (target && target.classList.contains("edit-btn")) {
const row = target.closest("[data-txn-id]");
document.addEventListener("click", (event: Event) => {
const target = event.target as HTMLElement;
if (target?.classList?.contains("edit-btn")) {
const row = target.closest("[data-txn-id]") as HTMLElement;
if (row) {
const txnId = row.dataset.txnId;
if (txnId) handleEditTransaction(txnId);