feat(admin): make telegram bot config and senior helper id management fully functional with live test ping and verification
This commit is contained in:
@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -257,15 +258,47 @@ public class AdminController {
|
|||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = Files.readAllBytes(path);
|
byte[] bytes = Files.readAllBytes(path);
|
||||||
return objectMapper.readValue(bytes, Map.class);
|
Map<String, Object> loaded = objectMapper.readValue(bytes, Map.class);
|
||||||
|
if (loaded != null) {
|
||||||
|
List<Object> rawChatIds = (List<Object>) loaded.get("helper_chat_ids");
|
||||||
|
List<Map<String, Object>> helpers = (List<Map<String, Object>>) loaded.get("seniorHelpers");
|
||||||
|
|
||||||
|
if (helpers == null && rawChatIds != null) {
|
||||||
|
helpers = new ArrayList<>();
|
||||||
|
for (Object id : rawChatIds) {
|
||||||
|
Map<String, Object> 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<Long> ids = new ArrayList<>();
|
||||||
|
for (Map<String, Object> 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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<String, Object> defaultCfg = new HashMap<>();
|
Map<String, Object> defaultCfg = new HashMap<>();
|
||||||
defaultCfg.put("community_bot_token", "");
|
defaultCfg.put("community_bot_token", "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY");
|
||||||
defaultCfg.put("telegram_bot_token", "");
|
defaultCfg.put("telegram_bot_token", "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY");
|
||||||
defaultCfg.put("helper_chat_ids", new ArrayList<Long>(List.of(971749136L, 5567776672L, 1873240361L)));
|
defaultCfg.put("helper_chat_ids", new ArrayList<Long>(List.of(1873240361L, 5567776672L, 8518850169L, 7238144438L)));
|
||||||
|
List<Map<String, Object>> 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");
|
defaultCfg.put("spring_backend_url", "http://localhost:8085");
|
||||||
return defaultCfg;
|
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("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("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("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<Map<String, Object>> helpers = (List<Map<String, Object>>) payload.get("seniorHelpers");
|
||||||
|
current.put("seniorHelpers", helpers);
|
||||||
|
List<Long> ids = new ArrayList<>();
|
||||||
|
if (helpers != null) {
|
||||||
|
for (Map<String, Object> 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);
|
saveTelegramConfigFile(current);
|
||||||
return ResponseEntity.ok(Map.of("success", true, "config", current));
|
return ResponseEntity.ok(Map.of("success", true, "config", current));
|
||||||
}
|
}
|
||||||
@@ -300,6 +352,7 @@ public class AdminController {
|
|||||||
@PostMapping("/telegram/helpers")
|
@PostMapping("/telegram/helpers")
|
||||||
public ResponseEntity<?> addSeniorHelper(@RequestBody Map<String, Object> payload) {
|
public ResponseEntity<?> addSeniorHelper(@RequestBody Map<String, Object> payload) {
|
||||||
Object chatIdObj = payload.get("chatId");
|
Object chatIdObj = payload.get("chatId");
|
||||||
|
String nameStr = payload.get("name") != null ? payload.get("name").toString().trim() : "Senior Responder";
|
||||||
if (chatIdObj == null) {
|
if (chatIdObj == null) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "chatId is required"));
|
return ResponseEntity.badRequest().body(Map.of("error", "chatId is required"));
|
||||||
}
|
}
|
||||||
@@ -313,38 +366,142 @@ public class AdminController {
|
|||||||
|
|
||||||
Map<String, Object> config = readTelegramConfigFile();
|
Map<String, Object> config = readTelegramConfigFile();
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Object> rawHelpers = (List<Object>) config.getOrDefault("helper_chat_ids", new ArrayList<>());
|
List<Map<String, Object>> helpers = (List<Map<String, Object>>) config.getOrDefault("seniorHelpers", new ArrayList<>());
|
||||||
List<Long> helpers = new ArrayList<>();
|
if (helpers == null) helpers = new ArrayList<>();
|
||||||
for (Object o : rawHelpers) {
|
List<Long> helperIds = new ArrayList<>();
|
||||||
helpers.add(Long.parseLong(o.toString()));
|
|
||||||
|
boolean found = false;
|
||||||
|
for (Map<String, Object> 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)) {
|
if (!found) {
|
||||||
helpers.add(chatId);
|
Map<String, Object> newHelper = new HashMap<>();
|
||||||
config.put("helper_chat_ids", helpers);
|
newHelper.put("chatId", chatId);
|
||||||
|
newHelper.put("name", nameStr);
|
||||||
|
helpers.add(newHelper);
|
||||||
|
helperIds.add(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.put("seniorHelpers", helpers);
|
||||||
|
config.put("helper_chat_ids", helperIds);
|
||||||
saveTelegramConfigFile(config);
|
saveTelegramConfigFile(config);
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers));
|
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers, "helper_chat_ids", helperIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/telegram/helpers/{chatId}")
|
@DeleteMapping("/telegram/helpers/{chatId}")
|
||||||
public ResponseEntity<?> removeSeniorHelper(@PathVariable Long chatId) {
|
public ResponseEntity<?> removeSeniorHelper(@PathVariable Long chatId) {
|
||||||
Map<String, Object> config = readTelegramConfigFile();
|
Map<String, Object> config = readTelegramConfigFile();
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Object> rawHelpers = (List<Object>) config.getOrDefault("helper_chat_ids", new ArrayList<>());
|
List<Map<String, Object>> helpers = (List<Map<String, Object>>) config.getOrDefault("seniorHelpers", new ArrayList<>());
|
||||||
List<Long> helpers = new ArrayList<>();
|
if (helpers != null) {
|
||||||
for (Object o : rawHelpers) {
|
helpers.removeIf(h -> h.get("chatId") != null && Long.parseLong(h.get("chatId").toString()) == chatId);
|
||||||
helpers.add(Long.parseLong(o.toString()));
|
} else {
|
||||||
|
helpers = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean removed = helpers.removeIf(id -> id.equals(chatId));
|
List<Long> helperIds = new ArrayList<>();
|
||||||
if (removed) {
|
for (Map<String, Object> h : helpers) {
|
||||||
config.put("helper_chat_ids", helpers);
|
if (h.get("chatId") != null) {
|
||||||
|
try {
|
||||||
|
helperIds.add(Long.parseLong(h.get("chatId").toString()));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.put("seniorHelpers", helpers);
|
||||||
|
config.put("helper_chat_ids", helperIds);
|
||||||
saveTelegramConfigFile(config);
|
saveTelegramConfigFile(config);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers, "helper_chat_ids", helperIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers));
|
@PostMapping("/telegram/test-bot")
|
||||||
|
public ResponseEntity<?> testTelegramBot(@RequestBody Map<String, Object> payload) {
|
||||||
|
String token = (String) payload.get("token");
|
||||||
|
if (token == null || token.trim().isEmpty()) {
|
||||||
|
Map<String, Object> 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<Map> 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<String, Object> 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<String, Object> 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<String, Object> body = new HashMap<>();
|
||||||
|
body.put("chat_id", chatIdObj);
|
||||||
|
body.put("text", message);
|
||||||
|
body.put("parse_mode", "Markdown");
|
||||||
|
ResponseEntity<Map> 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<String, Object> payload) {
|
||||||
|
Map<String, Object> 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<Object> rawHelpers = (List<Object>) cfg.getOrDefault("helper_chat_ids", new ArrayList<>());
|
||||||
|
int delivered = 0;
|
||||||
|
List<Map<String, Object>> 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<String, Object> body = new HashMap<>();
|
||||||
|
body.put("chat_id", idObj);
|
||||||
|
body.put("text", message);
|
||||||
|
body.put("parse_mode", "Markdown");
|
||||||
|
ResponseEntity<Map> 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
Clock, Palette, KeyRound, ExternalLink, ArrowRight,
|
Clock, Palette, KeyRound, ExternalLink, ArrowRight,
|
||||||
Code2, Check, HelpCircle, Users, Activity,
|
Code2, Check, HelpCircle, Users, Activity,
|
||||||
UserCheck, Building2, GraduationCap, Calculator, Search, GitMerge,
|
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';
|
} from 'lucide-react';
|
||||||
import { getBackendUrl } from '@/lib/utils';
|
import { getBackendUrl } from '@/lib/utils';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@@ -39,11 +41,24 @@ interface BusRouteItem {
|
|||||||
interface SeniorHelperItem {
|
interface SeniorHelperItem {
|
||||||
chatId: number;
|
chatId: number;
|
||||||
name: string;
|
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 {
|
interface TelegramConfig {
|
||||||
community_bot_token?: string;
|
community_bot_token?: string;
|
||||||
telegram_bot_token?: string;
|
telegram_bot_token?: string;
|
||||||
|
bot_username?: string;
|
||||||
spring_backend_url?: string;
|
spring_backend_url?: string;
|
||||||
helper_chat_ids?: number[];
|
helper_chat_ids?: number[];
|
||||||
seniorHelpers?: SeniorHelperItem[];
|
seniorHelpers?: SeniorHelperItem[];
|
||||||
@@ -175,12 +190,25 @@ export default function AdminDashboard() {
|
|||||||
const [busStatusTabFilter, setBusStatusTabFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ALL');
|
const [busStatusTabFilter, setBusStatusTabFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ALL');
|
||||||
const [gridStatusFilter, setGridStatusFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ACTIVE');
|
const [gridStatusFilter, setGridStatusFilter] = useState<'ALL' | 'ACTIVE' | 'STOPPED'>('ACTIVE');
|
||||||
|
|
||||||
// Telegram Q&A State
|
// Telegram Q&A & Bot State
|
||||||
const [telegramConfig, setTelegramConfig] = useState<TelegramConfig | null>(null);
|
const [telegramConfig, setTelegramConfig] = useState<TelegramConfig | null>(null);
|
||||||
const [loadingTelegram, setLoadingTelegram] = useState(false);
|
const [loadingTelegram, setLoadingTelegram] = useState(false);
|
||||||
const [newHelperChatId, setNewHelperChatId] = useState('');
|
const [newHelperChatId, setNewHelperChatId] = useState('');
|
||||||
const [newHelperName, setNewHelperName] = useState('');
|
const [newHelperName, setNewHelperName] = useState('');
|
||||||
|
const [newHelperDept, setNewHelperDept] = useState('CSE');
|
||||||
const [savingBotConfig, setSavingBotConfig] = useState(false);
|
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<TelegramBotInfo | null>(null);
|
||||||
|
const [botInfoCollab, setBotInfoCollab] = useState<TelegramBotInfo | null>(null);
|
||||||
|
const [pingingHelperId, setPingingHelperId] = useState<number | null>(null);
|
||||||
|
const [broadcastingAlert, setBroadcastingAlert] = useState(false);
|
||||||
|
const [copiedChatId, setCopiedChatId] = useState<number | null>(null);
|
||||||
|
const [editingHelper, setEditingHelper] = useState<SeniorHelperItem | null>(null);
|
||||||
|
const [editHelperName, setEditHelperName] = useState('');
|
||||||
|
const [editHelperDept, setEditHelperDept] = useState('CSE');
|
||||||
|
const [activeGuideTab, setActiveGuideTab] = useState<'botfather' | 'chatid' | 'workflow'>('botfather');
|
||||||
|
|
||||||
// Notes State
|
// Notes State
|
||||||
const [notes, setNotes] = useState<NoteItem[]>([]);
|
const [notes, setNotes] = useState<NoteItem[]>([]);
|
||||||
@@ -634,9 +662,6 @@ export default function AdminDashboard() {
|
|||||||
showToast('success', `All routes set to ${status === 'ACTIVE' ? '🟢 Active' : '🔴 Stopped'}`);
|
showToast('success', `All routes set to ${status === 'ACTIVE' ? '🟢 Active' : '🔴 Stopped'}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// TELEGRAM BOT & SENIOR HELPERS HANDLERS
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// 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)) {
|
if (!localConfig || (!localConfig.seniorHelpers?.length && !localConfig.helper_chat_ids?.length)) {
|
||||||
localConfig = {
|
localConfig = {
|
||||||
community_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY',
|
community_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY',
|
||||||
spring_backend_url: 'https://rit-services.in/api',
|
telegram_bot_token: '8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY',
|
||||||
helper_chat_ids: [971749136, 982314512],
|
bot_username: 'FreshersCommunityBot',
|
||||||
|
spring_backend_url: 'http://localhost:8085',
|
||||||
|
helper_chat_ids: [1873240361, 5567776672, 8518850169, 7238144438],
|
||||||
seniorHelpers: [
|
seniorHelpers: [
|
||||||
{ chatId: 971749136, name: 'Rahul (CSE 4th Yr)' },
|
{ chatId: 1873240361, name: 'Senior Mentor', department: 'CSE' },
|
||||||
{ chatId: 982314512, name: 'Sneha (ECE 3rd Yr)' }
|
{ 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));
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(localConfig));
|
||||||
@@ -663,6 +692,11 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
setTelegramConfig(localConfig);
|
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'))
|
fetch(getBackendUrl('/api/admin/telegram/config'))
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res.ok) throw new Error('Failed to fetch from backend');
|
if (!res.ok) throw new Error('Failed to fetch from backend');
|
||||||
@@ -672,18 +706,107 @@ export default function AdminDashboard() {
|
|||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
const merged: TelegramConfig = {
|
const merged: TelegramConfig = {
|
||||||
community_bot_token: data.community_bot_token || localConfig?.community_bot_token,
|
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,
|
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,
|
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
|
seniorHelpers: (data.seniorHelpers && data.seniorHelpers.length > 0) ? data.seniorHelpers : localConfig?.seniorHelpers
|
||||||
};
|
};
|
||||||
setTelegramConfig(merged);
|
setTelegramConfig(merged);
|
||||||
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(merged));
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(merged));
|
||||||
|
if (merged.community_bot_token) {
|
||||||
|
verifyBotTokenSilent(merged.community_bot_token, 'community');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => setLoadingTelegram(false));
|
.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) => {
|
const handleAddSeniorHelper = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!newHelperChatId.trim() || isNaN(Number(newHelperChatId.trim()))) {
|
if (!newHelperChatId.trim() || isNaN(Number(newHelperChatId.trim()))) {
|
||||||
@@ -692,6 +815,7 @@ export default function AdminDashboard() {
|
|||||||
}
|
}
|
||||||
const chatIdNum = parseInt(newHelperChatId.trim());
|
const chatIdNum = parseInt(newHelperChatId.trim());
|
||||||
const helperNameStr = newHelperName.trim() || 'Senior Responder';
|
const helperNameStr = newHelperName.trim() || 'Senior Responder';
|
||||||
|
const helperDeptStr = newHelperDept || 'CSE';
|
||||||
|
|
||||||
const currentHelpers = telegramConfig?.seniorHelpers || [];
|
const currentHelpers = telegramConfig?.seniorHelpers || [];
|
||||||
const currentChatIds = telegramConfig?.helper_chat_ids || [];
|
const currentChatIds = telegramConfig?.helper_chat_ids || [];
|
||||||
@@ -701,7 +825,7 @@ export default function AdminDashboard() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedHelpers = [...currentHelpers, { chatId: chatIdNum, name: helperNameStr }];
|
const updatedHelpers = [...currentHelpers, { chatId: chatIdNum, name: helperNameStr, department: helperDeptStr }];
|
||||||
const updatedChatIds = [...currentChatIds, chatIdNum];
|
const updatedChatIds = [...currentChatIds, chatIdNum];
|
||||||
const updatedConfig: TelegramConfig = {
|
const updatedConfig: TelegramConfig = {
|
||||||
...telegramConfig,
|
...telegramConfig,
|
||||||
@@ -718,7 +842,7 @@ export default function AdminDashboard() {
|
|||||||
fetch(getBackendUrl('/api/admin/telegram/helpers'), {
|
fetch(getBackendUrl('/api/admin/telegram/helpers'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ chatId: chatIdNum, name: helperNameStr }),
|
body: JSON.stringify({ chatId: chatIdNum, name: `${helperNameStr} (${helperDeptStr})` }),
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -740,18 +864,149 @@ export default function AdminDashboard() {
|
|||||||
fetch(getBackendUrl(`/api/admin/telegram/helpers/${chatId}`), { method: 'DELETE' }).catch(() => {});
|
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) => {
|
const handleSaveTelegramTokens = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!telegramConfig) return;
|
if (!telegramConfig) return;
|
||||||
setSavingBotConfig(true);
|
setSavingBotConfig(true);
|
||||||
|
|
||||||
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(telegramConfig));
|
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'), {
|
fetch(getBackendUrl('/api/admin/telegram/config'), {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(telegramConfig),
|
body: JSON.stringify(telegramConfig),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
if (telegramConfig.community_bot_token) {
|
||||||
|
verifyBotTokenSilent(telegramConfig.community_bot_token, 'community');
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => setSavingBotConfig(false));
|
.finally(() => setSavingBotConfig(false));
|
||||||
@@ -1562,155 +1817,560 @@ export default function AdminDashboard() {
|
|||||||
{/* TAB 2: TELEGRAM Q&A SENIORS & BOT CONFIGURATION */}
|
{/* TAB 2: TELEGRAM Q&A SENIORS & BOT CONFIGURATION */}
|
||||||
{/* ───────────────────────────────────────────────────────────── */}
|
{/* ───────────────────────────────────────────────────────────── */}
|
||||||
{activeTab === 'telegram' && (
|
{activeTab === 'telegram' && (
|
||||||
<div className="space-y-5">
|
<div className="space-y-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-5">
|
|
||||||
|
|
||||||
{/* Left 7 Cols: Active Senior Responders */}
|
{/* Top Status & Quick Action Ribbon */}
|
||||||
<div className="lg:col-span-7 bg-white border border-slate-200/80 rounded-3xl p-5 shadow-sm space-y-5">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{/* Bot Connection Card */}
|
||||||
|
<div className="bg-white border border-slate-200/80 rounded-2xl p-4 shadow-sm flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`w-10 h-10 rounded-xl flex items-center justify-center font-bold text-sm ${
|
||||||
|
botInfoCommunity ? 'bg-emerald-50 text-emerald-600 border border-emerald-200' : 'bg-orange-50 text-[#FF6B00] border border-orange-200'
|
||||||
|
}`}>
|
||||||
|
<Bot className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<h4 className="text-xs font-bold text-[#0F172A]">Community Q&A Bot</h4>
|
||||||
|
<span className={`w-2 h-2 rounded-full ${botInfoCommunity ? 'bg-emerald-500 animate-pulse' : 'bg-amber-400'}`} />
|
||||||
|
</div>
|
||||||
|
{botInfoCommunity ? (
|
||||||
|
<a
|
||||||
|
href={`https://t.me/${botInfoCommunity.username}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-[11px] font-semibold text-emerald-600 hover:underline flex items-center gap-1 mt-0.5"
|
||||||
|
>
|
||||||
|
@{botInfoCommunity.username} <ExternalLink className="w-2.5 h-2.5" />
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<p className="text-[11px] text-slate-500 font-medium mt-0.5">
|
||||||
|
{telegramConfig?.bot_username ? `@${telegramConfig.bot_username}` : 'Token configured'}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handleVerifyBotToken('community')}
|
||||||
|
disabled={testingBotType === 'community'}
|
||||||
|
className="px-2.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 text-[11px] font-bold transition-all cursor-pointer flex items-center gap-1"
|
||||||
|
title="Verify Bot Connection"
|
||||||
|
>
|
||||||
|
<RefreshCw className={`w-3 h-3 ${testingBotType === 'community' ? 'animate-spin' : ''}`} />
|
||||||
|
{testingBotType === 'community' ? 'Verifying...' : 'Test Bot'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active Responders Card */}
|
||||||
|
<div className="bg-white border border-slate-200/80 rounded-2xl p-4 shadow-sm flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-blue-50 text-blue-600 border border-blue-200 flex items-center justify-center font-bold text-sm">
|
||||||
|
<Users className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-bold text-[#0F172A]">Active Senior Responders</h4>
|
||||||
|
<p className="text-[11px] text-slate-500 font-medium mt-0.5">
|
||||||
|
<strong className="text-[#0F172A] font-bold">
|
||||||
|
{telegramConfig?.seniorHelpers?.length || telegramConfig?.helper_chat_ids?.length || 0}
|
||||||
|
</strong> Seniors in Dispatch Roster
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="px-2 py-1 rounded-lg bg-emerald-50 text-emerald-700 text-[10px] font-extrabold uppercase border border-emerald-200">
|
||||||
|
Ready
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Network Broadcast Card */}
|
||||||
|
<div className="bg-white border border-slate-200/80 rounded-2xl p-4 shadow-sm flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-orange-50 text-[#FF6B00] border border-orange-200 flex items-center justify-center font-bold text-sm">
|
||||||
|
<Radio className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-bold text-[#0F172A]">Network Broadcast Test</h4>
|
||||||
|
<p className="text-[11px] text-slate-500 font-medium mt-0.5">Ping all responders at once</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleBroadcastTestAlert}
|
||||||
|
disabled={broadcastingAlert}
|
||||||
|
className="px-3 py-1.5 rounded-xl bg-[#0F172A] hover:bg-slate-800 text-white text-[11px] font-bold transition-all cursor-pointer flex items-center gap-1.5 shadow-sm"
|
||||||
|
>
|
||||||
|
<Send className={`w-3 h-3 ${broadcastingAlert ? 'animate-bounce' : ''}`} />
|
||||||
|
{broadcastingAlert ? 'Broadcasting...' : 'Broadcast'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||||
|
|
||||||
|
{/* Left 7 Cols: Active Senior Responders & Dispatch Roster */}
|
||||||
|
<div className="lg:col-span-7 space-y-6">
|
||||||
|
|
||||||
|
{/* Section Card */}
|
||||||
|
<div className="bg-white border border-slate-200/80 rounded-3xl p-5 shadow-sm space-y-5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-bold text-[#0F172A] flex items-center gap-2">
|
<h3 className="text-base font-bold text-[#0F172A] flex items-center gap-2">
|
||||||
<Users className="w-4.5 h-4.5 text-[#FF6B00]" />
|
<Users className="w-4.5 h-4.5 text-[#FF6B00]" />
|
||||||
Telegram Senior Responders (Q&A Network)
|
Senior Responders (Q&A Network)
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-slate-500 font-medium mt-0.5">
|
<p className="text-xs text-slate-500 font-medium mt-0.5">
|
||||||
When a fresher posts a question on the portal, these Telegram users receive instant alert messages and can answer directly from Telegram.
|
Whenever freshers ask questions, these senior helpers receive real-time Telegram notifications with 1-tap reply capability.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={fetchTelegramConfig}
|
||||||
|
className="p-2 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 transition-colors cursor-pointer"
|
||||||
|
title="Refresh Telegram Helpers"
|
||||||
|
>
|
||||||
|
<RefreshCw className={`w-3.5 h-3.5 ${loadingTelegram ? 'animate-spin' : ''}`} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Add Senior Form */}
|
{/* Add Senior Form */}
|
||||||
<form onSubmit={handleAddSeniorHelper} className="bg-slate-50 p-4 rounded-2xl border border-slate-200/80 space-y-3">
|
<form onSubmit={handleAddSeniorHelper} className="bg-slate-50/80 p-4 rounded-2xl border border-slate-200/80 space-y-3">
|
||||||
<span className="text-[11px] font-bold uppercase tracking-wider text-[#0F172A] block">Add New Senior Responder</span>
|
<span className="text-[11px] font-bold uppercase tracking-wider text-[#0F172A] flex items-center gap-1.5">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
|
<Plus className="w-3.5 h-3.5 text-[#FF6B00]" />
|
||||||
|
Add New Senior Responder
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-12 gap-2.5">
|
||||||
|
<div className="sm:col-span-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newHelperChatId}
|
value={newHelperChatId}
|
||||||
onChange={(e) => setNewHelperChatId(e.target.value)}
|
onChange={(e) => setNewHelperChatId(e.target.value)}
|
||||||
placeholder="Telegram Chat ID (e.g. 971749136)"
|
placeholder="Telegram Chat ID *"
|
||||||
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"
|
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
|
required
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-5">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newHelperName}
|
value={newHelperName}
|
||||||
onChange={(e) => setNewHelperName(e.target.value)}
|
onChange={(e) => setNewHelperName(e.target.value)}
|
||||||
placeholder="Senior Name / Note (Optional)"
|
placeholder="Senior Name / Note (e.g. Rahul S.)"
|
||||||
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="sm:col-span-3">
|
||||||
|
<select
|
||||||
|
value={newHelperDept}
|
||||||
|
onChange={(e) => setNewHelperDept(e.target.value)}
|
||||||
|
className="w-full bg-white border border-slate-300 rounded-xl px-2.5 py-2 text-xs text-slate-900 outline-none focus:border-[#FF6B00] font-medium"
|
||||||
|
>
|
||||||
|
<option value="CSE">CSE</option>
|
||||||
|
<option value="AIDS">AIDS</option>
|
||||||
|
<option value="IT">IT</option>
|
||||||
|
<option value="ECE">ECE</option>
|
||||||
|
<option value="EEE">EEE</option>
|
||||||
|
<option value="MECH">MECH</option>
|
||||||
|
<option value="CIVIL">CIVIL</option>
|
||||||
|
<option value="ALL">All Depts</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full py-2.5 rounded-xl bg-[#FF6B00] hover:bg-[#EA580C] text-white font-bold text-xs shadow-sm shadow-orange-500/20 transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
className="w-full py-2.5 rounded-xl bg-[#FF6B00] hover:bg-[#EA580C] text-white font-bold text-xs shadow-sm shadow-orange-500/20 transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Plus className="w-3.5 h-3.5" />
|
<Plus className="w-3.5 h-3.5" />
|
||||||
Add Senior to Q&A Telegram Dispatch
|
Add Senior to Telegram Dispatch Network
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/* Active Seniors List */}
|
{/* Active Seniors List */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const helpersList = telegramConfig?.seniorHelpers && telegramConfig.seniorHelpers.length > 0
|
const helpersList: SeniorHelperItem[] = telegramConfig?.seniorHelpers && telegramConfig.seniorHelpers.length > 0
|
||||||
? telegramConfig.seniorHelpers
|
? telegramConfig.seniorHelpers
|
||||||
: (telegramConfig?.helper_chat_ids || []).map(id => ({ chatId: id, name: '' }));
|
: (telegramConfig?.helper_chat_ids || []).map(id => ({ chatId: id, name: 'Senior Responder', department: 'CSE' }));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2.5">
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-[11px] font-bold text-slate-700 uppercase tracking-wider block">
|
<span className="text-[11px] font-bold text-slate-700 uppercase tracking-wider block">
|
||||||
Active Senior Responders ({helpersList.length})
|
Active Responders Roster ({helpersList.length})
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-[11px] text-slate-400 font-medium">
|
||||||
|
Click <strong className="text-slate-600">⚡ Test</strong> to send an instant ping
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2.5">
|
||||||
{helpersList.length > 0 ? (
|
{helpersList.length > 0 ? (
|
||||||
helpersList.map((helper) => (
|
helpersList.map((helper) => (
|
||||||
<div
|
<div
|
||||||
key={helper.chatId}
|
key={helper.chatId}
|
||||||
className="bg-slate-50 border border-slate-200/80 rounded-2xl p-3.5 flex items-center justify-between gap-3"
|
className="bg-slate-50/70 border border-slate-200/80 rounded-2xl p-3.5 flex items-center justify-between gap-3 hover:border-slate-300 hover:bg-white transition-all shadow-xs"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<div className="w-8 h-8 rounded-xl bg-orange-50 text-[#FF6B00] border border-orange-200 flex items-center justify-center shrink-0 font-extrabold text-[11px]">
|
<div className="w-9 h-9 rounded-xl bg-orange-100/70 text-[#FF6B00] border border-orange-200 flex items-center justify-center shrink-0 font-extrabold text-xs">
|
||||||
TG
|
TG
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<p className="text-xs font-mono font-bold text-[#0F172A]">
|
<div className="flex items-center gap-2">
|
||||||
Chat ID: {helper.chatId} {helper.name ? <span className="text-[#FF6B00] font-sans font-bold ml-1">({helper.name})</span> : ''}
|
<p className="text-xs font-bold text-[#0F172A] truncate">
|
||||||
|
{helper.name || 'Senior Responder'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[11px] text-slate-500 font-medium">Receives questions from freshers in real-time</p>
|
{helper.department && (
|
||||||
|
<span className="px-1.5 py-0.5 rounded-md bg-slate-200/80 text-slate-700 text-[9px] font-extrabold uppercase shrink-0">
|
||||||
|
{helper.department}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mt-0.5">
|
||||||
|
<span className="text-[11px] font-mono text-slate-500 font-medium">
|
||||||
|
ID: {helper.chatId}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(helper.chatId.toString());
|
||||||
|
setCopiedChatId(helper.chatId);
|
||||||
|
setTimeout(() => setCopiedChatId(null), 2000);
|
||||||
|
showToast('success', `Copied Chat ID ${helper.chatId} to clipboard!`);
|
||||||
|
}}
|
||||||
|
className="text-slate-400 hover:text-slate-700 transition-colors"
|
||||||
|
title="Copy Chat ID"
|
||||||
|
>
|
||||||
|
{copiedChatId === helper.chatId ? (
|
||||||
|
<CheckCheck className="w-3 h-3 text-emerald-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1.5 shrink-0">
|
||||||
|
{/* Test Ping Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => handlePingSeniorHelper(helper.chatId, helper.name)}
|
||||||
|
disabled={pingingHelperId === helper.chatId}
|
||||||
|
className="px-2.5 py-1.5 rounded-xl bg-emerald-50 hover:bg-emerald-100 text-emerald-700 border border-emerald-200 text-xs font-bold transition-colors cursor-pointer flex items-center gap-1"
|
||||||
|
title="Send a live test message to this responder"
|
||||||
|
>
|
||||||
|
<Zap className={`w-3 h-3 text-emerald-600 ${pingingHelperId === helper.chatId ? 'animate-spin' : ''}`} />
|
||||||
|
{pingingHelperId === helper.chatId ? 'Pinging...' : 'Test'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Edit Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => handleOpenEditHelper(helper)}
|
||||||
|
className="p-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-600 transition-colors cursor-pointer"
|
||||||
|
title="Edit Responder Details"
|
||||||
|
>
|
||||||
|
<Edit className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Remove Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemoveSeniorHelper(helper.chatId)}
|
onClick={() => handleRemoveSeniorHelper(helper.chatId)}
|
||||||
className="p-1.5 rounded-xl bg-rose-50 hover:bg-rose-100 text-rose-600 transition-colors cursor-pointer"
|
className="p-1.5 rounded-xl bg-rose-50 hover:bg-rose-100 text-rose-600 transition-colors cursor-pointer"
|
||||||
title="Remove responder"
|
title="Remove Responder"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-slate-400 italic bg-slate-50 p-3.5 rounded-xl text-center">No senior helpers configured yet. Enter a Chat ID above to add!</p>
|
<div className="text-center py-8 bg-slate-50/50 rounded-2xl border border-dashed border-slate-300 space-y-2">
|
||||||
|
<Bot className="w-8 h-8 text-slate-300 mx-auto" />
|
||||||
|
<p className="text-xs text-slate-500 font-medium">No senior helpers configured yet.</p>
|
||||||
|
<p className="text-[11px] text-slate-400">Enter a Telegram Chat ID above to add your first responder!</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right 5 Cols: How-To Guide & Bot Tokens */}
|
|
||||||
<div className="lg:col-span-5 space-y-5">
|
|
||||||
{/* Guide Card */}
|
|
||||||
<div className="bg-white border border-slate-200/80 rounded-3xl p-5 shadow-sm space-y-3">
|
|
||||||
<h4 className="text-sm font-bold text-[#0F172A] flex items-center gap-2">
|
|
||||||
<HelpCircle className="w-4 h-4 text-[#FF6B00]" />
|
|
||||||
How Seniors Get Their Chat ID
|
|
||||||
</h4>
|
|
||||||
<ol className="space-y-2 text-xs text-slate-600 font-medium list-decimal list-inside leading-relaxed">
|
|
||||||
<li>Open Telegram and search for <strong>@userinfobot</strong>.</li>
|
|
||||||
<li>Click <strong>Start</strong> or send any message.</li>
|
|
||||||
<li>Copy the numeric <strong>Id</strong> (e.g. <code>971749136</code>).</li>
|
|
||||||
<li>Enter that Chat ID in the form on the left and click Add.</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Live Config Editor */}
|
{/* Right 5 Cols: Live Bot Token Config, Replacement & Guides */}
|
||||||
|
<div className="lg:col-span-5 space-y-6">
|
||||||
|
|
||||||
|
{/* Live Config & Bot Token Editor */}
|
||||||
{telegramConfig && (
|
{telegramConfig && (
|
||||||
<form onSubmit={handleSaveTelegramTokens} className="bg-white border border-slate-200/80 rounded-3xl p-5 shadow-sm space-y-3">
|
<form onSubmit={handleSaveTelegramTokens} className="bg-white border border-slate-200/80 rounded-3xl p-5 shadow-sm space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-sm font-bold text-[#0F172A] flex items-center gap-2">
|
<h4 className="text-sm font-bold text-[#0F172A] flex items-center gap-2">
|
||||||
<Bot className="w-4 h-4 text-[#FF6B00]" />
|
<Bot className="w-4 h-4 text-[#FF6B00]" />
|
||||||
Live Telegram Bot Config (VPS)
|
Telegram Bot Configuration
|
||||||
</h4>
|
</h4>
|
||||||
|
<span className="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-md bg-slate-100 text-slate-600 border border-slate-200">
|
||||||
<div>
|
VPS Sync
|
||||||
<label className="block text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-1">Community Bot Token</label>
|
</span>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={telegramConfig.community_bot_token || ''}
|
|
||||||
onChange={(e) => 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]"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{/* Community Q&A Bot Token */}
|
||||||
<label className="block text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-1">Backend Webhook URL</label>
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-[11px] font-bold text-slate-700 uppercase tracking-wider">
|
||||||
|
Community Q&A Bot Token *
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleVerifyBotToken('community')}
|
||||||
|
disabled={testingBotType === 'community'}
|
||||||
|
className="text-[10px] font-bold text-[#FF6B00] hover:underline flex items-center gap-1 cursor-pointer"
|
||||||
|
>
|
||||||
|
<Zap className="w-2.5 h-2.5" />
|
||||||
|
{testingBotType === 'community' ? 'Testing...' : 'Verify Token'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showCommunityToken ? 'text' : 'password'}
|
||||||
|
value={telegramConfig.community_bot_token || ''}
|
||||||
|
onChange={(e) => 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
|
||||||
|
/>
|
||||||
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowCommunityToken(!showCommunityToken)}
|
||||||
|
className="p-1 text-slate-400 hover:text-slate-600 rounded-lg"
|
||||||
|
title={showCommunityToken ? 'Hide Token' : 'Show Token'}
|
||||||
|
>
|
||||||
|
{showCommunityToken ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-slate-400 font-medium">Dispatches question alerts to senior responders.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* DevCollab Bot Token (Optional / Unified) */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-[11px] font-bold text-slate-700 uppercase tracking-wider">
|
||||||
|
DevCollab Bot Token
|
||||||
|
</label>
|
||||||
|
<span className="text-[10px] text-slate-400 font-medium">Optional</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showCollabToken ? 'text' : 'password'}
|
||||||
|
value={telegramConfig.telegram_bot_token || ''}
|
||||||
|
onChange={(e) => 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]"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowCollabToken(!showCollabToken)}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-slate-400 hover:text-slate-600 rounded-lg"
|
||||||
|
title={showCollabToken ? 'Hide Token' : 'Show Token'}
|
||||||
|
>
|
||||||
|
{showCollabToken ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Spring Backend Webhook URL */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="block text-[11px] font-bold text-slate-700 uppercase tracking-wider">
|
||||||
|
Spring Backend Dispatch URL
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={telegramConfig.spring_backend_url || ''}
|
value={telegramConfig.spring_backend_url || ''}
|
||||||
onChange={(e) => setTelegramConfig({ ...telegramConfig, spring_backend_url: e.target.value })}
|
onChange={(e) => 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]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={savingBotConfig}
|
disabled={savingBotConfig}
|
||||||
className="w-full py-2.5 rounded-xl bg-[#0F172A] hover:bg-slate-800 text-white font-bold text-xs shadow-sm transition-colors cursor-pointer"
|
className="w-full py-2.5 rounded-xl bg-[#0F172A] hover:bg-slate-800 text-white font-bold text-xs shadow-sm transition-colors cursor-pointer flex items-center justify-center gap-1.5"
|
||||||
>
|
>
|
||||||
{savingBotConfig ? 'Saving...' : '💾 Save Bot Configuration to VPS'}
|
<Check className="w-3.5 h-3.5 text-emerald-400" />
|
||||||
|
{savingBotConfig ? 'Saving & Deploying...' : 'Save & Deploy Configuration'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Interactive Setup Guides */}
|
||||||
|
<div className="bg-white border border-slate-200/80 rounded-3xl p-5 shadow-sm space-y-3.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h4 className="text-xs font-bold text-[#0F172A] flex items-center gap-1.5">
|
||||||
|
<HelpCircle className="w-4 h-4 text-[#FF6B00]" />
|
||||||
|
Telegram Setup & Guides
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-center gap-1 bg-slate-100 p-0.5 rounded-xl text-[10px] font-bold">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveGuideTab('botfather')}
|
||||||
|
className={`px-2 py-1 rounded-lg transition-all ${
|
||||||
|
activeGuideTab === 'botfather' ? 'bg-white text-[#FF6B00] shadow-xs' : 'text-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
BotFather
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveGuideTab('chatid')}
|
||||||
|
className={`px-2 py-1 rounded-lg transition-all ${
|
||||||
|
activeGuideTab === 'chatid' ? 'bg-white text-[#FF6B00] shadow-xs' : 'text-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Chat ID
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveGuideTab('workflow')}
|
||||||
|
className={`px-2 py-1 rounded-lg transition-all ${
|
||||||
|
activeGuideTab === 'workflow' ? 'bg-white text-[#FF6B00] shadow-xs' : 'text-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Workflow
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{activeGuideTab === 'botfather' && (
|
||||||
|
<div className="space-y-2 text-xs text-slate-600 leading-relaxed font-medium">
|
||||||
|
<p className="font-bold text-[#0F172A]">How to create or replace a Telegram Bot:</p>
|
||||||
|
<ol className="space-y-1.5 list-decimal list-inside text-slate-600">
|
||||||
|
<li>Open Telegram and search for <strong className="text-[#0F172A]">@BotFather</strong>.</li>
|
||||||
|
<li>Send <code className="bg-slate-100 text-orange-600 px-1 py-0.5 rounded font-mono text-[11px]">/newbot</code> and choose a name & username.</li>
|
||||||
|
<li>Copy the HTTP API token provided by BotFather.</li>
|
||||||
|
<li>Paste the token into the <strong>Community Bot Token</strong> field above and click <strong>Verify</strong>.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeGuideTab === 'chatid' && (
|
||||||
|
<div className="space-y-2 text-xs text-slate-600 leading-relaxed font-medium">
|
||||||
|
<p className="font-bold text-[#0F172A]">How seniors find their Telegram Chat ID:</p>
|
||||||
|
<ol className="space-y-1.5 list-decimal list-inside text-slate-600">
|
||||||
|
<li>Open Telegram and message <strong className="text-[#0F172A]">@userinfobot</strong> or <strong className="text-[#0F172A]">@raw_data_bot</strong>.</li>
|
||||||
|
<li>Press <strong>Start</strong> or send any message.</li>
|
||||||
|
<li>Copy the numeric <strong>Id</strong> (e.g. <code className="bg-slate-100 text-orange-600 px-1 py-0.5 rounded font-mono text-[11px]">971749136</code>).</li>
|
||||||
|
<li>Add the Chat ID in the form on the left!</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeGuideTab === 'workflow' && (
|
||||||
|
<div className="space-y-2 text-xs text-slate-600 leading-relaxed font-medium">
|
||||||
|
<p className="font-bold text-[#0F172A]">1-Tap Q&A Answer Workflow:</p>
|
||||||
|
<ul className="space-y-1.5 text-slate-600 list-disc list-inside">
|
||||||
|
<li>Fresher asks a question on Freshers Hub website.</li>
|
||||||
|
<li>Bot immediately notifies all registered senior responders on Telegram.</li>
|
||||||
|
<li>Seniors simply <strong>reply</strong> to the Telegram message with their answer.</li>
|
||||||
|
<li>The answer is instantly posted to the website community board!</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Edit Senior Responder Modal */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{editingHelper && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-xs">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.95 }}
|
||||||
|
className="bg-white rounded-3xl p-6 max-w-md w-full shadow-2xl border border-slate-200 space-y-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-sm font-bold text-[#0F172A] flex items-center gap-2">
|
||||||
|
<Edit className="w-4 h-4 text-[#FF6B00]" />
|
||||||
|
Edit Senior Responder
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditingHelper(null)}
|
||||||
|
className="p-1 text-slate-400 hover:text-slate-600 rounded-lg"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSaveEditedHelper} className="space-y-3.5">
|
||||||
|
<div>
|
||||||
|
<label className="block text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-1">
|
||||||
|
Telegram Chat ID
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editingHelper.chatId}
|
||||||
|
disabled
|
||||||
|
className="w-full bg-slate-100 border border-slate-200 rounded-xl px-3 py-2 text-xs font-mono text-slate-600 cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-1">
|
||||||
|
Responder Name / Title
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editHelperName}
|
||||||
|
onChange={(e) => 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
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-1">
|
||||||
|
Department
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={editHelperDept}
|
||||||
|
onChange={(e) => setEditHelperDept(e.target.value)}
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<option value="CSE">CSE</option>
|
||||||
|
<option value="AIDS">AIDS</option>
|
||||||
|
<option value="IT">IT</option>
|
||||||
|
<option value="ECE">ECE</option>
|
||||||
|
<option value="EEE">EEE</option>
|
||||||
|
<option value="MECH">MECH</option>
|
||||||
|
<option value="CIVIL">CIVIL</option>
|
||||||
|
<option value="ALL">All Depts</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-2 pt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setEditingHelper(null)}
|
||||||
|
className="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-bold transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="px-4 py-2 rounded-xl bg-[#FF6B00] hover:bg-[#EA580C] text-white text-xs font-bold transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
{
|
{
|
||||||
"community_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY",
|
"community_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY",
|
||||||
"telegram_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY",
|
"telegram_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY",
|
||||||
"helper_chat_ids" : [ 1873240361, 5567776672, 8518850169, 7238144438 ],
|
"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"
|
"spring_backend_url": "http://localhost:8085"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user