Files
Condopay/services/mockDb.ts
frakarr 26fc451871 feat: Add email configuration and alert system
Introduces SMTP configuration settings and alert definitions to enable automated email notifications.
This includes new types for `SmtpConfig` and `AlertDefinition`, and integrates these into the settings page and mock database.
Adds styling for select elements and scrollbar hiding in the main HTML.
Updates mock database logic to potentially support local development without a backend.
2025-12-06 23:01:02 +01:00

443 lines
15 KiB
TypeScript

import { Family, Payment, AppSettings, User, AuthResponse, AlertDefinition } from '../types';
// --- CONFIGURATION TOGGLE ---
// TRUE = WORK MODE (Localstorage, no backend required)
// FALSE = COMMIT MODE (Real API calls)
const FORCE_LOCAL_DB = false;
const API_URL = '/api';
const STORAGE_KEYS = {
SETTINGS: 'condo_settings',
FAMILIES: 'condo_families',
PAYMENTS: 'condo_payments',
TOKEN: 'condo_auth_token',
USER: 'condo_user_info',
USERS_LIST: 'condo_users_list',
ALERTS: 'condo_alerts_def'
};
const getLocal = <T>(key: string, defaultVal: T): T => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultVal;
} catch {
return defaultVal;
}
};
const setLocal = (key: string, val: any) => {
localStorage.setItem(key, JSON.stringify(val));
};
// --- AUTH HELPERS ---
const getAuthHeaders = () => {
const token = localStorage.getItem(STORAGE_KEYS.TOKEN);
return token ? { 'Authorization': `Bearer ${token}` } : {};
};
// --- SERVICE IMPLEMENTATION ---
export const CondoService = {
login: async (email, password) => {
if (FORCE_LOCAL_DB) {
// MOCK LOGIN for Preview
await new Promise(resolve => setTimeout(resolve, 600)); // Fake delay
// Allow any login, but give admin rights to specific email or generic
const role = email.includes('admin') || email === 'fcarra79@gmail.com' ? 'admin' : 'user';
const mockUser: User = {
id: 'local-user-' + Math.random().toString(36).substr(2, 9),
email,
name: email.split('@')[0],
role: role as any,
familyId: null,
receiveAlerts: true
};
localStorage.setItem(STORAGE_KEYS.TOKEN, 'mock-local-token-' + Date.now());
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(mockUser));
return { token: localStorage.getItem(STORAGE_KEYS.TOKEN)!, user: mockUser };
}
try {
const res = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!res.ok) throw new Error('Login fallito');
const data = await res.json();
localStorage.setItem(STORAGE_KEYS.TOKEN, data.token);
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(data.user));
return data;
} catch (e) {
console.warn("Backend unavailable or login failed");
throw e;
}
},
logout: () => {
localStorage.removeItem(STORAGE_KEYS.TOKEN);
localStorage.removeItem(STORAGE_KEYS.USER);
window.location.href = '#/login';
},
getCurrentUser: (): User | null => {
return getLocal<User | null>(STORAGE_KEYS.USER, null);
},
updateProfile: async (data: Partial<User> & { password?: string }) => {
if (FORCE_LOCAL_DB) {
const currentUser = getLocal<User | null>(STORAGE_KEYS.USER, null);
if (!currentUser) throw new Error("Not logged in");
// Update current user session
const updatedUser = { ...currentUser, ...data };
delete (updatedUser as any).password;
setLocal(STORAGE_KEYS.USER, updatedUser);
// Update in users list if it exists
const users = getLocal<User[]>(STORAGE_KEYS.USERS_LIST, []);
const userIndex = users.findIndex(u => u.id === currentUser.id || u.email === currentUser.email);
if (userIndex >= 0) {
users[userIndex] = { ...users[userIndex], ...data };
delete (users[userIndex] as any).password; // mock logic: don't store pw
setLocal(STORAGE_KEYS.USERS_LIST, users);
}
return { success: true, user: updatedUser };
}
const res = await fetch(`${API_URL}/profile`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(data)
});
if (!res.ok) throw new Error('Failed to update profile');
const response = await res.json();
// Update local session
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(response.user));
return response;
},
getSettings: async (): Promise<AppSettings> => {
if (FORCE_LOCAL_DB) {
return getLocal<AppSettings>(STORAGE_KEYS.SETTINGS, {
defaultMonthlyQuota: 100,
condoName: 'Condominio (Anteprima)',
currentYear: new Date().getFullYear(),
smtpConfig: {
host: '', port: 587, user: '', pass: '', secure: false, fromEmail: ''
}
});
}
try {
const res = await fetch(`${API_URL}/settings`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
return getLocal<AppSettings>(STORAGE_KEYS.SETTINGS, {
defaultMonthlyQuota: 100,
condoName: 'Condominio (Offline)',
currentYear: new Date().getFullYear()
});
}
},
updateSettings: async (settings: AppSettings): Promise<void> => {
if (FORCE_LOCAL_DB) {
setLocal(STORAGE_KEYS.SETTINGS, settings);
return;
}
try {
const res = await fetch(`${API_URL}/settings`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(settings)
});
if (!res.ok) throw new Error('API Error');
} catch (e) {
setLocal(STORAGE_KEYS.SETTINGS, settings);
}
},
getAvailableYears: async (): Promise<number[]> => {
// Shared logic works for both because it falls back to calculating from payments
if (FORCE_LOCAL_DB) {
const payments = getLocal<Payment[]>(STORAGE_KEYS.PAYMENTS, []);
const settings = getLocal<AppSettings>(STORAGE_KEYS.SETTINGS, { currentYear: new Date().getFullYear() } as AppSettings);
const years = new Set(payments.map(p => p.forYear));
years.add(settings.currentYear);
return Array.from(years).sort((a, b) => b - a);
}
try {
const res = await fetch(`${API_URL}/years`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
const payments = getLocal<Payment[]>(STORAGE_KEYS.PAYMENTS, []);
const settings = getLocal<AppSettings>(STORAGE_KEYS.SETTINGS, { currentYear: new Date().getFullYear() } as AppSettings);
const years = new Set(payments.map(p => p.forYear));
years.add(settings.currentYear);
return Array.from(years).sort((a, b) => b - a);
}
},
getFamilies: async (): Promise<Family[]> => {
if (FORCE_LOCAL_DB) {
return getLocal<Family[]>(STORAGE_KEYS.FAMILIES, []);
}
try {
const res = await fetch(`${API_URL}/families`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
return getLocal<Family[]>(STORAGE_KEYS.FAMILIES, []);
}
},
addFamily: async (familyData: Omit<Family, 'id' | 'balance'>): Promise<Family> => {
if (FORCE_LOCAL_DB) {
const families = getLocal<Family[]>(STORAGE_KEYS.FAMILIES, []);
const newFamily = { ...familyData, id: crypto.randomUUID(), balance: 0 };
setLocal(STORAGE_KEYS.FAMILIES, [...families, newFamily]);
return newFamily;
}
try {
const res = await fetch(`${API_URL}/families`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(familyData)
});
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
throw e;
}
},
updateFamily: async (family: Family): Promise<Family> => {
if (FORCE_LOCAL_DB) {
const families = getLocal<Family[]>(STORAGE_KEYS.FAMILIES, []);
const updated = families.map(f => f.id === family.id ? family : f);
setLocal(STORAGE_KEYS.FAMILIES, updated);
return family;
}
try {
const res = await fetch(`${API_URL}/families/${family.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(family)
});
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
throw e;
}
},
deleteFamily: async (familyId: string): Promise<void> => {
if (FORCE_LOCAL_DB) {
const families = getLocal<Family[]>(STORAGE_KEYS.FAMILIES, []);
setLocal(STORAGE_KEYS.FAMILIES, families.filter(f => f.id !== familyId));
return;
}
try {
const res = await fetch(`${API_URL}/families/${familyId}`, {
method: 'DELETE',
headers: getAuthHeaders()
});
if (!res.ok) throw new Error('API Error');
} catch (e) {
throw e;
}
},
getPaymentsByFamily: async (familyId: string): Promise<Payment[]> => {
if (FORCE_LOCAL_DB) {
const payments = getLocal<Payment[]>(STORAGE_KEYS.PAYMENTS, []);
return payments.filter(p => p.familyId === familyId);
}
try {
const res = await fetch(`${API_URL}/payments?familyId=${familyId}`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
const payments = getLocal<Payment[]>(STORAGE_KEYS.PAYMENTS, []);
return payments.filter(p => p.familyId === familyId);
}
},
addPayment: async (payment: Omit<Payment, 'id'>): Promise<Payment> => {
if (FORCE_LOCAL_DB) {
const payments = getLocal<Payment[]>(STORAGE_KEYS.PAYMENTS, []);
const newPayment = { ...payment, id: crypto.randomUUID() };
setLocal(STORAGE_KEYS.PAYMENTS, [...payments, newPayment]);
return newPayment;
}
try {
const res = await fetch(`${API_URL}/payments`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(payment)
});
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
throw e;
}
},
getUsers: async (): Promise<User[]> => {
if (FORCE_LOCAL_DB) {
return getLocal<User[]>(STORAGE_KEYS.USERS_LIST, []);
}
try {
const res = await fetch(`${API_URL}/users`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
return [];
}
},
createUser: async (userData: any) => {
if (FORCE_LOCAL_DB) {
const users = getLocal<User[]>(STORAGE_KEYS.USERS_LIST, []);
const newUser = { ...userData, id: crypto.randomUUID() };
if (newUser.receiveAlerts === undefined) newUser.receiveAlerts = true;
// Don't save password in local mock
delete newUser.password;
setLocal(STORAGE_KEYS.USERS_LIST, [...users, newUser]);
return { success: true, id: newUser.id };
}
const res = await fetch(`${API_URL}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(userData)
});
if (!res.ok) throw new Error('Failed to create user');
return res.json();
},
updateUser: async (id: string, userData: any) => {
if (FORCE_LOCAL_DB) {
const users = getLocal<User[]>(STORAGE_KEYS.USERS_LIST, []);
const updatedUsers = users.map(u => u.id === id ? { ...u, ...userData, id } : u);
setLocal(STORAGE_KEYS.USERS_LIST, updatedUsers);
return { success: true };
}
const res = await fetch(`${API_URL}/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(userData)
});
if (!res.ok) throw new Error('Failed to update user');
return res.json();
},
deleteUser: async (id: string) => {
if (FORCE_LOCAL_DB) {
const users = getLocal<User[]>(STORAGE_KEYS.USERS_LIST, []);
setLocal(STORAGE_KEYS.USERS_LIST, users.filter(u => u.id !== id));
return;
}
const res = await fetch(`${API_URL}/users/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
});
if (!res.ok) throw new Error('Failed to delete user');
},
// --- ALERTS SERVICE ---
getAlerts: async (): Promise<AlertDefinition[]> => {
if (FORCE_LOCAL_DB) {
return getLocal<AlertDefinition[]>(STORAGE_KEYS.ALERTS, []);
}
try {
const res = await fetch(`${API_URL}/alerts`, { headers: getAuthHeaders() });
if (!res.ok) throw new Error('API Error');
return res.json();
} catch (e) {
return [];
}
},
saveAlert: async (alert: AlertDefinition): Promise<AlertDefinition> => {
if (FORCE_LOCAL_DB) {
const alerts = getLocal<AlertDefinition[]>(STORAGE_KEYS.ALERTS, []);
const existingIndex = alerts.findIndex(a => a.id === alert.id);
let newAlerts;
if (existingIndex >= 0) {
newAlerts = alerts.map(a => a.id === alert.id ? alert : a);
} else {
newAlerts = [...alerts, { ...alert, id: alert.id || crypto.randomUUID() }];
}
setLocal(STORAGE_KEYS.ALERTS, newAlerts);
return alert;
}
const method = alert.id ? 'PUT' : 'POST';
const url = alert.id ? `${API_URL}/alerts/${alert.id}` : `${API_URL}/alerts`;
const res = await fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(alert)
});
if (!res.ok) throw new Error('Failed to save alert');
return res.json();
},
deleteAlert: async (id: string) => {
if (FORCE_LOCAL_DB) {
const alerts = getLocal<AlertDefinition[]>(STORAGE_KEYS.ALERTS, []);
setLocal(STORAGE_KEYS.ALERTS, alerts.filter(a => a.id !== id));
return;
}
const res = await fetch(`${API_URL}/alerts/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
});
if (!res.ok) throw new Error('Failed to delete alert');
},
seedPayments: () => {
if (!FORCE_LOCAL_DB) return; // Don't seed if connected to DB
const families = getLocal<Family[]>(STORAGE_KEYS.FAMILIES, []);
if (families.length === 0) {
// Seed only if completely empty
const demoFamilies: Family[] = [
{ id: 'f1', name: 'Rossi Mario', unitNumber: 'A1', contactEmail: 'rossi@email.com', balance: 0 },
{ id: 'f2', name: 'Bianchi Luigi', unitNumber: 'A2', contactEmail: 'bianchi@email.com', balance: 0 },
{ id: 'f3', name: 'Verdi Anna', unitNumber: 'B1', contactEmail: 'verdi@email.com', balance: 0 },
];
setLocal(STORAGE_KEYS.FAMILIES, demoFamilies);
const demoUsers: User[] = [
{ id: 'u1', email: 'admin@condo.it', name: 'Amministratore', role: 'admin', phone: '3331234567', familyId: null, receiveAlerts: true },
{ id: 'u2', email: 'rossi@email.com', name: 'Mario Rossi', role: 'user', phone: '', familyId: 'f1', receiveAlerts: true }
];
setLocal(STORAGE_KEYS.USERS_LIST, demoUsers);
}
}
};