diff options
Diffstat (limited to 'frontend/src/lib/api.ts')
-rw-r--r-- | frontend/src/lib/api.ts | 304 |
1 files changed, 135 insertions, 169 deletions
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 11cee62..67ea975 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,72 +1,52 @@ +import axios from 'axios'; + // API base URL const API_BASE_URL = 'http://localhost:8080/api/v1'; -// Helper function for fetching data with authorization -async function fetchWithAuth(url: string, options: RequestInit = {}) { - // Get token from local storage - const token = localStorage.getItem('token'); - - // Set up headers with authorization - const headers = { +// Create axios instance with defaults +export const api = axios.create({ + baseURL: API_BASE_URL, + headers: { 'Content-Type': 'application/json', - ...(token ? { 'Authorization': `Bearer ${token}` } : {}), - ...options.headers - }; - - // Perform fetch - const response = await fetch(`${API_BASE_URL}${url}`, { - ...options, - headers - }); - - // Handle unauthorized responses - if (response.status === 401) { - localStorage.removeItem('token'); - window.location.href = '/login'; - throw new Error('Unauthorized'); - } - - // Parse response - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error(errorData?.error || `API Error: ${response.status}`); + }, +}); + +// Add auth interceptor +api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) +); + +// Handle auth errors +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401 && typeof window !== 'undefined') { + localStorage.removeItem('token'); + window.location.href = '/login'; + } + return Promise.reject(error); } - - return response.json(); -} +); // Auth API export const authApi = { login: async (email: string, password: string) => { - const response = await fetch(`${API_BASE_URL}/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email, password }) - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error(errorData?.error || 'Login failed'); - } - - const data = await response.json(); - localStorage.setItem('token', data.token); - return data; + const response = await api.post('/auth/login', { email, password }); + const { token } = response.data; + localStorage.setItem('token', token); + return response.data; }, signup: async (name: string, email: string, password: string) => { - const response = await fetch(`${API_BASE_URL}/auth/signup`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, email, password }) - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error(errorData?.error || 'Signup failed'); - } - - return response.json(); + const response = await api.post('/auth/signup', { name, email, password }); + return response.data; }, logout: () => { @@ -77,14 +57,14 @@ export const authApi = { // Account API export interface Account { - ID: number; - CreatedAt: string; - UpdatedAt: string; - DeletedAt: string | null; - UserID: number; - Name: string; - Type: string; - Balance: number; + id: number; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + userID: number; + name: string; + type: string; + balance: number; } export interface AccountInput { @@ -94,34 +74,26 @@ export interface AccountInput { } export const accountApi = { - getAccounts: () => fetchWithAuth('/accounts'), - getAccount: (id: number) => fetchWithAuth(`/accounts/${id}`), - createAccount: (account: AccountInput) => fetchWithAuth('/accounts', { - method: 'POST', - body: JSON.stringify(account) - }), - updateAccount: (id: number, account: Partial<AccountInput>) => fetchWithAuth(`/accounts/${id}`, { - method: 'PUT', - body: JSON.stringify(account) - }), - deleteAccount: (id: number) => fetchWithAuth(`/accounts/${id}`, { - method: 'DELETE' - }) + getAccounts: () => api.get('/accounts').then(res => res.data), + getAccount: (id: number) => api.get(`/accounts/${id}`).then(res => res.data), + createAccount: (account: AccountInput) => api.post('/accounts', account).then(res => res.data), + updateAccount: (id: number, account: Partial<AccountInput>) => api.put(`/accounts/${id}`, account).then(res => res.data), + deleteAccount: (id: number) => api.delete(`/accounts/${id}`).then(res => res.data) }; // Transaction API export interface Transaction { - ID: number; - CreatedAt: string; - UpdatedAt: string; - DeletedAt: string | null; - UserID: number; - AccountID: number | null; - Description: string; - Amount: number; - Type: "Income" | "Expense"; - Date: string; - Category: string; + id: number; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + userID: number; + accountID: number | null; + description: string; + amount: number; + type: "Income" | "Expense"; + date: string; + category: string; } export interface TransactionInput { @@ -139,55 +111,54 @@ export interface TransactionFilters { category?: string; startDate?: string; // YYYY-MM-DD format endDate?: string; // YYYY-MM-DD format + goalId?: number; } export const transactionApi = { getTransactions: (filters?: TransactionFilters) => { - let queryParams = ''; - + const params: Record<string, string | number | undefined> = {}; if (filters) { - const params = new URLSearchParams(); - if (filters.type) params.append('type', filters.type); - if (filters.accountId) params.append('account_id', filters.accountId.toString()); - if (filters.category) params.append('category', filters.category); - if (filters.startDate) params.append('start_date', filters.startDate); - if (filters.endDate) params.append('end_date', filters.endDate); - - queryParams = `?${params.toString()}`; + if (filters.type) params.type = filters.type; + if (filters.accountId) params.account_id = filters.accountId; + if (filters.category) params.category = filters.category; + if (filters.startDate) params.start_date = filters.startDate; + if (filters.endDate) params.end_date = filters.endDate; + if (filters.goalId) params.goal_id = filters.goalId; } - - return fetchWithAuth(`/transactions${queryParams}`); + return api.get('/transactions', { params }).then(res => res.data); }, - getTransaction: (id: number) => fetchWithAuth(`/transactions/${id}`), + getTransaction: (id: number) => api.get(`/transactions/${id}`).then(res => res.data), - createTransaction: (transaction: TransactionInput) => fetchWithAuth('/transactions', { - method: 'POST', - body: JSON.stringify(transaction) - }), + createTransaction: (transaction: TransactionInput) => api.post('/transactions', transaction).then(res => res.data), - updateTransaction: (id: number, transaction: Partial<TransactionInput>) => fetchWithAuth(`/transactions/${id}`, { - method: 'PUT', - body: JSON.stringify(transaction) - }), + updateTransaction: (id: number, transaction: Partial<TransactionInput>) => api.put(`/transactions/${id}`, transaction).then(res => res.data), - deleteTransaction: (id: number) => fetchWithAuth(`/transactions/${id}`, { - method: 'DELETE' - }) + deleteTransaction: (id: number) => api.delete(`/transactions/${id}`).then(res => res.data) }; // Goal API export interface Goal { - ID: number; - CreatedAt: string; - UpdatedAt: string; - DeletedAt: string | null; - UserID: number; - Name: string; - TargetAmount: number; - CurrentAmount: number; - TargetDate: string | null; - Status: "Active" | "Achieved" | "Cancelled"; + id: number; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + userID: number; + name: string; + targetAmount: number; + currentAmount: number; + targetDate: string | null; + status: "Active" | "Paused" | "Achieved" | "Cancelled"; +} + +export interface GoalProgress { + goal: Goal; + percentComplete: number; + amountRemaining: number; + daysRemaining: number; + requiredPerDay: number; + requiredPerMonth: number; + onTrack: boolean; } export interface GoalInput { @@ -195,51 +166,54 @@ export interface GoalInput { targetAmount: number; currentAmount?: number; targetDate?: string; // YYYY-MM-DD format - status?: "Active" | "Achieved" | "Cancelled"; + status?: "Active" | "Paused" | "Achieved" | "Cancelled"; } export const goalApi = { - getGoals: (status?: "Active" | "Achieved" | "Cancelled") => { - const queryParams = status ? `?status=${status}` : ''; - return fetchWithAuth(`/goals${queryParams}`); + getGoals: (status?: "Active" | "Paused" | "Achieved" | "Cancelled") => { + const params = status ? { status } : {}; + return api.get('/goals', { params }).then(res => res.data); }, - getGoal: (id: number) => fetchWithAuth(`/goals/${id}`), + getGoal: (id: number) => api.get(`/goals/${id}`).then(res => res.data), + + createGoal: (goal: GoalInput) => api.post('/goals', goal).then(res => res.data), - createGoal: (goal: GoalInput) => fetchWithAuth('/goals', { - method: 'POST', - body: JSON.stringify(goal) - }), + updateGoal: (id: number, goal: Partial<GoalInput>) => api.put(`/goals/${id}`, goal).then(res => res.data), - updateGoal: (id: number, goal: Partial<GoalInput>) => fetchWithAuth(`/goals/${id}`, { - method: 'PUT', - body: JSON.stringify(goal) - }), + updateGoalProgress: (id: number, currentAmount: number) => + api.patch(`/goals/${id}/progress`, { currentAmount }).then(res => res.data), - updateGoalProgress: (id: number, currentAmount: number) => fetchWithAuth(`/goals/${id}/progress`, { - method: 'PATCH', - body: JSON.stringify({ currentAmount }) - }), + deleteGoal: (id: number) => api.delete(`/goals/${id}`).then(res => res.data), - deleteGoal: (id: number) => fetchWithAuth(`/goals/${id}`, { - method: 'DELETE' - }) + // New goal progress tracking endpoints + getGoalProgress: (id: number) => api.get(`/goals/${id}/progress`).then(res => res.data), + + getAllGoalsProgress: (status?: string) => { + const params = status ? { status } : {}; + return api.get('/goals/progress/all', { params }).then(res => res.data); + }, + + linkTransactionToGoal: (goalId: number, transactionId: number) => + api.post(`/goals/${goalId}/link-transaction`, { transactionId }).then(res => res.data), + + recalculateGoalProgress: (id: number) => api.post(`/goals/${id}/recalculate`).then(res => res.data) }; // Loan API export interface Loan { - ID: number; - CreatedAt: string; - UpdatedAt: string; - DeletedAt: string | null; - UserID: number; - AccountID: number | null; - Name: string; - OriginalAmount: number; - CurrentBalance: number; - InterestRate: number; - StartDate: string; - EndDate: string; + id: number; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + userID: number; + accountID: number | null; + name: string; + originalAmount: number; + currentBalance: number; + interestRate: number; + startDate: string; + endDate: string; } export interface LoanInput { @@ -253,22 +227,14 @@ export interface LoanInput { } export const loanApi = { - getLoans: () => fetchWithAuth('/loans'), - getLoan: (id: number) => fetchWithAuth(`/loans/${id}`), - createLoan: (loan: LoanInput) => fetchWithAuth('/loans', { - method: 'POST', - body: JSON.stringify(loan) - }), - updateLoan: (id: number, loan: Partial<LoanInput>) => fetchWithAuth(`/loans/${id}`, { - method: 'PUT', - body: JSON.stringify(loan) - }), - deleteLoan: (id: number) => fetchWithAuth(`/loans/${id}`, { - method: 'DELETE' - }) + getLoans: () => api.get('/loans').then(res => res.data), + getLoan: (id: number) => api.get(`/loans/${id}`).then(res => res.data), + createLoan: (loan: LoanInput) => api.post('/loans', loan).then(res => res.data), + updateLoan: (id: number, loan: Partial<LoanInput>) => api.put(`/loans/${id}`, loan).then(res => res.data), + deleteLoan: (id: number) => api.delete(`/loans/${id}`).then(res => res.data) }; // User API export const userApi = { - getProfile: () => fetchWithAuth('/users/me') + getProfile: () => api.get('/users/me').then(res => res.data) };
\ No newline at end of file |