diff --git a/client/api.ts b/client/api.ts index aabd5a4..aff49e6 100644 --- a/client/api.ts +++ b/client/api.ts @@ -146,7 +146,6 @@ export const api = { spendingSummary: (p?: QueryParams) => get('/spending/summary', p), spendingTransactions:(p?: QueryParams) => get('/spending/transactions', p), categorizeTransaction: (id: Id, d: Body) => patch(`/spending/transactions/${id}/category`, d), - spendingBudgets: (p?: QueryParams) => get('/spending/budgets', p), setSpendingBudget: (d: Body) => put('/spending/budgets', d), copySpendingBudgets: (d: Body) => post('/spending/budgets/copy', d), spendingIncome: (p?: QueryParams) => get('/spending/income', p), @@ -232,8 +231,6 @@ export const api = { saveNotifAdmin: (data: Body) => put('/notifications/admin', data), testEmail: (data: Body) => post('/notifications/test', data), testPushNotification: () => post('/notifications/test-push', {}), - notifMe: () => get('/notifications/me'), - saveNotifMe: (data: Body) => put('/notifications/me', data), // Profile profile: () => get('/profile'), @@ -241,12 +238,9 @@ export const api = { profileSettings: () => get('/profile/settings'), updateProfileSettings: (data: Body) => _fetch('PATCH', '/profile/settings', data), changeProfilePassword: (data: Body) => post('/profile/change-password', data), - profileExports: () => get('/profile/exports'), - profileImportHistory: () => get('/profile/import-history'), // Tracker tracker: (y: number, m: number, params: QueryParams = {}) => get(`/tracker${queryString({ year: y, month: m, ...params })}`), - upcomingBills: (days = 30) => get(`/tracker/upcoming?days=${days}`), overdueCount: () => get('/tracker/overdue-count'), snoozeOverdue: (id: Id, data: Body) => put(`/bills/${id}/monthly-state`, data), @@ -273,15 +267,7 @@ export const api = { createBill: (data: Body) => post('/bills', data), updateBill: (id: Id, data: Body) => put(`/bills/${id}`, data), reorderBills: (order: Body) => put('/bills/reorder', order), - archiveBill: (id: Id, archived = true) => put(`/bills/${id}/archived`, { archived }), updateBillBalance: (id: Id, bal: unknown) => _fetch('PATCH', `/bills/${id}/balance`, { current_balance: bal }), - billAmortization: (id: Id, opts: { payment?: number | string; max_months?: number | string } = {}) => { - const params = new URLSearchParams(); - if (opts.payment) params.set('payment', String(opts.payment)); - if (opts.max_months) params.set('max_months', String(opts.max_months)); - const qs = params.toString(); - return get(`/bills/${id}/amortization${qs ? `?${qs}` : ''}`); - }, updateBillSnowball: (id: Id, data: Body) => _fetch('PATCH', `/bills/${id}/snowball`, data), deleteBill: (id: Id) => del(`/bills/${id}`), restoreBill: (id: Id) => post(`/bills/${id}/restore`), @@ -299,7 +285,6 @@ export const api = { merchantRuleCandidates: (id: Id) => get(`/bills/${id}/merchant-rules/candidates`), toggleRuleAutoAttribute: (id: Id, ruleId: Id, on: unknown) => _fetch('PATCH', `/bills/${id}/merchant-rules/${ruleId}/auto-attribute`, { enabled: on }), importHistoricalPayments: (id: Id, ids: unknown) => post(`/bills/${id}/merchant-rules/import-historical`, { transaction_ids: ids }), - billMonthlyState: (id: Id, y: number, m: number) => get(`/bills/${id}/monthly-state?year=${y}&month=${m}`), saveBillMonthlyState: (id: Id, data: Body) => put(`/bills/${id}/monthly-state`, data), billHistoryRanges: (id: Id) => get(`/bills/${id}/history-ranges`), createBillHistoryRange: (id: Id, data: Body) => post(`/bills/${id}/history-ranges`, data), @@ -309,7 +294,6 @@ export const api = { snoozeBillDrift: (id: Id) => post(`/bills/${id}/snooze-drift`, {}), billTemplates: () => get('/bills/templates'), saveBillTemplate: (data: Body) => post('/bills/templates', data), - deleteBillTemplate: (id: Id) => del(`/bills/templates/${id}`), // Subscriptions subscriptions: () => get('/subscriptions'), @@ -357,7 +341,6 @@ export const api = { snowballPlans: () => get<{ plans?: unknown[] }>('/snowball/plans'), snowballActivePlan: () => get('/snowball/plans/active'), startSnowballPlan: (data: Body) => post('/snowball/plans', data), - updateSnowballPlan: (id: Id, d: Body) => _fetch('PATCH', `/snowball/plans/${id}`, d), pauseSnowballPlan: (id: Id) => post(`/snowball/plans/${id}/pause`, {}), resumeSnowballPlan: (id: Id) => post(`/snowball/plans/${id}/resume`, {}), completeSnowballPlan: (id: Id) => post(`/snowball/plans/${id}/complete`, {}), @@ -398,7 +381,6 @@ export const api = { // Version (public) about: () => get('/about'), privacy: () => get('/privacy'), - aboutAdmin: () => get('/about-admin'), roadmap: (refresh = false) => get(`/about-admin/roadmap${refresh ? '?refresh=1' : ''}`), updateStatus: () => get('/version/update-status'), checkForUpdates: () => post('/about-admin/check-updates'), @@ -409,7 +391,6 @@ export const api = { releaseHistory: () => get('/version/history'), // Export (returns a URL to navigate to, not a fetch) - exportUrl: (year: Id, fmt?: string): string => `/api/export?year=${year}&format=${fmt||'csv'}`, // Export a specific date range (both bounds inclusive) — used to export one // tracker month. The /api/export route accepts ?from&to in addition to ?year. exportRangeUrl: (from: string, to: string, fmt?: string): string => `/api/export?from=${from}&to=${to}&format=${fmt||'csv'}`, @@ -497,15 +478,11 @@ export const api = { // Transactions transactions: (params: QueryParams = {}) => get(`/transactions${queryString(params)}`), bankTransactionsLedger: (params: QueryParams = {}) => get(`/transactions/bank-ledger${queryString(params)}`), - createManualTransaction: (data: Body) => post('/transactions/manual', data), - updateTransaction: (id: Id, data: Body) => put(`/transactions/${id}`, data), - deleteTransaction: (id: Id) => del(`/transactions/${id}`), matchTransaction: (id: Id, billId: Id) => post<{ transaction: BankLedgerTransaction }>(`/transactions/${id}/match`, { billId }), unmatchTransaction: (id: Id) => post<{ transaction: BankLedgerTransaction }>(`/transactions/${id}/unmatch`), unmatchTransactionBulk: (matches: Body) => post('/transactions/unmatch-bulk', { matches }), ignoreTransaction: (id: Id) => post(`/transactions/${id}/ignore`), unignoreTransaction: (id: Id) => post(`/transactions/${id}/unignore`), - transactionMerchantMatch: (id: Id) => get(`/transactions/${id}/merchant-match`), applyTransactionMerchantMatch: (id: Id) => post<{ matched?: boolean; category?: { id: number; name: string } }>(`/transactions/${id}/apply-merchant-match`), autoCategorizeTransactions: (opts: Body = {}) => post('/transactions/auto-categorize', opts), diff --git a/client/components/ErrorBoundary.tsx b/client/components/ErrorBoundary.tsx index f823a00..676aa97 100644 --- a/client/components/ErrorBoundary.tsx +++ b/client/components/ErrorBoundary.tsx @@ -108,23 +108,4 @@ class ErrorBoundary extends React.Component( - Component: React.ComponentType

, - displayName: string = Component.displayName || Component.name || 'Component', -) { - function WrappedComponent(props: P) { - return ( - - - - ); - } - - WrappedComponent.displayName = `withErrorBoundary(${displayName})`; - return WrappedComponent; -} - export default ErrorBoundary; diff --git a/client/components/SummaryCard.tsx b/client/components/SummaryCard.tsx deleted file mode 100644 index f8d07b3..0000000 --- a/client/components/SummaryCard.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React, { useMemo } from 'react'; -import { cn, fmt } from '@/lib/utils'; -import { AlertCircle, CheckCircle2, Clock, TrendingUp, Settings2, type LucideIcon } from 'lucide-react'; -import type { Dollars } from '@/lib/money'; - -type CardType = 'starting' | 'paid' | 'remaining' | 'overdue'; - -interface CardDef { - label: string; - icon: LucideIcon; - bar: string; - glow: string; - borderActive?: string; - valueClass: string; - activateWhen: (v: number) => boolean; -} - -const CARD_DEFS: Record = { - starting: { - label: 'Starting', - icon: TrendingUp, - bar: 'from-slate-400 to-slate-300', - glow: '', - valueClass: 'text-foreground', - activateWhen: () => true, - }, - paid: { - label: 'Total Paid', - icon: CheckCircle2, - bar: 'from-emerald-500 to-emerald-300', - glow: 'shadow-[0_4px_20px_rgba(16,185,129,0.15)]', - borderActive: 'border-emerald-400/40', - valueClass: 'text-emerald-600 dark:text-emerald-100', - activateWhen: (v) => v > 0, - }, - remaining: { - label: 'Remaining', - icon: Clock, - bar: 'from-blue-400 to-indigo-300', - glow: '', - valueClass: 'text-foreground', - activateWhen: () => true, - }, - overdue: { - label: 'Overdue', - icon: AlertCircle, - bar: 'from-rose-400 to-orange-300', - glow: 'shadow-[0_4px_20px_rgba(251,113,133,0.10)]', - borderActive: 'border-rose-400/35', - valueClass: 'text-red-500 dark:text-rose-100', - activateWhen: (v) => v > 0, - }, -}; - -interface SummaryCardProps { - type: CardType; - value?: Dollars; - onEdit?: () => void; - hint?: string; -} - -export const SummaryCard = React.memo(function SummaryCard({ type, value, onEdit, hint }: SummaryCardProps) { - const def = useMemo(() => CARD_DEFS[type], [type]); - const isActive = useMemo(() => def.activateWhen(value || 0), [def, value]); - const Icon = useMemo(() => def.icon, [def]); - - return ( -

-
-
- -

- {def.label} -

- {type === 'starting' && onEdit && ( - - )} -
-

- {fmt(value)} -

- {hint &&

{hint}

} -
- ); -}); - -SummaryCard.displayName = 'SummaryCard';