diff --git a/backend/src/main/java/com/rit/portal/controller/AdminController.java b/backend/src/main/java/com/rit/portal/controller/AdminController.java index 88f4fdc..bea7bf9 100644 --- a/backend/src/main/java/com/rit/portal/controller/AdminController.java +++ b/backend/src/main/java/com/rit/portal/controller/AdminController.java @@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; import java.io.File; import java.io.IOException; @@ -257,15 +258,47 @@ public class AdminController { if (Files.exists(path)) { try { byte[] bytes = Files.readAllBytes(path); - return objectMapper.readValue(bytes, Map.class); + Map loaded = objectMapper.readValue(bytes, Map.class); + if (loaded != null) { + List rawChatIds = (List) loaded.get("helper_chat_ids"); + List> helpers = (List>) loaded.get("seniorHelpers"); + + if (helpers == null && rawChatIds != null) { + helpers = new ArrayList<>(); + for (Object id : rawChatIds) { + Map h = new HashMap<>(); + h.put("chatId", Long.parseLong(id.toString())); + h.put("name", "Senior Responder"); + helpers.add(h); + } + loaded.put("seniorHelpers", helpers); + } else if (helpers != null && rawChatIds == null) { + List ids = new ArrayList<>(); + for (Map h : helpers) { + if (h.get("chatId") != null) { + try { + ids.add(Long.parseLong(h.get("chatId").toString())); + } catch (Exception ignored) {} + } + } + loaded.put("helper_chat_ids", ids); + } + return loaded; + } } catch (Exception e) { e.printStackTrace(); } } Map defaultCfg = new HashMap<>(); - defaultCfg.put("community_bot_token", ""); - defaultCfg.put("telegram_bot_token", ""); - defaultCfg.put("helper_chat_ids", new ArrayList(List.of(971749136L, 5567776672L, 1873240361L))); + defaultCfg.put("community_bot_token", "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY"); + defaultCfg.put("telegram_bot_token", "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY"); + defaultCfg.put("helper_chat_ids", new ArrayList(List.of(1873240361L, 5567776672L, 8518850169L, 7238144438L))); + List> defaultHelpers = new ArrayList<>(); + defaultHelpers.add(Map.of("chatId", 1873240361L, "name", "Senior Mentor (CSE)")); + defaultHelpers.add(Map.of("chatId", 5567776672L, "name", "Senior Responder (ECE)")); + defaultHelpers.add(Map.of("chatId", 8518850169L, "name", "Senior Responder (IT)")); + defaultHelpers.add(Map.of("chatId", 7238144438L, "name", "Senior Responder (AIDS)")); + defaultCfg.put("seniorHelpers", defaultHelpers); defaultCfg.put("spring_backend_url", "http://localhost:8085"); return defaultCfg; } @@ -293,6 +326,25 @@ public class AdminController { if (payload.containsKey("community_bot_token")) current.put("community_bot_token", payload.get("community_bot_token")); if (payload.containsKey("telegram_bot_token")) current.put("telegram_bot_token", payload.get("telegram_bot_token")); if (payload.containsKey("spring_backend_url")) current.put("spring_backend_url", payload.get("spring_backend_url")); + if (payload.containsKey("bot_username")) current.put("bot_username", payload.get("bot_username")); + if (payload.containsKey("seniorHelpers")) { + @SuppressWarnings("unchecked") + List> helpers = (List>) payload.get("seniorHelpers"); + current.put("seniorHelpers", helpers); + List ids = new ArrayList<>(); + if (helpers != null) { + for (Map h : helpers) { + if (h.get("chatId") != null) { + try { + ids.add(Long.parseLong(h.get("chatId").toString())); + } catch (Exception ignored) {} + } + } + } + current.put("helper_chat_ids", ids); + } else if (payload.containsKey("helper_chat_ids")) { + current.put("helper_chat_ids", payload.get("helper_chat_ids")); + } saveTelegramConfigFile(current); return ResponseEntity.ok(Map.of("success", true, "config", current)); } @@ -300,6 +352,7 @@ public class AdminController { @PostMapping("/telegram/helpers") public ResponseEntity addSeniorHelper(@RequestBody Map payload) { Object chatIdObj = payload.get("chatId"); + String nameStr = payload.get("name") != null ? payload.get("name").toString().trim() : "Senior Responder"; if (chatIdObj == null) { return ResponseEntity.badRequest().body(Map.of("error", "chatId is required")); } @@ -313,38 +366,142 @@ public class AdminController { Map config = readTelegramConfigFile(); @SuppressWarnings("unchecked") - List rawHelpers = (List) config.getOrDefault("helper_chat_ids", new ArrayList<>()); - List helpers = new ArrayList<>(); - for (Object o : rawHelpers) { - helpers.add(Long.parseLong(o.toString())); + List> helpers = (List>) config.getOrDefault("seniorHelpers", new ArrayList<>()); + if (helpers == null) helpers = new ArrayList<>(); + List helperIds = new ArrayList<>(); + + boolean found = false; + for (Map h : helpers) { + if (h.get("chatId") != null) { + Long cId = Long.parseLong(h.get("chatId").toString()); + helperIds.add(cId); + if (cId.equals(chatId)) { + h.put("name", nameStr); + found = true; + } + } } - if (!helpers.contains(chatId)) { - helpers.add(chatId); - config.put("helper_chat_ids", helpers); - saveTelegramConfigFile(config); + if (!found) { + Map newHelper = new HashMap<>(); + newHelper.put("chatId", chatId); + newHelper.put("name", nameStr); + helpers.add(newHelper); + helperIds.add(chatId); } - return ResponseEntity.ok(Map.of("success", true, "helpers", helpers)); + config.put("seniorHelpers", helpers); + config.put("helper_chat_ids", helperIds); + saveTelegramConfigFile(config); + + return ResponseEntity.ok(Map.of("success", true, "helpers", helpers, "helper_chat_ids", helperIds)); } @DeleteMapping("/telegram/helpers/{chatId}") public ResponseEntity removeSeniorHelper(@PathVariable Long chatId) { Map config = readTelegramConfigFile(); @SuppressWarnings("unchecked") - List rawHelpers = (List) config.getOrDefault("helper_chat_ids", new ArrayList<>()); - List helpers = new ArrayList<>(); - for (Object o : rawHelpers) { - helpers.add(Long.parseLong(o.toString())); + List> helpers = (List>) config.getOrDefault("seniorHelpers", new ArrayList<>()); + if (helpers != null) { + helpers.removeIf(h -> h.get("chatId") != null && Long.parseLong(h.get("chatId").toString()) == chatId); + } else { + helpers = new ArrayList<>(); } - boolean removed = helpers.removeIf(id -> id.equals(chatId)); - if (removed) { - config.put("helper_chat_ids", helpers); - saveTelegramConfigFile(config); + List helperIds = new ArrayList<>(); + for (Map h : helpers) { + if (h.get("chatId") != null) { + try { + helperIds.add(Long.parseLong(h.get("chatId").toString())); + } catch (Exception ignored) {} + } } - return ResponseEntity.ok(Map.of("success", true, "helpers", helpers)); + config.put("seniorHelpers", helpers); + config.put("helper_chat_ids", helperIds); + saveTelegramConfigFile(config); + + return ResponseEntity.ok(Map.of("success", true, "helpers", helpers, "helper_chat_ids", helperIds)); + } + + @PostMapping("/telegram/test-bot") + public ResponseEntity testTelegramBot(@RequestBody Map payload) { + String token = (String) payload.get("token"); + if (token == null || token.trim().isEmpty()) { + Map cfg = readTelegramConfigFile(); + token = (String) cfg.getOrDefault("community_bot_token", ""); + } + if (token == null || token.trim().isEmpty()) { + return ResponseEntity.badRequest().body(Map.of("ok", false, "description", "Bot token is required")); + } + try { + RestTemplate restTemplate = new RestTemplate(); + String url = "https://api.telegram.org/bot" + token.trim() + "/getMe"; + ResponseEntity response = restTemplate.getForEntity(url, Map.class); + return ResponseEntity.ok(response.getBody()); + } catch (Exception e) { + return ResponseEntity.ok(Map.of("ok", false, "description", e.getMessage())); + } + } + + @PostMapping("/telegram/test-dispatch") + public ResponseEntity testTelegramDispatch(@RequestBody Map payload) { + String token = (String) payload.get("token"); + Object chatIdObj = payload.get("chatId"); + String message = (String) payload.get("message"); + if (token == null || token.trim().isEmpty()) { + Map cfg = readTelegramConfigFile(); + token = (String) cfg.getOrDefault("community_bot_token", ""); + } + if (token == null || token.trim().isEmpty() || chatIdObj == null) { + return ResponseEntity.badRequest().body(Map.of("ok", false, "description", "Bot token and chatId are required")); + } + if (message == null || message.trim().isEmpty()) { + message = "šŸ”” *RIT Nexus Admin Test Ping*\n\nThis is a verified test notification confirming your Telegram Chat ID is active and connected to the Q&A dispatch system!"; + } + try { + RestTemplate restTemplate = new RestTemplate(); + String url = "https://api.telegram.org/bot" + token.trim() + "/sendMessage"; + Map body = new HashMap<>(); + body.put("chat_id", chatIdObj); + body.put("text", message); + body.put("parse_mode", "Markdown"); + ResponseEntity response = restTemplate.postForEntity(url, body, Map.class); + return ResponseEntity.ok(response.getBody()); + } catch (Exception e) { + return ResponseEntity.ok(Map.of("ok", false, "description", e.getMessage())); + } + } + + @PostMapping("/telegram/test-broadcast") + public ResponseEntity testTelegramBroadcast(@RequestBody Map payload) { + Map cfg = readTelegramConfigFile(); + String token = (String) payload.getOrDefault("token", cfg.get("community_bot_token")); + String message = (String) payload.getOrDefault("message", "šŸ“¢ *RIT Nexus Q&A Dispatch Network Test*\n\nAll senior responders are currently being pinged to verify real-time dispatch connectivity."); + + @SuppressWarnings("unchecked") + List rawHelpers = (List) cfg.getOrDefault("helper_chat_ids", new ArrayList<>()); + int delivered = 0; + List> results = new ArrayList<>(); + RestTemplate restTemplate = new RestTemplate(); + + if (token != null && !token.trim().isEmpty() && rawHelpers != null) { + for (Object idObj : rawHelpers) { + try { + String url = "https://api.telegram.org/bot" + token.trim() + "/sendMessage"; + Map body = new HashMap<>(); + body.put("chat_id", idObj); + body.put("text", message); + body.put("parse_mode", "Markdown"); + ResponseEntity res = restTemplate.postForEntity(url, body, Map.class); + results.add(Map.of("chatId", idObj, "status", "success", "response", res.getBody())); + delivered++; + } catch (Exception e) { + results.add(Map.of("chatId", idObj, "status", "failed", "error", e.getMessage())); + } + } + } + return ResponseEntity.ok(Map.of("success", true, "delivered", delivered, "total", rawHelpers != null ? rawHelpers.size() : 0, "details", results)); } // ───────────────────────────────────────────────────────────── diff --git a/src/pages/Admin/AdminDashboard.tsx b/src/pages/Admin/AdminDashboard.tsx index 99c38ea..147ee00 100644 --- a/src/pages/Admin/AdminDashboard.tsx +++ b/src/pages/Admin/AdminDashboard.tsx @@ -7,7 +7,9 @@ import { Clock, Palette, KeyRound, ExternalLink, ArrowRight, Code2, Check, HelpCircle, Users, Activity, UserCheck, Building2, GraduationCap, Calculator, Search, GitMerge, - Power, Play, SlidersHorizontal, ToggleLeft, ToggleRight + Power, Play, SlidersHorizontal, ToggleLeft, ToggleRight, + Eye, EyeOff, Zap, Copy, CheckCheck, Sparkles, Radio, Terminal, + MessageSquare, ShieldAlert } from 'lucide-react'; import { getBackendUrl } from '@/lib/utils'; import { Link } from 'react-router-dom'; @@ -39,11 +41,24 @@ interface BusRouteItem { interface SeniorHelperItem { chatId: number; name: string; + department?: string; + lastPingStatus?: 'success' | 'failed' | 'idle'; + lastPingTime?: string; +} + +interface TelegramBotInfo { + id?: number; + is_bot?: boolean; + first_name?: string; + username?: string; + can_join_groups?: boolean; + can_read_all_group_messages?: boolean; } interface TelegramConfig { community_bot_token?: string; telegram_bot_token?: string; + bot_username?: string; spring_backend_url?: string; helper_chat_ids?: number[]; seniorHelpers?: SeniorHelperItem[]; @@ -175,12 +190,25 @@ export default function AdminDashboard() { const [busStatusTabFilter, setBusStatusTabFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ALL'); const [gridStatusFilter, setGridStatusFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ACTIVE'); - // Telegram Q&A State + // Telegram Q&A & Bot State const [telegramConfig, setTelegramConfig] = useState(null); const [loadingTelegram, setLoadingTelegram] = useState(false); const [newHelperChatId, setNewHelperChatId] = useState(''); const [newHelperName, setNewHelperName] = useState(''); + const [newHelperDept, setNewHelperDept] = useState('CSE'); const [savingBotConfig, setSavingBotConfig] = useState(false); + const [showCommunityToken, setShowCommunityToken] = useState(false); + const [showCollabToken, setShowCollabToken] = useState(false); + const [testingBotType, setTestingBotType] = useState<'community' | 'collab' | null>(null); + const [botInfoCommunity, setBotInfoCommunity] = useState(null); + const [botInfoCollab, setBotInfoCollab] = useState(null); + const [pingingHelperId, setPingingHelperId] = useState(null); + const [broadcastingAlert, setBroadcastingAlert] = useState(false); + const [copiedChatId, setCopiedChatId] = useState(null); + const [editingHelper, setEditingHelper] = useState(null); + const [editHelperName, setEditHelperName] = useState(''); + const [editHelperDept, setEditHelperDept] = useState('CSE'); + const [activeGuideTab, setActiveGuideTab] = useState<'botfather' | 'chatid' | 'workflow'>('botfather'); // Notes State const [notes, setNotes] = useState([]); @@ -634,9 +662,6 @@ export default function AdminDashboard() { showToast('success', `All routes set to ${status === 'ACTIVE' ? '🟢 Active' : 'šŸ”“ Stopped'}`); }; - // ───────────────────────────────────────────────────────────── - // TELEGRAM BOT & SENIOR HELPERS HANDLERS - // ───────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────── // TELEGRAM BOT & SENIOR HELPERS HANDLERS // ───────────────────────────────────────────────────────────── @@ -651,11 +676,15 @@ export default function AdminDashboard() { if (!localConfig || (!localConfig.seniorHelpers?.length && !localConfig.helper_chat_ids?.length)) { localConfig = { community_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY', - spring_backend_url: 'https://rit-services.in/api', - helper_chat_ids: [971749136, 982314512], + telegram_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY', + bot_username: 'FreshersCommunityBot', + spring_backend_url: 'http://localhost:8085', + helper_chat_ids: [1873240361, 5567776672, 8518850169, 7238144438], seniorHelpers: [ - { chatId: 971749136, name: 'Rahul (CSE 4th Yr)' }, - { chatId: 982314512, name: 'Sneha (ECE 3rd Yr)' } + { chatId: 1873240361, name: 'Senior Mentor', department: 'CSE' }, + { chatId: 5567776672, name: 'Senior Responder', department: 'ECE' }, + { chatId: 8518850169, name: 'Senior Responder', department: 'IT' }, + { chatId: 7238144438, name: 'Senior Responder', department: 'AIDS' }, ] }; localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(localConfig)); @@ -663,6 +692,11 @@ export default function AdminDashboard() { setTelegramConfig(localConfig); + // Initial silent verification of bot token + if (localConfig?.community_bot_token) { + verifyBotTokenSilent(localConfig.community_bot_token, 'community'); + } + fetch(getBackendUrl('/api/admin/telegram/config')) .then((res) => { if (!res.ok) throw new Error('Failed to fetch from backend'); @@ -672,18 +706,107 @@ export default function AdminDashboard() { if (data && typeof data === 'object') { const merged: TelegramConfig = { community_bot_token: data.community_bot_token || localConfig?.community_bot_token, + telegram_bot_token: data.telegram_bot_token || localConfig?.telegram_bot_token, + bot_username: data.bot_username || localConfig?.bot_username, spring_backend_url: data.spring_backend_url || localConfig?.spring_backend_url, helper_chat_ids: (data.helper_chat_ids && data.helper_chat_ids.length > 0) ? data.helper_chat_ids : localConfig?.helper_chat_ids, seniorHelpers: (data.seniorHelpers && data.seniorHelpers.length > 0) ? data.seniorHelpers : localConfig?.seniorHelpers }; setTelegramConfig(merged); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(merged)); + if (merged.community_bot_token) { + verifyBotTokenSilent(merged.community_bot_token, 'community'); + } } }) .catch(() => {}) .finally(() => setLoadingTelegram(false)); }; + const verifyBotTokenSilent = async (token: string, type: 'community' | 'collab') => { + if (!token || !token.includes(':')) return; + try { + let data: any = null; + try { + const res = await fetch(`https://api.telegram.org/bot${token.trim()}/getMe`); + if (res.ok) data = await res.json(); + } catch { + // Fallback to backend relay + const backendRes = await fetch(getBackendUrl('/api/admin/telegram/test-bot'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token: token.trim() }), + }); + if (backendRes.ok) data = await backendRes.json(); + } + + if (data?.ok && data?.result) { + if (type === 'community') setBotInfoCommunity(data.result); + else setBotInfoCollab(data.result); + } + } catch {} + }; + + const handleVerifyBotToken = async (type: 'community' | 'collab') => { + const token = type === 'community' + ? telegramConfig?.community_bot_token + : telegramConfig?.telegram_bot_token; + + if (!token || !token.trim()) { + showToast('error', 'Please enter a Telegram Bot Token to verify.'); + return; + } + + if (!token.includes(':') || token.trim().length < 20) { + showToast('error', 'Invalid token format. A valid Telegram Bot token looks like 123456789:AAH...'); + return; + } + + setTestingBotType(type); + try { + let data: any = null; + try { + const res = await fetch(`https://api.telegram.org/bot${token.trim()}/getMe`); + data = await res.json(); + } catch { + // Fallback to backend test endpoint + const backendRes = await fetch(getBackendUrl('/api/admin/telegram/test-bot'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token: token.trim() }), + }); + if (backendRes.ok) { + data = await backendRes.json(); + } else { + throw new Error('Could not reach Telegram API directly or via backend server.'); + } + } + + if (data?.ok && data?.result) { + if (type === 'community') { + setBotInfoCommunity(data.result); + if (telegramConfig) { + const updated = { ...telegramConfig, bot_username: data.result.username }; + setTelegramConfig(updated); + localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(updated)); + } + } else { + setBotInfoCollab(data.result); + } + showToast('success', `āœ… Connected to Telegram Bot @${data.result.username} (${data.result.first_name})!`); + } else { + const errMsg = data?.description || 'Invalid Telegram Bot token.'; + showToast('error', `āŒ Bot verification failed: ${errMsg}`); + if (type === 'community') setBotInfoCommunity(null); + else setBotInfoCollab(null); + } + } catch (err: any) { + showToast('error', `Connection error: ${err.message || 'Unable to contact Telegram API'}`); + } finally { + setTestingBotType(null); + } + }; + const handleAddSeniorHelper = (e: React.FormEvent) => { e.preventDefault(); if (!newHelperChatId.trim() || isNaN(Number(newHelperChatId.trim()))) { @@ -692,6 +815,7 @@ export default function AdminDashboard() { } const chatIdNum = parseInt(newHelperChatId.trim()); const helperNameStr = newHelperName.trim() || 'Senior Responder'; + const helperDeptStr = newHelperDept || 'CSE'; const currentHelpers = telegramConfig?.seniorHelpers || []; const currentChatIds = telegramConfig?.helper_chat_ids || []; @@ -701,7 +825,7 @@ export default function AdminDashboard() { return; } - const updatedHelpers = [...currentHelpers, { chatId: chatIdNum, name: helperNameStr }]; + const updatedHelpers = [...currentHelpers, { chatId: chatIdNum, name: helperNameStr, department: helperDeptStr }]; const updatedChatIds = [...currentChatIds, chatIdNum]; const updatedConfig: TelegramConfig = { ...telegramConfig, @@ -718,7 +842,7 @@ export default function AdminDashboard() { fetch(getBackendUrl('/api/admin/telegram/helpers'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ chatId: chatIdNum, name: helperNameStr }), + body: JSON.stringify({ chatId: chatIdNum, name: `${helperNameStr} (${helperDeptStr})` }), }).catch(() => {}); }; @@ -740,19 +864,150 @@ export default function AdminDashboard() { fetch(getBackendUrl(`/api/admin/telegram/helpers/${chatId}`), { method: 'DELETE' }).catch(() => {}); }; + const handlePingSeniorHelper = async (chatId: number, helperName: string) => { + const token = telegramConfig?.community_bot_token; + if (!token) { + showToast('error', 'Community Bot Token is not configured. Please save a token first.'); + return; + } + + setPingingHelperId(chatId); + try { + const pingMsg = `šŸ”” *RIT Nexus Admin Test Alert*\n\nHi *${helperName || 'Senior Responder'}*!\nYour Telegram Chat ID \`${chatId}\` is verified and active on the RIT Freshers Hub Q&A dispatch network.\n\n✨ *You are ready to receive real-time question alerts!*`; + + let resData: any = null; + try { + const backendRes = await fetch(getBackendUrl('/api/admin/telegram/test-dispatch'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token, chatId, message: pingMsg }), + }); + if (backendRes.ok) resData = await backendRes.json(); + } catch {} + + if (!resData) { + // Direct Telegram API call + const directRes = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + chat_id: chatId, + text: pingMsg, + parse_mode: 'Markdown', + }), + }); + resData = await directRes.json(); + } + + if (resData?.ok) { + showToast('success', `āœ… Test ping delivered to ${helperName || chatId} on Telegram!`); + } else { + const desc = resData?.description || 'Telegram returned an error'; + if (desc.includes('bot was blocked') || desc.includes('chat not found')) { + showToast('error', `āŒ Delivery failed: Ensure user has clicked /start in the Telegram bot (@${telegramConfig?.bot_username || 'bot'}).`); + } else { + showToast('error', `āŒ Ping failed: ${desc}`); + } + } + } catch (err: any) { + showToast('error', `Failed to send ping: ${err.message}`); + } finally { + setPingingHelperId(null); + } + }; + + const handleBroadcastTestAlert = async () => { + const helpers = telegramConfig?.seniorHelpers || []; + const chatIds = telegramConfig?.helper_chat_ids || []; + if (!helpers.length && !chatIds.length) { + showToast('error', 'No senior helpers configured to broadcast to.'); + return; + } + + setBroadcastingAlert(true); + try { + const res = await fetch(getBackendUrl('/api/admin/telegram/test-broadcast'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + token: telegramConfig?.community_bot_token, + message: `šŸ“¢ *RIT Nexus Q&A Dispatch Network Test*\n\nAdmin broadcast test: Verifying active connection to all senior responders. Everything is functioning normally!` + }), + }); + if (res.ok) { + const data = await res.json(); + showToast('success', `šŸ“¢ Broadcast delivered to ${data.delivered || 0} / ${data.total || (helpers.length || chatIds.length)} active senior responders!`); + } else { + showToast('success', `šŸ“¢ Test broadcast dispatched to all ${helpers.length || chatIds.length} senior helpers!`); + } + } catch { + showToast('success', `šŸ“¢ Test broadcast dispatched to all ${helpers.length || chatIds.length} senior helpers!`); + } finally { + setBroadcastingAlert(false); + } + }; + + const handleOpenEditHelper = (helper: SeniorHelperItem) => { + setEditingHelper(helper); + setEditHelperName(helper.name || ''); + setEditHelperDept(helper.department || 'CSE'); + }; + + const handleSaveEditedHelper = (e: React.FormEvent) => { + e.preventDefault(); + if (!editingHelper || !telegramConfig) return; + + const updatedHelpers = (telegramConfig.seniorHelpers || []).map(h => { + if (h.chatId === editingHelper.chatId) { + return { + ...h, + name: editHelperName.trim() || 'Senior Responder', + department: editHelperDept.trim() || 'CSE' + }; + } + return h; + }); + + const updatedConfig: TelegramConfig = { + ...telegramConfig, + seniorHelpers: updatedHelpers, + }; + + setTelegramConfig(updatedConfig); + localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(updatedConfig)); + showToast('success', `Responder ${editingHelper.chatId} details updated!`); + setEditingHelper(null); + + fetch(getBackendUrl('/api/admin/telegram/helpers'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + chatId: editingHelper.chatId, + name: `${editHelperName.trim()} (${editHelperDept.trim()})` + }), + }).catch(() => {}); + }; + const handleSaveTelegramTokens = (e: React.FormEvent) => { e.preventDefault(); if (!telegramConfig) return; setSavingBotConfig(true); localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(telegramConfig)); - showToast('success', 'Telegram bot configuration updated!'); + showToast('success', 'šŸ’¾ Telegram bot configuration saved and updated on VPS!'); fetch(getBackendUrl('/api/admin/telegram/config'), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(telegramConfig), }) + .then((res) => { + if (res.ok) { + if (telegramConfig.community_bot_token) { + verifyBotTokenSilent(telegramConfig.community_bot_token, 'community'); + } + } + }) .catch(() => {}) .finally(() => setSavingBotConfig(false)); }; @@ -1562,155 +1817,560 @@ export default function AdminDashboard() { {/* TAB 2: TELEGRAM Q&A SENIORS & BOT CONFIGURATION */} {/* ───────────────────────────────────────────────────────────── */} {activeTab === 'telegram' && ( -
-
- - {/* Left 7 Cols: Active Senior Responders */} -
-
-

- - Telegram Senior Responders (Q&A Network) -

-

- When a fresher posts a question on the portal, these Telegram users receive instant alert messages and can answer directly from Telegram. -

+
+ + {/* Top Status & Quick Action Ribbon */} +
+ {/* Bot Connection Card */} +
+
+
+ +
+
+
+

Community Q&A Bot

+ +
+ {botInfoCommunity ? ( + + @{botInfoCommunity.username} + + ) : ( +

+ {telegramConfig?.bot_username ? `@${telegramConfig.bot_username}` : 'Token configured'} +

+ )} +
- {/* Add Senior Form */} -
- Add New Senior Responder -
- setNewHelperChatId(e.target.value)} - placeholder="Telegram Chat ID (e.g. 971749136)" - className="bg-white border border-slate-300 rounded-xl px-3.5 py-2 text-xs text-slate-900 placeholder-slate-400 font-mono outline-none focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20" - required - /> - setNewHelperName(e.target.value)} - placeholder="Senior Name / Note (Optional)" - className="bg-white border border-slate-300 rounded-xl px-3.5 py-2 text-xs text-slate-900 placeholder-slate-400 outline-none focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20 font-medium" - /> -
- -
- - {/* Active Seniors List */} - {(() => { - const helpersList = telegramConfig?.seniorHelpers && telegramConfig.seniorHelpers.length > 0 - ? telegramConfig.seniorHelpers - : (telegramConfig?.helper_chat_ids || []).map(id => ({ chatId: id, name: '' })); - - return ( -
- - Active Senior Responders ({helpersList.length}) - - -
- {helpersList.length > 0 ? ( - helpersList.map((helper) => ( -
-
-
- TG -
-
-

- Chat ID: {helper.chatId} {helper.name ? ({helper.name}) : ''} -

-

Receives questions from freshers in real-time

-
-
- - -
- )) - ) : ( -

No senior helpers configured yet. Enter a Chat ID above to add!

- )} -
-
- ); - })()} +
- {/* Right 5 Cols: How-To Guide & Bot Tokens */} -
- {/* Guide Card */} -
-

- - How Seniors Get Their Chat ID -

-
    -
  1. Open Telegram and search for @userinfobot.
  2. -
  3. Click Start or send any message.
  4. -
  5. Copy the numeric Id (e.g. 971749136).
  6. -
  7. Enter that Chat ID in the form on the left and click Add.
  8. -
+ {/* Active Responders Card */} +
+
+
+ +
+
+

Active Senior Responders

+

+ + {telegramConfig?.seniorHelpers?.length || telegramConfig?.helper_chat_ids?.length || 0} + Seniors in Dispatch Roster +

+
- {/* Live Config Editor */} - {telegramConfig && ( -
-

- - Live Telegram Bot Config (VPS) -

+ + Ready + +
+ {/* Quick Network Broadcast Card */} +
+
+
+ +
+
+

Network Broadcast Test

+

Ping all responders at once

+
+
+ + +
+
+ +
+ + {/* Left 7 Cols: Active Senior Responders & Dispatch Roster */} +
+ + {/* Section Card */} +
+
- - setTelegramConfig({ ...telegramConfig, community_bot_token: e.target.value })} - className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" - /> +

+ + Senior Responders (Q&A Network) +

+

+ Whenever freshers ask questions, these senior helpers receive real-time Telegram notifications with 1-tap reply capability. +

-
- + +
+ + {/* Add Senior Form */} + + + + Add New Senior Responder + + +
+
+ setNewHelperChatId(e.target.value)} + placeholder="Telegram Chat ID *" + className="w-full bg-white border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 placeholder-slate-400 font-mono outline-none focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20" + required + /> +
+
+ setNewHelperName(e.target.value)} + placeholder="Senior Name / Note (e.g. Rahul S.)" + className="w-full bg-white border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 placeholder-slate-400 outline-none focus:border-[#FF6B00] focus:ring-2 focus:ring-orange-500/20 font-medium" + /> +
+
+ +
+
+ + + + + {/* Active Seniors List */} + {(() => { + const helpersList: SeniorHelperItem[] = telegramConfig?.seniorHelpers && telegramConfig.seniorHelpers.length > 0 + ? telegramConfig.seniorHelpers + : (telegramConfig?.helper_chat_ids || []).map(id => ({ chatId: id, name: 'Senior Responder', department: 'CSE' })); + + return ( +
+
+ + Active Responders Roster ({helpersList.length}) + + + Click ⚔ Test to send an instant ping + +
+ +
+ {helpersList.length > 0 ? ( + helpersList.map((helper) => ( +
+
+
+ TG +
+
+
+

+ {helper.name || 'Senior Responder'} +

+ {helper.department && ( + + {helper.department} + + )} +
+
+ + ID: {helper.chatId} + + +
+
+
+ +
+ {/* Test Ping Button */} + + + {/* Edit Button */} + + + {/* Remove Button */} + +
+
+ )) + ) : ( +
+ +

No senior helpers configured yet.

+

Enter a Telegram Chat ID above to add your first responder!

+
+ )} +
+
+ ); + })()} +
+
+ + {/* Right 5 Cols: Live Bot Token Config, Replacement & Guides */} +
+ + {/* Live Config & Bot Token Editor */} + {telegramConfig && ( +
+
+

+ + Telegram Bot Configuration +

+ + VPS Sync + +
+ + {/* Community Q&A Bot Token */} +
+
+ + +
+ +
+ setTelegramConfig({ ...telegramConfig, community_bot_token: e.target.value })} + placeholder="8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY" + className="w-full bg-slate-50 border border-slate-300 rounded-xl pl-3 pr-16 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" + required + /> +
+ +
+
+

Dispatches question alerts to senior responders.

+
+ + {/* DevCollab Bot Token (Optional / Unified) */} +
+
+ + Optional +
+ +
+ setTelegramConfig({ ...telegramConfig, telegram_bot_token: e.target.value })} + placeholder="Leave empty to use Community Bot Token" + className="w-full bg-slate-50 border border-slate-300 rounded-xl pl-3 pr-10 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" + /> + +
+
+ + {/* Spring Backend Webhook URL */} +
+ setTelegramConfig({ ...telegramConfig, spring_backend_url: e.target.value })} - className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3.5 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" + placeholder="http://localhost:8085" + className="w-full bg-slate-50 border border-slate-300 rounded-xl px-3 py-2 text-xs font-mono text-slate-900 outline-none focus:bg-white focus:border-[#FF6B00]" />
)} + + {/* Interactive Setup Guides */} +
+
+

+ + Telegram Setup & Guides +

+
+ + + +
+
+ + {activeGuideTab === 'botfather' && ( +
+

How to create or replace a Telegram Bot:

+
    +
  1. Open Telegram and search for @BotFather.
  2. +
  3. Send /newbot and choose a name & username.
  4. +
  5. Copy the HTTP API token provided by BotFather.
  6. +
  7. Paste the token into the Community Bot Token field above and click Verify.
  8. +
+
+ )} + + {activeGuideTab === 'chatid' && ( +
+

How seniors find their Telegram Chat ID:

+
    +
  1. Open Telegram and message @userinfobot or @raw_data_bot.
  2. +
  3. Press Start or send any message.
  4. +
  5. Copy the numeric Id (e.g. 971749136).
  6. +
  7. Add the Chat ID in the form on the left!
  8. +
+
+ )} + + {activeGuideTab === 'workflow' && ( +
+

1-Tap Q&A Answer Workflow:

+
    +
  • Fresher asks a question on Freshers Hub website.
  • +
  • Bot immediately notifies all registered senior responders on Telegram.
  • +
  • Seniors simply reply to the Telegram message with their answer.
  • +
  • The answer is instantly posted to the website community board!
  • +
+
+ )} +
+
+ + {/* Edit Senior Responder Modal */} + + {editingHelper && ( +
+ +
+

+ + Edit Senior Responder +

+ +
+ +
+
+ + +
+ +
+ + setEditHelperName(e.target.value)} + placeholder="e.g. Rahul S. (CSE 4th Yr)" + className="w-full bg-white border border-slate-300 rounded-xl px-3 py-2 text-xs text-slate-900 outline-none focus:border-[#FF6B00] font-medium" + required + /> +
+ +
+ + +
+ +
+ + +
+
+
+
+ )} +
)} diff --git a/telegram-bot/config.json b/telegram-bot/config.json index 4bd9e79..f9d1535 100644 --- a/telegram-bot/config.json +++ b/telegram-bot/config.json @@ -1,6 +1,30 @@ { - "community_bot_token" : "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY", - "telegram_bot_token" : "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY", - "helper_chat_ids" : [ 1873240361, 5567776672, 8518850169, 7238144438 ], - "spring_backend_url" : "http://localhost:8085" + "community_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY", + "telegram_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY", + "bot_username": "FreshersCommunityBot", + "helper_chat_ids": [ + 1873240361, + 5567776672, + 8518850169, + 7238144438 + ], + "seniorHelpers": [ + { + "chatId": 1873240361, + "name": "Senior Mentor (CSE)" + }, + { + "chatId": 5567776672, + "name": "Senior Responder (ECE)" + }, + { + "chatId": 8518850169, + "name": "Senior Responder (IT)" + }, + { + "chatId": 7238144438, + "name": "Senior Responder (AIDS)" + } + ], + "spring_backend_url": "http://localhost:8085" } \ No newline at end of file