Files
ui-tracker/frontend/src/components/SettingsModal.tsx
2026-03-27 23:33:31 -05:00

151 lines
5.0 KiB
TypeScript

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>
);
}