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

@@ -15,6 +15,15 @@ export default defineConfig({
vite: { vite: {
resolve: { resolve: {
alias: { alias: {
'@': '/src',
'@components': '/src/components',
'@layouts': '/src/layouts',
'@data': '/src/data',
'@pages': '/src/pages',
'@styles': '/src/styles',
'@stores': '/src/stores',
'@utils': '/src/utils',
'@types': '/src/types.ts',
// Use the browser version of react-dom/server for client-side rendering // Use the browser version of react-dom/server for client-side rendering
'react-dom/server.browser': 'react-dom/cjs/react-dom-server.browser.production.min.js', 'react-dom/server.browser': 'react-dom/cjs/react-dom-server.browser.production.min.js',
}, },

View File

@@ -1,7 +1,7 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { currentAccountId as currentAccountIdStore, refreshKey } from '@stores/transactionStore'; import { currentAccountId as currentAccountIdStore, refreshKey } from '@stores/transactionStore';
import type { Account } from '@types'; import type { Account } from '@types';
import { formatCurrency } from '@utils/formatters'; import { formatCurrency } from '@utils';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export default function AccountSummary() { 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 { interface Props {
account: Account; account: Account;
transactions: Transaction[];
} }
const { account } = Astro.props; const { account, transactions } = Astro.props;
--- ---
<main class="main-content"> <main class="main-content">
<header class="main-header"> <header class="main-header">
<h1> <h1>
Transactions for <span id="current-account-name" Transactions for <span id="current-account-name"
>{account.name} (***{account.accountNumber.slice(-3)})</span> >{account.name} (***{account.accountNumber.slice(-3)})</span
>
</h1> </h1>
</header> </header>
<TransactionTable client:load /> <TransactionTable client:load transactions={transactions} />
</main> </main>

View File

@@ -1,5 +1,7 @@
--- ---
import type { Account } from '@types'; import type { Account } from "@types";
import AddTransactionForm from "./AddTransactionForm";
import AccountSummary from "./AccountSummary";
interface Props { interface Props {
accounts: Account[]; accounts: Account[];

View File

@@ -1,5 +1,5 @@
--- ---
interface Props { export interface Props {
title: string; title: string;
} }

View File

@@ -2,11 +2,13 @@ import { AccountStatus, AccountType, accountService } from '@data/db.service';
import type { Account } from '@types'; import type { Account } from '@types';
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => { export const GET: APIRoute = async ({ params, request }) => {
try { try {
console.log('GET /api/accounts - Fetching all accounts');
const accounts = await accountService.getAll(); 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, status: 200,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -14,12 +16,19 @@ export const GET: APIRoute = async () => {
}); });
} catch (error) { } catch (error) {
console.error('Error fetching accounts:', error); console.error('Error fetching accounts:', error);
return new Response(JSON.stringify({ error: 'Failed to fetch accounts' }), { // 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, status: 500,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); },
);
} }
}; };

View File

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

View File

@@ -14,7 +14,7 @@
"@pages/*": ["src/pages/*"], "@pages/*": ["src/pages/*"],
"@styles/*": ["src/styles/*"], "@styles/*": ["src/styles/*"],
"@stores/*": ["src/stores/*"], "@stores/*": ["src/stores/*"],
"@utils/*": ["src/utils.ts"], "@utils": ["src/utils"],
"@types": ["src/types.ts"] "@types": ["src/types.ts"]
} }
} }