diff --git a/.dockerignore b/.dockerignore index 21edd64..276bbec 100644 Binary files a/.dockerignore and b/.dockerignore differ diff --git a/Dockerfile b/Dockerfile index bb87fa8..e69de29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +0,0 @@ -# Stage 1: Build Frontend -FROM node:18-alpine as build -WORKDIR /app -COPY package*.json ./ -RUN npm install -COPY . . -RUN npm run build - -# Stage 2: Serve with Nginx -FROM nginx:alpine -COPY --from=build /app/dist /usr/share/nginx/html -# Copy the nginx configuration file (using the .txt extension as provided in source) -COPY nginx.txt /etc/nginx/nginx.conf -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf index f8625d9..e69de29 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,38 +0,0 @@ -worker_processes 1; - -events { worker_connections 1024; } - -http { - include mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - - server { - listen 80; - root /usr/share/nginx/html; - index index.html; - - # Limite upload per allegati (es. foto/video ticket) - Allineato con il backend - client_max_body_size 50M; - - # Compressione Gzip - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - # Gestione SPA (React Router) - location / { - try_files $uri $uri/ /index.html; - } - - # Proxy API verso il backend - location /api/ { - proxy_pass http://backend:3001; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - } -} diff --git a/pages/Settings.tsx b/pages/Settings.tsx index 992b006..5d0bac3 100644 --- a/pages/Settings.tsx +++ b/pages/Settings.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { CondoService } from '../services/mockDb'; import { AppSettings, Family, User, AlertDefinition, Condo, Notice, NoticeIconType, NoticeRead } from '../types'; -import { Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck, AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText, Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon, Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard, ToggleLeft, ToggleRight, LayoutGrid, PieChart, Users } from 'lucide-react'; +import { Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck, AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText, Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon, Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard, ToggleLeft, ToggleRight, LayoutGrid, PieChart, Users, Send } from 'lucide-react'; export const SettingsPage: React.FC = () => { const currentUser = CondoService.getCurrentUser(); @@ -91,6 +91,8 @@ export const SettingsPage: React.FC = () => { // SMTP Modal State const [showSmtpModal, setShowSmtpModal] = useState(false); + const [testingSmtp, setTestingSmtp] = useState(false); + const [testSmtpMsg, setTestSmtpMsg] = useState(''); // Notices (Bacheca) State const [notices, setNotices] = useState([]); @@ -238,6 +240,20 @@ export const SettingsPage: React.FC = () => { } }; + const handleSmtpTest = async () => { + if (!globalSettings?.smtpConfig) return; + setTestingSmtp(true); + setTestSmtpMsg(''); + try { + await CondoService.testSmtpConfig(globalSettings.smtpConfig); + setTestSmtpMsg('Successo! Email di prova inviata.'); + } catch(e: any) { + setTestSmtpMsg('Errore: ' + (e.message || "Impossibile connettersi")); + } finally { + setTestingSmtp(false); + } + }; + const handleNewYear = async () => { if (!globalSettings) return; const nextYear = globalSettings.currentYear + 1; @@ -1041,10 +1057,21 @@ export const SettingsPage: React.FC = () => { setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), fromEmail: e.target.value} as any} : null)} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="no-reply@condominio.it"/> -
- setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), secure: e.target.checked} as any} : null)} className="w-4 h-4 text-blue-600"/> - +
+
+ setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), secure: e.target.checked} as any} : null)} className="w-4 h-4 text-blue-600"/> + +
+
+ {testSmtpMsg &&

{testSmtpMsg}

}
{successMsg} diff --git a/server/Dockerfile b/server/Dockerfile index 9a6d3cb..e69de29 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,13 +0,0 @@ -FROM node:18-alpine -WORKDIR /app - -# Set production environment -ENV NODE_ENV=production - -COPY package*.json ./ -RUN npm install --production - -COPY . . - -EXPOSE 3001 -CMD ["node", "server.js"] diff --git a/server/server.js b/server/server.js index 4e5a9f3..6ca0745 100644 --- a/server/server.js +++ b/server/server.js @@ -163,6 +163,42 @@ app.put('/api/settings', authenticateToken, requireAdmin, async (req, res) => { res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); } }); + +// SMTP TEST +app.post('/api/settings/smtp-test', authenticateToken, requireAdmin, async (req, res) => { + const config = req.body; // Expects SmtpConfig object + const userEmail = req.user.email; + + try { + if (!config.host || !config.user || !config.pass) { + return res.status(400).json({ message: 'Parametri SMTP incompleti' }); + } + + const transporter = nodemailer.createTransport({ + host: config.host, + port: config.port, + secure: config.secure, + auth: { user: config.user, pass: config.pass }, + }); + + // Verify connection + await transporter.verify(); + + // Send Test Email + await transporter.sendMail({ + from: config.fromEmail || config.user, + to: userEmail, + subject: 'CondoPay - Test Configurazione SMTP', + text: 'Se leggi questo messaggio, la configurazione SMTP รจ corretta.', + }); + + res.json({ success: true }); + } catch (e) { + console.error("SMTP Test Error", e); + res.status(400).json({ message: e.message }); + } +}); + app.get('/api/years', authenticateToken, async (req, res) => { try { const [rows] = await pool.query('SELECT DISTINCT for_year FROM payments ORDER BY for_year DESC'); @@ -199,7 +235,7 @@ app.post('/api/condos', authenticateToken, requireAdmin, async (req, res) => { try { await pool.query( 'INSERT INTO condos (id, name, address, street_number, city, province, zip_code, notes, default_monthly_quota, paypal_client_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', - [id, name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota, paypalClientId] + [id, name, address, streetNumber, city, province, zip_code, notes, defaultMonthlyQuota, paypalClientId] ); res.json({ id, name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota, paypalClientId }); } catch (e) { res.status(500).json({ error: e.message }); } diff --git a/services/mockDb.ts b/services/mockDb.ts index a349726..92b1e61 100644 --- a/services/mockDb.ts +++ b/services/mockDb.ts @@ -1,5 +1,5 @@ -import { Family, Payment, AppSettings, User, AlertDefinition, Condo, Notice, NoticeRead, Ticket, TicketAttachment, TicketComment } from '../types'; +import { Family, Payment, AppSettings, User, AlertDefinition, Condo, Notice, NoticeRead, Ticket, TicketAttachment, TicketComment, SmtpConfig } from '../types'; // --- CONFIGURATION TOGGLE --- const FORCE_LOCAL_DB = false; @@ -186,6 +186,13 @@ export const CondoService = { }); }, + testSmtpConfig: async (config: SmtpConfig): Promise => { + await request('/settings/smtp-test', { + method: 'POST', + body: JSON.stringify(config) + }); + }, + getAvailableYears: async (): Promise => { return request('/years'); },