Compare commits

..

2 Commits

Author SHA1 Message Date
Shanmuga Krishnan S M
dad13527c6 style: unify module theme colors to enterprise violet and remove AI buzzwords
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-13 14:15:35 +05:30
Shanmuga Krishnan S M
ff606eb255 feat(dashboard): transform home dashboard into full viewport video overlay with transparent quick launch modules bar 2026-08-13 14:12:47 +05:30
6 changed files with 538 additions and 532 deletions

View File

@@ -1,17 +1,85 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Search, Bell, Menu, Sparkles, User, Calendar, ShieldCheck, LogIn } from 'lucide-react'; import {
Search,
Bell,
Menu,
Calendar,
LogIn,
Check,
X,
BookOpen,
Bus,
Users,
Calendar as ClubIcon,
Code2,
Trophy,
Bot,
Rocket,
ChevronRight
} from 'lucide-react';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { BUS_ROUTES, FACULTY_DATA, CLUBS_DATA } from '@/constants';
interface HeaderProps { interface HeaderProps {
onToggleSidebar: () => void; onToggleSidebar: () => void;
} }
interface NotificationItem {
id: string;
title: string;
message: string;
time: string;
read: boolean;
type: 'academic' | 'dev' | 'campus' | 'bus';
}
export default function Header({ onToggleSidebar }: HeaderProps) { export default function Header({ onToggleSidebar }: HeaderProps) {
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [isNotifOpen, setIsNotifOpen] = useState(false);
const [currentDate, setCurrentDate] = useState(''); const [currentDate, setCurrentDate] = useState('');
const navigate = useNavigate(); const navigate = useNavigate();
const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle } = useAuth(); const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle } = useAuth();
const notifRef = useRef<HTMLDivElement>(null);
const searchRef = useRef<HTMLDivElement>(null);
const [notifications, setNotifications] = useState<NotificationItem[]>([
{
id: '1',
title: 'Moodle Notes & PYQs Updated',
message: '1st to 8th Semester notes and previous year question papers are published on Moodle.',
time: '10m ago',
read: false,
type: 'academic',
},
{
id: '2',
title: 'Dev Collab Hub Open',
message: 'Post open-source project ideas or find co-developers across departments.',
time: '1h ago',
read: false,
type: 'dev',
},
{
id: '3',
title: 'LeetCode Campus Rankings Live',
message: 'Monthly competitive programming leaderboard has been updated.',
time: '3h ago',
read: false,
type: 'academic',
},
{
id: '4',
title: 'Campus Bus Schedule Confirmed',
message: 'All 4 campus bus routes operating on 7:00 AM morning schedule.',
time: '1d ago',
read: true,
type: 'bus',
},
]);
const unreadCount = notifications.filter((n) => !n.read).length;
useEffect(() => { useEffect(() => {
const now = new Date(); const now = new Date();
@@ -24,33 +92,95 @@ export default function Header({ onToggleSidebar }: HeaderProps) {
setCurrentDate(formatted); setCurrentDate(formatted);
}, []); }, []);
const handleSearch = (e: React.FormEvent) => { // Listen for ⌘K shortcut
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault(); e.preventDefault();
if (!searchQuery.trim()) return; setIsSearchOpen((prev) => !prev);
const q = searchQuery.toLowerCase();
if (q.includes('note') || q.includes('pyq') || q.includes('syllabus')) {
navigate('/notes');
} else if (q.includes('bus') || q.includes('route')) {
navigate('/bus-routes');
} else if (q.includes('faculty') || q.includes('teacher') || q.includes('prof')) {
navigate('/faculty');
} else if (q.includes('club') || q.includes('event')) {
navigate('/events');
} else if (q.includes('ai') || q.includes('chat') || q.includes('bot')) {
navigate('/ai-assistant');
} else if (q.includes('community') || q.includes('qa') || q.includes('question')) {
navigate('/community');
} else if (q.includes('collab') || q.includes('dev')) {
navigate('/collab');
} else if (q.includes('raise') || q.includes('incubator') || q.includes('pitch')) {
navigate('/raise');
} else {
navigate(`/notes?search=${encodeURIComponent(searchQuery)}`);
} }
}; };
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
// Close dropdowns on outside click
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (notifRef.current && !notifRef.current.contains(e.target as Node)) {
setIsNotifOpen(false);
}
if (searchRef.current && !searchRef.current.contains(e.target as Node)) {
setIsSearchOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const markAllAsRead = () => {
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
};
const clearNotifications = () => {
setNotifications([]);
};
// Search indexing & results calculation
const searchResults = (() => {
if (!searchQuery.trim()) return [];
const q = searchQuery.toLowerCase();
const results: { title: string; desc: string; path: string; icon: any; category: string }[] = [];
// Check modules
if ('notes pyqs moodle syllabus question paper'.includes(q)) {
results.push({ title: 'Academic Notes & PYQs', desc: 'Semester course notes & past papers', path: '/notes', icon: BookOpen, category: 'Academics' });
}
if ('bus route timing transport travel pickup'.includes(q)) {
results.push({ title: 'Campus Bus Routes', desc: 'Schedules and stops for all campus buses', path: '/bus-routes', icon: Bus, category: 'Utilities' });
}
if ('chatbot assistant question help contact rules'.includes(q)) {
results.push({ title: 'Campus Chatbot', desc: 'Instant Q&A about campus life & rules', path: '/ai-assistant', icon: Bot, category: 'Academics' });
}
if ('collab dev project developer team coding github'.includes(q)) {
results.push({ title: 'Dev Collab Hub', desc: 'Find co-developers & build projects', path: '/collab', icon: Code2, category: 'Dev' });
}
if ('leetcode ranking leaderboard coding rank'.includes(q)) {
results.push({ title: 'LeetCode Leaderboard', desc: 'Campus competitive coding rankings', path: '/leetcode', icon: Trophy, category: 'Dev' });
}
// Search Bus Routes
BUS_ROUTES.forEach((r) => {
if (r.name.toLowerCase().includes(q) || r.number.toLowerCase().includes(q) || r.from.toLowerCase().includes(q)) {
results.push({ title: `${r.number}: ${r.name}`, desc: `Departure: ${r.departureTime} from ${r.from}`, path: '/bus-routes', icon: Bus, category: 'Bus Route' });
}
});
// Search Faculty
FACULTY_DATA.forEach((f) => {
if (f.name.toLowerCase().includes(q) || f.department.toLowerCase().includes(q) || (f.specialization && f.specialization.toLowerCase().includes(q))) {
results.push({ title: f.name, desc: `${f.designation} — ${f.department}`, path: '/faculty', icon: Users, category: 'Faculty' });
}
});
// Search Clubs
CLUBS_DATA.forEach((c) => {
if (c.name.toLowerCase().includes(q) || c.description.toLowerCase().includes(q)) {
results.push({ title: c.name, desc: c.description, path: '/events', icon: ClubIcon, category: 'Club' });
}
});
return results.slice(0, 6);
})();
const handleSelectResult = (path: string) => {
setIsSearchOpen(false);
setSearchQuery('');
navigate(path);
};
return ( return (
<header className="sticky top-0 z-30 w-full bg-white/80 backdrop-blur-md border-b border-slate-200/80 px-4 lg:px-8 py-3 flex items-center justify-between shadow-xs"> <header className="sticky top-0 z-30 w-full bg-white/90 backdrop-blur-md border-b border-slate-200/80 px-4 lg:px-8 py-3 flex items-center justify-between shadow-xs">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{/* Mobile Toggle */} {/* Mobile Toggle */}
<button <button
@@ -61,40 +191,191 @@ export default function Header({ onToggleSidebar }: HeaderProps) {
<Menu className="w-5 h-5" /> <Menu className="w-5 h-5" />
</button> </button>
{/* Global Search Bar */} {/* Global Interactive Search Input Container */}
<form onSubmit={handleSearch} className="relative w-64 sm:w-80 md:w-96"> <div className="relative" ref={searchRef}>
<div
onClick={() => setIsSearchOpen(true)}
className="relative w-64 sm:w-80 md:w-96 cursor-text"
>
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" /> <Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input <input
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => {
setSearchQuery(e.target.value);
setIsSearchOpen(true);
}}
onFocus={() => setIsSearchOpen(true)}
placeholder="Search notes, faculty, bus routes, clubs..." placeholder="Search notes, faculty, bus routes, clubs..."
className="w-full pl-10 pr-12 py-2 text-xs font-medium bg-slate-100/90 hover:bg-slate-100 focus:bg-white border border-transparent focus:border-purple-500/40 rounded-xl text-slate-800 placeholder-slate-400 outline-none transition-all shadow-inner" className="w-full pl-10 pr-12 py-2 text-xs font-medium bg-slate-100/90 hover:bg-slate-100 focus:bg-white border border-transparent focus:border-indigo-500/40 rounded-xl text-slate-800 placeholder-slate-400 outline-none transition-all shadow-inner"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
/> />
<span className="hidden sm:inline-flex absolute right-3 top-1/2 -translate-y-1/2 text-[10px] font-semibold text-slate-400 bg-white border border-slate-200 px-1.5 py-0.5 rounded-md shadow-2xs"> <span className="hidden sm:inline-flex absolute right-3 top-1/2 -translate-y-1/2 text-[10px] font-semibold text-slate-400 bg-white border border-slate-200 px-1.5 py-0.5 rounded-md shadow-2xs">
⌘K ⌘K
</span> </span>
</form> </div>
{/* Search Live Results Dropdown Overlay */}
{isSearchOpen && (
<div className="absolute top-full left-0 mt-2 w-full sm:w-[420px] bg-white rounded-2xl border border-slate-200 shadow-xl overflow-hidden z-50 p-2 text-left">
<div className="flex items-center justify-between px-3 py-2 border-b border-slate-100">
<span className="text-[11px] font-bold text-slate-400 uppercase tracking-wider">
{searchQuery.trim() ? 'Search Results' : 'Quick Navigation'}
</span>
<button
onClick={() => setIsSearchOpen(false)}
className="p-1 rounded-md text-slate-400 hover:text-slate-600 hover:bg-slate-100"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
<div className="max-h-72 overflow-y-auto custom-scrollbar p-1 space-y-1">
{searchResults.length > 0 ? (
searchResults.map((res, i) => {
const Icon = res.icon;
return (
<div
key={i}
onClick={() => handleSelectResult(res.path)}
className="flex items-center justify-between p-2.5 rounded-xl hover:bg-indigo-50/70 cursor-pointer transition-colors group"
>
<div className="flex items-center gap-3 min-w-0">
<div className="w-8 h-8 rounded-lg bg-indigo-100 text-indigo-600 flex items-center justify-center shrink-0">
<Icon className="w-4 h-4" />
</div>
<div className="flex flex-col min-w-0">
<span className="text-xs font-bold text-slate-900 group-hover:text-indigo-700 truncate" style={{ fontFamily: 'Poppins, sans-serif' }}>
{res.title}
</span>
<span className="text-[11px] text-slate-500 truncate">{res.desc}</span>
</div>
</div>
<span className="text-[9px] font-bold px-2 py-0.5 rounded-md bg-slate-100 text-slate-600 shrink-0">
{res.category}
</span>
</div>
);
})
) : (
<div className="p-4 text-center space-y-2">
<span className="text-xs text-slate-500 block">
{searchQuery.trim() ? `No matches found for "${searchQuery}"` : 'Type to search notes, faculty, routes & clubs'}
</span>
<div className="flex flex-wrap justify-center gap-1.5 pt-1">
<button
onClick={() => handleSelectResult('/notes')}
className="text-[11px] px-2.5 py-1 rounded-lg bg-slate-100 text-slate-700 font-medium hover:bg-indigo-50 hover:text-indigo-600"
>
Notes
</button>
<button
onClick={() => handleSelectResult('/bus-routes')}
className="text-[11px] px-2.5 py-1 rounded-lg bg-slate-100 text-slate-700 font-medium hover:bg-indigo-50 hover:text-indigo-600"
>
Bus Routes
</button>
<button
onClick={() => handleSelectResult('/faculty')}
className="text-[11px] px-2.5 py-1 rounded-lg bg-slate-100 text-slate-700 font-medium hover:bg-indigo-50 hover:text-indigo-600"
>
Faculty
</button>
</div>
</div>
)}
</div>
</div>
)}
</div>
</div> </div>
{/* Right Header Status Bar */} {/* Right Header Status Bar */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Date Display Badge */} {/* Date Display Badge */}
<div className="hidden md:flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-slate-100 text-slate-600 text-xs font-medium border border-slate-200/60"> <div className="hidden md:flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-slate-100 text-slate-600 text-xs font-medium border border-slate-200/60">
<Calendar className="w-3.5 h-3.5 text-purple-600" /> <Calendar className="w-3.5 h-3.5 text-indigo-600" />
<span>{currentDate}</span> <span>{currentDate}</span>
</div> </div>
{/* Notifications Icon */} {/* Functional Notifications Dropdown */}
<div className="relative" ref={notifRef}>
<button <button
className="relative p-2 rounded-xl bg-slate-100 hover:bg-purple-50 text-slate-600 hover:text-purple-600 transition-colors cursor-pointer border border-slate-200/60" onClick={() => setIsNotifOpen((prev) => !prev)}
className="relative p-2 rounded-xl bg-slate-100 hover:bg-indigo-50 text-slate-600 hover:text-indigo-600 transition-colors cursor-pointer border border-slate-200/60"
title="Notifications" title="Notifications"
> >
<Bell className="w-4 h-4" /> <Bell className="w-4 h-4" />
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-purple-600 ring-2 ring-white" /> {unreadCount > 0 && (
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-indigo-600 ring-2 ring-white" />
)}
</button> </button>
{/* Notifications Panel Dropdown */}
{isNotifOpen && (
<div className="absolute right-0 top-full mt-2 w-80 sm:w-96 bg-white rounded-2xl border border-slate-200 shadow-xl overflow-hidden z-50 p-3 text-left">
<div className="flex items-center justify-between pb-2 border-b border-slate-100">
<div className="flex items-center gap-2">
<span className="text-xs font-bold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
Notifications
</span>
{unreadCount > 0 && (
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-indigo-100 text-indigo-700">
{unreadCount} new
</span>
)}
</div>
<div className="flex items-center gap-2">
{unreadCount > 0 && (
<button
onClick={markAllAsRead}
className="text-[11px] font-semibold text-indigo-600 hover:text-indigo-800"
>
Mark all read
</button>
)}
{notifications.length > 0 && (
<button
onClick={clearNotifications}
className="text-[11px] font-semibold text-slate-400 hover:text-slate-600"
>
Clear
</button>
)}
</div>
</div>
<div className="max-h-72 overflow-y-auto custom-scrollbar pt-2 space-y-2">
{notifications.length > 0 ? (
notifications.map((n) => (
<div
key={n.id}
className={`p-2.5 rounded-xl border transition-colors ${
n.read ? 'bg-slate-50/60 border-slate-100' : 'bg-indigo-50/40 border-indigo-100'
}`}
>
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-bold text-slate-900 leading-snug" style={{ fontFamily: 'Poppins, sans-serif' }}>
{n.title}
</span>
<span className="text-[10px] text-slate-400 font-medium">{n.time}</span>
</div>
<p className="text-[11px] text-slate-600 leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
{n.message}
</p>
</div>
))
) : (
<div className="p-6 text-center text-xs text-slate-400">
No new notifications
</div>
)}
</div>
</div>
)}
</div>
{/* User Auth Quick Badge */} {/* User Auth Quick Badge */}
{isAuthenticated && user ? ( {isAuthenticated && user ? (
<div className="flex items-center gap-2 pl-2 border-l border-slate-200"> <div className="flex items-center gap-2 pl-2 border-l border-slate-200">
@@ -102,11 +383,11 @@ export default function Header({ onToggleSidebar }: HeaderProps) {
<img <img
src={user.pictureUrl} src={user.pictureUrl}
alt={user.name} alt={user.name}
className="w-8 h-8 rounded-full border border-purple-300 object-cover" className="w-8 h-8 rounded-full border border-indigo-300 object-cover"
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
/> />
) : ( ) : (
<div className="w-8 h-8 rounded-full bg-purple-600 text-white font-bold text-xs flex items-center justify-center"> <div className="w-8 h-8 rounded-full bg-indigo-600 text-white font-bold text-xs flex items-center justify-center">
{user.name.charAt(0)} {user.name.charAt(0)}
</div> </div>
)} )}
@@ -114,7 +395,7 @@ export default function Header({ onToggleSidebar }: HeaderProps) {
<span className="text-xs font-bold text-slate-800 leading-tight" style={{ fontFamily: 'Poppins, sans-serif' }}> <span className="text-xs font-bold text-slate-800 leading-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>
{user.name} {user.name}
</span> </span>
<span className="text-[10px] text-purple-600 font-semibold"> <span className="text-[10px] text-indigo-600 font-semibold">
{isVerifiedStudent ? 'Verified Student' : 'RIT Account'} {isVerifiedStudent ? 'Verified Student' : 'RIT Account'}
</span> </span>
</div> </div>
@@ -122,7 +403,7 @@ export default function Header({ onToggleSidebar }: HeaderProps) {
) : ( ) : (
<button <button
onClick={loginWithGoogle} onClick={loginWithGoogle}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-purple-600 hover:bg-purple-700 text-white text-xs font-bold shadow-sm transition-all cursor-pointer" className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-bold shadow-sm transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Poppins, sans-serif' }}
> >
<LogIn className="w-3.5 h-3.5" /> <LogIn className="w-3.5 h-3.5" />

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { import {
LayoutDashboard, LayoutDashboard,
@@ -14,13 +14,12 @@ import {
Code2, Code2,
Trophy, Trophy,
Rocket, Rocket,
ShieldAlert,
LogOut, LogOut,
LogIn, LogIn,
ShieldCheck, ShieldCheck,
ChevronLeft,
ChevronRight, ChevronRight,
Sparkles, GitBranch,
Menu,
X X
} from 'lucide-react'; } from 'lucide-react';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
@@ -28,13 +27,14 @@ import { useAuth } from '@/context/AuthContext';
interface SidebarProps { interface SidebarProps {
isOpen: boolean; isOpen: boolean;
onToggle: () => void; onToggle: () => void;
isCollapsed: boolean;
onToggleCollapse: () => void;
} }
interface NavItem { interface NavItem {
label: string; label: string;
path: string; path: string;
icon: any; icon: any;
badge?: string;
highlight?: boolean; highlight?: boolean;
} }
@@ -43,9 +43,13 @@ interface NavGroup {
items: NavItem[]; items: NavItem[];
} }
export default function Sidebar({ isOpen, onToggle }: SidebarProps) { export default function Sidebar({
isOpen,
onToggle,
isCollapsed,
onToggleCollapse,
}: SidebarProps) {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate();
const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth(); const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth();
const checkIsActive = (linkPath: string) => { const checkIsActive = (linkPath: string) => {
@@ -72,7 +76,7 @@ export default function Sidebar({ isOpen, onToggle }: SidebarProps) {
{ label: 'Dashboard', path: '/', icon: LayoutDashboard }, { label: 'Dashboard', path: '/', icon: LayoutDashboard },
{ label: 'Notes & PYQs', path: '/notes', icon: BookOpen }, { label: 'Notes & PYQs', path: '/notes', icon: BookOpen },
{ label: 'Student Toolkit', path: '/notes?section=toolkit', icon: Wrench }, { label: 'Student Toolkit', path: '/notes?section=toolkit', icon: Wrench },
{ label: 'RIT AI Chatbot', path: '/ai-assistant', icon: Bot, badge: 'AI' }, { label: 'RIT Chatbot', path: '/ai-assistant', icon: Bot },
], ],
}, },
{ {
@@ -88,9 +92,9 @@ export default function Sidebar({ isOpen, onToggle }: SidebarProps) {
title: 'COMMUNITY & DEV', title: 'COMMUNITY & DEV',
items: [ items: [
{ label: 'Freshers Q&A', path: '/community', icon: MessageSquare }, { label: 'Freshers Q&A', path: '/community', icon: MessageSquare },
{ label: 'Dev Collab Hub', path: '/collab', icon: Code2 }, { label: 'Dev Collab Hub', path: '/collab', icon: Code2, highlight: true },
{ label: 'LeetCode Board', path: '/leetcode', icon: Trophy }, { label: 'LeetCode Board', path: '/leetcode', icon: Trophy },
{ label: 'RAISE Incubator', path: '/raise', icon: Rocket, highlight: true }, { label: 'RAISE Incubator', path: '/raise', icon: Rocket },
], ],
}, },
]; ];
@@ -110,75 +114,88 @@ export default function Sidebar({ isOpen, onToggle }: SidebarProps) {
)} )}
</AnimatePresence> </AnimatePresence>
{/* Main Sidebar Container */} {/* Main Sidebar Container - Sleek Professional Deep Indigo */}
<aside <aside
className={`fixed top-0 left-0 z-50 h-screen w-64 xl:w-72 bg-gradient-to-b from-[#1E1B4B] via-[#2E1065] to-[#3B0764] text-white transition-transform duration-300 ease-in-out lg:translate-x-0 ${ className={`fixed top-0 left-0 z-50 h-screen bg-[#0F172A] text-white transition-all duration-300 ease-in-out lg:translate-x-0 ${
isOpen ? 'translate-x-0' : '-translate-x-full' isOpen ? 'translate-x-0' : '-translate-x-full'
} flex flex-col justify-between border-r border-white/10 shadow-[8px_0_32px_rgba(0,0,0,0.3)]`} } ${
isCollapsed ? 'w-20' : 'w-64 xl:w-72'
} flex flex-col justify-between border-r border-slate-800 shadow-[8px_0_30px_rgba(0,0,0,0.3)]`}
> >
{/* Top Header & Branding */} {/* Top Branding & Collapse Button */}
<div className="p-6 flex flex-col gap-6"> <div className="p-4 sm:p-5 flex flex-col gap-5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-3 group"> <Link to="/" className="flex items-center gap-3 group min-w-0">
<div className="relative p-2 rounded-2xl bg-gradient-to-br from-purple-500/30 to-indigo-500/20 border border-white/20 shadow-lg group-hover:scale-105 transition-transform"> <div className="p-2 rounded-2xl bg-indigo-950/80 border border-indigo-500/20 shadow-sm shrink-0">
<img <img
src="/logo.png" src="/logo.png"
alt="RIT Logo" alt="RIT Logo"
className="w-8 h-8 object-contain rounded-full" className="w-7 h-7 object-contain rounded-full"
/> />
</div> </div>
<div className="flex flex-col"> {!isCollapsed && (
<div className="flex items-center gap-1.5"> <div className="flex flex-col min-w-0">
<span className="font-extrabold text-white text-xl tracking-tight" style={{ fontFamily: 'Poppins, sans-serif' }}> <span className="font-bold text-white text-lg tracking-tight truncate" style={{ fontFamily: 'Poppins, sans-serif' }}>
RIT Portal RIT Portal
</span> </span>
<span className="text-[10px] font-bold px-1.5 py-0.5 rounded-full bg-purple-500/30 text-purple-200 border border-purple-400/30"> <span className="text-[10px] text-indigo-300/70 font-medium tracking-wider uppercase truncate" style={{ fontFamily: 'Inter, sans-serif' }}>
v2.0 Freshers Hub
</span>
</div>
<span className="text-[10px] font-medium text-purple-200/70 tracking-wider uppercase" style={{ fontFamily: 'Inter, sans-serif' }}>
Rajalakshmi Tech
</span> </span>
</div> </div>
)}
</Link> </Link>
{/* Mobile Close Button */}
<button <button
onClick={onToggle} onClick={onToggle}
className="lg:hidden p-2 rounded-xl bg-white/10 hover:bg-white/20 text-purple-200 transition-colors" className="lg:hidden p-1.5 rounded-xl bg-slate-800 text-slate-200 hover:bg-slate-700"
> >
<X className="w-5 h-5" /> <X className="w-5 h-5" />
</button> </button>
{/* Desktop Collapse Toggle Button */}
<button
onClick={onToggleCollapse}
className="hidden lg:flex p-1.5 rounded-xl bg-slate-800/80 hover:bg-indigo-900/60 text-indigo-300 hover:text-white border border-slate-700/60 transition-colors"
title={isCollapsed ? 'Expand Sidebar' : 'Collapse Sidebar'}
>
{isCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
</button>
</div> </div>
{/* Quick Pitch CTA inside Sidebar */} {/* Quick Dev Collab Launch CTA inside Sidebar (Expanded only) */}
{!isCollapsed && (
<Link <Link
to="/raise" to="/collab"
className="w-full flex items-center justify-between p-3.5 rounded-2xl bg-gradient-to-r from-purple-600 via-indigo-600 to-purple-700 hover:from-purple-500 hover:to-indigo-500 border border-purple-400/30 shadow-md shadow-purple-900/40 text-white group transition-all duration-200" className="w-full flex items-center justify-between p-3 rounded-2xl bg-indigo-600 hover:bg-indigo-500 border border-indigo-400/30 text-white group transition-all duration-200 shadow-md shadow-indigo-950/50"
> >
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5 min-w-0">
<div className="w-7 h-7 rounded-xl bg-amber-400/20 border border-amber-300/40 flex items-center justify-center text-amber-300"> <div className="w-7 h-7 rounded-xl bg-white/20 border border-white/30 flex items-center justify-center text-white shrink-0">
<Sparkles className="w-4 h-4" /> <GitBranch className="w-4 h-4" />
</div> </div>
<div className="flex flex-col text-left"> <div className="flex flex-col text-left min-w-0">
<span className="text-xs font-bold leading-tight" style={{ fontFamily: 'Poppins, sans-serif' }}> <span className="text-xs font-bold leading-tight truncate" style={{ fontFamily: 'Poppins, sans-serif' }}>
RAISE Incubator Dev Collab Hub
</span> </span>
<span className="text-[10px] text-purple-200/80">Pitch Startup Idea</span> <span className="text-[10px] text-indigo-100/80 truncate">Co-Developer Projects</span>
</div> </div>
</div> </div>
<ChevronRight className="w-4 h-4 text-purple-200 group-hover:translate-x-1 transition-transform" /> <ChevronRight className="w-4 h-4 text-indigo-200 group-hover:translate-x-0.5 transition-transform" />
</Link> </Link>
)}
{/* Navigation Groups */} {/* Navigation Groups */}
<nav className="flex flex-col gap-6 overflow-y-auto max-h-[calc(100vh-340px)] custom-scrollbar pr-1"> <nav className="flex flex-col gap-5 overflow-y-auto max-h-[calc(100vh-320px)] custom-scrollbar pr-0.5">
{navGroups.map((group) => ( {navGroups.map((group) => (
<div key={group.title} className="flex flex-col gap-1"> <div key={group.title} className="flex flex-col gap-1">
{!isCollapsed && (
<span <span
className="px-3 text-[10px] font-bold text-purple-300/60 tracking-widest uppercase mb-1" className="px-3 text-[10px] font-bold text-slate-400/70 tracking-widest uppercase mb-1"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
{group.title} {group.title}
</span> </span>
)}
{group.items.map((item) => { {group.items.map((item) => {
const isActive = checkIsActive(item.path); const isActive = checkIsActive(item.path);
@@ -188,32 +205,27 @@ export default function Sidebar({ isOpen, onToggle }: SidebarProps) {
<Link <Link
key={item.path} key={item.path}
to={item.path} to={item.path}
className={`relative flex items-center justify-between px-3.5 py-2.5 rounded-xl text-xs font-semibold transition-all duration-200 group ${ title={isCollapsed ? item.label : undefined}
className={`relative flex items-center ${
isCollapsed ? 'justify-center px-0 py-3' : 'justify-between px-3 py-2.5'
} rounded-xl text-xs font-semibold transition-all duration-200 group ${
isActive isActive
? 'bg-gradient-to-r from-purple-600/90 via-purple-700/80 to-indigo-800/90 text-white shadow-lg shadow-purple-950/50 border border-purple-400/30' ? 'bg-indigo-600 text-white shadow-sm border border-indigo-400/30'
: 'text-purple-200/80 hover:text-white hover:bg-white/10' : 'text-slate-300 hover:text-white hover:bg-slate-800/60'
}`} }`}
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Poppins, sans-serif' }}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Icon <Icon
className={`w-4 h-4 transition-colors ${ className={`w-4 h-4 shrink-0 ${
isActive isActive ? 'text-white' : 'text-slate-400 group-hover:text-slate-200'
? 'text-white'
: 'text-purple-300/70 group-hover:text-purple-200'
}`} }`}
/> />
<span>{item.label}</span> {!isCollapsed && <span>{item.label}</span>}
</div> </div>
{item.badge && ( {!isCollapsed && item.highlight && !isActive && (
<span className="text-[9px] font-bold px-1.5 py-0.5 rounded-md bg-amber-500/20 text-amber-300 border border-amber-400/30"> <span className="w-2 h-2 rounded-full bg-indigo-400 animate-pulse" />
{item.badge}
</span>
)}
{item.highlight && !isActive && (
<span className="w-2 h-2 rounded-full bg-rose-400 animate-pulse" />
)} )}
</Link> </Link>
); );
@@ -224,54 +236,55 @@ export default function Sidebar({ isOpen, onToggle }: SidebarProps) {
</div> </div>
{/* Footer User Profile & Auth Section */} {/* Footer User Profile & Auth Section */}
<div className="p-4 border-t border-white/10 bg-black/20 backdrop-blur-md"> <div className="p-3 border-t border-slate-800 bg-slate-950/60">
{isAuthenticated && user ? ( {isAuthenticated && user ? (
<div className="flex items-center justify-between p-2.5 rounded-2xl bg-white/5 border border-white/10"> <div className={`flex items-center ${isCollapsed ? 'justify-center p-2' : 'justify-between p-2.5'} rounded-2xl bg-white/5 border border-white/10`}>
<div className="flex items-center gap-3 min-w-0"> <div className="flex items-center gap-2.5 min-w-0">
{user.pictureUrl ? ( {user.pictureUrl ? (
<img <img
src={user.pictureUrl} src={user.pictureUrl}
alt={user.name} alt={user.name}
className="w-9 h-9 rounded-xl border border-purple-400/40 object-cover shrink-0" className="w-8 h-8 rounded-xl border border-indigo-400/40 object-cover shrink-0"
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
/> />
) : ( ) : (
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center text-white font-bold text-sm shrink-0"> <div className="w-8 h-8 rounded-xl bg-indigo-600 text-white font-bold text-xs flex items-center justify-center shrink-0">
{user.name.charAt(0).toUpperCase()} {user.name.charAt(0).toUpperCase()}
</div> </div>
)} )}
<div className="flex flex-col min-w-0"> {!isCollapsed && (
<div className="flex flex-col min-w-0 text-left">
<span className="text-xs font-bold text-white truncate" style={{ fontFamily: 'Poppins, sans-serif' }}> <span className="text-xs font-bold text-white truncate" style={{ fontFamily: 'Poppins, sans-serif' }}>
{user.name} {user.name}
</span> </span>
<div className="flex items-center gap-1 text-[10px] text-purple-200/70 truncate"> <span className="text-[10px] text-slate-400 truncate">
{isVerifiedStudent ? ( {isVerifiedStudent ? 'Verified Student' : 'RIT Account'}
<span className="text-emerald-400 font-semibold flex items-center gap-0.5">
<ShieldCheck className="w-3 h-3" /> Verified Student
</span> </span>
) : ( </div>
<span>RIT Account</span>
)} )}
</div> </div>
</div>
</div>
{!isCollapsed && (
<button <button
onClick={logout} onClick={logout}
title="Sign Out" title="Sign Out"
className="p-2 rounded-xl hover:bg-white/10 text-purple-300 hover:text-white transition-colors cursor-pointer" className="p-1.5 rounded-xl hover:bg-white/10 text-slate-400 hover:text-white transition-colors cursor-pointer"
> >
<LogOut className="w-4 h-4" /> <LogOut className="w-4 h-4" />
</button> </button>
)}
</div> </div>
) : ( ) : (
<button <button
onClick={loginWithGoogle} onClick={loginWithGoogle}
className="w-full flex items-center justify-center gap-2 p-3 rounded-2xl bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-500 hover:to-indigo-500 text-white font-bold text-xs shadow-md border border-purple-400/30 transition-all cursor-pointer" title={isCollapsed ? 'Student Sign In' : undefined}
className={`w-full flex items-center justify-center gap-2 ${
isCollapsed ? 'p-2.5' : 'p-2.5'
} rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition-all cursor-pointer`}
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Poppins, sans-serif' }}
> >
<LogIn className="w-4 h-4" /> <LogIn className="w-4 h-4 shrink-0" />
<span>Student Sign In</span> {!isCollapsed && <span>Student Sign In</span>}
</button> </button>
)} )}
</div> </div>

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Outlet, useLocation } from 'react-router-dom'; import { Outlet, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import Sidebar from '@/components/Sidebar/Sidebar'; import Sidebar from '@/components/Sidebar/Sidebar';
@@ -13,17 +13,36 @@ const pageVariants = {
export default function MainLayout() { export default function MainLayout() {
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => {
return localStorage.getItem('rit_sidebar_collapsed') === 'true';
});
const location = useLocation(); const location = useLocation();
const toggleSidebar = () => setIsSidebarOpen((prev) => !prev); const toggleSidebar = () => setIsSidebarOpen((prev) => !prev);
const toggleSidebarCollapse = () => {
setIsSidebarCollapsed((prev) => {
const next = !prev;
localStorage.setItem('rit_sidebar_collapsed', String(next));
return next;
});
};
return ( return (
<div className="min-h-screen bg-[#F8FAFC] flex flex-col lg:flex-row font-sans selection:bg-purple-600 selection:text-white"> <div className="min-h-screen bg-[#F8FAFC] flex flex-col lg:flex-row font-sans selection:bg-purple-700 selection:text-white">
{/* Left Sidebar Navigation */} {/* Left Sidebar Navigation */}
<Sidebar isOpen={isSidebarOpen} onToggle={toggleSidebar} /> <Sidebar
isOpen={isSidebarOpen}
onToggle={toggleSidebar}
isCollapsed={isSidebarCollapsed}
onToggleCollapse={toggleSidebarCollapse}
/>
{/* Main Right Viewport Wrapper */} {/* Main Right Viewport Wrapper */}
<div className="flex-1 flex flex-col min-w-0 lg:pl-64 xl:pl-72 transition-all duration-300"> <div
className={`flex-1 flex flex-col min-w-0 transition-all duration-300 ${
isSidebarCollapsed ? 'lg:pl-20' : 'lg:pl-64 xl:pl-72'
}`}
>
{/* Top Header */} {/* Top Header */}
<Header onToggleSidebar={toggleSidebar} /> <Header onToggleSidebar={toggleSidebar} />

View File

@@ -1,400 +1,93 @@
import { useState, useRef } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import { import {
BookOpen, BookOpen,
Bot, Bot,
Bus, Bus,
Users, Calendar,
Trophy,
Rocket,
Code2, Code2,
MapPin, Trophy
Sparkles,
ArrowRight,
Play,
Pause,
Volume2,
VolumeX,
FileText,
Clock,
ShieldCheck,
CheckCircle2,
ExternalLink,
ChevronRight,
Bell,
GraduationCap
} from 'lucide-react'; } from 'lucide-react';
import { FEATURES, FACULTY_DATA, BUS_ROUTES } from '@/constants';
import { useAuth } from '@/context/AuthContext';
export default function Home() { export default function Home() {
const { user, isAuthenticated } = useAuth(); const quickLaunchModules = [
const [isPlaying, setIsPlaying] = useState(true); { label: 'Bus Routes', path: '/bus-routes', icon: Bus, desc: 'Schedules & Stops' },
const [isMuted, setIsMuted] = useState(true); { label: 'Notes & PYQs', path: '/notes', icon: BookOpen, desc: 'Moodle Materials' },
const videoRef = useRef<HTMLVideoElement>(null); { label: 'Campus Chatbot', path: '/ai-assistant', icon: Bot, desc: 'Instant Q&A' },
{ label: 'Student Clubs', path: '/events', icon: Calendar, desc: 'Clubs & Events' },
const togglePlay = () => { { label: 'LeetCode Ranking', path: '/leetcode', icon: Trophy, desc: 'Coder Leaderboard' },
if (videoRef.current) { { label: 'Dev Collab', path: '/collab', icon: Code2, desc: 'Co-Developers' },
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
}
};
const toggleMute = () => {
if (videoRef.current) {
videoRef.current.muted = !isMuted;
setIsMuted(!isMuted);
}
};
const dailyNotices = [
{
id: '1',
title: 'Official Moodle Notes & PYQs Updated',
date: 'Aug 12, 2026',
category: 'Academics',
desc: '1st to 8th semester course notes and previous year question papers are live on Moodle portal.',
color: '#8B5CF6',
},
{
id: '2',
title: 'RAISE Incubator Pitching Registration Open',
date: 'Aug 15, 2026',
category: 'Innovation',
desc: 'Submit your startup ideation proposals to win prototyping grants up to ₹2 Lakhs.',
color: '#EC4899',
},
{
id: '3',
title: 'RIT Code Fest 24-Hour Hackathon',
date: 'Aug 20, 2026',
category: 'Events',
desc: 'Annual coding hackathon hosted by CSE Department. Cash prizes & internship opportunities.',
color: '#F97316',
},
]; ];
return ( return (
<div className="space-y-8"> <div className="relative overflow-hidden rounded-3xl h-[calc(100vh-120px)] min-h-[500px] flex flex-col justify-between p-6 sm:p-10 text-white shadow-2xl border border-slate-800 bg-[#0F172A]">
{/* ─── 1. Welcome Hero Banner with Video Playback ─────────────────── */} {/* Background Video (Low Bitrate & AutoPlay Muted) */}
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="relative overflow-hidden rounded-3xl bg-gradient-to-r from-[#1E1B4B] via-[#2E1065] to-[#3B0764] p-6 sm:p-8 lg:p-10 text-white shadow-xl shadow-purple-950/20 border border-purple-500/20"
>
{/* Subtle Decorative Background Glows */}
<div className="absolute -right-20 -top-20 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl pointer-events-none" />
<div className="absolute -left-20 -bottom-20 w-96 h-96 bg-indigo-500/20 rounded-full blur-3xl pointer-events-none" />
<div className="relative z-10 grid grid-cols-1 lg:grid-cols-12 gap-8 items-center">
{/* Left Text & CTAs */}
<div className="lg:col-span-7 space-y-5 text-left">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 border border-white/20 text-xs font-semibold text-purple-200">
<Clock className="w-3.5 h-3.5 text-purple-300" />
<span>{new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</span>
</div>
<h1 className="text-3xl sm:text-4xl xl:text-5xl font-extrabold tracking-tight leading-tight" style={{ fontFamily: 'Playfair Display, serif' }}>
Welcome back,{' '}
<span className="bg-gradient-to-r from-purple-200 via-pink-200 to-amber-200 bg-clip-text text-transparent">
{isAuthenticated && user ? user.name.split(' ')[0] : 'Student'}!
</span>
</h1>
<p className="text-sm sm:text-base text-purple-200/90 leading-relaxed max-w-xl" style={{ fontFamily: 'Inter, sans-serif' }}>
Always stay updated with semester notes, live bus routes, AI chatbot assistant, community Q&A, and startup incubation pitching.
</p>
{/* Quick Action CTAs */}
<div className="pt-2 flex flex-wrap items-center gap-3">
<Link
to="/notes"
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-white hover:bg-purple-50 text-purple-950 font-bold text-xs shadow-lg transition-all cursor-pointer hover:scale-[1.02]"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<BookOpen className="w-4 h-4 text-purple-700" />
<span>Launch Moodle Notes</span>
<ArrowRight className="w-3.5 h-3.5" />
</Link>
<Link
to="/ai-assistant"
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-white/10 hover:bg-white/20 border border-white/20 text-white font-semibold text-xs transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Bot className="w-4 h-4 text-amber-300" />
<span>Ask AI Chatbot</span>
</Link>
<Link
to="/raise"
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white font-bold text-xs shadow-md border border-white/20 transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Rocket className="w-4 h-4 text-amber-200" />
<span>RAISE Incubator</span>
</Link>
</div>
</div>
{/* Right Video Playback & Preview Showcase */}
<div className="lg:col-span-5 relative">
<div className="relative rounded-2xl overflow-hidden border border-white/20 shadow-2xl bg-black/40 group">
<video <video
ref={videoRef}
src="/campus-video.mp4" src="/campus-video.mp4"
autoPlay autoPlay
loop loop
muted={isMuted} muted
playsInline playsInline
className="w-full h-48 sm:h-56 lg:h-64 object-cover" preload="auto"
className="absolute inset-0 w-full h-full object-cover pointer-events-none"
/> />
{/* Video Overlay Controls */} {/* Clean Dark Vignette Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-black/20 opacity-90 group-hover:opacity-100 transition-opacity p-4 flex flex-col justify-between"> <div className="absolute inset-0 bg-slate-950/45 backdrop-blur-[1px]" />
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-md bg-purple-600/80 text-white border border-purple-400/40"> {/* Top Branding Badge */}
Campus Tour Video <div className="relative z-10 text-center sm:text-left">
</span> <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-black/40 border border-white/20 text-xs font-semibold text-white backdrop-blur-md">
<button <span>Rajalakshmi Institute of Technology</span>
onClick={toggleMute} </div>
className="p-1.5 rounded-lg bg-black/50 hover:bg-black/80 text-white transition-colors cursor-pointer" </div>
title={isMuted ? 'Unmute Sound' : 'Mute Sound'}
{/* Center Main Hero Title */}
<div className="relative z-10 max-w-3xl mx-auto text-center space-y-4 my-auto py-4">
<h1
className="text-4xl sm:text-6xl font-black tracking-tight leading-tight text-white drop-shadow-[0_4px_24px_rgba(0,0,0,0.95)]"
style={{ fontFamily: 'Playfair Display, serif', color: '#FFFFFF', textShadow: '0 4px 24px rgba(0,0,0,0.95)' }}
> >
{isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />} RIT Freshers Hub
</button> </h1>
</div> <p
className="text-sm sm:text-base text-slate-100 font-medium leading-relaxed max-w-2xl mx-auto drop-shadow-md"
<div className="flex items-center justify-between"> style={{ fontFamily: 'Inter, sans-serif' }}
<div className="flex flex-col">
<span className="text-xs font-bold text-white" style={{ fontFamily: 'Poppins, sans-serif' }}>
RIT Campus Infrastructure
</span>
<span className="text-[10px] text-slate-300">Official Rajalakshmi Institute of Technology Overview</span>
</div>
<button
onClick={togglePlay}
className="p-2.5 rounded-full bg-white/90 hover:bg-white text-purple-900 shadow-lg transition-transform hover:scale-110 cursor-pointer"
> >
{isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4 fill-current ml-0.5" />} Centralized campus portal — academic notes, chatbot, bus routes, faculty directory & student developer tools.
</button>
</div>
</div>
</div>
</div>
</div>
</motion.div>
{/* ─── 2. Enterprise Quick Metrics Grid ────────────────────────────── */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5">
<div className="p-5 rounded-2xl bg-white border border-slate-200/80 shadow-xs flex items-center justify-between hover:border-purple-300 transition-all">
<div className="space-y-1 text-left">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>
Academic Notes
</span>
<div className="text-2xl font-extrabold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
1st – 8th Sem
</div>
<span className="text-[11px] text-emerald-600 font-semibold flex items-center gap-1">
<CheckCircle2 className="w-3.5 h-3.5" /> Moodle Verified
</span>
</div>
<div className="w-12 h-12 rounded-2xl bg-purple-50 text-purple-600 border border-purple-200/60 flex items-center justify-center font-bold">
<BookOpen className="w-6 h-6" />
</div>
</div>
<div className="p-5 rounded-2xl bg-white border border-slate-200/80 shadow-xs flex items-center justify-between hover:border-purple-300 transition-all">
<div className="space-y-1 text-left">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>
Bus Routes
</span>
<div className="text-2xl font-extrabold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
{BUS_ROUTES.length} Active Routes
</div>
<span className="text-[11px] text-blue-600 font-semibold flex items-center gap-1">
<Clock className="w-3.5 h-3.5" /> 7:00 AM Departures
</span>
</div>
<div className="w-12 h-12 rounded-2xl bg-blue-50 text-blue-600 border border-blue-200/60 flex items-center justify-center font-bold">
<Bus className="w-6 h-6" />
</div>
</div>
<div className="p-5 rounded-2xl bg-white border border-slate-200/80 shadow-xs flex items-center justify-between hover:border-purple-300 transition-all">
<div className="space-y-1 text-left">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>
AI Assistant
</span>
<div className="text-2xl font-extrabold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
24/7 Live
</div>
<span className="text-[11px] text-purple-600 font-semibold flex items-center gap-1">
<Sparkles className="w-3.5 h-3.5" /> Instant Answers
</span>
</div>
<div className="w-12 h-12 rounded-2xl bg-purple-50 text-purple-600 border border-purple-200/60 flex items-center justify-center font-bold">
<Bot className="w-6 h-6" />
</div>
</div>
<div className="p-5 rounded-2xl bg-white border border-slate-200/80 shadow-xs flex items-center justify-between hover:border-purple-300 transition-all">
<div className="space-y-1 text-left">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>
Community Q&A
</span>
<div className="text-2xl font-extrabold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
Senior Helpers
</div>
<span className="text-[11px] text-rose-600 font-semibold flex items-center gap-1">
<ShieldCheck className="w-3.5 h-3.5" /> Verified Answers
</span>
</div>
<div className="w-12 h-12 rounded-2xl bg-rose-50 text-rose-600 border border-rose-200/60 flex items-center justify-center font-bold">
<Users className="w-6 h-6" />
</div>
</div>
</div>
{/* ─── 3. Main Dashboard Layout (Two Columns: Courses Grid + Right Widgets) ────── */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 text-left">
{/* Left 8 Cols: Enrolled Modules / Quick Access */}
<div className="lg:col-span-8 space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h2 className="text-xl font-bold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
Academic Modules & Tools
</h2>
<p className="text-xs text-slate-500">Quick access to all essential student portal tools</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
{FEATURES.map((feature) => {
return (
<div
key={feature.id}
className="p-5 rounded-2xl bg-white border border-slate-200/80 hover:border-purple-400/60 shadow-xs hover:shadow-md transition-all flex flex-col justify-between gap-4 group"
>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div
className="w-10 h-10 rounded-xl flex items-center justify-center font-bold"
style={{ backgroundColor: feature.bgColor, color: feature.color }}
>
<BookOpen className="w-5 h-5" />
</div>
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-slate-100 text-slate-600">
Official Module
</span>
</div>
<h3 className="text-base font-bold text-slate-900 group-hover:text-purple-700 transition-colors" style={{ fontFamily: 'Poppins, sans-serif' }}>
{feature.title}
</h3>
<p className="text-xs text-slate-600 leading-relaxed line-clamp-2" style={{ fontFamily: 'Inter, sans-serif' }}>
{feature.description}
</p> </p>
</div> </div>
<Link {/* Bottom Transparent Glassmorphic Quick Launch Bar */}
to={feature.path} <div className="relative z-10 pt-2">
className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-slate-100 group-hover:bg-purple-600 text-slate-700 group-hover:text-white text-xs font-bold transition-all cursor-pointer" <div className="text-center mb-3">
style={{ fontFamily: 'Poppins, sans-serif' }} <span className="text-[11px] font-bold text-slate-300 uppercase tracking-widest bg-black/50 px-3.5 py-1.5 rounded-full border border-white/15 backdrop-blur-md">
> Quick Launch Portal Modules
<span>Launch Module</span> </span>
<ChevronRight className="w-3.5 h-3.5 group-hover:translate-x-1 transition-transform" />
</Link>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3 sm:gap-4">
{quickLaunchModules.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.label}
to={item.path}
className="flex flex-col items-center justify-center p-3.5 sm:p-4 rounded-2xl bg-white/10 hover:bg-white/25 border border-white/20 backdrop-blur-md text-white transition-all cursor-pointer hover:scale-[1.04] shadow-lg group text-center"
>
<div className="w-9 h-9 rounded-xl bg-white/20 flex items-center justify-center mb-2 group-hover:bg-indigo-600 transition-colors">
<Icon className="w-4 h-4 text-white" />
</div>
<span className="text-xs sm:text-sm font-bold truncate max-w-full" style={{ fontFamily: 'Poppins, sans-serif' }}>
{item.label}
</span>
<span className="text-[10px] sm:text-[11px] text-slate-300 truncate max-w-full">
{item.desc}
</span>
</Link>
); );
})} })}
</div> </div>
</div> </div>
{/* Right 4 Cols: Faculty Spotlight & Daily Notices Widget */}
<div className="lg:col-span-4 space-y-6">
{/* Featured Instructors / Faculty */}
<div className="p-6 rounded-2xl bg-white border border-slate-200/80 shadow-xs space-y-4">
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
<h3 className="text-sm font-bold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
Course Instructors
</h3>
<Link to="/faculty" className="text-xs font-bold text-purple-600 hover:text-purple-700">
See all
</Link>
</div>
<div className="space-y-3">
{FACULTY_DATA.slice(0, 4).map((prof) => (
<div key={prof.id} className="flex items-center gap-3 p-2.5 rounded-xl hover:bg-slate-50 transition-colors">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 text-white font-bold text-xs flex items-center justify-center shrink-0">
{prof.name.replace('Dr. ', '').replace('Prof. ', '').charAt(0)}
</div>
<div className="flex-1 min-w-0">
<h4 className="text-xs font-bold text-slate-900 truncate" style={{ fontFamily: 'Poppins, sans-serif' }}>
{prof.name}
</h4>
<span className="text-[11px] text-slate-500 block truncate">{prof.department}</span>
</div>
<Link
to="/faculty"
className="p-1.5 rounded-lg bg-slate-100 text-slate-600 hover:bg-purple-100 hover:text-purple-700 transition-colors"
>
<ChevronRight className="w-4 h-4" />
</Link>
</div>
))}
</div>
</div>
{/* Daily Notices & Announcements */}
<div className="p-6 rounded-2xl bg-white border border-slate-200/80 shadow-xs space-y-4">
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
<div className="flex items-center gap-2">
<Bell className="w-4 h-4 text-purple-600" />
<h3 className="text-sm font-bold text-slate-900" style={{ fontFamily: 'Poppins, sans-serif' }}>
Daily Campus Notices
</h3>
</div>
<span className="text-[10px] font-bold text-emerald-600 bg-emerald-50 border border-emerald-200/60 px-2 py-0.5 rounded-full">
Live Feed
</span>
</div>
<div className="space-y-4">
{dailyNotices.map((notice) => (
<div key={notice.id} className="p-3.5 rounded-xl bg-slate-50/80 border border-slate-200/60 space-y-2 text-left">
<div className="flex items-center justify-between">
<span
className="text-[10px] font-bold px-2 py-0.5 rounded-md text-white"
style={{ backgroundColor: notice.color }}
>
{notice.category}
</span>
<span className="text-[10px] text-slate-400 font-medium">{notice.date}</span>
</div>
<h4 className="text-xs font-bold text-slate-900 leading-snug" style={{ fontFamily: 'Poppins, sans-serif' }}>
{notice.title}
</h4>
<p className="text-[11px] text-slate-600 leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
{notice.desc}
</p>
</div>
))}
</div>
</div>
</div>
</div>
</div> </div>
); );
} }

View File

@@ -39,7 +39,7 @@ const SUBJECTS_BY_SEM: Record<number, string[]> = {
const TYPE_COLORS: Record<string, { bg: string; text: string; icon: React.ComponentType<any> }> = { const TYPE_COLORS: Record<string, { bg: string; text: string; icon: React.ComponentType<any> }> = {
notes: { bg: '#EFF6FF', text: '#3B82F6', icon: BookOpen }, notes: { bg: '#EFF6FF', text: '#3B82F6', icon: BookOpen },
pyq: { bg: '#FFF7ED', text: '#F97316', icon: FileText }, pyq: { bg: '#EEF2FF', text: '#6366F1', icon: FileText },
syllabus: { bg: '#ECFDF5', text: '#10B981', icon: ScrollText }, syllabus: { bg: '#ECFDF5', text: '#10B981', icon: ScrollText },
assignment: { bg: '#FDF2F8', text: '#EC4899', icon: BookMarked }, assignment: { bg: '#FDF2F8', text: '#EC4899', icon: BookMarked },
}; };
@@ -1421,7 +1421,7 @@ export default function Notes() {
</div> </div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}> <h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Notes &{' '} Notes &{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}> <span style={{ background: 'linear-gradient(135deg, #6366F1, #4F46E5)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
PYQs PYQs
</span> </span>
</h1> </h1>

View File

@@ -684,13 +684,13 @@ export default function Toolkit() {
<div className="container-custom"> <div className="container-custom">
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}> <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}>
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}> <div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
<Link to="/" className="hover:text-[#F97316]">Home</Link> <Link to="/" className="hover:text-[#6366F1]">Home</Link>
<ChevronRight className="w-3 h-3" /> <ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Student Toolkit</span> <span className="text-[#6366F1]">Student Toolkit</span>
</div> </div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}> <h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Student{' '} Student{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}> <span style={{ background: 'linear-gradient(135deg, #6366F1, #4F46E5)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Toolkit Toolkit
</span> </span>
</h1> </h1>