Update campus map layout, refine navbar, remove discord integration
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled

This commit is contained in:
Shanmuga Krishnan S M
2026-08-17 09:02:33 +05:30
parent 29377ce56e
commit d827f4aab9
15 changed files with 376 additions and 633 deletions

View File

@@ -101,7 +101,7 @@ public class CollabController {
request.setApplicationsCount(request.getApplicationsCount() + 1); request.setApplicationsCount(request.getApplicationsCount() + 1);
collabRequestRepository.save(request); collabRequestRepository.save(request);
// Notify Telegram/Discord intermediary bot asynchronously // Notify Telegram intermediary bot asynchronously
new Thread(() -> { new Thread(() -> {
try { try {
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
@@ -112,7 +112,6 @@ public class CollabController {
payload.put("author_name", request.getAuthorName()); payload.put("author_name", request.getAuthorName());
payload.put("contact_info", request.getContactInfo()); payload.put("contact_info", request.getContactInfo());
payload.put("telegram_chat_id", request.getTelegramChatId()); payload.put("telegram_chat_id", request.getTelegramChatId());
payload.put("discord_user_id", request.getDiscordUserId());
payload.put("applicant_name", savedApp.getApplicantName()); payload.put("applicant_name", savedApp.getApplicantName());
payload.put("applicant_email", savedApp.getApplicantEmail()); payload.put("applicant_email", savedApp.getApplicantEmail());
payload.put("applicant_dept", savedApp.getApplicantDept()); payload.put("applicant_dept", savedApp.getApplicantDept());
@@ -172,11 +171,6 @@ public class CollabController {
return collabRequestRepository.findByTelegramChatIdAndStatusOrderByCreatedAtDesc(chatId, "OPEN"); return collabRequestRepository.findByTelegramChatIdAndStatusOrderByCreatedAtDesc(chatId, "OPEN");
} }
@GetMapping("/active/discord/{userId}")
public List<CollabRequest> getActiveRequestsByDiscord(@PathVariable String userId) {
return collabRequestRepository.findByDiscordUserIdAndStatusOrderByCreatedAtDesc(userId, "OPEN");
}
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrCancelCollabRequest(@PathVariable Integer id) { public ResponseEntity<Void> deleteOrCancelCollabRequest(@PathVariable Integer id) {
return collabRequestRepository.findById(id).map(req -> { return collabRequestRepository.findById(id).map(req -> {

View File

@@ -46,9 +46,6 @@ public class CollabRequest {
@Column(name = "telegram_chat_id") @Column(name = "telegram_chat_id")
private Long telegramChatId; private Long telegramChatId;
@Column(name = "discord_user_id")
private String discordUserId;
@Column(name = "status") @Column(name = "status")
@Builder.Default @Builder.Default
private String status = "OPEN"; private String status = "OPEN";

View File

@@ -10,5 +10,4 @@ public interface CollabRequestRepository extends JpaRepository<CollabRequest, In
List<CollabRequest> findAllByOrderByCreatedAtDesc(); List<CollabRequest> findAllByOrderByCreatedAtDesc();
List<CollabRequest> findByAuthorEmailOrderByCreatedAtDesc(String authorEmail); List<CollabRequest> findByAuthorEmailOrderByCreatedAtDesc(String authorEmail);
List<CollabRequest> findByTelegramChatIdAndStatusOrderByCreatedAtDesc(Long telegramChatId, String status); List<CollabRequest> findByTelegramChatIdAndStatusOrderByCreatedAtDesc(Long telegramChatId, String status);
List<CollabRequest> findByDiscordUserIdAndStatusOrderByCreatedAtDesc(String discordUserId, String status);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 394 KiB

View File

@@ -1,34 +1,77 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { Menu, X, LogIn, LogOut, ShieldCheck, ChevronDown, Rocket, Sparkles } from 'lucide-react'; import {
import { NAV_LINKS } from '@/constants'; Menu,
X,
LogIn,
LogOut,
ShieldCheck,
ChevronDown,
Map,
Bus,
UserCheck,
MessageCircle,
Rocket,
BookOpen,
Wrench,
Bot,
Users,
Code2,
Trophy,
Home as HomeIcon,
} from 'lucide-react';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
interface NavItem {
label: string;
path: string;
icon?: any;
}
const PRIMARY_LINKS: NavItem[] = [
{ label: 'Home', path: '/', icon: HomeIcon },
{ label: 'Notes', path: '/notes', icon: BookOpen },
{ label: 'Toolkit', path: '/toolkit', icon: Wrench },
{ label: 'Chatbot', path: '/ai-assistant', icon: Bot },
{ label: 'Clubs & Centers', path: '/events', icon: Users },
{ label: 'Dev Collab', path: '/collab', icon: Code2 },
{ label: 'LeetCode', path: '/leetcode', icon: Trophy },
];
const MORE_LINKS: NavItem[] = [
{ label: 'Campus Map', path: '/campus', icon: Map },
{ label: 'Bus Routes', path: '/bus-routes', icon: Bus },
{ label: 'Faculty Directory', path: '/faculty', icon: UserCheck },
{ label: 'Community', path: '/community', icon: MessageCircle },
{ label: 'RAISE Incubator', path: '/raise', icon: Rocket },
];
const ALL_MOBILE_LINKS: NavItem[] = [...PRIMARY_LINKS, ...MORE_LINKS];
export default function Navbar() { export default function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false); const [isProfileOpen, setIsProfileOpen] = useState(false);
const [isMoreOpen, setIsMoreOpen] = useState(false);
const location = useLocation(); const location = useLocation();
const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth(); const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth();
const profileRef = useRef<HTMLDivElement>(null); const profileRef = useRef<HTMLDivElement>(null);
const moreRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 20);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => { useEffect(() => {
setIsMobileOpen(false); setIsMobileOpen(false);
setIsMoreOpen(false);
}, [location.pathname]); }, [location.pathname]);
// Close profile dropdown when clicking outside // Close dropdowns when clicking outside
useEffect(() => { useEffect(() => {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if (profileRef.current && !profileRef.current.contains(e.target as Node)) { if (profileRef.current && !profileRef.current.contains(e.target as Node)) {
setIsProfileOpen(false); setIsProfileOpen(false);
} }
if (moreRef.current && !moreRef.current.contains(e.target as Node)) {
setIsMoreOpen(false);
}
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
@@ -36,84 +79,240 @@ export default function Navbar() {
const checkIsActive = (linkPath: string) => { const checkIsActive = (linkPath: string) => {
const [pathName] = linkPath.split('?'); const [pathName] = linkPath.split('?');
return location.pathname === pathName; if (pathName === '/') return location.pathname === '/';
return location.pathname.startsWith(pathName);
}; };
const isMoreActive = MORE_LINKS.some((link) => checkIsActive(link.path));
return ( return (
<> <>
{/* Fixed Header Layout Wrapper */} {/* Unified Floating Navbar Container */}
<div className="fixed top-3 left-1/2 -translate-x-1/2 z-50 w-[96%] xl:w-[97%] max-w-[1560px] flex items-start justify-between gap-2.5 lg:gap-3.5 pointer-events-none px-1 lg:px-2"> <header className="fixed top-3.5 left-1/2 -translate-x-1/2 z-50 w-[95%] max-w-[1440px] pointer-events-none">
{/* Grey Rectangular Capsule Container (Logo + Links ending with LeetCode) */}
<motion.nav <motion.nav
initial={{ y: -100, opacity: 0 }} initial={{ y: -60, opacity: 0 }}
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, ease: 'easeOut' }} transition={{ duration: 0.45, ease: 'easeOut' }}
className="pointer-events-auto flex items-center justify-between flex-1 min-w-0 pl-2.5 pr-4 lg:pl-3.5 lg:pr-5 py-2 transition-all duration-300 shadow-[0_8px_32px_rgba(0,0,0,0.25)]" className="pointer-events-auto flex items-center justify-between gap-2 lg:gap-4 px-3 sm:px-4 py-2 rounded-2xl bg-slate-950/90 backdrop-blur-xl border border-white/10 shadow-[0_8px_30px_rgba(0,0,0,0.35)] transition-all"
style={{
background: 'linear-gradient(to right, rgba(19, 9, 36, 0.88) 0%, rgba(30, 12, 54, 0.82) 50%, rgba(19, 9, 36, 0.88) 100%)',
backdropFilter: 'blur(16px)',
WebkitBackdropFilter: 'blur(16px)',
borderRadius: '20px',
border: '1px solid rgba(255, 255, 255, 0.12)',
}}
> >
{/* Logo - RIT Event Hub Style */} {/* Left: Logo & College Brand */}
<a href="/" className="flex items-center gap-1.5 lg:gap-2 group shrink-0"> <Link to="/" className="flex items-center gap-2.5 group shrink-0">
{/* Logo Image */}
<motion.img <motion.img
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.06 }}
src="/logo.png" src="/logo.png"
alt="RIT Logo" alt="RIT Logo"
className="w-8 h-8 lg:w-9 lg:h-9 object-contain rounded-full shrink-0" className="w-8 h-8 lg:w-9 lg:h-9 object-contain rounded-full shrink-0"
/> />
<div className="flex items-center"> <div className="flex items-center">
<span className="font-extrabold text-white text-lg lg:text-xl xl:text-2xl tracking-tighter" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}> <span
className="font-extrabold text-white text-lg lg:text-xl tracking-tight group-hover:text-[#FF6B00] transition-colors"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
rit rit
</span> </span>
<div className="h-5 lg:h-6 w-[1px] bg-white/20 mx-1 lg:mx-1.5" /> <div className="h-4 lg:h-5 w-[1px] bg-white/20 mx-2" />
<div className="hidden sm:flex flex-col text-[7.5px] lg:text-[8px] xl:text-[8.5px] font-bold text-slate-200 leading-tight tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}> <div
<span>RAJALAKSHMI</span> className="hidden xl:flex flex-col text-[7.5px] lg:text-[8px] font-bold text-slate-300 leading-[1.15] tracking-wider uppercase"
<span>INSTITUTE OF</span> style={{ fontFamily: 'Inter, sans-serif' }}
<span>TECHNOLOGY</span> >
<span>Rajalakshmi</span>
<span>Institute of</span>
<span>Technology</span>
</div> </div>
</div> </div>
</a> </Link>
{/* Desktop Nav - Ends with LeetCode */} {/* Center: Desktop Navigation Links */}
<div className="hidden md:flex items-center gap-0.5 lg:gap-0.5 xl:gap-1"> <div className="hidden lg:flex items-center gap-1 xl:gap-1.5 min-w-0">
{NAV_LINKS.map((link) => { {PRIMARY_LINKS.map((link) => {
const isActive = checkIsActive(link.path); const isActive = checkIsActive(link.path);
return ( return (
<Link <Link
key={link.path} key={link.path}
to={link.path} to={link.path}
className="relative px-1 lg:px-1.5 xl:px-2 2xl:px-2.5 py-1.5 rounded-lg text-[8.5px] lg:text-[9px] xl:text-[10px] 2xl:text-[11px] font-semibold uppercase tracking-wider transition-all duration-200 whitespace-nowrap shrink-0" className={`relative px-2.5 xl:px-3 py-1.5 rounded-xl text-xs font-semibold tracking-wide transition-all duration-200 whitespace-nowrap ${
style={{ isActive ? 'text-white' : 'text-slate-300 hover:text-white hover:bg-white/10'
fontFamily: 'Plus Jakarta Sans, sans-serif', }`}
color: isActive ? '#FFFFFF' : '#CBD5E1', style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
}}
> >
{isActive && ( {isActive && (
<motion.div <motion.div
layoutId="nav-active" layoutId="nav-pill-active"
className="absolute inset-0 rounded-xl" className="absolute inset-0 rounded-xl bg-gradient-to-r from-[#FF6B00] to-[#F97316] shadow-sm"
style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)' }} transition={{ type: 'spring', bounce: 0.15, duration: 0.35 }}
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
/> />
)} )}
<span className="relative z-10">{link.label}</span> <span className="relative z-10">{link.label}</span>
</Link> </Link>
); );
})} })}
{/* "More" Dropdown Menu */}
<div className="relative" ref={moreRef}>
<button
onClick={() => setIsMoreOpen(!isMoreOpen)}
className={`relative flex items-center gap-1 px-2.5 xl:px-3 py-1.5 rounded-xl text-xs font-semibold tracking-wide transition-all cursor-pointer ${
isMoreActive ? 'text-white' : 'text-slate-300 hover:text-white hover:bg-white/10'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{isMoreActive && (
<motion.div
layoutId="nav-pill-active"
className="absolute inset-0 rounded-xl bg-gradient-to-r from-[#FF6B00] to-[#F97316] shadow-sm"
transition={{ type: 'spring', bounce: 0.15, duration: 0.35 }}
/>
)}
<span className="relative z-10">More</span>
<ChevronDown
className={`w-3.5 h-3.5 relative z-10 transition-transform duration-200 ${
isMoreOpen ? 'rotate-180 text-white' : 'text-slate-400'
}`}
/>
</button>
<AnimatePresence>
{isMoreOpen && (
<motion.div
initial={{ opacity: 0, y: 8, scale: 0.96 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.96 }}
transition={{ duration: 0.15 }}
className="absolute right-0 top-full mt-2 w-52 bg-slate-900/95 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl p-1.5 z-50 overflow-hidden"
>
{MORE_LINKS.map((item) => {
const IconComponent = item.icon;
const active = checkIsActive(item.path);
return (
<Link
key={item.path}
to={item.path}
onClick={() => setIsMoreOpen(false)}
className={`flex items-center gap-2.5 px-3 py-2 rounded-xl text-xs font-medium transition-all ${
active
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white font-bold'
: 'text-slate-300 hover:text-white hover:bg-white/10'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{IconComponent && <IconComponent className="w-4 h-4 shrink-0 text-slate-300" />}
<span>{item.label}</span>
</Link>
);
})}
</motion.div>
)}
</AnimatePresence>
</div>
</div> </div>
{/* Mobile Toggle */} {/* Right: Auth Profile / Sign In */}
<div className="md:hidden flex items-center"> <div className="hidden lg:flex items-center gap-2 shrink-0">
{isAuthenticated && user ? (
<div className="relative" ref={profileRef}>
<button
onClick={() => setIsProfileOpen(!isProfileOpen)}
className="flex items-center gap-2 px-3 py-1.5 rounded-xl bg-slate-800/80 hover:bg-slate-800 border border-white/10 transition-all cursor-pointer"
>
{user.pictureUrl ? (
<img
src={user.pictureUrl}
alt={user.name}
className="w-6 h-6 rounded-full border border-white/30 object-cover"
referrerPolicy="no-referrer"
/>
) : (
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-[#FF6B00] to-[#F97316] flex items-center justify-center text-white font-bold text-xs">
{user.name.charAt(0).toUpperCase()}
</div>
)}
<span
className="text-white text-xs font-semibold max-w-[100px] truncate"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{user.name.split(' ')[0]}
</span>
<ChevronDown className="w-3.5 h-3.5 text-slate-400" />
</button>
{/* Profile Dropdown */}
<AnimatePresence>
{isProfileOpen && (
<motion.div
initial={{ opacity: 0, y: 8, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute right-0 top-full mt-2 w-64 bg-slate-900 border border-white/10 rounded-2xl shadow-2xl overflow-hidden z-50"
>
<div className="p-4 border-b border-white/10">
<div className="flex items-center gap-3">
{user.pictureUrl ? (
<img
src={user.pictureUrl}
alt={user.name}
className="w-10 h-10 rounded-full border-2 border-orange-500/40 object-cover"
referrerPolicy="no-referrer"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-[#FF6B00] to-[#F97316] flex items-center justify-center text-white font-bold text-lg">
{user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="flex-1 min-w-0">
<p className="text-white text-sm font-bold truncate">{user.name}</p>
<p className="text-slate-400 text-[11px] truncate">{user.email}</p>
</div>
</div>
{isVerifiedStudent ? (
<div className="mt-3 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-emerald-500/15 border border-emerald-500/25">
<ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
<span className="text-[11px] font-bold text-emerald-400">Verified RIT Student</span>
</div>
) : (
<div className="mt-3 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-amber-500/15 border border-amber-500/25">
<span className="text-[11px] font-semibold text-amber-400">
Sign in with @ritchennai.edu.in
</span>
</div>
)}
</div>
<div className="p-2">
<button
onClick={() => {
logout();
setIsProfileOpen(false);
}}
className="w-full flex items-center gap-2.5 px-3 py-2 rounded-xl text-slate-300 hover:text-white hover:bg-red-500/15 text-xs font-semibold transition-all cursor-pointer"
>
<LogOut className="w-4 h-4 text-red-400" />
Sign Out
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
) : (
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={loginWithGoogle}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-white text-xs font-bold bg-white/10 hover:bg-white/15 border border-white/15 transition-all cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<LogIn className="w-3.5 h-3.5 text-[#FF6B00]" />
<span>Sign In</span>
</motion.button>
)}
</div>
{/* Mobile Hamburger Toggle */}
<div className="lg:hidden flex items-center">
<motion.button <motion.button
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
onClick={() => setIsMobileOpen(!isMobileOpen)} onClick={() => setIsMobileOpen(!isMobileOpen)}
className="w-9 h-9 rounded-xl flex items-center justify-center text-slate-200 hover:text-white bg-slate-800/60 hover:bg-slate-700/60 border border-slate-700/50 transition-all active:scale-95" className="w-9 h-9 rounded-xl flex items-center justify-center text-slate-200 hover:text-white bg-slate-800/80 border border-white/10 transition-all active:scale-95"
aria-label="Toggle menu" aria-label="Toggle menu"
> >
{isMobileOpen ? <X className="w-5 h-5 stroke-[2.5]" /> : <Menu className="w-5 h-5 stroke-[2.5]" />} {isMobileOpen ? <X className="w-5 h-5 stroke-[2.5]" /> : <Menu className="w-5 h-5 stroke-[2.5]" />}
@@ -128,189 +327,69 @@ export default function Navbar() {
animate={{ height: 'auto', opacity: 1 }} animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }} exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }} transition={{ duration: 0.25 }}
className="absolute left-0 top-full mt-2 w-full overflow-hidden md:hidden max-h-[80vh] overflow-y-auto z-50" className="absolute left-0 top-full mt-2 w-full overflow-hidden lg:hidden max-h-[82vh] overflow-y-auto z-50 rounded-2xl border border-white/10 bg-slate-950/95 backdrop-blur-2xl shadow-2xl"
> >
<div className="px-4 pb-5 pt-3 grid grid-cols-2 gap-2 border border-white/10 bg-slate-950/95 backdrop-blur-2xl rounded-2xl shadow-2xl"> <div className="p-4 space-y-3">
{NAV_LINKS.map((link) => { <div className="grid grid-cols-2 gap-2">
const isActive = checkIsActive(link.path); {ALL_MOBILE_LINKS.map((link) => {
return ( const isActive = checkIsActive(link.path);
<Link const IconComp = link.icon;
key={link.path} return (
to={link.path} <Link
onClick={() => setIsMobileOpen(false)} key={link.path}
className="px-3.5 py-3 rounded-xl text-xs font-semibold uppercase tracking-wider flex items-center justify-center text-center transition-all min-h-[44px]" to={link.path}
style={{ onClick={() => setIsMobileOpen(false)}
fontFamily: 'Poppins, sans-serif', className={`px-3 py-2.5 rounded-xl text-xs font-semibold flex items-center gap-2 transition-all ${
color: isActive ? '#FFFFFF' : '#CBD5E1', isActive
backgroundColor: isActive ? '#F97316' : 'rgba(255, 255, 255, 0.05)', ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-sm'
border: isActive ? '1px solid #FB923C' : '1px solid rgba(255, 255, 255, 0.08)', : 'bg-white/5 text-slate-200 hover:bg-white/10 border border-white/5'
}} }`}
> style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
{link.label} >
</Link> {IconComp && <IconComp className="w-3.5 h-3.5 opacity-80" />}
); <span className="truncate">{link.label}</span>
})} </Link>
);
{/* Mobile RAISE Button */} })}
<Link </div>
to="/raise"
onClick={() => setIsMobileOpen(false)}
className="col-span-2 mt-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-white text-xs font-bold bg-gradient-to-r from-rose-500 to-pink-600 border border-rose-400/40 shadow-md shadow-rose-500/20 transition-all active:scale-95"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Sparkles className="w-4 h-4 text-amber-200" />
Pitch your Idea - RAISE Incubator
</Link>
{/* Mobile Auth Button */} {/* Mobile Auth Button */}
{!isAuthenticated ? ( <div className="pt-2 border-t border-white/10">
<button {!isAuthenticated ? (
onClick={() => { <button
loginWithGoogle(); onClick={() => {
setIsMobileOpen(false); loginWithGoogle();
}} setIsMobileOpen(false);
className="col-span-2 mt-2 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-white text-xs font-semibold border border-orange-500/40 bg-orange-500/10 hover:bg-orange-500/20 transition-all cursor-pointer" }}
style={{ fontFamily: 'Poppins, sans-serif' }} className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-white text-xs font-semibold border border-orange-500/40 bg-orange-500/15 hover:bg-orange-500/25 transition-all cursor-pointer"
> style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
<LogIn className="w-4 h-4" /> >
Sign In with Google <LogIn className="w-4 h-4 text-orange-400" />
</button> Sign In with Google
) : ( </button>
<button ) : (
onClick={() => { <button
logout(); onClick={() => {
setIsMobileOpen(false); logout();
}} setIsMobileOpen(false);
className="col-span-2 mt-2 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-red-400 text-xs font-semibold border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 transition-all cursor-pointer" }}
style={{ fontFamily: 'Poppins, sans-serif' }} className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-red-400 text-xs font-semibold border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 transition-all cursor-pointer"
> style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
<LogOut className="w-4 h-4" /> >
Sign Out ({user?.name.split(' ')[0]}) <LogOut className="w-4 h-4" />
</button> Sign Out ({user?.name.split(' ')[0]})
)} </button>
)}
</div>
</div> </div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
</motion.nav> </motion.nav>
</header>
{/* Separate Top Right Section: Sign In on Top, RAISE Incubator Below */} {/* Spacer to prevent content overlap */}
<motion.div <div className="h-20" />
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.05 }}
className="hidden md:flex flex-col items-center gap-2 pointer-events-auto shrink-0"
>
{/* Sign Option (Top Right, Centered) */}
{isAuthenticated && user ? (
<div className="relative" ref={profileRef}>
<button
onClick={() => setIsProfileOpen(!isProfileOpen)}
className="flex items-center justify-center gap-2 px-4 py-2 rounded-xl bg-slate-900/90 backdrop-blur-md border border-slate-700/80 hover:border-orange-500/50 hover:bg-slate-800/95 shadow-lg transition-all cursor-pointer min-w-[120px]"
>
{user.pictureUrl ? (
<img
src={user.pictureUrl}
alt={user.name}
className="w-6 h-6 rounded-full border border-white/30 object-cover"
referrerPolicy="no-referrer"
/>
) : (
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center text-white font-bold text-xs">
{user.name.charAt(0).toUpperCase()}
</div>
)}
<span className="text-white text-xs lg:text-sm font-semibold max-w-[100px] truncate" style={{ fontFamily: 'Poppins, sans-serif' }}>
{user.name.split(' ')[0]}
</span>
<ChevronDown className="w-4 h-4 text-slate-400" />
</button>
{/* Profile Dropdown */}
<AnimatePresence>
{isProfileOpen && (
<motion.div
initial={{ opacity: 0, y: 8, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute right-0 top-full mt-2 w-64 bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl overflow-hidden z-50"
>
<div className="p-4 border-b border-slate-700/60">
<div className="flex items-center gap-3">
{user.pictureUrl ? (
<img
src={user.pictureUrl}
alt={user.name}
className="w-10 h-10 rounded-full border-2 border-orange-500/40 object-cover"
referrerPolicy="no-referrer"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center text-white font-bold text-lg">
{user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="flex-1 min-w-0">
<p className="text-white text-sm font-bold truncate">{user.name}</p>
<p className="text-slate-400 text-[11px] truncate">{user.email}</p>
</div>
</div>
{isVerifiedStudent ? (
<div className="mt-3 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-emerald-500/15 border border-emerald-500/25">
<ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
<span className="text-[11px] font-bold text-emerald-400">Verified RIT Student</span>
</div>
) : (
<div className="mt-3 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-amber-500/15 border border-amber-500/25">
<span className="text-[11px] font-semibold text-amber-400">
Sign in with @ritchennai.edu.in for verified badge
</span>
</div>
)}
</div>
<div className="p-2">
<button
onClick={() => {
logout();
setIsProfileOpen(false);
}}
className="w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-slate-300 hover:text-white hover:bg-red-500/15 text-xs font-semibold transition-all cursor-pointer"
>
<LogOut className="w-4 h-4" />
Sign Out
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
) : (
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={loginWithGoogle}
className="flex items-center justify-center gap-2 px-5 py-2 rounded-xl text-white text-xs lg:text-sm font-bold bg-slate-900/90 backdrop-blur-md border border-slate-700/80 hover:border-orange-500/60 hover:bg-slate-800/95 shadow-lg shadow-black/30 transition-all cursor-pointer min-w-[120px]"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<LogIn className="w-4 h-4 text-orange-400" />
<span className="text-white">Sign In</span>
</motion.button>
)}
{/* RAISE Incubator Module (Below Sign Option) */}
<Link
to="/raise"
className="flex items-center justify-center gap-1.5 px-3.5 py-1.5 lg:px-4 lg:py-2 rounded-xl bg-gradient-to-r from-rose-500 via-pink-500 to-rose-600 hover:from-rose-600 hover:to-pink-600 text-white text-[10.5px] lg:text-[11.5px] font-bold shadow-lg shadow-rose-500/25 border border-white/20 transition-all duration-200 hover:scale-[1.03] active:scale-95 whitespace-nowrap"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Sparkles className="w-3.5 h-3.5 text-amber-200 shrink-0" />
<span>Pitch your Idea - RAISE Incubator</span>
</Link>
</motion.div>
</div>
{/* Spacer */}
<div className="h-24" />
</> </>
); );
} }

View File

@@ -116,6 +116,15 @@ export const FEATURES: Feature[] = [
color: '#F59E0B', color: '#F59E0B',
bgColor: '#FFFBEB', bgColor: '#FFFBEB',
}, },
{
id: 'raise',
icon: 'Rocket',
title: 'RAISE Incubator',
description: 'Pitch startup ideas, get incubation support, seed funding opportunities, and entrepreneurial mentoring.',
path: '/raise',
color: '#EC4899',
bgColor: '#FDF2F8',
},
]; ];
// ─── Bus Routes ────────────────────────────────────────────────────────────── // ─── Bus Routes ──────────────────────────────────────────────────────────────
@@ -3106,7 +3115,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Harish A (CSE)', vicePresidentName: 'Harish A (CSE)',
coordinatorName: 'Pradakshina Rajesh Bhavani (AI&DS)', coordinatorName: 'Pradakshina Rajesh Bhavani (AI&DS)',
year: 'Senior Year', year: 'Senior Year',
contactEmail: '', contactEmail: 'nationalservicescheme@ritchennai.edu.in',
contactPhone: '+91 8668130804', contactPhone: '+91 8668130804',
icon: 'HeartHandshake', icon: 'HeartHandshake',
logoUrl: '/clubs/nss.png', logoUrl: '/clubs/nss.png',
@@ -3125,7 +3134,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Supeckshana M (CSE)', vicePresidentName: 'Supeckshana M (CSE)',
coordinatorName: 'Dr. S. Niranjana (VLSI)', coordinatorName: 'Dr. S. Niranjana (VLSI)',
year: 'Senior Year', year: 'Senior Year',
contactEmail: '', contactEmail: 'ubarit@ritchennai.edu.in',
contactPhone: '+91 7305642992', contactPhone: '+91 7305642992',
icon: 'Users', icon: 'Users',
logoUrl: '/clubs/unnat_bharat.png', logoUrl: '/clubs/unnat_bharat.png',
@@ -3144,7 +3153,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Danish (VLSI)', vicePresidentName: 'Danish (VLSI)',
coordinatorName: 'Rotaract Faculty Lead', coordinatorName: 'Rotaract Faculty Lead',
year: 'Senior Year', year: 'Senior Year',
contactEmail: '', contactEmail: 'rotaract@ritchennai.edu.in',
contactPhone: '+91 7200696226', contactPhone: '+91 7200696226',
icon: 'HeartHandshake', icon: 'HeartHandshake',
logoUrl: '/clubs/rotaract.png', logoUrl: '/clubs/rotaract.png',
@@ -3163,7 +3172,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Julinsa (CSE)', vicePresidentName: 'Julinsa (CSE)',
coordinatorName: 'Niraimathy Marthandan (Biotech)', coordinatorName: 'Niraimathy Marthandan (Biotech)',
year: 'Senior Year', year: 'Senior Year',
contactEmail: '', contactEmail: 'youthredcross@ritchennai.edu.in',
contactPhone: '+91 9344061221', contactPhone: '+91 9344061221',
icon: 'Heart', icon: 'Heart',
logoUrl: '/clubs/nss.png', logoUrl: '/clubs/nss.png',
@@ -3182,7 +3191,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Indhuvadhani M (ECE)', vicePresidentName: 'Indhuvadhani M (ECE)',
coordinatorName: 'Ms. N. Suganthi (ECE)', coordinatorName: 'Ms. N. Suganthi (ECE)',
year: 'Third Year', year: 'Third Year',
contactEmail: '', contactEmail: 'womenempowermentcell@ritchennai.edu.in',
contactPhone: '+91 6385227710', contactPhone: '+91 6385227710',
icon: 'Sparkles', icon: 'Sparkles',
logoUrl: '/clubs/wec.png', logoUrl: '/clubs/wec.png',
@@ -3201,7 +3210,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Pradakshina RB', vicePresidentName: 'Pradakshina RB',
coordinatorName: 'YUVA Faculty Lead', coordinatorName: 'YUVA Faculty Lead',
year: 'Senior Year', year: 'Senior Year',
contactEmail: '', contactEmail: 'ciiyiclub@ritchennai.edu.in',
contactPhone: '+91 9677043565', contactPhone: '+91 9677043565',
icon: 'Rocket', icon: 'Rocket',
logoUrl: '/clubs/yuva.png', logoUrl: '/clubs/yuva.png',
@@ -3220,7 +3229,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Keerthana S (III CSE)', vicePresidentName: 'Keerthana S (III CSE)',
coordinatorName: 'Dr. J. Anitha (H&S)', coordinatorName: 'Dr. J. Anitha (H&S)',
year: 'III Year, CSE', year: 'III Year, CSE',
contactEmail: '', contactEmail: 'wistemclub@ritchennai.edu.in',
contactPhone: '+91 9600110150', contactPhone: '+91 9600110150',
icon: 'Atom', icon: 'Atom',
logoUrl: '/clubs/wistem.png', logoUrl: '/clubs/wistem.png',
@@ -3240,7 +3249,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Barath S (AIML)', vicePresidentName: 'Barath S (AIML)',
coordinatorName: 'Prof. Avudai Selvi (CSE)', coordinatorName: 'Prof. Avudai Selvi (CSE)',
year: 'Senior Year', year: 'Senior Year',
contactEmail: '', contactEmail: 'techspark@ritchennai.edu.in',
contactPhone: '+91 8667466390', contactPhone: '+91 8667466390',
icon: 'Zap', icon: 'Zap',
logoUrl: '/clubs/techspark.png', logoUrl: '/clubs/techspark.png',
@@ -3259,7 +3268,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Sanjay Y (III ECE)', vicePresidentName: 'Sanjay Y (III ECE)',
coordinatorName: 'Dr. A. Thilagavathy Vinoth (H&S)', coordinatorName: 'Dr. A. Thilagavathy Vinoth (H&S)',
year: 'Third Year', year: 'Third Year',
contactEmail: '', contactEmail: 'infinitus@ritchennai.edu.in',
contactPhone: '+91 8838552068', contactPhone: '+91 8838552068',
icon: 'Calculator', icon: 'Calculator',
logoUrl: '/clubs/infinitus.png', logoUrl: '/clubs/infinitus.png',
@@ -3278,7 +3287,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Abhi Harish (III VLSI)', vicePresidentName: 'Abhi Harish (III VLSI)',
coordinatorName: 'Mr. Mohan Ram (AP/ECE)', coordinatorName: 'Mr. Mohan Ram (AP/ECE)',
year: 'III Year, VLSI', year: 'III Year, VLSI',
contactEmail: '', contactEmail: 'steam@ritchennai.edu.in',
contactPhone: '+91 7550271227', contactPhone: '+91 7550271227',
icon: 'Printer', icon: 'Printer',
logoUrl: '/clubs/steam.png', logoUrl: '/clubs/steam.png',
@@ -3297,7 +3306,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Jonathi (III AIDS)', vicePresidentName: 'Jonathi (III AIDS)',
coordinatorName: 'Prof. Shri Janani P (CSE)', coordinatorName: 'Prof. Shri Janani P (CSE)',
year: 'III Year, CSE', year: 'III Year, CSE',
contactEmail: '', contactEmail: 'culturalcell@ritchennai.edu.in',
contactPhone: '+91 9025726185', contactPhone: '+91 9025726185',
icon: 'Sparkles', icon: 'Sparkles',
logoUrl: '/clubs/artist_league.png', logoUrl: '/clubs/artist_league.png',
@@ -3316,7 +3325,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Sarvesh V (III VLSI)', vicePresidentName: 'Sarvesh V (III VLSI)',
coordinatorName: 'Mr. Keerthana Pandiyan (Biotech)', coordinatorName: 'Mr. Keerthana Pandiyan (Biotech)',
year: 'III Year, AIML', year: 'III Year, AIML',
contactEmail: '', contactEmail: 'vaarithitamilclub@ritchennai.edu.in',
contactPhone: '+91 8248701027', contactPhone: '+91 8248701027',
icon: 'BookOpen', icon: 'BookOpen',
logoUrl: '/clubs/vaarithi.png', logoUrl: '/clubs/vaarithi.png',
@@ -3335,7 +3344,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Farren Jeanvil', vicePresidentName: 'Farren Jeanvil',
coordinatorName: 'Ms. Roopavathi', coordinatorName: 'Ms. Roopavathi',
year: 'Third Year', year: 'Third Year',
contactEmail: '', contactEmail: 'nippon@ritchennai.edu.in',
contactPhone: '+91 9042294493', contactPhone: '+91 9042294493',
icon: 'Globe', icon: 'Globe',
logoUrl: '/clubs/nippon.png', logoUrl: '/clubs/nippon.png',
@@ -3354,7 +3363,7 @@ export const CLUBS_DATA: Club[] = [
vicePresidentName: 'Thirumalai (III VLSI)', vicePresidentName: 'Thirumalai (III VLSI)',
coordinatorName: 'Ms. Fouzia Sulthana (AIDS)', coordinatorName: 'Ms. Fouzia Sulthana (AIDS)',
year: 'III Year, ECE', year: 'III Year, ECE',
contactEmail: '', contactEmail: 'podxrit@ritchennai.edu.in',
contactPhone: '+91 9962840247', contactPhone: '+91 9962840247',
icon: 'Mic', icon: 'Mic',
logoUrl: '/clubs/podx.png', logoUrl: '/clubs/podx.png',

View File

@@ -139,19 +139,6 @@ export default function AIAssistant() {
<Send className="w-3.5 h-3.5" /> <Send className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Telegram Bot</span> <span className="hidden sm:inline">Telegram Bot</span>
</a> </a>
<a
href="https://discord.com/api/oauth2/authorize?client_id=1476942599167414506&permissions=8&scope=bot"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium text-white transition-all shadow-xs hover:brightness-105 active:scale-95"
style={{
background: 'linear-gradient(135deg, #5865F2, #7289DA)',
fontFamily: 'Inter, sans-serif'
}}
>
<Bot className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Discord Bot</span>
</a>
<motion.button <motion.button
whileHover={{ scale: 1.03 }} whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }} whileTap={{ scale: 0.97 }}

View File

@@ -32,38 +32,37 @@ const BLUEPRINT_LEGEND = [
]; ];
const LOCATION_SPOTS: LocationSpot[] = [ const LOCATION_SPOTS: LocationSpot[] = [
{ name: 'Girls Hostel', x: 16.0, y: 8.5, category: 'Hostel / Mess', desc: 'Residential Quarter for Women', area: 'Top Left' }, { name: 'Girls Hostel', x: 29.4, y: 7.3, category: 'Hostel / Mess', desc: 'Residential Quarter for Women', area: 'Top Left' },
{ name: 'Playground', x: 40.0, y: 11.0, category: 'Amenity', desc: 'Main Sports & Athletic Field', area: 'Top Center' }, { name: 'Playground', x: 48.5, y: 8.8, category: 'Amenity', desc: 'Main Sports & Athletic Field', area: 'Top Center' },
{ name: 'Canteen', x: 67.0, y: 8.5, category: 'Amenity', desc: 'Food Court & Refreshments', area: 'Top Right' }, { name: 'Canteen', x: 68.3, y: 8.3, category: 'Amenity', desc: 'Food Court & Refreshments', area: 'Top Right' },
{ name: 'Steve Jobs Block', x: 16.0, y: 21.0, category: 'Admin / Facility', desc: 'Auditorium, Computer Labs & Staff Rooms', area: 'West Wing' }, { name: 'Steve Jobs Block', x: 27.2, y: 17.1, category: 'Admin / Facility', desc: 'Auditorium, Computer Labs & Staff Rooms', area: 'West Wing' },
{ name: 'Girls Mess', x: 14.5, y: 31.0, category: 'Hostel / Mess', desc: 'Dedicated Dining Hall for Women', area: 'West Wing' }, { name: 'Girls Mess', x: 25.7, y: 25.9, category: 'Hostel / Mess', desc: 'Dedicated Dining Hall for Women', area: 'West Wing' },
{ name: 'MEC Lab', x: 14.0, y: 39.5, category: 'Lab / Academic', desc: 'Mechanical Engineering Laboratory', area: 'West Wing' }, { name: 'MEC Lab', x: 23.5, y: 34.7, category: 'Lab / Academic', desc: 'Mechanical Engineering Laboratory', area: 'West Wing' },
{ name: 'EPL Lab', x: 13.0, y: 47.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'West Wing' }, { name: 'EPL Lab', x: 21.3, y: 43.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'West Wing' },
{ name: 'Nikola Tesla Block', x: 40.5, y: 36.5, category: 'B Block', desc: 'Main Electrical & Engineering Research Block', area: 'Central Block' }, { name: 'Astronaut Statue', x: 51.4, y: 16.1, category: 'Amenity', desc: 'Iconic Landmark Monument', area: 'Central Park' },
{ name: 'Astronaut Statue', x: 51.0, y: 23.0, category: 'Amenity', desc: 'Iconic Landmark Monument', area: 'Central Park' }, { name: 'Campus Centre', x: 65.3, y: 20.5, category: 'Admin / Facility', desc: 'Student Activity & Administrative Center', area: 'North East' },
{ name: 'Campus Centre', x: 67.0, y: 23.0, category: 'Admin / Facility', desc: 'Student Activity & Administrative Center', area: 'North East' }, { name: 'Nikola Tesla Block', x: 43.3, y: 34.7, category: 'B Block', desc: 'Main Electrical & Engineering Research Block', area: 'Central Block' },
{ name: 'Hut', x: 70.0, y: 37.0, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central East' }, { name: 'Hut', x: 66.8, y: 31.3, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central East' },
{ name: 'Homi Jehangir Bhabha Block', x: 87.0, y: 24.0, category: 'A Block', desc: 'Science & Physics Research Block', area: 'East Wing' }, { name: 'Homi Jehangir Bhabha Block', x: 79.3, y: 22.9, category: 'A Block', desc: 'Science & Physics Research Block', area: 'East Wing' },
{ name: 'Boys Mess', x: 87.0, y: 17.5, category: 'Hostel / Mess', desc: 'Ground Floor Dining Hall for Men (Homi Bhabha Block)', area: 'East Wing' }, { name: 'C Block', x: 78.6, y: 37.6, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', area: 'East Wing' },
{ name: 'Staff Mess', x: 87.0, y: 30.5, category: 'Amenity', desc: 'First Floor Dining Hall for Faculty & Staff (Homi Bhabha Block)', area: 'East Wing' }, { name: 'Alan Turing Block', x: 66.8, y: 50.8, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' },
{ name: 'C Block', x: 87.0, y: 38.0, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', area: 'East Wing' }, { name: 'Arcade', x: 36.7, y: 53.2, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' },
{ name: 'Alan Turing Block', x: 77.0, y: 53.0, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' }, { name: 'Green Building', x: 27.2, y: 63.5, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' },
{ name: 'Arcade', x: 35.0, y: 57.0, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' }, { name: 'Security Booth', x: 44.8, y: 65.9, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' },
{ name: 'Green Building', x: 23.0, y: 71.0, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' }, { name: 'ATM', x: 44.3, y: 73.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' },
{ name: 'Security Booth', x: 49.0, y: 77.0, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' }, { name: 'Bike Parking', x: 65.3, y: 61.5, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' },
{ name: 'ATM', x: 49.0, y: 86.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' }, { name: 'Generator Room', x: 63.1, y: 71.8, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' },
{ name: 'Bike Parking', x: 75.0, y: 67.0, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' }, { name: 'Side Gate', x: 22.8, y: 75.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' },
{ name: 'Generator Room', x: 76.0, y: 81.0, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' }, { name: 'Main Gate', x: 49.2, y: 77.9, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
{ name: 'Side Gate', x: 23.0, y: 93.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' }, { name: 'Bus Stop', x: 49.6, y: 92.8, category: 'Amenity', desc: 'Public & College Bus Boarding Point', area: 'Highway' },
{ name: 'Main Gate', x: 59.0, y: 93.0, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
]; ];
const CATEGORY_MAP_SPOTS: Record<string, string[]> = { const CATEGORY_MAP_SPOTS: Record<string, string[]> = {
dept: ['Homi Jehangir Bhabha Block', 'Nikola Tesla Block', 'C Block', 'Steve Jobs Block', 'Alan Turing Block', 'Green Building'], dept: ['Homi Jehangir Bhabha Block', 'Nikola Tesla Block', 'C Block', 'Steve Jobs Block', 'Alan Turing Block', 'Green Building'],
library: ['Green Building'], library: ['Green Building'],
labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'], labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'],
hostel: ['Girls Hostel', 'Boys Mess'], hostel: ['Girls Hostel', 'Girls Mess'],
canteen: ['Canteen', 'Girls Mess', 'Boys Mess', 'Staff Mess', 'Arcade'], canteen: ['Canteen', 'Girls Mess', 'Arcade'],
auditorium: ['Steve Jobs Block', 'Campus Centre'], auditorium: ['Steve Jobs Block', 'Campus Centre'],
sports: ['Playground'], sports: ['Playground'],
}; };

View File

@@ -24,7 +24,6 @@ export interface CollabRequestItem {
acceptedCount?: number; acceptedCount?: number;
contactInfo?: string; contactInfo?: string;
telegramChatId?: number; telegramChatId?: number;
discordUserId?: string;
status: string; status: string;
applicationsCount: number; applicationsCount: number;
createdAt: string; createdAt: string;

View File

@@ -90,19 +90,6 @@ export default function Home() {
Ask Assistant Ask Assistant
</motion.button> </motion.button>
</Link> </Link>
<Link to="/raise">
<motion.button
whileHover={{ scale: 1.04, boxShadow: '0 10px 30px -5px rgba(233,30,99,0.5)' }}
whileTap={{ scale: 0.96 }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl font-bold text-sm text-white bg-gradient-to-r from-rose-500 to-pink-600 border border-white/30 backdrop-blur-md transition-all cursor-pointer shadow-lg shadow-pink-500/20"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<Sparkles className="w-4 h-4 text-amber-200" />
Pitch your Idea - RAISE
<ArrowRight className="w-4 h-4" />
</motion.button>
</Link>
</motion.div> </motion.div>
</motion.div> </motion.div>
</section> </section>

View File

@@ -309,7 +309,7 @@ const CS_TOOLS_CATEGORIES = [
bg: '#FEFCE8', bg: '#FEFCE8',
tools: [ tools: [
{ name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' }, { name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' },
{ name: 'Team Communication', desc: 'Slack, Microsoft Teams, Discord, and Mattermost.', url: 'https://slack.com/' }, { name: 'Team Communication', desc: 'Slack, Microsoft Teams, and Mattermost.', url: 'https://slack.com/' },
{ name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' }, { name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' },
{ name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' }, { name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' },
{ name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' } { name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' }

View File

@@ -278,7 +278,7 @@ const CS_TOOLS_CATEGORIES = [
bg: '#FEFCE8', bg: '#FEFCE8',
tools: [ tools: [
{ name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' }, { name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' },
{ name: 'Team Communication', desc: 'Slack, Microsoft Teams, Discord, and Mattermost.', url: 'https://slack.com/' }, { name: 'Team Communication', desc: 'Slack, Microsoft Teams, and Mattermost.', url: 'https://slack.com/' },
{ name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' }, { name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' },
{ name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' }, { name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' },
{ name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' } { name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' }
@@ -766,7 +766,20 @@ export default function Toolkit() {
const [calculationType, setCalculationType] = useState<'gpa' | 'internal' | null>(null); const [calculationType, setCalculationType] = useState<'gpa' | 'internal' | null>(null);
const [gpaStep, setGpaStep] = useState<1 | 2>(1); const [gpaStep, setGpaStep] = useState<1 | 2>(1);
const [calculatedScore, setCalculatedScore] = useState<string | null>(null); const [calculatedScore, setCalculatedScore] = useState<string | null>(null);
const [subjects, setSubjects] = useState([ interface SubjectItem {
name: string;
credits: number;
grade: string;
cat1: string;
cat2: string;
assignment: string;
attendance: string;
isElective?: boolean;
electiveSlot?: number;
electiveCode?: string;
}
const [subjects, setSubjects] = useState<SubjectItem[]>([
{ name: 'Subject 1', credits: 4, grade: 'O', cat1: '45', cat2: '48', assignment: '10', attendance: '95' }, { name: 'Subject 1', credits: 4, grade: 'O', cat1: '45', cat2: '48', assignment: '10', attendance: '95' },
{ name: 'Subject 2', credits: 3, grade: 'A+', cat1: '42', cat2: '44', assignment: '9', attendance: '92' }, { name: 'Subject 2', credits: 3, grade: 'A+', cat1: '42', cat2: '44', assignment: '9', attendance: '92' },
{ name: 'Subject 3', credits: 3, grade: 'A', cat1: '38', cat2: '40', assignment: '8', attendance: '88' }, { name: 'Subject 3', credits: 3, grade: 'A', cat1: '38', cat2: '40', assignment: '8', attendance: '88' },

View File

@@ -1,8 +1,6 @@
{ {
"community_bot_token": "", "community_bot_token": "",
"telegram_bot_token": "", "telegram_bot_token": "",
"helper_chat_ids": [971749136, 5567776672,1873240361], "helper_chat_ids": [971749136, 5567776672, 1873240361],
"discord_bot_token": "",
"discord_helper_user_ids": [789393727641878568],
"spring_backend_url": "http://localhost:8085" "spring_backend_url": "http://localhost:8085"
} }

View File

@@ -2,4 +2,3 @@ requests>=2.28.0
fastapi>=0.95.0 fastapi>=0.95.0
uvicorn>=0.20.0 uvicorn>=0.20.0
pydantic>=2.0 pydantic>=2.0
discord.py>=2.3.0

View File

@@ -6,7 +6,6 @@ import time
import logging import logging
import requests import requests
import asyncio import asyncio
import discord
from typing import Optional from typing import Optional
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
@@ -53,7 +52,6 @@ def load_config():
config = load_config() config = load_config()
COMMUNITY_BOT_TOKEN = os.environ.get("COMMUNITY_BOT_TOKEN") or config.get("community_bot_token", "8859374355:AAH0dhwstkTBhRerRTjzmb2RG2fjPbigzvo") COMMUNITY_BOT_TOKEN = os.environ.get("COMMUNITY_BOT_TOKEN") or config.get("community_bot_token", "8859374355:AAH0dhwstkTBhRerRTjzmb2RG2fjPbigzvo")
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN") or config.get("telegram_bot_token") BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN") or config.get("telegram_bot_token")
DISCORD_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") or config.get("discord_bot_token")
BACKEND_URL = os.environ.get("SPRING_BACKEND_URL") or config.get("spring_backend_url", "http://localhost:8085") BACKEND_URL = os.environ.get("SPRING_BACKEND_URL") or config.get("spring_backend_url", "http://localhost:8085")
# Database Setup # Database Setup
@@ -611,292 +609,8 @@ def telegram_polling_thread():
logging.error(f"Error in 24/7 chatbot polling loop: {e}") logging.error(f"Error in 24/7 chatbot polling loop: {e}")
time.sleep(5) time.sleep(5)
# Discord Action View for Collaboration Applications
class DiscordCollabActionView(discord.ui.View):
def __init__(self, application_id: int, applicant_contact: str):
super().__init__(timeout=86400)
self.application_id = application_id
self.applicant_contact = applicant_contact
@discord.ui.button(label="Accept Collaboration", style=discord.ButtonStyle.success, emoji="✅")
async def accept_button(self, interaction: discord.Interaction, button: discord.ui.Button):
try:
requests.put(f"{BACKEND_URL}/api/collab/applications/{self.application_id}/status?status=ACCEPTED", timeout=5)
await interaction.response.send_message(
f"✅ **Collaboration Request Accepted!**\nDirect contact details: **{self.applicant_contact}**",
ephemeral=False
)
for item in self.children:
item.disabled = True
await interaction.message.edit(view=self)
except Exception as e:
await interaction.response.send_message(f"❌ Error updating status: {e}", ephemeral=True)
@discord.ui.button(label="Decline Request", style=discord.ButtonStyle.danger, emoji="❌")
async def decline_button(self, interaction: discord.Interaction, button: discord.ui.Button):
try:
requests.put(f"{BACKEND_URL}/api/collab/applications/{self.application_id}/status?status=REJECTED", timeout=5)
await interaction.response.send_message("❌ **Collaboration Request Declined.**", ephemeral=False)
for item in self.children:
item.disabled = True
await interaction.message.edit(view=self)
except Exception as e:
await interaction.response.send_message(f"❌ Error updating status: {e}", ephemeral=True)
async def broadcast_discord_collab_application(application_id: int, project_idea: str, tag: str, applicant_name: str, applicant_dept: str, applicant_year: str, applicant_contact: str, message: str, user_ids: list):
formatted_msg = (
f"🤝 **New Collaboration Request for your Project!**\n\n"
f"📌 **Project Idea:** {project_idea}\n"
f"🏷️ **Tag:** `{tag}`\n\n"
f"👤 **Applicant:** {applicant_name} ({applicant_dept}, {applicant_year})\n"
f"💬 **Message:** {message or 'No message provided'}\n"
f"📱 **Contact Info:** {applicant_contact}\n\n"
f"Click a button below to respond:"
)
view = DiscordCollabActionView(application_id, applicant_contact)
for user_id_val in user_ids:
try:
user_id = int(user_id_val)
user = await discord_client.fetch_user(user_id)
if user:
await user.send(content=formatted_msg, view=view)
logging.info(f"Sent Discord collab notification DM to user {user_id}")
except Exception as e:
logging.error(f"Failed to send Discord collab DM to {user_id_val}: {e}")
# Discord Remove Select View
class DiscordCollabRemoveSelect(discord.ui.Select):
def __init__(self, active_requests: list):
options = []
for req in active_requests:
req_id = str(req["id"])
snippet = req.get("projectIdea", "Project")[:45]
options.append(discord.SelectOption(
label=f"Cancel: {snippet}",
value=req_id,
description=f"Tag: {req.get('tag')} | Dept: {req.get('department')}",
emoji="❌"
))
super().__init__(placeholder="Select a request to remove...", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
req_id = self.values[0]
try:
res = requests.delete(f"{BACKEND_URL}/api/collab/{req_id}", timeout=5)
if res.status_code in [200, 204]:
await interaction.response.send_message("✅ **Collaboration Request cancelled and removed from RIT Dev Hub.**", ephemeral=False)
else:
await interaction.response.send_message("❌ Failed to cancel request.", ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error: {e}", ephemeral=True)
class DiscordCollabRemoveView(discord.ui.View):
def __init__(self, active_requests: list):
super().__init__(timeout=180)
self.add_item(DiscordCollabRemoveSelect(active_requests))
# Discord Native Interactive UI Components (Modal & Dropdown View)
class CollabModal(discord.ui.Modal, title="Post Collaboration Request"):
author_name = discord.ui.TextInput(label="1) Your Name", placeholder="e.g. Priyan Sharma", required=True)
dept_and_year = discord.ui.TextInput(label="2) Dept & Year", placeholder="e.g. CSE, 1st Year", required=True, default="CSE, 1st Year")
collaborators_needed = discord.ui.TextInput(label="3) Collaborators Needed", placeholder="e.g. 2", required=True, default="1")
project_idea = discord.ui.TextInput(label="4) Project Idea & Details", style=discord.TextStyle.paragraph, placeholder="Describe your project idea...", required=True)
github_link = discord.ui.TextInput(label="5) GitHub Link (Optional)", placeholder="https://github.com/...", required=False)
def __init__(self, tag: str):
super().__init__()
self.selected_tag = tag
async def on_submit(self, interaction: discord.Interaction):
try:
num_needed = int(self.collaborators_needed.value)
except ValueError:
num_needed = 1
dept_val = "CSE"
year_val = "1st Year"
if "," in self.dept_and_year.value:
parts = [p.strip() for p in self.dept_and_year.value.split(",", 1)]
dept_val = parts[0]
year_val = parts[1]
else:
dept_val = self.dept_and_year.value.strip()
payload = {
"authorName": self.author_name.value,
"department": dept_val,
"year": year_val,
"collaboratorsNeeded": num_needed,
"tag": self.selected_tag,
"projectIdea": self.project_idea.value,
"githubLink": self.github_link.value or None,
"contactInfo": f"Discord: {interaction.user.name}",
"discordUserId": str(interaction.user.id)
}
try:
res = requests.post(f"{BACKEND_URL}/api/collab", json=payload, timeout=10)
if res.status_code in [200, 201]:
await interaction.response.send_message(
f"🎉 **Collaboration Request posted live to RIT Dev Hub!**\n"
f"👤 **Author:** {self.author_name.value} ({dept_val}, {year_val})\n"
f"📌 **Project:** {self.project_idea.value}\n"
f"🏷️ **Tag:** `{self.selected_tag}`\n"
f"👥 **Collaborators Needed:** `{num_needed}`",
ephemeral=False
)
else:
await interaction.response.send_message(f"❌ Failed to save request (Status: {res.status_code})", ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error connecting to backend: {e}", ephemeral=True)
class CollabTagSelect(discord.ui.Select):
def __init__(self):
options = [
discord.SelectOption(
label="Co-developing from scratch",
value="looking for co-developing a project from scratch",
description="Build a brand new project from scratch together",
emoji="🚀"
),
discord.SelectOption(
label="Beta Testers needed",
value="looking for beta testers",
description="Test early builds and provide user feedback",
emoji="🧪"
),
discord.SelectOption(
label="Open-Source Contributors",
value="looking for Open-source Collaborators/Contributers",
description="Open repository seeking PRs and contributors",
emoji="🌐"
),
]
super().__init__(placeholder="Scroll down to select a tag for your request...", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
selected_tag = self.values[0]
modal = CollabModal(tag=selected_tag)
await interaction.response.send_modal(modal)
class CollabView(discord.ui.View):
def __init__(self):
super().__init__(timeout=180)
self.add_item(CollabTagSelect())
# Discord Bot Client Setup
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
discord_client = discord.Client(intents=intents)
discord_loop = None
@discord_client.event
async def on_ready():
logging.info(f"Discord Bot logged in as {discord_client.user}!")
@discord_client.event
async def on_message(message):
if message.author == discord_client.user:
return
is_dm = isinstance(message.channel, discord.DMChannel)
is_mention = discord_client.user in message.mentions
if not is_dm and not is_mention:
return
content = message.content
if is_mention:
mention_str = f"<@{discord_client.user.id}>"
mention_nick_str = f"<@!{discord_client.user.id}>"
content = content.replace(mention_str, "").replace(mention_nick_str, "").strip()
# Discord /collab & /remove commands disabled as requested
if content.lower().startswith("/collab") or content.lower().startswith("/remove") or content.lower().startswith("/cancel"):
await message.reply("ℹ️ **Dev Hub collaboration requests via Discord are currently disabled.** Please use the RIT Dev Hub website (https://rit-services.in/dev-collab) or the Telegram bot (@Ritchatbot_bot).")
return
# Check if this message is a reply to a question DM sent to a helper
if message.reference and message.reference.message_id:
question_id = get_question_id(message.author.id, message.reference.message_id)
if question_id:
author_name = message.author.display_name or message.author.name or "Senior Helper"
logging.info(f"Submitting answer for question {question_id} by Discord helper '{author_name}'")
backend_endpoint = f"{BACKEND_URL}/api/questions/{question_id}/answers"
answer_payload = {
"body": content,
"author": author_name
}
try:
def call_backend():
return requests.post(backend_endpoint, json=answer_payload, timeout=10)
loop = asyncio.get_event_loop()
res = await loop.run_in_executor(None, call_backend)
if res.status_code in [200, 201]:
await message.reply("✅ **Answer posted successfully to the Q&A board!**")
else:
await message.reply(f"❌ **Failed to post answer to backend.** (Status: {res.status_code})")
except Exception as e:
logging.error(f"Error calling backend endpoint {backend_endpoint}: {e}")
await message.reply(f"❌ **Connection error to backend.** ({e})")
return
# Direct query fallback to Go chatbot service
if not content.strip():
return
logging.info(f"Querying Go chatbot service for Discord user {message.author.id}: '{content}'")
chatbot_service_url = "http://localhost:8081/api/chat"
try:
def call_chatbot():
return requests.post(chatbot_service_url, json={"message": content}, timeout=10)
loop = asyncio.get_event_loop()
res = await loop.run_in_executor(None, call_chatbot)
if res.status_code == 200:
ans_data = res.json()
bot_response = ans_data.get("answer", "I am having trouble processing that question.")
await message.reply(bot_response)
else:
logging.error(f"Go chatbot API returned status code {res.status_code}")
await message.reply("⚠️ The RIT Chatbot service is currently experiencing issues. Please try again later.")
except Exception as e:
logging.error(f"Failed to connect to Go chatbot service: {e}")
await message.reply("⚠️ I cannot connect to the RIT Chatbot database right now. Please make sure the service is online.")
async def broadcast_discord_question(question_id: int, title: str, body: str, author: str, user_ids: list):
formatted_msg = (
f"❓ **New Student Question!**\n\n"
f"👤 **Author:** {author}\n"
f"📌 **Topic:** {title}\n"
f"📝 **Details:** {body}\n\n"
f"💬 **Reply directly to this message to submit your answer.**"
)
for user_id_val in user_ids:
try:
user_id = int(user_id_val)
user = await discord_client.fetch_user(user_id)
if user:
msg = await user.send(formatted_msg)
save_mapping(user_id, msg.id, question_id)
logging.info(f"Sent Discord DM to helper {user_id}")
except Exception as e:
logging.error(f"Failed to send Discord DM to helper {user_id_val}: {e}")
async def run_discord_bot():
global discord_loop
logging.info("Starting Discord bot...")
discord_loop = asyncio.get_running_loop()
try:
await discord_client.start(DISCORD_TOKEN)
except Exception as e:
logging.error(f"Discord Bot failed to run: {e}")
# FastAPI Web Server Setup # FastAPI Web Server Setup
app = FastAPI(title="RIT Telegram & Discord Intermediary Bot HTTP Server") app = FastAPI(title="RIT Telegram Intermediary Bot HTTP Server")
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@@ -920,7 +634,6 @@ class CollabApplicationPayload(BaseModel):
author_name: str author_name: str
contact_info: Optional[str] = None contact_info: Optional[str] = None
telegram_chat_id: Optional[int] = None telegram_chat_id: Optional[int] = None
discord_user_id: Optional[str] = None
applicant_name: str applicant_name: str
applicant_dept: str applicant_dept: str
applicant_year: str applicant_year: str
@@ -932,7 +645,7 @@ def send_question(payload: QuestionPayload):
current_config = load_config() current_config = load_config()
comm_token = os.environ.get("COMMUNITY_BOT_TOKEN") or current_config.get("community_bot_token", COMMUNITY_BOT_TOKEN) comm_token = os.environ.get("COMMUNITY_BOT_TOKEN") or current_config.get("community_bot_token", COMMUNITY_BOT_TOKEN)
# 1. Telegram Broadcast via Senior Helper Community Bot # Telegram Broadcast via Senior Helper Community Bot
telegram_helpers = current_config.get("helper_chat_ids", []) telegram_helpers = current_config.get("helper_chat_ids", [])
telegram_sent = 0 telegram_sent = 0
if telegram_helpers and comm_token: if telegram_helpers and comm_token:
@@ -951,27 +664,9 @@ def send_question(payload: QuestionPayload):
save_mapping(chat_id, message_id, payload.question_id) save_mapping(chat_id, message_id, payload.question_id)
telegram_sent += 1 telegram_sent += 1
# 2. Discord Broadcast
discord_helpers = current_config.get("discord_helper_user_ids", [])
discord_sent = 0
if discord_helpers and DISCORD_TOKEN:
logging.info(f"Broadcasting question {payload.question_id} to {len(discord_helpers)} Discord helpers.")
if discord_loop:
try:
asyncio.run_coroutine_threadsafe(
broadcast_discord_question(payload.question_id, payload.title, payload.body, payload.author, discord_helpers),
discord_loop
)
discord_sent = len(discord_helpers)
except Exception as e:
logging.error(f"Error scheduling Discord broadcast: {e}")
else:
logging.warning("Discord loop not running. Skipping Discord broadcast.")
return { return {
"status": "success", "status": "success",
"telegram_delivered_to": telegram_sent, "telegram_delivered_to": telegram_sent
"discord_queued_for": discord_sent
} }
@app.post("/send_collab_application") @app.post("/send_collab_application")
@@ -980,9 +675,8 @@ def send_collab_application(payload: CollabApplicationPayload):
bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") or current_config.get("telegram_bot_token") bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") or current_config.get("telegram_bot_token")
telegram_sent = False telegram_sent = False
discord_sent = False
# 1. Telegram Notification strictly to Person A (the project author) # Telegram Notification strictly to Person A (the project author)
chat_id = payload.telegram_chat_id chat_id = payload.telegram_chat_id
if chat_id: if chat_id:
@@ -1009,13 +703,9 @@ def send_collab_application(payload: CollabApplicationPayload):
else: else:
logging.info(f"Skipping Telegram notification for collab application {payload.application_id}: No telegram_chat_id provided for author.") logging.info(f"Skipping Telegram notification for collab application {payload.application_id}: No telegram_chat_id provided for author.")
# 2. Discord Broadcast - Disabled as requested
discord_sent = False
return { return {
"status": "success", "status": "success",
"telegram_sent": telegram_sent, "telegram_sent": telegram_sent
"discord_sent": discord_sent
} }
async def run_uvicorn(): async def run_uvicorn():
@@ -1030,14 +720,7 @@ async def main():
chat_thread = threading.Thread(target=telegram_polling_thread, daemon=True) chat_thread = threading.Thread(target=telegram_polling_thread, daemon=True)
chat_thread.start() chat_thread.start()
tasks = [] await run_uvicorn()
if DISCORD_TOKEN:
tasks.append(run_discord_bot())
else:
logging.warning("Discord Bot Token is empty. Skipping Discord bot startup.")
tasks.append(run_uvicorn())
await asyncio.gather(*tasks)
if __name__ == "__main__": if __name__ == "__main__":
try: try: