mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
feat: enhance module resolution and improve API error handling
This commit is contained in:
@@ -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',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
---
|
---
|
||||||
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[];
|
||||||
initialAccount: Account;
|
initialAccount: Account;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { accounts, initialAccount } = Astro.props;
|
const { accounts, initialAccount } = Astro.props;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Accessibility Improvements
|
// TODO: Accessibility Improvements
|
||||||
|
|||||||
@@ -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
|
||||||
status: 500,
|
return new Response(
|
||||||
headers: {
|
JSON.stringify({
|
||||||
'Content-Type': 'application/json',
|
error: 'Failed to fetch accounts',
|
||||||
|
details: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// 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();
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<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);
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user