This commit is contained in:
Huzaifa Inam 2026-08-07 15:35:45 +05:00
parent ef6f77a126
commit 726e15cffa
8 changed files with 255 additions and 112 deletions

View file

@ -2,9 +2,9 @@
PORT=3000
NODE_ENV=production
# Host Port Mappings for Docker / Coolify (Prevents conflict if host ports 3000 or 5432 are occupied)
HOST_PORT=8080
POSTGRES_HOST_PORT=5433
# Host Port Mappings for Docker / Coolify (Exposed on host as 7000 and 7001 to prevent conflicts with other applications)
HOST_PORT=7000
POSTGRES_HOST_PORT=7001
# PostgreSQL Database Configuration
DATABASE_URL=postgresql://postgres:postgres_secure_pass_2026@postgres:5432/balance_sheet_db
@ -13,9 +13,12 @@ POSTGRES_PASSWORD=postgres_secure_pass_2026
# Fallback File Storage Path (Used if DATABASE_URL is omitted or offline)
DATA_PATH=/app/data/portal-data.json
# Optional SMTP Email Configuration (If using real SMTP server)
# SMTP Email Configuration (Environment Variable Overrides)
# SMTP_HOST=smtp.gmail.com
# SMTP_PORT=587
# SMTP_USER=your-email@domain.com
# SMTP_PASS=your-app-password
# SMTP_FROM="Balance Sheet Portal <noreply@domain.com>"
# SMTP_USER=alerts@networkbank.com
# SMTP_PASS=your_secret_smtp_password
# SMTP_FROM="Balance Sheet Portal <noreply@networkbank.com>"
# SMTP_USE_TLS=true
# SMTP_AUTO_REMINDERS_ENABLED=true
# SMTP_REMINDER_FREQUENCY_DAYS=7

View file

@ -1,7 +1,7 @@
# Coolify & PostgreSQL Deployment Guide
This application consists of:
1. **Server Side**: Node.js + Express backend (`server.ts`) serving REST APIs, managing authentication, ledger reconciliation, audit logs, FX rates, and financial reports.
1. **Server Side**: Node.js + Express backend (`server.ts`) serving REST APIs, managing authentication, ledger reconciliation, audit logs, FX rates, SMTP dispatching, and financial reports.
2. **Database**: PostgreSQL (via `pg` connection pool) with automatic JSON fallback backup.
3. **Frontend**: React SPA served directly via Express in production.
@ -19,12 +19,20 @@ This application consists of:
```env
NODE_ENV=production
PORT=3000
HOST_PORT=8080 # Customize if host port 3000 is occupied by another app
POSTGRES_HOST_PORT=5433 # Customize if host port 5432 is occupied by another database
HOST_PORT=7000 # Mapped host port (7000:3000) to avoid conflicts
POSTGRES_HOST_PORT=7001 # Mapped host database port (7001:5432) to avoid conflicts
POSTGRES_PASSWORD=your_custom_secure_password
# SMTP Email Dispatch Settings
SMTP_HOST=smtp.your-server.com
SMTP_PORT=587
SMTP_USER=alerts@networkbank.com
SMTP_PASS=your_smtp_password
SMTP_FROM="Balance Sheet Portal <noreply@networkbank.com>"
SMTP_USE_TLS=true
```
3. **Deploy**:
- Coolify will build the app container and spin up a dedicated `postgres:16-alpine` database container with health checks and persistent volume storage (`balance_sheet_postgres_data`).
- Coolify will build the app container and spin up a dedicated `postgres:16-alpine` database container with health checks and persistent volume storage (`balance_sheet_postgres_data`). Access the app on host port `7000`.
---
@ -41,16 +49,23 @@ If you prefer using Coolify's built-in managed PostgreSQL database resource:
```env
DATABASE_URL=postgresql://postgres:pass@coolify-postgres-host:5432/balance_sheet_db
PORT=3000
# SMTP Email Settings
SMTP_HOST=smtp.your-server.com
SMTP_PORT=587
SMTP_USER=alerts@networkbank.com
SMTP_PASS=your_smtp_password
```
3. **Deploy**:
- The Express server will automatically connect to the PostgreSQL database, create the necessary table structures on boot, and store all portal state in PostgreSQL.
---
## 2. Server-Side Architecture & Port Configuration
## 2. Server-Side Architecture & Configuration
- **Internal Container Port**: Application listens on `3000` inside the container.
- **Conflict Prevention**: Host port mappings in `docker-compose.yml` use `${HOST_PORT:-8080}:3000` and `${POSTGRES_HOST_PORT:-5433}:5432`. If port 3000 or 5432 is already used by another app on your server, simply set `HOST_PORT` or `POSTGRES_HOST_PORT` in your `.env` to any available port (e.g. `8080`, `8081`, `5433`).
- **Conflict Prevention**: Host port mappings in `docker-compose.yml` use `${HOST_PORT:-7000}:3000` and `${POSTGRES_HOST_PORT:-7001}:5432` to avoid conflicts on host ports 3000, 8080, and 5432.
- **SMTP Environment Overrides**: SMTP configuration (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`, `SMTP_USE_TLS`) is fully managed via environment variables.
- **Automatic Table Creation**: On server boot, the backend runs `initPgDatabase()` to verify/create `app_portal_state`.
- **Automatic State Recovery**: If PostgreSQL contains existing data, portal state is loaded directly from PostgreSQL.
- **Resilient Fallback**: If `DATABASE_URL` is omitted, the app gracefully operates using persistent JSON storage (`/app/data/portal-data.json`).

View file

@ -18,32 +18,32 @@ A full-stack enterprise web application built for multi-branch balance sheet con
| File | Description |
| :--- | :--- |
| `Dockerfile` & `dockerfile` | Multi-stage production container build (Vite client + Express CJS server). |
| `docker-compose.yml` | Full-stack orchestration (App container + PostgreSQL 16 container with configurable host port mapping). |
| `docker-compose.yml` | Full-stack orchestration (App container mapped to host port 7000 + PostgreSQL container mapped to host port 7001). |
| `.dockerignore` | Defines context exclusions for slim Docker image builds. |
| `.env.example` | Template environment variables (`DATABASE_URL`, `PORT`, `HOST_PORT`, `POSTGRES_HOST_PORT`, `DATA_PATH`). |
| `.env.example` | Template environment variables (`DATABASE_URL`, `PORT`, `HOST_PORT`, `POSTGRES_HOST_PORT`, `DATA_PATH`, `SMTP_*`). |
| `COOLIFY.md` | Complete deployment guide for Coolify platform & managed databases. |
| `server.ts` | Express server entry point with PostgreSQL initialization and REST API routes. |
| `server.ts` | Express server entry point with PostgreSQL initialization, environment SMTP dispatching, and REST API routes. |
---
## 🛠️ Local Development & Running via Docker
### 1. Run via Docker Compose (With PostgreSQL & Configurable Host Ports)
### 1. Run via Docker Compose (With PostgreSQL & Non-Conflicting Host Ports)
```bash
docker-compose up --build -d
```
By default, the application will be accessible on host port `http://localhost:8080` (mapped to internal container port `3000`), and PostgreSQL on host port `5433` (mapped to internal container port `5432`).
By default, the application will be accessible on host port `http://localhost:7000` (mapped to internal container port `3000`), and PostgreSQL on host port `7001` (mapped to internal container port `5432`), preventing conflicts with common host ports like 3000, 8080, or 5432.
If host ports `3000` or `5432` are already used by another application, set custom host ports in `.env`:
If you wish to use different custom host ports, set them in `.env`:
```env
HOST_PORT=8080
POSTGRES_HOST_PORT=5433
HOST_PORT=7000
POSTGRES_HOST_PORT=7001
```
### 2. Run via Docker CLI
```bash
docker build -t balance-sheet-portal .
docker run -p 8080:3000 -v portal_data:/app/data balance-sheet-portal
docker run -p 7000:3000 -v portal_data:/app/data balance-sheet-portal
```
### 3. Native Node.js Development

View file

@ -11,8 +11,8 @@ services:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres_secure_pass_2026}
ports:
# Use POSTGRES_HOST_PORT (default 5433) to prevent conflicts if host port 5432 is already occupied
- "${POSTGRES_HOST_PORT:-5433}:5432"
# Use POSTGRES_HOST_PORT (default 7001) to prevent conflicts on standard DB host ports
- "${POSTGRES_HOST_PORT:-7001}:5432"
volumes:
- postgres_db_data:/var/lib/postgresql/data
healthcheck:
@ -33,16 +33,23 @@ services:
postgres:
condition: service_healthy
ports:
- "7000"
# Use HOST_PORT (default 7000) to prevent conflicts with other web applications
- "${HOST_PORT:-7000}:3000"
environment:
- NODE_ENV=production
- PORT=7000
- PORT=3000
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres_secure_pass_2026}@postgres:5432/balance_sheet_db
- DATA_PATH=/app/data/portal-data.json
- SMTP_HOST=${SMTP_HOST:-}
- SMTP_PORT=${SMTP_PORT:-}
- SMTP_USER=${SMTP_USER:-}
- SMTP_PASS=${SMTP_PASS:-}
- SMTP_FROM=${SMTP_FROM:-}
- SMTP_USE_TLS=${SMTP_USE_TLS:-}
volumes:
- portal_data:/app/data
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7000/api/health"]
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
retries: 3

116
server.ts
View file

@ -6,6 +6,7 @@ import nodemailer from 'nodemailer';
import {
INITIAL_USERS,
INITIAL_SMTP_CONFIG,
getSmtpConfigFromEnv,
RAW_15_MAY_26,
RAW_22_MAY_26,
INITIAL_VARIANCE_COMMENTS_22_MAY,
@ -79,6 +80,59 @@ const DEFAULT_PKR_RATES: Record<string, number> = {
CADPKR: 204.53,
};
function getEffectiveSmtpConfig(storedConfig?: SmtpConfig): SmtpConfig {
const envConfig = getSmtpConfigFromEnv();
const base = storedConfig || INITIAL_SMTP_CONFIG;
return {
host: process.env.SMTP_HOST || base.host || envConfig.host,
port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : (base.port || envConfig.port),
username: process.env.SMTP_USER || process.env.SMTP_USERNAME || base.username || envConfig.username,
password: process.env.SMTP_PASS || process.env.SMTP_PASSWORD || base.password || envConfig.password || '',
fromEmail: process.env.SMTP_FROM || process.env.SMTP_FROM_EMAIL || base.fromEmail || envConfig.fromEmail,
useTls: process.env.SMTP_USE_TLS !== undefined ? process.env.SMTP_USE_TLS === 'true' : (base.useTls ?? envConfig.useTls),
autoRemindersEnabled: process.env.SMTP_AUTO_REMINDERS_ENABLED !== undefined ? process.env.SMTP_AUTO_REMINDERS_ENABLED === 'true' : (base.autoRemindersEnabled ?? envConfig.autoRemindersEnabled),
reminderFrequencyDays: process.env.SMTP_REMINDER_FREQUENCY_DAYS ? parseInt(process.env.SMTP_REMINDER_FREQUENCY_DAYS, 10) : (base.reminderFrequencyDays || envConfig.reminderFrequencyDays),
};
}
async function sendEmailNotification(to: string, subject: string, text: string, config: SmtpConfig): Promise<boolean> {
const host = process.env.SMTP_HOST || config.host;
const port = process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : config.port;
const user = process.env.SMTP_USER || process.env.SMTP_USERNAME || config.username;
const pass = process.env.SMTP_PASS || process.env.SMTP_PASSWORD || config.password;
const from = process.env.SMTP_FROM || process.env.SMTP_FROM_EMAIL || config.fromEmail;
const secure = process.env.SMTP_USE_TLS !== undefined ? process.env.SMTP_USE_TLS === 'true' : config.useTls;
if (host && user && pass && host !== 'smtp.networkbank.com') {
try {
const transporter = nodemailer.createTransport({
host,
port,
secure: port === 465 || secure,
auth: {
user,
pass,
},
tls: {
rejectUnauthorized: false
}
});
await transporter.sendMail({
from: from || user,
to,
subject,
text,
});
console.log(`[SMTP] Dispatched email to ${to} via ${host}:${port}`);
return true;
} catch (err) {
console.error(`[SMTP] Failed to send email to ${to} via ${host}:${port}:`, err);
return false;
}
}
return true;
}
function loadDB(): DB {
const defaultFx: ExchangeRateRecord[] = Object.entries(DEFAULT_FX_RATES).map(([curr, rate], idx) => ({
id: `fx-${idx + 1}`,
@ -93,6 +147,7 @@ function loadDB(): DB {
try {
const raw = fs.readFileSync(dbPath, 'utf-8');
const parsed = JSON.parse(raw);
parsed.smtpConfig = getEffectiveSmtpConfig(parsed.smtpConfig);
if (!parsed.pkrRates) {
parsed.pkrRates = { ...DEFAULT_PKR_RATES };
}
@ -140,7 +195,7 @@ function loadDB(): DB {
const db: DB = {
users: [...INITIAL_USERS],
submissions: initialSubmissions,
smtpConfig: { ...INITIAL_SMTP_CONFIG },
smtpConfig: getEffectiveSmtpConfig(),
emailLogs: [
{
id: 'log-1',
@ -1496,29 +1551,60 @@ app.post('/api/v1/submissions/status', (req, res) => {
// -------------------------------------------------------------
app.get('/api/smtp/config', (req, res) => {
res.json({ config: db.smtpConfig, logs: db.emailLogs });
res.json({ config: getEffectiveSmtpConfig(db.smtpConfig), logs: db.emailLogs });
});
app.post('/api/smtp/config', (req, res) => {
const { host, port, username, fromEmail, useTls, autoRemindersEnabled } = req.body;
db.smtpConfig = {
host: host || db.smtpConfig.host,
port: Number(port) || db.smtpConfig.port,
username: username || db.smtpConfig.username,
fromEmail: fromEmail || db.smtpConfig.fromEmail,
useTls: useTls !== undefined ? useTls : db.smtpConfig.useTls,
autoRemindersEnabled: autoRemindersEnabled !== undefined ? autoRemindersEnabled : db.smtpConfig.autoRemindersEnabled,
reminderFrequencyDays: 7,
};
const { autoRemindersEnabled } = req.body;
if (!db.smtpConfig) {
db.smtpConfig = getEffectiveSmtpConfig();
}
if (autoRemindersEnabled !== undefined) {
db.smtpConfig.autoRemindersEnabled = Boolean(autoRemindersEnabled);
}
saveDB(db);
res.json({ config: db.smtpConfig });
res.json({ config: getEffectiveSmtpConfig(db.smtpConfig) });
});
// Test SMTP connection by sending a test message
app.post('/api/smtp/test', async (req, res) => {
const { testEmail } = req.body;
const currentSmtp = getEffectiveSmtpConfig(db.smtpConfig);
const targetEmail = testEmail || currentSmtp.fromEmail || 'admin@networkbank.com';
const subject = `[SMTP TEST] Network Bank Balance Sheet Portal Connection Test`;
const body = `This is a test notification dispatched from the Head Office Balance Sheet Portal to verify SMTP server connectivity.\n\nServer Host: ${currentSmtp.host}:${currentSmtp.port}\nSender: ${currentSmtp.fromEmail}\nTimestamp: ${new Date().toISOString()}`;
const sentOk = await sendEmailNotification(targetEmail, subject, body, currentSmtp);
const emailLog: EmailLog = {
id: `log-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
recipientEmail: targetEmail,
subject,
body,
sentAt: new Date().toISOString(),
status: sentOk ? 'sent' : 'failed',
triggerType: 'manual_reminder',
};
db.emailLogs.unshift(emailLog);
saveDB(db);
if (sentOk) {
res.json({ success: true, message: `SMTP test notification dispatched successfully to ${targetEmail}.`, logs: db.emailLogs });
} else {
res.status(500).json({ success: false, message: `Failed to send test email to ${targetEmail}. Please verify SMTP environment configuration on the host server.`, logs: db.emailLogs });
}
});
// Send reminders to branches
app.post('/api/smtp/send-reminders', async (req, res) => {
const period = db.activePeriod;
const submissions = db.submissions[period] || {};
const currentSmtp = getEffectiveSmtpConfig(db.smtpConfig);
const submittedBranchIds = Object.keys(submissions);
const pendingBranches = BRANCHES_LIST.filter((b) => !submittedBranchIds.includes(b.id));
@ -1535,6 +1621,8 @@ app.post('/api/smtp/send-reminders', async (req, res) => {
? `Dear ${branch.name} Team, Thank you. Your Balance Sheet submission for period ${period} has been received and verified by Head Office.`
: `Dear ${branch.name} Team, This is an automated reminder from Head Office Balance Sheet Portal. Please complete and submit your fixed format balance sheet for period ${period}. Ensure all variances > USD 2 Mn carry explanatory notes.`;
const sentOk = await sendEmailNotification(branch.contactEmail, subject, body, currentSmtp);
const emailLog: EmailLog = {
id: `log-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
recipientEmail: branch.contactEmail,
@ -1542,7 +1630,7 @@ app.post('/api/smtp/send-reminders', async (req, res) => {
subject,
body,
sentAt: new Date().toISOString(),
status: 'sent',
status: sentOk ? 'sent' : 'failed',
triggerType: 'manual_reminder',
};

View file

@ -34,6 +34,8 @@ export const AdminUserManagement: React.FC<AdminUserManagementProps> = ({ onUser
const [emailLogs, setEmailLogs] = useState<EmailLog[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [reminderSending, setReminderSending] = useState<boolean>(false);
const [testingSmtp, setTestingSmtp] = useState<boolean>(false);
const [testEmailAddress, setTestEmailAddress] = useState<string>('');
const [statusMsg, setStatusMsg] = useState<string | null>(null);
// FX Rates State (Head Office PKR Rates)
@ -153,20 +155,43 @@ export const AdminUserManagement: React.FC<AdminUserManagementProps> = ({ onUser
}
};
// Save SMTP Settings
const handleSaveSmtp = async (e: React.FormEvent) => {
e.preventDefault();
// Toggle Automated Notifications / Alerts
const handleToggleAutoReminders = async (enabled: boolean) => {
try {
const updated = { ...smtpConfig, autoRemindersEnabled: enabled };
setSmtpConfig(updated);
const res = await fetch('/api/smtp/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(smtpConfig),
body: JSON.stringify({ autoRemindersEnabled: enabled }),
});
if (res.ok) {
setStatusMsg('SMTP Server Configuration saved successfully.');
setStatusMsg(`Automated notifications & submission alerts ${enabled ? 'ENABLED' : 'DISABLED'}.`);
}
} catch (err) {
console.error('Error saving SMTP config:', err);
console.error('Error toggling auto reminders:', err);
}
};
// Test SMTP Connection
const handleTestSmtp = async (e: React.FormEvent) => {
e.preventDefault();
setTestingSmtp(true);
setStatusMsg(null);
try {
const res = await fetch('/api/smtp/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ testEmail: testEmailAddress || undefined }),
});
const data = await res.json();
if (data.logs) setEmailLogs(data.logs);
setStatusMsg(data.message || (res.ok ? 'Test email dispatched successfully.' : 'SMTP test failed.'));
} catch (err) {
console.error('Error testing SMTP connection:', err);
setStatusMsg('Failed to test SMTP connection.');
} finally {
setTestingSmtp(false);
}
};
@ -407,72 +432,73 @@ export const AdminUserManagement: React.FC<AdminUserManagementProps> = ({ onUser
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<h3 className="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2">
<Mail className="w-4 h-4 text-amber-400" />
SMTP Server & Automated Notifications
Automated Notifications & SMTP Verification
</h3>
<span className="text-xs font-mono text-emerald-400">SMTP Ready</span>
<span className="text-xs font-mono text-emerald-400 flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
Env Config Active
</span>
</div>
<form onSubmit={handleSaveSmtp} className="space-y-3 text-xs">
<div className="grid grid-cols-2 gap-3">
{/* Environmental Variable Info Banner */}
<div className="bg-slate-950/80 border border-slate-800 rounded-lg p-3 text-xs space-y-1">
<div className="text-slate-300 font-semibold flex items-center justify-between">
<span>Server SMTP Host:</span>
<span className="font-mono text-amber-300">{smtpConfig.host}:{smtpConfig.port}</span>
</div>
<div className="text-slate-400 text-[11px] font-mono">
Sender: {smtpConfig.fromEmail} ({smtpConfig.useTls ? 'TLS Enabled' : 'Standard'})
</div>
<p className="text-[10px] text-slate-500 pt-1 border-t border-slate-800/80">
Note: SMTP server credentials, port, and host are securely configured via server environment variables (<code className="text-slate-400">SMTP_HOST</code>, <code className="text-slate-400">SMTP_PORT</code>, <code className="text-slate-400">SMTP_USER</code>, <code className="text-slate-400">SMTP_PASS</code>).
</p>
</div>
{/* Toggle Controls for Automated Alerts & Notifications */}
<div className="bg-slate-950 p-3.5 rounded-lg border border-slate-800 flex items-center justify-between">
<div>
<label className="text-slate-400 block mb-1 font-semibold">SMTP Host Server</label>
<input
type="text"
value={smtpConfig.host}
onChange={(e) => setSmtpConfig({ ...smtpConfig, host: e.target.value })}
className="w-full bg-slate-950 border border-slate-700 rounded px-2.5 py-1.5 text-white focus:outline-none focus:ring-1 focus:ring-amber-500 font-mono"
/>
<div className="text-xs font-bold text-slate-200">Automated Submission Notifications & Alerts</div>
<div className="text-[11px] text-slate-400">Automatically dispatch email alerts & reminders for pending balance sheet submissions.</div>
</div>
<div>
<label className="text-slate-400 block mb-1 font-semibold">Port</label>
<input
type="number"
value={smtpConfig.port}
onChange={(e) => setSmtpConfig({ ...smtpConfig, port: Number(e.target.value) })}
className="w-full bg-slate-950 border border-slate-700 rounded px-2.5 py-1.5 text-white focus:outline-none focus:ring-1 focus:ring-amber-500 font-mono"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-slate-400 block mb-1 font-semibold">From Sender Email</label>
<input
type="email"
value={smtpConfig.fromEmail}
onChange={(e) => setSmtpConfig({ ...smtpConfig, fromEmail: e.target.value })}
className="w-full bg-slate-950 border border-slate-700 rounded px-2.5 py-1.5 text-white focus:outline-none focus:ring-1 focus:ring-amber-500 font-mono"
/>
</div>
<div>
<label className="text-slate-400 block mb-1 font-semibold">Username / Service Account</label>
<input
type="text"
value={smtpConfig.username}
onChange={(e) => setSmtpConfig({ ...smtpConfig, username: e.target.value })}
className="w-full bg-slate-950 border border-slate-700 rounded px-2.5 py-1.5 text-white focus:outline-none focus:ring-1 focus:ring-amber-500 font-mono"
/>
</div>
</div>
<div className="flex items-center justify-between pt-2 border-t border-slate-800">
<label className="flex items-center space-x-2 cursor-pointer text-slate-300">
<label className="relative inline-flex items-center cursor-pointer shrink-0 ml-3">
<input
type="checkbox"
checked={smtpConfig.useTls}
onChange={(e) => setSmtpConfig({ ...smtpConfig, useTls: e.target.checked })}
className="rounded text-amber-500 focus:ring-0"
checked={smtpConfig.autoRemindersEnabled}
onChange={(e) => handleToggleAutoReminders(e.target.checked)}
className="sr-only peer"
/>
<span>Enable TLS / SSL Secure Enclosure</span>
<div className="w-9 h-5 bg-slate-800 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-emerald-600"></div>
</label>
</div>
{/* Test SMTP Connection Form */}
<form onSubmit={handleTestSmtp} className="bg-slate-950 p-3.5 rounded-lg border border-slate-800 space-y-2.5">
<h4 className="text-xs font-bold text-slate-300 flex items-center gap-1.5">
<Send className="w-3.5 h-3.5 text-amber-400" />
Test SMTP Connection
</h4>
<p className="text-[11px] text-slate-400">
Dispatch a test email message to verify host connectivity and credential authentication.
</p>
<div className="flex items-center gap-2">
<input
type="email"
placeholder={smtpConfig.fromEmail || "recipient@networkbank.com"}
value={testEmailAddress}
onChange={(e) => setTestEmailAddress(e.target.value)}
className="flex-1 bg-slate-900 border border-slate-700 rounded px-2.5 py-1.5 text-xs text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-1 focus:ring-amber-500"
/>
<button
type="submit"
className="px-3 py-1.5 bg-amber-600 hover:bg-amber-500 text-white font-bold rounded text-xs transition-colors"
disabled={testingSmtp}
className="px-3.5 py-1.5 bg-amber-600 hover:bg-amber-500 text-white font-bold rounded text-xs flex items-center gap-1.5 transition-colors cursor-pointer shrink-0"
>
Save SMTP Config
{testingSmtp ? (
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
) : (
<Send className="w-3.5 h-3.5" />
)}
<span>Test SMTP</span>
</button>
</div>
</form>

View file

@ -135,15 +135,18 @@ export const INITIAL_USERS: User[] = [
}
];
export const INITIAL_SMTP_CONFIG: SmtpConfig = {
host: 'smtp.networkbank.com',
port: 587,
username: 'alerts@networkbank.com',
fromEmail: 'noreply-portal@networkbank.com',
useTls: true,
autoRemindersEnabled: true,
reminderFrequencyDays: 7,
};
export const getSmtpConfigFromEnv = (): SmtpConfig => ({
host: process.env.SMTP_HOST || 'smtp.networkbank.com',
port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
username: process.env.SMTP_USER || process.env.SMTP_USERNAME || 'alerts@networkbank.com',
password: process.env.SMTP_PASS || process.env.SMTP_PASSWORD || '',
fromEmail: process.env.SMTP_FROM || process.env.SMTP_FROM_EMAIL || 'noreply-portal@networkbank.com',
useTls: process.env.SMTP_USE_TLS !== undefined ? process.env.SMTP_USE_TLS === 'true' : true,
autoRemindersEnabled: process.env.SMTP_AUTO_REMINDERS_ENABLED !== undefined ? process.env.SMTP_AUTO_REMINDERS_ENABLED === 'true' : true,
reminderFrequencyDays: process.env.SMTP_REMINDER_FREQUENCY_DAYS ? parseInt(process.env.SMTP_REMINDER_FREQUENCY_DAYS, 10) : 7,
});
export const INITIAL_SMTP_CONFIG: SmtpConfig = getSmtpConfigFromEnv();
// Raw baseline submissions for 15-May-26
export const RAW_15_MAY_26: Record<BranchId, BalanceSheetItems> = {

View file

@ -292,6 +292,7 @@ export interface SmtpConfig {
host: string;
port: number;
username: string;
password?: string;
fromEmail: string;
useTls: boolean;
autoRemindersEnabled: boolean;