Files
rosterchirp/frontend/src/components/SettingsModal.jsx

138 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}