build 1
This commit is contained in:
150
frontend/src/components/SettingsModal.tsx
Normal file
150
frontend/src/components/SettingsModal.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X, Send } from 'lucide-react';
|
||||
import type { Settings } from '../types';
|
||||
import { updateSettings, testTelegram } from '../api/client';
|
||||
|
||||
interface Props {
|
||||
settings: Settings | null;
|
||||
onClose: () => void;
|
||||
onSaved: (s: Settings) => void;
|
||||
}
|
||||
|
||||
export default function SettingsModal({ settings, onClose, onSaved }: Props) {
|
||||
const [token, setToken] = useState('');
|
||||
const [chatId, setChatId] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [testMsg, setTestMsg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (settings) {
|
||||
setToken(settings.telegram_bot_token ?? '');
|
||||
setChatId(settings.telegram_chat_id ?? '');
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setTestMsg('');
|
||||
setSaving(true);
|
||||
try {
|
||||
const updated = await updateSettings({
|
||||
telegram_bot_token: token.trim(),
|
||||
telegram_chat_id: chatId.trim(),
|
||||
});
|
||||
onSaved(updated);
|
||||
} catch (e: unknown) {
|
||||
setError(e instanceof Error ? e.message : 'Failed to save settings');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTest = async () => {
|
||||
setTestMsg('');
|
||||
setError('');
|
||||
setTesting(true);
|
||||
try {
|
||||
// Save first so the test uses current values
|
||||
await updateSettings({
|
||||
telegram_bot_token: token.trim(),
|
||||
telegram_chat_id: chatId.trim(),
|
||||
});
|
||||
const result = await testTelegram();
|
||||
if (result.success) {
|
||||
setTestMsg('✓ Test message sent! Check your Telegram.');
|
||||
} else {
|
||||
setError(result.error ?? 'Test failed — check your token and chat ID.');
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
setError(e instanceof Error ? e.message : 'Test failed');
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={e => e.target === e.currentTarget && onClose()}>
|
||||
<div className="modal" role="dialog" aria-modal="true" aria-labelledby="settings-title">
|
||||
<div className="modal-header">
|
||||
<div>
|
||||
<div className="modal-title" id="settings-title">Telegram Settings</div>
|
||||
<div className="modal-subtitle">Configure your alert notifications</div>
|
||||
</div>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="info-box">
|
||||
<strong>Getting your Chat ID</strong><br />
|
||||
Your Telegram <em>username</em> (@ALWISPER) can't be used directly — you need your numeric
|
||||
chat ID. The easiest way: message{' '}
|
||||
<strong>@userinfobot</strong> on Telegram and it will reply with your numeric ID.
|
||||
Then paste it below.
|
||||
</div>
|
||||
|
||||
{error && <div className="form-error">{error}</div>}
|
||||
{testMsg && <div className="success-box">{testMsg}</div>}
|
||||
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="tg-token">Bot Token</label>
|
||||
<input
|
||||
id="tg-token"
|
||||
className="form-input"
|
||||
type="text"
|
||||
placeholder="1234567890:AABBCCDDEEFFaabbccddeeff..."
|
||||
value={token}
|
||||
onChange={e => setToken(e.target.value)}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className="form-hint">
|
||||
From <strong>@BotFather</strong> → your bot → API Token.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="tg-chatid">Chat ID (numeric)</label>
|
||||
<input
|
||||
id="tg-chatid"
|
||||
className="form-input"
|
||||
type="text"
|
||||
placeholder="123456789"
|
||||
value={chatId}
|
||||
onChange={e => setChatId(e.target.value)}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className="form-hint">
|
||||
Message <strong>@userinfobot</strong> to get your numeric ID.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-secondary"
|
||||
disabled={testing || saving || !token || !chatId}
|
||||
onClick={handleTest}
|
||||
>
|
||||
{testing
|
||||
? <><div className="spinner" style={{ width: 13, height: 13 }} /> Sending…</>
|
||||
: <><Send size={13} /> Test Alert</>
|
||||
}
|
||||
</button>
|
||||
<button type="button" className="btn-secondary" onClick={onClose} disabled={saving}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn-primary" disabled={saving}>
|
||||
{saving ? <><div className="spinner" style={{ width: 14, height: 14 }} /> Saving…</> : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user