138 lines
5.7 KiB
JavaScript
138 lines
5.7 KiB
JavaScript
import { useState, useEffect } from 'react';
|
||
import { api } from '../utils/api.js';
|
||
import { useToast } from '../contexts/ToastContext.jsx';
|
||
|
||
export default function SettingsModal({ onClose }) {
|
||
const toast = useToast();
|
||
const [vapidPublic, setVapidPublic] = useState('');
|
||
const [loading, setLoading] = useState(true);
|
||
const [generating, setGenerating] = useState(false);
|
||
const [showRegenWarning, setShowRegenWarning] = useState(false);
|
||
|
||
useEffect(() => {
|
||
api.getSettings().then(({ settings }) => {
|
||
setVapidPublic(settings.vapid_public || '');
|
||
setLoading(false);
|
||
}).catch(() => setLoading(false));
|
||
}, []);
|
||
|
||
const doGenerate = async () => {
|
||
setGenerating(true);
|
||
setShowRegenWarning(false);
|
||
try {
|
||
const { publicKey } = await api.generateVapidKeys();
|
||
setVapidPublic(publicKey);
|
||
toast('VAPID keys generated. Push notifications are now active.', 'success');
|
||
} catch (e) {
|
||
toast(e.message || 'Failed to generate keys', 'error');
|
||
} finally {
|
||
setGenerating(false);
|
||
}
|
||
};
|
||
|
||
const handleGenerateClick = () => {
|
||
if (vapidPublic) {
|
||
setShowRegenWarning(true);
|
||
} else {
|
||
doGenerate();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="modal-overlay" onClick={e => e.target === e.currentTarget && onClose()}>
|
||
<div className="modal" style={{ maxWidth: 480 }}>
|
||
|
||
<div className="flex items-center justify-between" style={{ marginBottom: 20 }}>
|
||
<h2 className="modal-title" style={{ margin: 0 }}>Settings</h2>
|
||
<button className="btn-icon" onClick={onClose}>
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="settings-section-label">Web Push Notifications (VAPID)</div>
|
||
|
||
{loading ? (
|
||
<p style={{ fontSize: 13, color: "var(--text-secondary)" }}>Loading…</p>
|
||
) : (
|
||
<>
|
||
{vapidPublic ? (
|
||
<div style={{ marginBottom: 16 }}>
|
||
<div style={{
|
||
background: "var(--surface-variant)",
|
||
border: "1px solid var(--border)",
|
||
borderRadius: "var(--radius)",
|
||
padding: "10px 12px",
|
||
marginBottom: 10,
|
||
}}>
|
||
<div style={{ fontSize: 11, color: "var(--text-tertiary)", marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.5px" }}>
|
||
Public Key
|
||
</div>
|
||
<code style={{
|
||
fontSize: 11,
|
||
color: "var(--text-primary)",
|
||
wordBreak: "break-all",
|
||
lineHeight: 1.5,
|
||
display: "block",
|
||
}}>
|
||
{vapidPublic}
|
||
</code>
|
||
</div>
|
||
<span style={{ fontSize: 13, color: "var(--success)", display: "flex", alignItems: "center", gap: 5 }}>
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
||
Push notifications active
|
||
</span>
|
||
</div>
|
||
) : (
|
||
<p style={{ fontSize: 13, color: "var(--text-secondary)", marginBottom: 12 }}>
|
||
No VAPID keys found. Generate keys to enable Web Push notifications — users can then receive alerts when the app is closed or in the background.
|
||
</p>
|
||
)}
|
||
|
||
{showRegenWarning && (
|
||
<div style={{
|
||
background: "#fce8e6",
|
||
border: "1px solid #f5c6c2",
|
||
borderRadius: "var(--radius)",
|
||
padding: "14px 16px",
|
||
marginBottom: 16,
|
||
}}>
|
||
<p style={{ fontSize: 13, fontWeight: 600, color: "var(--error)", marginBottom: 8 }}>
|
||
⚠️ Regenerate VAPID keys?
|
||
</p>
|
||
<p style={{ fontSize: 13, color: "#5c2c28", marginBottom: 12, lineHeight: 1.5 }}>
|
||
Generating new keys will <strong>invalidate all existing push subscriptions</strong>. Every user will stop receiving push notifications immediately and will need to re-enable them by opening the app. This cannot be undone.
|
||
</p>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<button
|
||
className="btn btn-sm"
|
||
style={{ background: "var(--error)", color: "white" }}
|
||
onClick={doGenerate}
|
||
disabled={generating}
|
||
>
|
||
{generating ? "Generating…" : "Yes, regenerate keys"}
|
||
</button>
|
||
<button className="btn btn-secondary btn-sm" onClick={() => setShowRegenWarning(false)}>
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{!showRegenWarning && (
|
||
<button className="btn btn-primary btn-sm" onClick={handleGenerateClick} disabled={generating}>
|
||
{generating ? "Generating…" : vapidPublic ? "Regenerate Keys" : "Generate Keys"}
|
||
</button>
|
||
)}
|
||
|
||
<p style={{ fontSize: 12, color: "var(--text-tertiary)", marginTop: 12, lineHeight: 1.5 }}>
|
||
Requires HTTPS. After generating, users will be prompted to enable notifications on their next visit. On iOS, the app must be installed to the home screen first.
|
||
</p>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|