Skip to content

bug: createBudgetSource / updateBudgetSource / createSubsidyProgram / updateSubsidyProgram API client functions return wrong type — causes page crash #175

@steilerDev

Description

@steilerDev

BUG: API client create/update functions return wrapper object instead of entity — causes page crash

Severity: Critical
Component: Frontend UI / Budget Sources / Subsidy Programs
Found in: E2E tests — budget-sources.spec.ts (create + update scenarios), subsidy-programs.spec.ts (create + update scenarios)

Steps to Reproduce

Budget Sources

  1. Navigate to /budget/sources
  2. Click Add Source
  3. Fill in a name and amount
  4. Click Create Source

Subsidy Programs

  1. Navigate to /budget/subsidies
  2. Click Add Program
  3. Fill in a name and reduction value
  4. Click Create Program

Expected Behavior

  • Create form closes
  • Success banner appears with the newly-created entity's name (e.g. Budget source "My Source" created successfully)
  • Entity appears in the list
  • Same for edit (update) flows

Actual Behavior

Create flow

The page goes blank (React crashes). No success banner, no list update.

Root cause: createBudgetSource() in client/src/lib/budgetSourcesApi.ts is typed as Promise<BudgetSource> but the server wraps the response in { budgetSource: BudgetSource }. The call site does:

const created = await createBudgetSource(payload);
setSources([...sources, created]);            // 'created' is { budgetSource: {...} }
setSuccessMessage(`Budget source "${created.name}" created successfully`); // name = undefined

Then when the list tries to render the malformed entry, getSourceTypeClass(styles, created.sourceType) is called with sourceType = undefined. The function does:

type?.charAt(0).toUpperCase()    // type is undefined, optional chain stops here → undefined
// then calls .toUpperCase() on undefined → TypeError!

No ErrorBoundary → entire page goes blank.

Edit (update) flow

The page does NOT crash (the item was created correctly via API and the response is separately fetched). However the success banner reads:

Budget source "undefined" updated successfully

Because updated.name is undefined — the updateBudgetSource function has the same wrong return type.

Affected Files

File Function Returns Should return
client/src/lib/budgetSourcesApi.ts createBudgetSource Promise<BudgetSource> unwrapped entity from { budgetSource: BudgetSource }
client/src/lib/budgetSourcesApi.ts updateBudgetSource Promise<BudgetSource> unwrapped entity from { budgetSource: BudgetSource }
client/src/lib/subsidyProgramsApi.ts createSubsidyProgram Promise<SubsidyProgram> unwrapped entity from { subsidyProgram: SubsidyProgram }
client/src/lib/subsidyProgramsApi.ts updateSubsidyProgram Promise<SubsidyProgram> unwrapped entity from { subsidyProgram: SubsidyProgram }

Fix Options

Option A (preferred): Fix the API client functions to use the correct response type and unwrap in place:

// budgetSourcesApi.ts
export async function createBudgetSource(data: CreateBudgetSourceRequest): Promise<BudgetSource> {
  const response = await post<{ budgetSource: BudgetSource }>('/budget-sources', data);
  return response.budgetSource;
}

Option B: Fix the call sites in BudgetSourcesPage.tsx / SubsidyProgramsPage.tsx to cast the response correctly.

Environment

  • Browser: All (Chromium + WebKit)
  • Viewport: All (Desktop, Tablet, Mobile)
  • Docker: Not tested (reproducible via dev server)

Evidence

  • E2E CI artifact screenshots: blank white page on create (Budget Sources), success banner showing "undefined" on update
  • Test failures: all Create source and Edit source E2E tests across all viewports (~24 failures total)

Notes

A secondary concern: BudgetSourcesPage.tsx calls getSourceTypeClass(styles, source.sourceType) without guarding against undefined. Even if the API client is fixed, this utility function should handle undefined gracefully (optional chain the full chain, not just the first property access).

[qa-integration-tester] Filed after E2E test investigation. This is a frontend-developer task.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions