Init
This commit is contained in:
136
frontend/src/lib/api.ts
Normal file
136
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
const API_BASE = '/api'
|
||||
|
||||
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${API_BASE}${path}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
...options,
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}))
|
||||
throw new ApiError(res.status, body.error || res.statusText)
|
||||
}
|
||||
|
||||
if (res.status === 204) return undefined as T
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
// Auth
|
||||
export interface User {
|
||||
id: string
|
||||
email: string
|
||||
display_name: string
|
||||
preferred_locale: string
|
||||
}
|
||||
|
||||
export const auth = {
|
||||
register: (data: { email: string; password: string; display_name: string }) =>
|
||||
request<User>('/auth/register', { method: 'POST', body: JSON.stringify(data) }),
|
||||
login: (data: { email: string; password: string }) =>
|
||||
request<User>('/auth/login', { method: 'POST', body: JSON.stringify(data) }),
|
||||
logout: () => request<void>('/auth/logout', { method: 'POST' }),
|
||||
me: () => request<User>('/auth/me'),
|
||||
}
|
||||
|
||||
// Categories
|
||||
export type CategoryType = 'bill' | 'variable_expense' | 'debt' | 'saving' | 'investment' | 'income'
|
||||
|
||||
export interface Category {
|
||||
id: string
|
||||
name: string
|
||||
type: CategoryType
|
||||
icon: string
|
||||
sort_order: number
|
||||
}
|
||||
|
||||
export const categories = {
|
||||
list: () => request<Category[]>('/categories'),
|
||||
create: (data: Partial<Category>) =>
|
||||
request<Category>('/categories', { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: (id: string, data: Partial<Category>) =>
|
||||
request<Category>(`/categories/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id: string) => request<void>(`/categories/${id}`, { method: 'DELETE' }),
|
||||
}
|
||||
|
||||
// Budgets
|
||||
export interface Budget {
|
||||
id: string
|
||||
name: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
currency: string
|
||||
carryover_amount: number
|
||||
}
|
||||
|
||||
export interface BudgetItem {
|
||||
id: string
|
||||
budget_id: string
|
||||
category_id: string
|
||||
category_name: string
|
||||
category_type: CategoryType
|
||||
budgeted_amount: number
|
||||
actual_amount: number
|
||||
notes: string
|
||||
}
|
||||
|
||||
export interface BudgetDetail extends Budget {
|
||||
items: BudgetItem[]
|
||||
totals: {
|
||||
income_budget: number
|
||||
income_actual: number
|
||||
bills_budget: number
|
||||
bills_actual: number
|
||||
expenses_budget: number
|
||||
expenses_actual: number
|
||||
debts_budget: number
|
||||
debts_actual: number
|
||||
savings_budget: number
|
||||
savings_actual: number
|
||||
investments_budget: number
|
||||
investments_actual: number
|
||||
available: number
|
||||
}
|
||||
}
|
||||
|
||||
export const budgets = {
|
||||
list: () => request<Budget[]>('/budgets'),
|
||||
create: (data: Partial<Budget>) =>
|
||||
request<Budget>('/budgets', { method: 'POST', body: JSON.stringify(data) }),
|
||||
get: (id: string) => request<BudgetDetail>(`/budgets/${id}`),
|
||||
update: (id: string, data: Partial<Budget>) =>
|
||||
request<Budget>(`/budgets/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id: string) => request<void>(`/budgets/${id}`, { method: 'DELETE' }),
|
||||
copyFrom: (id: string, srcId: string) =>
|
||||
request<BudgetDetail>(`/budgets/${id}/copy-from/${srcId}`, { method: 'POST' }),
|
||||
}
|
||||
|
||||
// Budget Items
|
||||
export const budgetItems = {
|
||||
create: (budgetId: string, data: Partial<BudgetItem>) =>
|
||||
request<BudgetItem>(`/budgets/${budgetId}/items`, { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: (budgetId: string, itemId: string, data: Partial<BudgetItem>) =>
|
||||
request<BudgetItem>(`/budgets/${budgetId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (budgetId: string, itemId: string) =>
|
||||
request<void>(`/budgets/${budgetId}/items/${itemId}`, { method: 'DELETE' }),
|
||||
}
|
||||
|
||||
// Settings
|
||||
export const settings = {
|
||||
get: () => request<User>('/settings'),
|
||||
update: (data: Partial<User>) =>
|
||||
request<User>('/settings', { method: 'PUT', body: JSON.stringify(data) }),
|
||||
}
|
||||
6
frontend/src/lib/format.ts
Normal file
6
frontend/src/lib/format.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function formatCurrency(amount: number, currency: string = 'EUR'): string {
|
||||
return new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency,
|
||||
}).format(amount)
|
||||
}
|
||||
6
frontend/src/lib/utils.ts
Normal file
6
frontend/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user