diff --git a/services/api.ts b/services/api.ts new file mode 100644 index 0000000..5d63cdd --- /dev/null +++ b/services/api.ts @@ -0,0 +1,369 @@ + +import { + Condo, Family, Payment, AppSettings, User, AuthResponse, + Ticket, TicketComment, ExtraordinaryExpense, Notice, + AlertDefinition, NoticeRead +} from '../types'; + +const API_URL = '/api'; + +async function request(endpoint: string, options: RequestInit = {}): Promise { + const token = localStorage.getItem('condo_token'); + const headers: HeadersInit = { + 'Content-Type': 'application/json', + ...options.headers as any, + }; + + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const response = await fetch(`${API_URL}${endpoint}`, { + ...options, + headers, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(errorText || response.statusText); + } + + // Handle empty responses + const text = await response.text(); + return text ? JSON.parse(text) : undefined; +} + +export const CondoService = { + // Auth & User + login: async (email: string, password: string): Promise => { + const data = await request('/auth/login', { + method: 'POST', + body: JSON.stringify({ email, password }) + }); + localStorage.setItem('condo_token', data.token); + localStorage.setItem('condo_user', JSON.stringify(data.user)); + }, + + logout: () => { + localStorage.removeItem('condo_token'); + localStorage.removeItem('condo_user'); + window.location.href = '/#/login'; + }, + + getCurrentUser: (): User | null => { + const u = localStorage.getItem('condo_user'); + return u ? JSON.parse(u) : null; + }, + + updateProfile: async (data: any): Promise => { + const res = await request<{success: boolean, user: User}>('/profile', { + method: 'PUT', + body: JSON.stringify(data) + }); + if (res.user) { + localStorage.setItem('condo_user', JSON.stringify(res.user)); + } + }, + + // Settings + getSettings: async (): Promise => { + return request('/settings'); + }, + + updateSettings: async (settings: AppSettings): Promise => { + return request('/settings', { + method: 'PUT', + body: JSON.stringify(settings) + }); + }, + + testSmtpConfig: async (config: any): Promise => { + return request('/settings/smtp-test', { + method: 'POST', + body: JSON.stringify(config) + }); + }, + + getAvailableYears: async (): Promise => { + return request('/years'); + }, + + // Condos + getCondos: async (): Promise => { + return request('/condos'); + }, + + getActiveCondoId: (): string | undefined => { + return localStorage.getItem('active_condo_id') || undefined; + }, + + getActiveCondo: async (): Promise => { + const id = localStorage.getItem('active_condo_id'); + const condos = await CondoService.getCondos(); + if (id) { + return condos.find(c => c.id === id); + } + return condos.length > 0 ? condos[0] : undefined; + }, + + setActiveCondo: (id: string) => { + localStorage.setItem('active_condo_id', id); + window.dispatchEvent(new Event('condo-updated')); + window.location.reload(); + }, + + saveCondo: async (condo: Condo): Promise => { + if (condo.id) { + await request(`/condos/${condo.id}`, { + method: 'PUT', + body: JSON.stringify(condo) + }); + return condo; + } else { + return request('/condos', { + method: 'POST', + body: JSON.stringify(condo) + }); + } + }, + + deleteCondo: async (id: string): Promise => { + return request(`/condos/${id}`, { method: 'DELETE' }); + }, + + // Families + getFamilies: async (condoId?: string): Promise => { + let url = '/families'; + const activeId = condoId || CondoService.getActiveCondoId(); + if (activeId) url += `?condoId=${activeId}`; + return request(url); + }, + + addFamily: async (family: any): Promise => { + let activeId = CondoService.getActiveCondoId(); + if (!activeId) { + const condos = await CondoService.getCondos(); + if (condos.length > 0) activeId = condos[0].id; + } + return request('/families', { + method: 'POST', + body: JSON.stringify({ ...family, condoId: activeId }) + }); + }, + + updateFamily: async (family: Family): Promise => { + return request(`/families/${family.id}`, { + method: 'PUT', + body: JSON.stringify(family) + }); + }, + + deleteFamily: async (id: string): Promise => { + return request(`/families/${id}`, { method: 'DELETE' }); + }, + + // Payments + seedPayments: () => { /* No-op for real backend */ }, + + getPaymentsByFamily: async (familyId: string): Promise => { + return request(`/payments?familyId=${familyId}`); + }, + + getCondoPayments: async (condoId: string): Promise => { + return request(`/payments?condoId=${condoId}`); + }, + + addPayment: async (payment: any): Promise => { + return request('/payments', { + method: 'POST', + body: JSON.stringify(payment) + }); + }, + + // Users + getUsers: async (condoId?: string): Promise => { + let url = '/users'; + if (condoId) url += `?condoId=${condoId}`; + return request(url); + }, + + createUser: async (user: any): Promise => { + return request('/users', { + method: 'POST', + body: JSON.stringify(user) + }); + }, + + updateUser: async (id: string, user: any): Promise => { + return request(`/users/${id}`, { + method: 'PUT', + body: JSON.stringify(user) + }); + }, + + deleteUser: async (id: string): Promise => { + return request(`/users/${id}`, { method: 'DELETE' }); + }, + + // Alerts + getAlerts: async (condoId?: string): Promise => { + let url = '/alerts'; + if (condoId) url += `?condoId=${condoId}`; + return request(url); + }, + + saveAlert: async (alert: AlertDefinition): Promise => { + let activeId = CondoService.getActiveCondoId(); + if (!activeId) { + const condos = await CondoService.getCondos(); + if (condos.length > 0) activeId = condos[0].id; + } + if (alert.id) { + await request(`/alerts/${alert.id}`, { method: 'PUT', body: JSON.stringify(alert) }); + return alert; + } else { + return request('/alerts', { + method: 'POST', + body: JSON.stringify({ ...alert, condoId: activeId }) + }); + } + }, + + deleteAlert: async (id: string): Promise => { + return request(`/alerts/${id}`, { method: 'DELETE' }); + }, + + // Notices + getNotices: async (condoId?: string): Promise => { + let url = '/notices'; + const activeId = CondoService.getActiveCondoId(); + if (activeId) url += `?condoId=${activeId}`; + return request(url); + }, + + getUnreadNoticesForUser: async (userId: string, condoId: string): Promise => { + return request(`/notices/unread?userId=${userId}&condoId=${condoId}`); + }, + + getNoticeReadStatus: async (noticeId: string): Promise => { + return request(`/notices/${noticeId}/read-status`); + }, + + markNoticeAsRead: async (noticeId: string, userId: string): Promise => { + return request(`/notices/${noticeId}/read`, { + method: 'POST', + body: JSON.stringify({ userId }) + }); + }, + + saveNotice: async (notice: Notice): Promise => { + if (notice.id) { + return request(`/notices/${notice.id}`, { method: 'PUT', body: JSON.stringify(notice) }); + } else { + return request('/notices', { method: 'POST', body: JSON.stringify(notice) }); + } + }, + + deleteNotice: async (id: string): Promise => { + return request(`/notices/${id}`, { method: 'DELETE' }); + }, + + // Tickets + getTickets: async (): Promise => { + const activeId = CondoService.getActiveCondoId(); + return request(`/tickets?condoId=${activeId}`); + }, + + createTicket: async (data: any): Promise => { + let activeId = CondoService.getActiveCondoId(); + // Robustness: If no activeId (e.g. standard user), fetch condos and use the first one + if (!activeId) { + const condos = await CondoService.getCondos(); + if (condos.length > 0) activeId = condos[0].id; + } + return request('/tickets', { + method: 'POST', + body: JSON.stringify({ ...data, condoId: activeId }) + }); + }, + + updateTicket: async (id: string, data: any): Promise => { + return request(`/tickets/${id}`, { + method: 'PUT', + body: JSON.stringify(data) + }); + }, + + deleteTicket: async (id: string): Promise => { + return request(`/tickets/${id}`, { method: 'DELETE' }); + }, + + getTicketComments: async (ticketId: string): Promise => { + return request(`/tickets/${ticketId}/comments`); + }, + + addTicketComment: async (ticketId: string, text: string): Promise => { + return request(`/tickets/${ticketId}/comments`, { + method: 'POST', + body: JSON.stringify({ text }) + }); + }, + + getTicketAttachment: async (ticketId: string, attachmentId: string): Promise => { + return request(`/tickets/${ticketId}/attachments/${attachmentId}`); + }, + + // Extraordinary Expenses + getExpenses: async (condoId?: string): Promise => { + let url = '/expenses'; + const activeId = condoId || CondoService.getActiveCondoId(); + if (activeId) url += `?condoId=${activeId}`; + return request(url); + }, + + getExpenseDetails: async (id: string): Promise => { + return request(`/expenses/${id}`); + }, + + createExpense: async (data: any): Promise => { + let activeId = CondoService.getActiveCondoId(); + if (!activeId) { + const condos = await CondoService.getCondos(); + if (condos.length > 0) activeId = condos[0].id; + } + if (!activeId) throw new Error("No active condo"); + return request('/expenses', { + method: 'POST', + body: JSON.stringify({ ...data, condoId: activeId }) + }); + }, + + updateExpense: async (id: string, data: any): Promise => { + return request(`/expenses/${id}`, { + method: 'PUT', + body: JSON.stringify(data) + }); + }, + + deleteExpense: async (id: string): Promise => { + return request(`/expenses/${id}`, { + method: 'DELETE' + }); + }, + + getExpenseAttachment: async (expenseId: string, attachmentId: string): Promise => { + return request(`/expenses/${expenseId}/attachments/${attachmentId}`); + }, + + getMyExpenses: async (): Promise => { + const activeId = CondoService.getActiveCondoId(); + return request(`/my-expenses?condoId=${activeId}`); + }, + + payExpense: async (expenseId: string, amount: number): Promise => { + return request(`/expenses/${expenseId}/pay`, { + method: 'POST', + body: JSON.stringify({ amount, notes: 'PayPal Payment' }) + }); + } +};