Add Telegram notification integration for geofencing

Features:
- Multi-channel notifications (Email + Telegram)
- User-configurable notification settings per channel
- Telegram bot integration with rich messages, location pins, and inline buttons
- QR code generation for easy bot access (@myidbot support)
- Admin UI for notification settings management
- Test functionality for Telegram connection
- Comprehensive documentation

Implementation:
- lib/telegram-service.ts: Telegram API integration
- lib/notification-settings-db.ts: Database layer for user notification preferences
- lib/geofence-notifications.ts: Extended for parallel multi-channel delivery
- API routes for settings management and testing
- Admin UI with QR code display and step-by-step instructions
- Database table: UserNotificationSettings

Documentation:
- docs/telegram.md: Technical implementation guide
- docs/telegram-anleitung.md: User guide with @myidbot instructions
- docs/telegram-setup.md: Admin setup guide
- README.md: Updated NPM scripts section

Docker:
- Updated Dockerfile to copy public directory
- Added TELEGRAM_BOT_TOKEN environment variable
- Integrated notification settings initialization in db:init

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 14:54:19 +00:00
parent 17aaf130a8
commit 0d1dbeafda
18 changed files with 3200 additions and 21 deletions

View File

@@ -0,0 +1,208 @@
'use client';
import { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
export default function NotificationSettingsPage() {
const { data: session } = useSession();
const [settings, setSettings] = useState({
email_enabled: true,
telegram_enabled: false,
telegram_chat_id: '',
});
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState('');
useEffect(() => {
if (session?.user) {
loadSettings();
}
}, [session]);
async function loadSettings() {
try {
const userId = (session!.user as any).id;
const res = await fetch(`/api/users/${userId}/notification-settings`);
const data = await res.json();
setSettings(data.settings);
} catch (error) {
console.error('Failed to load settings:', error);
}
}
async function handleSave() {
setLoading(true);
setMessage('');
try {
const userId = (session!.user as any).id;
const res = await fetch(`/api/users/${userId}/notification-settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings),
});
if (res.ok) {
setMessage('✅ Einstellungen gespeichert!');
} else {
setMessage('❌ Fehler beim Speichern');
}
} catch (error) {
setMessage('❌ Fehler beim Speichern');
} finally {
setLoading(false);
}
}
async function handleTest() {
setLoading(true);
setMessage('');
try {
const userId = (session!.user as any).id;
const res = await fetch(
`/api/users/${userId}/notification-settings/test`,
{ method: 'POST' }
);
if (res.ok) {
setMessage('✅ Test-Nachricht gesendet!');
} else {
const data = await res.json();
setMessage(`❌ Fehler: ${data.error}`);
}
} catch (error) {
setMessage('❌ Fehler beim Senden der Test-Nachricht');
} finally {
setLoading(false);
}
}
return (
<div className="max-w-2xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Benachrichtigungseinstellungen</h1>
<div className="space-y-6 bg-white p-6 rounded-lg shadow">
{/* Email Settings */}
<div>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.email_enabled}
onChange={(e) =>
setSettings({ ...settings, email_enabled: e.target.checked })
}
className="w-4 h-4"
/>
<span className="font-medium">📧 E-Mail Benachrichtigungen</span>
</label>
<p className="text-sm text-gray-600 ml-7 mt-1">
Geofence-Ereignisse per E-Mail erhalten
</p>
</div>
{/* Telegram Settings */}
<div>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.telegram_enabled}
onChange={(e) =>
setSettings({ ...settings, telegram_enabled: e.target.checked })
}
className="w-4 h-4"
/>
<span className="font-medium">📱 Telegram Benachrichtigungen</span>
</label>
<p className="text-sm text-gray-600 ml-7 mt-1">
Geofence-Ereignisse per Telegram erhalten (inkl. Karte und Buttons)
</p>
{settings.telegram_enabled && (
<div className="ml-7 mt-3">
<label className="block text-sm font-medium mb-1">
Telegram Chat ID
</label>
<input
type="text"
value={settings.telegram_chat_id}
onChange={(e) =>
setSettings({ ...settings, telegram_chat_id: e.target.value })
}
placeholder="z.B. 123456789"
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<p className="text-xs text-gray-500 mt-1">
Deine Telegram Chat ID findest du über @userinfobot
</p>
</div>
)}
</div>
{/* Action Buttons */}
<div className="flex gap-3 pt-4 border-t">
<button
onClick={handleSave}
disabled={loading}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Speichern...' : 'Speichern'}
</button>
{settings.telegram_enabled && settings.telegram_chat_id && (
<button
onClick={handleTest}
disabled={loading}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50"
>
Telegram Test
</button>
)}
</div>
{/* Status Message */}
{message && (
<div className="p-3 bg-gray-100 rounded-md text-sm">{message}</div>
)}
</div>
{/* Help Section */}
<div className="mt-6 p-4 bg-blue-50 rounded-lg">
<h3 className="font-medium mb-3">📱 Telegram Bot aktivieren</h3>
<div className="grid md:grid-cols-2 gap-4">
{/* QR Code */}
<div className="flex flex-col items-center justify-center bg-white p-4 rounded-lg">
<p className="text-sm font-medium mb-2">Schritt 1: Bot starten</p>
<img
src="/telegram-bot-qr.png"
alt="Telegram Bot QR Code"
className="w-48 h-48 mb-2"
/>
<p className="text-xs text-gray-500 text-center">
Scanne den QR-Code mit deinem Smartphone
</p>
<p className="text-xs text-gray-500 mt-1">
oder öffne: <a href="https://t.me/MeinTracking_bot" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">@MeinTracking_bot</a>
</p>
</div>
{/* Instructions */}
<div className="flex flex-col justify-center">
<p className="text-sm font-medium mb-2">Schritt 2: Chat ID holen</p>
<ol className="text-sm space-y-2 list-decimal list-inside text-gray-700">
<li>Starte den Bot mit <code className="bg-gray-200 px-1 rounded">/start</code></li>
<li>Suche in Telegram nach <code className="bg-gray-200 px-1 rounded">@myidbot</code></li>
<li>Sende <code className="bg-gray-200 px-1 rounded">/getid</code> an @myidbot</li>
<li>Der Bot antwortet mit deiner Chat ID</li>
<li>Kopiere die Nummer und trage sie oben ein</li>
</ol>
<p className="text-xs text-gray-500 mt-2">
Alternative: <code className="bg-gray-200 px-1 rounded">@userinfobot</code> mit <code className="bg-gray-200 px-1 rounded">/start</code>
</p>
</div>
</div>
</div>
</div>
);
}