BillTracker/client/components/admin/EmailNotifCard.jsx

208 lines
7.8 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Eye, EyeOff } from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import {
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { SectionHeading, FieldRow, Toggle } from './adminShared';
export default function EmailNotifCard() {
const DEFAULTS = {
enabled: false,
sender_name: '', sender_address: '',
smtp_host: '', smtp_port: '587', smtp_encryption: 'starttls',
smtp_self_signed: false,
smtp_username: '', smtp_password: '',
allow_user_config: false,
global_recipient: '',
};
const [cfg, setCfg] = useState(DEFAULTS);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [showPw, setShowPw] = useState(false);
const [testEmail, setTestEmail] = useState('');
const [testing, setTesting] = useState(false);
useEffect(() => {
api.notifAdmin()
.then(d => setCfg({ ...DEFAULTS, ...d }))
.catch(() => {})
.finally(() => setLoading(false));
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const set = (k, v) => setCfg(p => ({ ...p, [k]: v }));
const handleSave = async () => {
setSaving(true);
try {
await api.saveNotifAdmin(cfg);
toast.success('Email settings saved.');
} catch (err) {
toast.error(err.message || 'Failed to save.');
} finally {
setSaving(false);
}
};
const handleTest = async () => {
if (!testEmail) { toast.error('Enter a recipient email.'); return; }
setTesting(true);
try {
await api.testEmail({ to: testEmail });
toast.success('Test email sent.');
} catch (err) {
toast.error(err.message || 'Failed to send test email.');
} finally {
setTesting(false);
}
};
if (loading) return <Card><CardContent className="py-8 text-center text-muted-foreground text-sm">Loading</CardContent></Card>;
return (
<Card>
<CardHeader className="pb-4">
<CardTitle>Email Notifications</CardTitle>
</CardHeader>
<CardContent className="space-y-5">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">Enable email notifications</p>
<p className="text-xs text-muted-foreground">Configure SMTP to send bill reminders</p>
</div>
<Toggle checked={cfg.enabled} onChange={v => set('enabled', v)} label="Enable email notifications" />
</div>
<div className="border-t border-border" />
<div className="space-y-4">
<SectionHeading>Sender</SectionHeading>
<FieldRow label="Sender name">
<Input value={cfg.sender_name} onChange={e => set('sender_name', e.target.value)} placeholder="BillTracker" />
</FieldRow>
<FieldRow label="Sender address">
<Input value={cfg.sender_address} onChange={e => set('sender_address', e.target.value)} placeholder="no-reply@example.com" type="email" />
</FieldRow>
</div>
<div className="border-t border-border" />
<div className="space-y-4">
<SectionHeading>SMTP Server</SectionHeading>
<FieldRow label="SMTP host">
<Input value={cfg.smtp_host} onChange={e => set('smtp_host', e.target.value)} placeholder="smtp.example.com" />
</FieldRow>
<FieldRow label="Port">
<Input value={cfg.smtp_port} onChange={e => set('smtp_port', e.target.value)} placeholder="587" type="number" className="w-28" />
</FieldRow>
<FieldRow label="Encryption">
<Select value={cfg.smtp_encryption} onValueChange={v => set('smtp_encryption', v)}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="starttls">STARTTLS</SelectItem>
<SelectItem value="ssl">SSL/TLS</SelectItem>
<SelectItem value="none">None</SelectItem>
</SelectContent>
</Select>
</FieldRow>
<FieldRow label="Allow self-signed cert">
<div className="flex items-center h-9">
<input
type="checkbox"
id="self-signed"
checked={cfg.smtp_self_signed}
onChange={e => set('smtp_self_signed', e.target.checked)}
className="h-4 w-4 rounded border-input bg-input accent-primary"
/>
<label htmlFor="self-signed" className="ml-2 text-sm text-muted-foreground">
Accept self-signed certificates
</label>
</div>
</FieldRow>
<FieldRow label="SMTP username">
<Input value={cfg.smtp_username} onChange={e => set('smtp_username', e.target.value)} placeholder="user@example.com" />
</FieldRow>
<FieldRow label="SMTP password">
<div className="relative">
<Input
type={showPw ? 'text' : 'password'}
value={cfg.smtp_password}
onChange={e => set('smtp_password', e.target.value)}
placeholder="••••••••"
className="pr-9"
/>
<button
type="button"
onClick={() => setShowPw(p => !p)}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
>
{showPw ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</FieldRow>
</div>
<div className="border-t border-border" />
<div className="space-y-4">
<SectionHeading>User Access</SectionHeading>
<FieldRow label="Allow user config">
<div className="flex items-center h-9">
<input
type="checkbox"
id="allow-user"
checked={cfg.allow_user_config}
onChange={e => set('allow_user_config', e.target.checked)}
className="h-4 w-4 rounded border-input bg-input accent-primary"
/>
<label htmlFor="allow-user" className="ml-2 text-sm text-muted-foreground">
Let users configure their own notification preferences
</label>
</div>
</FieldRow>
<FieldRow label="Global recipient">
<Input
value={cfg.global_recipient}
onChange={e => set('global_recipient', e.target.value)}
placeholder="recipient@example.com"
type="email"
/>
</FieldRow>
</div>
<div className="border-t border-border" />
<div className="space-y-4">
<SectionHeading>Test Email</SectionHeading>
<FieldRow label="Send test to">
<div className="flex gap-2">
<Input
value={testEmail}
onChange={e => setTestEmail(e.target.value)}
placeholder="you@example.com"
type="email"
/>
<Button variant="outline" onClick={handleTest} disabled={testing} className="shrink-0">
{testing ? 'Sending…' : 'Send Test Email'}
</Button>
</div>
</FieldRow>
</div>
<div className="flex justify-end pt-2">
<Button onClick={handleSave} disabled={saving}>
{saving ? 'Saving…' : 'Save Settings'}
</Button>
</div>
</CardContent>
</Card>
);
}