feat: initial frontend build for RIT Freshers Hub
This commit is contained in:
252
src/pages/AIAssistant/AIAssistant.tsx
Normal file
252
src/pages/AIAssistant/AIAssistant.tsx
Normal file
@@ -0,0 +1,252 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Bot, Send, Sparkles, RefreshCw, Mic, X, ChevronRight } from 'lucide-react';
|
||||
import { AI_SUGGESTED_PROMPTS } from '@/constants';
|
||||
import type { ChatMessage } from '@/types';
|
||||
|
||||
const INITIAL_MESSAGES: ChatMessage[] = [
|
||||
{
|
||||
id: '1',
|
||||
role: 'assistant',
|
||||
content: "👋 Hi there! I'm your **RIT AI Assistant**, powered by Google Gemini. I have comprehensive knowledge about Rajalakshmi Institute of Technology — from admission procedures to campus facilities.\n\nHow can I help you today?",
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
function formatContent(text: string) {
|
||||
return text
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\n/g, '<br/>');
|
||||
}
|
||||
|
||||
export default function AIAssistant() {
|
||||
const [messages, setMessages] = useState<ChatMessage[]>(INITIAL_MESSAGES);
|
||||
const [input, setInput] = useState('');
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages, isTyping]);
|
||||
|
||||
const sendMessage = async (text: string) => {
|
||||
if (!text.trim()) return;
|
||||
|
||||
const userMsg: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
role: 'user',
|
||||
content: text,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
setMessages((prev) => [...prev, userMsg]);
|
||||
setInput('');
|
||||
setIsTyping(true);
|
||||
|
||||
// Simulate AI response
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
setIsTyping(false);
|
||||
|
||||
const responses: Record<string, string> = {
|
||||
hostel: "**RIT Hostel Facilities:**\n\n• Separate hostels for boys and girls\n• Wi-Fi connectivity in all rooms\n• 24/7 security and CCTV\n• Hygienic canteen with vegetarian & non-vegetarian options\n• Common rooms with TV and recreation\n• In-house medical facility\n\nFor hostel admission, contact the hostel office with your Aadhaar card, medical certificate, and filled hostel application form.",
|
||||
bus: "**RIT Bus Routes:**\n\nRIT operates **15+ bus routes** covering major areas of Chennai:\n\n• Route 01: Chennai Central → Koyambedu → Porur → RIT (7:00 AM)\n• Route 02: Tambaram → Chrompet → Pallavaram → RIT (7:15 AM)\n• Route 03: Anna Nagar → Vadapalani → RIT (7:20 AM)\n\nAll buses depart from respective stops by 7:30 AM. Return buses leave RIT at 4:30 PM and 6:00 PM.",
|
||||
library: "**RIT Library Information:**\n\n• **Timings:** Mon-Sat 8:00 AM – 8:00 PM, Sunday 10:00 AM – 5:00 PM\n• **Collection:** Over 50,000 books, 200+ journals\n• **Digital Access:** IEEE Xplore, ACM Digital Library, Scopus\n• **Services:** Book borrowing (4 books, 14 days), Reference, Photocopying\n• **Wi-Fi:** Available throughout the library\n\nYou'll need your student ID card to access the library.",
|
||||
};
|
||||
|
||||
const lower = text.toLowerCase();
|
||||
let responseText = '';
|
||||
|
||||
if (lower.includes('hostel')) responseText = responses.hostel;
|
||||
else if (lower.includes('bus') || lower.includes('route')) responseText = responses.bus;
|
||||
else if (lower.includes('library')) responseText = responses.library;
|
||||
else {
|
||||
responseText = `Thank you for your question about **"${text}"**!\n\nI'm connected to RIT's knowledge base and can help you with:\n\n• 📚 Academic information & syllabus\n• 🏠 Hostel & accommodation\n• 🚌 Bus routes & timings\n• 📋 Admission procedures\n• 🏛️ Campus facilities\n• 👩🏫 Faculty information\n• 🎉 Events & clubs\n\nCould you be more specific about what you'd like to know? I'll give you the most accurate information!`;
|
||||
}
|
||||
|
||||
const aiMsg: ChatMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant',
|
||||
content: responseText,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
setMessages((prev) => [...prev, aiMsg]);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage(input);
|
||||
}
|
||||
};
|
||||
|
||||
const clearChat = () => {
|
||||
setMessages(INITIAL_MESSAGES);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-[#E5E7EB] py-4 sticky top-16 z-40">
|
||||
<div className="container-custom flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||
<Bot className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-semibold text-[#1E293B] text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT AI Assistant</h1>
|
||||
<div className="flex items-center gap-1.5 text-xs text-[#94A3B8]">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
||||
Powered by Gemini AI · Campus Knowledge Enabled
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={clearChat}
|
||||
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-[#94A3B8] hover:text-[#475569] hover:bg-gray-100 transition-all"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5" />
|
||||
New Chat
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 container-custom py-6 flex flex-col gap-4 max-w-4xl mx-auto w-full">
|
||||
{/* Features strip */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="flex flex-wrap gap-2"
|
||||
>
|
||||
{[
|
||||
{ icon: Sparkles, label: 'Gemini AI' },
|
||||
{ icon: Bot, label: 'RAG Enabled' },
|
||||
{ icon: ChevronRight, label: 'Campus Knowledge' },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white border border-[#E5E7EB] text-xs text-[#475569]">
|
||||
<item.icon className="w-3.5 h-3.5 text-[#F97316]" />
|
||||
{item.label}
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Messages */}
|
||||
<div
|
||||
className="flex-1 bg-white rounded-3xl border border-[#E5E7EB] p-6 flex flex-col gap-4 overflow-y-auto"
|
||||
style={{ minHeight: '400px', maxHeight: '65vh', boxShadow: '0 4px 25px -5px rgba(0,0,0,0.08)' }}
|
||||
>
|
||||
{messages.map((msg) => (
|
||||
<motion.div
|
||||
key={msg.id}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
|
||||
>
|
||||
{msg.role === 'assistant' && (
|
||||
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 mt-1" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||
<Bot className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="max-w-[80%] px-4 py-3 text-sm leading-relaxed"
|
||||
style={{
|
||||
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #FB923C)' : '#F8FAFC',
|
||||
color: msg.role === 'user' ? 'white' : '#1E293B',
|
||||
borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px',
|
||||
border: msg.role === 'ai' ? '1px solid #E5E7EB' : 'none',
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: formatContent(msg.content) }}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{/* Typing */}
|
||||
<AnimatePresence>
|
||||
{isTyping && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="flex gap-3"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||
<Bot className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-4 py-3 bg-[#F8FAFC] rounded-2xl border border-[#E5E7EB]">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<motion.span
|
||||
key={i}
|
||||
className="w-2 h-2 bg-[#94A3B8] rounded-full"
|
||||
animate={{ y: [0, -5, 0] }}
|
||||
transition={{ duration: 0.8, repeat: Infinity, delay: i * 0.15 }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Suggested Prompts */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{AI_SUGGESTED_PROMPTS.slice(0, 4).map((prompt, i) => (
|
||||
<motion.button
|
||||
key={i}
|
||||
whileHover={{ scale: 1.02, backgroundColor: '#FFF7ED' }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => sendMessage(prompt)}
|
||||
className="px-3.5 py-2 rounded-xl border border-[#E5E7EB] text-xs text-[#475569] bg-white transition-all hover:border-[#F97316] hover:text-[#F97316]"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
>
|
||||
{prompt}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-3 flex items-center gap-3" style={{ boxShadow: '0 4px 20px -4px rgba(0,0,0,0.08)' }}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Ask anything about RIT..."
|
||||
className="flex-1 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none bg-transparent"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
{input && (
|
||||
<motion.button
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={() => setInput('')}
|
||||
className="w-8 h-8 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</motion.button>
|
||||
)}
|
||||
<button className="w-9 h-9 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all">
|
||||
<Mic className="w-4.5 h-4.5" />
|
||||
</button>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => sendMessage(input)}
|
||||
disabled={!input.trim()}
|
||||
className="w-10 h-10 rounded-xl flex items-center justify-center text-white transition-all disabled:opacity-50"
|
||||
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
230
src/pages/Campus/Campus.tsx
Normal file
230
src/pages/Campus/Campus.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Search, ChevronRight, MapPin } from 'lucide-react';
|
||||
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||
import BusCard from '@/components/BusCard/BusCard';
|
||||
import FacultyCard from '@/components/FacultyCard/FacultyCard';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { BUS_ROUTES, FACULTY_DATA, CAMPUS_LOCATIONS, DEPARTMENTS } from '@/constants';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
|
||||
type Tab = 'map' | 'bus' | 'faculty';
|
||||
|
||||
export default function Campus() {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('map');
|
||||
const [searchFaculty, setSearchFaculty] = useState('');
|
||||
const [selectedDept, setSelectedDept] = useState('All Departments');
|
||||
const [busSearch, setBusSearch] = useState('');
|
||||
|
||||
const filteredFaculty = FACULTY_DATA.filter((f) => {
|
||||
const matchSearch = f.name.toLowerCase().includes(searchFaculty.toLowerCase()) ||
|
||||
f.department.toLowerCase().includes(searchFaculty.toLowerCase());
|
||||
const matchDept = selectedDept === 'All Departments' || f.department === selectedDept;
|
||||
return matchSearch && matchDept;
|
||||
});
|
||||
|
||||
const filteredRoutes = BUS_ROUTES.filter((r) =>
|
||||
r.name.toLowerCase().includes(busSearch.toLowerCase()) ||
|
||||
r.from.toLowerCase().includes(busSearch.toLowerCase()) ||
|
||||
r.number.toLowerCase().includes(busSearch.toLowerCase())
|
||||
);
|
||||
|
||||
const TABS: { id: Tab; label: string; icon: string }[] = [
|
||||
{ id: 'map', label: 'Campus Map', icon: 'Map' },
|
||||
{ id: 'bus', label: 'Bus Routes', icon: 'Bus' },
|
||||
{ id: 'faculty', label: 'Faculty Directory', icon: 'Users' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||
<div className="container-custom">
|
||||
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3">
|
||||
<span>Home</span>
|
||||
<ChevronRight className="w-3 h-3" />
|
||||
<span className="text-[#F97316]">Campus</span>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
Explore{' '}
|
||||
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
Campus
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Navigate campus, find bus routes, and connect with faculty.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-custom py-8">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-2 bg-white rounded-2xl p-2 border border-[#E5E7EB] mb-8 w-fit" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
{TABS.map((tab) => {
|
||||
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[tab.icon];
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className="relative flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
color: activeTab === tab.id ? 'white' : '#475569',
|
||||
}}
|
||||
>
|
||||
{activeTab === tab.id && (
|
||||
<motion.div
|
||||
layoutId="campus-tab"
|
||||
className="absolute inset-0 rounded-xl"
|
||||
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
|
||||
/>
|
||||
)}
|
||||
<span className="relative z-10 flex items-center gap-2">
|
||||
{Icon && <Icon className="w-4 h-4" />}
|
||||
{tab.label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Map Tab */}
|
||||
{activeTab === 'map' && (
|
||||
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden mb-8" style={{ boxShadow: '0 4px 25px -5px rgba(0,0,0,0.1)' }}>
|
||||
<div
|
||||
className="h-96 flex items-center justify-center relative overflow-hidden"
|
||||
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 opacity-10"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)',
|
||||
backgroundSize: '40px 40px',
|
||||
}}
|
||||
/>
|
||||
{/* Mock campus blocks */}
|
||||
{[
|
||||
{ label: 'CSE Block', x: '20%', y: '25%', w: '14%', h: '18%' },
|
||||
{ label: 'ECE Block', x: '38%', y: '25%', w: '12%', h: '18%' },
|
||||
{ label: 'Library', x: '58%', y: '30%', w: '16%', h: '14%' },
|
||||
{ label: 'Canteen', x: '65%', y: '58%', w: '12%', h: '12%' },
|
||||
{ label: 'Hostel', x: '18%', y: '58%', w: '14%', h: '20%' },
|
||||
{ label: 'Auditorium', x: '40%', y: '55%', w: '18%', h: '14%' },
|
||||
].map((block, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="absolute flex items-center justify-center rounded-lg cursor-pointer group"
|
||||
style={{
|
||||
left: block.x,
|
||||
top: block.y,
|
||||
width: block.w,
|
||||
height: block.h,
|
||||
backgroundColor: 'rgba(249,115,22,0.15)',
|
||||
border: '1px solid rgba(249,115,22,0.4)',
|
||||
}}
|
||||
>
|
||||
<span className="text-[10px] text-orange-300 font-medium text-center leading-tight px-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{block.label}
|
||||
</span>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{/* Pin */}
|
||||
<div className="relative z-10 text-center">
|
||||
<div className="flex items-center gap-2 text-white/40 text-sm">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>RIT Campus, Kuthambakkam, Chennai</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Nav */}
|
||||
<div className="p-6 grid grid-cols-4 sm:grid-cols-8 gap-4">
|
||||
{CAMPUS_LOCATIONS.map((loc) => {
|
||||
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
|
||||
return (
|
||||
<motion.button
|
||||
key={loc.id}
|
||||
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
|
||||
className="flex flex-col items-center gap-2 p-3 rounded-xl transition-all hover:border-[#FED7AA] border border-transparent"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-xl bg-[#F8FAFC] flex items-center justify-center">
|
||||
{Icon && <Icon className="w-5 h-5 text-[#F97316]" />}
|
||||
</div>
|
||||
<span className="text-[11px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{loc.name}
|
||||
</span>
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Bus Routes Tab */}
|
||||
{activeTab === 'bus' && (
|
||||
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search bus routes..."
|
||||
value={busSearch}
|
||||
onChange={(e) => setBusSearch(e.target.value)}
|
||||
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
</div>
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{filteredRoutes.map((route) => (
|
||||
<StaggerItem key={route.id}>
|
||||
<BusCard route={route} />
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Faculty Tab */}
|
||||
{activeTab === 'faculty' && (
|
||||
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 mb-6">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search faculty by name or department..."
|
||||
value={searchFaculty}
|
||||
onChange={(e) => setSearchFaculty(e.target.value)}
|
||||
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
value={selectedDept}
|
||||
onChange={(e) => setSelectedDept(e.target.value)}
|
||||
className="px-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#475569] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
>
|
||||
{DEPARTMENTS.map((dept) => (
|
||||
<option key={dept} value={dept}>{dept}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{filteredFaculty.map((faculty) => (
|
||||
<StaggerItem key={faculty.id}>
|
||||
<FacultyCard faculty={faculty} />
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
394
src/pages/Community/Community.tsx
Normal file
394
src/pages/Community/Community.tsx
Normal file
@@ -0,0 +1,394 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
MessageCircle, Heart, TrendingUp, ThumbsUp, Send,
|
||||
Shield, Lock, Smile, ChevronRight, Search, Plus
|
||||
} from 'lucide-react';
|
||||
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { QUESTIONS_DATA, CONFESSIONS_DATA } from '@/constants';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
|
||||
type Tab = 'qa' | 'confession';
|
||||
|
||||
const TRENDING_TAGS = ['hostel', 'academics', 'clubs', 'campus', 'canteen', 'sports', 'placement', 'library'];
|
||||
|
||||
export default function Community() {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('qa');
|
||||
const [confessionText, setConfessionText] = useState('');
|
||||
const [questionText, setQuestionText] = useState('');
|
||||
const [confessionPosted, setConfessionPosted] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [likedIds, setLikedIds] = useState<Set<string>>(new Set());
|
||||
|
||||
const filteredQuestions = QUESTIONS_DATA.filter((q) =>
|
||||
q.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
q.tags.some((t) => t.includes(searchQuery.toLowerCase()))
|
||||
);
|
||||
|
||||
const handleConfess = () => {
|
||||
if (!confessionText.trim()) return;
|
||||
setConfessionPosted(true);
|
||||
setConfessionText('');
|
||||
setTimeout(() => setConfessionPosted(false), 3000);
|
||||
};
|
||||
|
||||
const toggleLike = (id: string) => {
|
||||
setLikedIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||
<div className="container-custom">
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
RIT{' '}
|
||||
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
Community
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Ask questions, share confessions, and connect with fellow RIT students.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-custom py-8">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-2 bg-white rounded-2xl p-2 border border-[#E5E7EB] mb-8 w-fit" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
{[
|
||||
{ id: 'qa' as Tab, label: 'Freshers Q&A', icon: MessageCircle },
|
||||
{ id: 'confession' as Tab, label: 'Confessions', icon: Heart },
|
||||
].map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className="relative flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
|
||||
style={{ fontFamily: 'Poppins, sans-serif', color: activeTab === tab.id ? 'white' : '#475569' }}
|
||||
>
|
||||
{activeTab === tab.id && (
|
||||
<motion.div
|
||||
layoutId="community-tab"
|
||||
className="absolute inset-0 rounded-xl"
|
||||
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
|
||||
/>
|
||||
)}
|
||||
<span className="relative z-10 flex items-center gap-2">
|
||||
<tab.icon className="w-4 h-4" />
|
||||
{tab.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
{/* Q&A Tab */}
|
||||
{activeTab === 'qa' && (
|
||||
<motion.div
|
||||
key="qa"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -16 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2">
|
||||
{/* Ask Question Box */}
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5 mb-6" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
<h3 className="text-sm font-semibold text-[#1E293B] mb-3" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3>
|
||||
<textarea
|
||||
value={questionText}
|
||||
onChange={(e) => setQuestionText(e.target.value)}
|
||||
placeholder="What's on your mind? Ask your seniors anything about RIT..."
|
||||
rows={3}
|
||||
className="w-full border border-[#E5E7EB] rounded-xl p-3 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] resize-none transition-colors mb-3"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Your question will be visible to all students
|
||||
</span>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl text-white text-sm font-semibold"
|
||||
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Post Question
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-5">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-[#94A3B8]" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search questions..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2.5 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Questions */}
|
||||
<StaggerContainer className="flex flex-col gap-4">
|
||||
{filteredQuestions.map((q) => (
|
||||
<StaggerItem key={q.id}>
|
||||
<motion.div
|
||||
whileHover={{ y: -2 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Votes */}
|
||||
<div className="flex flex-col items-center gap-1 shrink-0">
|
||||
<button
|
||||
onClick={() => toggleLike(q.id)}
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center transition-all"
|
||||
style={{ backgroundColor: likedIds.has(q.id) ? '#FFF7ED' : '#F8FAFC' }}
|
||||
>
|
||||
<ThumbsUp className="w-4 h-4" style={{ color: likedIds.has(q.id) ? '#F97316' : '#94A3B8' }} />
|
||||
</button>
|
||||
<span className="text-xs font-semibold text-[#475569]">{q.votes + (likedIds.has(q.id) ? 1 : 0)}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-semibold text-[#1E293B] mb-1.5 hover:text-[#F97316] cursor-pointer transition-colors"
|
||||
style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
{q.title}
|
||||
</h3>
|
||||
<p className="text-xs text-[#94A3B8] mb-3 line-clamp-2" style={{ fontFamily: 'Inter, sans-serif' }}>{q.body}</p>
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{q.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="px-2 py-0.5 rounded-full text-[10px] font-medium bg-[#F8FAFC] text-[#94A3B8] border border-[#E5E7EB]"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-[#94A3B8]">
|
||||
<span className="flex items-center gap-1">
|
||||
<MessageCircle className="w-3.5 h-3.5" />
|
||||
{q.answers} answers
|
||||
</span>
|
||||
{q.isAnswered && (
|
||||
<span className="px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-600 text-[10px] font-semibold">✓ Answered</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="flex flex-col gap-5">
|
||||
{/* Trending */}
|
||||
<AnimatedContainer direction="right" delay={0.1}>
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<TrendingUp className="w-4.5 h-4.5 text-[#F97316]" />
|
||||
<h3 className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Trending Topics</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TRENDING_TAGS.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={() => setSearchQuery(tag)}
|
||||
className="px-3 py-1.5 rounded-full text-xs font-medium bg-[#FFF7ED] text-[#F97316] hover:bg-[#F97316] hover:text-white transition-all border border-[#FED7AA]"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
|
||||
{/* Stats */}
|
||||
<AnimatedContainer direction="right" delay={0.2}>
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
{[
|
||||
{ label: 'Total Questions', value: '124' },
|
||||
{ label: 'Answered', value: '98%' },
|
||||
{ label: 'Active Students', value: '340+' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className={`flex justify-between py-2.5 ${i < 2 ? 'border-b border-[#E5E7EB]' : ''}`}>
|
||||
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{stat.label}</span>
|
||||
<span className="text-xs font-bold text-[#F97316]" style={{ fontFamily: 'Poppins, sans-serif' }}>{stat.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Confessions Tab */}
|
||||
{activeTab === 'confession' && (
|
||||
<motion.div
|
||||
key="confession"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -16 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2">
|
||||
{/* Post confession */}
|
||||
<div
|
||||
className="rounded-3xl p-6 mb-8 border"
|
||||
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)', borderColor: 'rgba(255,255,255,0.1)' }}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249,115,22,0.2)' }}>
|
||||
<Lock className="w-5 h-5 text-[#F97316]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-white font-semibold text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>Share Anonymously</h3>
|
||||
<p className="text-slate-400 text-xs">Your identity is never revealed</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
value={confessionText}
|
||||
onChange={(e) => setConfessionText(e.target.value)}
|
||||
placeholder="Share your thoughts, stories, crushes, or anything on your mind... It's completely anonymous 🤫"
|
||||
rows={4}
|
||||
className="w-full rounded-xl p-4 text-sm placeholder-slate-500 focus:outline-none resize-none mb-4"
|
||||
style={{
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
backgroundColor: 'rgba(255,255,255,0.06)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 rounded-xl mb-4" style={{ backgroundColor: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.2)' }}>
|
||||
<Shield className="w-4 h-4 text-[#F97316] shrink-0" />
|
||||
<span className="text-xs text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
No IP tracking. No username. 100% anonymous posting.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
onClick={handleConfess}
|
||||
className="flex items-center gap-2 px-6 py-2.5 rounded-xl text-white text-sm font-semibold"
|
||||
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
>
|
||||
<Smile className="w-4 h-4" />
|
||||
Post Anonymously
|
||||
</motion.button>
|
||||
|
||||
<AnimatePresence>
|
||||
{confessionPosted && (
|
||||
<motion.span
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="text-xs text-emerald-400"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
>
|
||||
✓ Posted successfully!
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Confessions Feed */}
|
||||
<StaggerContainer className="flex flex-col gap-4">
|
||||
{CONFESSIONS_DATA.map((conf) => (
|
||||
<StaggerItem key={conf.id}>
|
||||
<motion.div
|
||||
whileHover={{ y: -2 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
|
||||
<Shield className="w-4 h-4 text-[#F97316]" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Anonymous</span>
|
||||
<p className="text-[10px] text-[#94A3B8]">{formatDate(conf.createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{conf.category && (
|
||||
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-semibold bg-[#FFF7ED] text-[#F97316] border border-[#FED7AA]" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
{conf.category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-[#475569] leading-relaxed mb-4" style={{ fontFamily: 'Inter, sans-serif' }}>{conf.content}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => toggleLike(conf.id)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-xl transition-all"
|
||||
style={{ backgroundColor: likedIds.has(conf.id) ? '#FFF7ED' : '#F8FAFC' }}
|
||||
>
|
||||
<Heart
|
||||
className="w-3.5 h-3.5"
|
||||
style={{ color: likedIds.has(conf.id) ? '#F97316' : '#94A3B8' }}
|
||||
fill={likedIds.has(conf.id) ? '#F97316' : 'none'}
|
||||
/>
|
||||
<span className="text-xs text-[#94A3B8]">{conf.reactions + (likedIds.has(conf.id) ? 1 : 0)}</span>
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<AnimatedContainer direction="right" delay={0.15}>
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
<h3 className="text-sm font-semibold text-[#1E293B] mb-4 flex items-center gap-2" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
<Shield className="w-4 h-4 text-[#F97316]" />
|
||||
Community Rules
|
||||
</h3>
|
||||
{[
|
||||
'Be respectful and kind',
|
||||
'No hate speech or bullying',
|
||||
'No personal information',
|
||||
'Keep it relevant to campus life',
|
||||
'Confessions are 100% anonymous',
|
||||
].map((rule, i) => (
|
||||
<div key={i} className="flex items-start gap-2.5 py-2.5 border-b border-[#E5E7EB] last:border-0">
|
||||
<ChevronRight className="w-3.5 h-3.5 text-[#F97316] mt-0.5 shrink-0" />
|
||||
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{rule}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
263
src/pages/Events/Events.tsx
Normal file
263
src/pages/Events/Events.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Calendar, MapPin, Clock, Users, ChevronLeft, ChevronRight, Tag, ExternalLink } from 'lucide-react';
|
||||
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { EVENTS_DATA, CLUBS_DATA } from '@/constants';
|
||||
|
||||
const CATEGORY_COLORS: Record<string, { bg: string; text: string; border: string }> = {
|
||||
technical: { bg: '#EFF6FF', text: '#3B82F6', border: '#BFDBFE' },
|
||||
cultural: { bg: '#FDF2F8', text: '#EC4899', border: '#FBCFE8' },
|
||||
sports: { bg: '#ECFDF5', text: '#10B981', border: '#A7F3D0' },
|
||||
seminar: { bg: '#FFF7ED', text: '#F97316', border: '#FED7AA' },
|
||||
workshop: { bg: '#F5F3FF', text: '#8B5CF6', border: '#DDD6FE' },
|
||||
};
|
||||
|
||||
const CLUB_CATEGORY_COLORS: Record<string, string> = {
|
||||
Technical: '#3B82F6',
|
||||
Cultural: '#EC4899',
|
||||
Social: '#10B981',
|
||||
Creative: '#F59E0B',
|
||||
};
|
||||
|
||||
export default function Events() {
|
||||
const [currentIdx, setCurrentIdx] = useState(0);
|
||||
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||
|
||||
const categories = ['All', 'Technical', 'Cultural', 'Seminar', 'Workshop'];
|
||||
|
||||
const filteredEvents = EVENTS_DATA.filter((e) =>
|
||||
selectedCategory === 'All' || e.category === selectedCategory.toLowerCase()
|
||||
);
|
||||
|
||||
const prev = () => setCurrentIdx((i) => (i === 0 ? EVENTS_DATA.length - 1 : i - 1));
|
||||
const next = () => setCurrentIdx((i) => (i === EVENTS_DATA.length - 1 ? 0 : i + 1));
|
||||
|
||||
const featured = EVENTS_DATA[currentIdx];
|
||||
const featCfg = CATEGORY_COLORS[featured.category] || CATEGORY_COLORS.seminar;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||
<div className="container-custom">
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
Clubs &{' '}
|
||||
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
Events
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Discover events, join clubs, and be part of the RIT community.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-custom py-10">
|
||||
{/* Featured Event Carousel */}
|
||||
<SectionTitle tag="Upcoming" title="Featured" highlight="Events" />
|
||||
|
||||
<AnimatedContainer className="relative mb-14">
|
||||
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={featured.id}
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -30 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col md:flex-row"
|
||||
>
|
||||
{/* Image placeholder */}
|
||||
<div
|
||||
className="w-full md:w-2/5 h-56 md:h-auto flex items-center justify-center"
|
||||
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="text-5xl mb-2">
|
||||
{featured.category === 'technical' ? '💻' : featured.category === 'cultural' ? '🎭' : featured.category === 'seminar' ? '🎤' : '📅'}
|
||||
</div>
|
||||
<div className="text-white/60 text-sm">{featured.venue}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 p-8">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span
|
||||
className="px-3 py-1 rounded-full text-xs font-semibold capitalize"
|
||||
style={{ backgroundColor: featCfg.bg, color: featCfg.text, border: `1px solid ${featCfg.border}`, fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
{featured.category}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-600 border border-emerald-200" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
Upcoming
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl font-bold text-[#1E293B] mb-3" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
{featured.title}
|
||||
</h3>
|
||||
<p className="text-[#475569] text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{featured.description}
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 mb-6">
|
||||
{[
|
||||
{ icon: Calendar, label: new Date(featured.date).toLocaleDateString('en-IN', { day: 'numeric', month: 'long', year: 'numeric' }) },
|
||||
{ icon: Clock, label: featured.time },
|
||||
{ icon: MapPin, label: featured.venue },
|
||||
{ icon: Users, label: featured.organizer },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-sm text-[#475569]">
|
||||
<item.icon className="w-4 h-4 text-[#F97316] shrink-0" />
|
||||
<span style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03 }}
|
||||
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-semibold"
|
||||
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
>
|
||||
Register Now
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</motion.button>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03 }}
|
||||
className="px-5 py-2.5 rounded-xl text-sm font-semibold border-2 border-[#E5E7EB] text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all"
|
||||
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
More Details
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Carousel Controls */}
|
||||
<div className="flex items-center justify-center gap-3 mt-4">
|
||||
<button onClick={prev} className="w-9 h-9 rounded-xl border border-[#E5E7EB] bg-white flex items-center justify-center text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all">
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</button>
|
||||
{EVENTS_DATA.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentIdx(i)}
|
||||
className="transition-all rounded-full"
|
||||
style={{ width: i === currentIdx ? '24px' : '8px', height: '8px', backgroundColor: i === currentIdx ? '#F97316' : '#E5E7EB' }}
|
||||
/>
|
||||
))}
|
||||
<button onClick={next} className="w-9 h-9 rounded-xl border border-[#E5E7EB] bg-white flex items-center justify-center text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all">
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
|
||||
{/* All Events Grid */}
|
||||
<div className="mb-14">
|
||||
<div className="flex items-center justify-between mb-6 flex-wrap gap-4">
|
||||
<h2 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>All Events</h2>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setSelectedCategory(cat)}
|
||||
className="px-3.5 py-1.5 rounded-xl text-xs font-medium transition-all"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
backgroundColor: selectedCategory === cat ? '#F97316' : '#F8FAFC',
|
||||
color: selectedCategory === cat ? 'white' : '#475569',
|
||||
border: `1px solid ${selectedCategory === cat ? '#F97316' : '#E5E7EB'}`,
|
||||
}}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-4">
|
||||
{filteredEvents.map((event) => {
|
||||
const cfg = CATEGORY_COLORS[event.category] || CATEGORY_COLORS.seminar;
|
||||
return (
|
||||
<StaggerItem key={event.id}>
|
||||
<motion.div
|
||||
whileHover={{ y: -4 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 flex gap-4"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div className="w-14 h-14 rounded-xl flex flex-col items-center justify-center shrink-0" style={{ backgroundColor: cfg.bg }}>
|
||||
<span className="text-xs font-bold" style={{ color: cfg.text, fontFamily: 'Poppins, sans-serif' }}>
|
||||
{new Date(event.date).toLocaleDateString('en-IN', { day: 'numeric' })}
|
||||
</span>
|
||||
<span className="text-[10px]" style={{ color: cfg.text }}>
|
||||
{new Date(event.date).toLocaleDateString('en-IN', { month: 'short' })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold capitalize" style={{ backgroundColor: cfg.bg, color: cfg.text, fontFamily: 'Poppins, sans-serif' }}>
|
||||
{event.category}
|
||||
</span>
|
||||
<h3 className="text-sm font-semibold text-[#1E293B] mt-1.5 mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>{event.title}</h3>
|
||||
<div className="flex items-center gap-3 text-xs text-[#94A3B8]">
|
||||
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{event.time}</span>
|
||||
<span className="flex items-center gap-1"><MapPin className="w-3 h-3" />{event.venue}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
);
|
||||
})}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
|
||||
{/* Clubs Section */}
|
||||
<SectionTitle tag="Join a Club" title="Student" highlight="Clubs" subtitle="Explore clubs and societies at RIT. Find your passion and connect with like-minded peers." />
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{CLUBS_DATA.map((club) => (
|
||||
<StaggerItem key={club.id}>
|
||||
<motion.div
|
||||
whileHover={{ y: -4 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div
|
||||
className="w-11 h-11 rounded-xl flex items-center justify-center text-white font-bold text-lg"
|
||||
style={{ background: `linear-gradient(135deg, ${CLUB_CATEGORY_COLORS[club.category] || '#F97316'}, ${CLUB_CATEGORY_COLORS[club.category] || '#F97316'}CC)`, fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
{club.name[0]}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs text-[#94A3B8]">
|
||||
<Users className="w-3.5 h-3.5" />
|
||||
<span>{club.members} members</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-semibold text-[#1E293B] mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>{club.name}</h3>
|
||||
<p className="text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>{club.description}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="flex items-center gap-1 text-xs" style={{ color: CLUB_CATEGORY_COLORS[club.category] || '#F97316' }}>
|
||||
<Tag className="w-3 h-3" />
|
||||
{club.category}
|
||||
</span>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.04 }}
|
||||
className="px-4 py-1.5 rounded-lg text-xs font-semibold text-white"
|
||||
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
>
|
||||
Join Club
|
||||
</motion.button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
562
src/pages/Home/Home.tsx
Normal file
562
src/pages/Home/Home.tsx
Normal file
@@ -0,0 +1,562 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
ArrowRight, Sparkles, BookOpen, Bot, GraduationCap,
|
||||
CheckCircle, Zap, Shield, Brain
|
||||
} from 'lucide-react';
|
||||
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||
import StatsCard from '@/components/StatsCard/StatsCard';
|
||||
import FeatureCard from '@/components/FeatureCard/FeatureCard';
|
||||
import FloatingCard from '@/components/FloatingCard/FloatingCard';
|
||||
import ChatPreview from '@/components/ChatPreview/ChatPreview';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { STATS, FEATURES, CAMPUS_LOCATIONS } from '@/constants';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
{/* ─── Hero Section ─────────────────────────────────────────────────────── */}
|
||||
<section className="relative overflow-hidden pt-12 pb-24" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Background Decoration */}
|
||||
<div
|
||||
className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full opacity-[0.04] -translate-y-1/2 translate-x-1/3 pointer-events-none"
|
||||
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
|
||||
/>
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-[350px] h-[350px] rounded-full opacity-[0.03] translate-y-1/2 -translate-x-1/3 pointer-events-none"
|
||||
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
|
||||
/>
|
||||
|
||||
<div className="container-custom">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 items-center">
|
||||
{/* Left – Visual Section (RIT Events Hub Style) */}
|
||||
<div className="lg:col-span-6 relative flex justify-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.7 }}
|
||||
className="relative w-full max-w-lg"
|
||||
>
|
||||
{/* Main campus image/graphic card */}
|
||||
<div
|
||||
className="rounded-[24px] overflow-hidden bg-white border border-[#E5E7EB] relative aspect-[4/3] w-full"
|
||||
style={{ boxShadow: '0 24px 60px -12px rgba(0,0,0,0.15)' }}
|
||||
>
|
||||
{/* Inline Premium Vector SVG Illustration of RIT Campus Building & Canopy */}
|
||||
<svg viewBox="0 0 800 600" className="w-full h-full select-none" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#1E3A8A" />
|
||||
<stop offset="40%" stopColor="#3B82F6" />
|
||||
<stop offset="100%" stopColor="#93C5FD" />
|
||||
</linearGradient>
|
||||
<linearGradient id="grassGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#059669" />
|
||||
<stop offset="100%" stopColor="#065F46" />
|
||||
</linearGradient>
|
||||
<linearGradient id="pathGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#F1F5F9" />
|
||||
<stop offset="100%" stopColor="#CBD5E1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="buildGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#E2E8F0" />
|
||||
<stop offset="100%" stopColor="#F8FAFC" />
|
||||
</linearGradient>
|
||||
<linearGradient id="canopyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#F97316" />
|
||||
<stop offset="100%" stopColor="#EA580C" />
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="15" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
{/* Sky */}
|
||||
<rect width="800" height="600" fill="url(#skyGrad)" />
|
||||
|
||||
{/* Glowing Sun */}
|
||||
<circle cx="700" cy="100" r="50" fill="#FEF08A" opacity="0.8" filter="url(#glow)" />
|
||||
|
||||
{/* Distant Hills / Tree Line */}
|
||||
<path d="M0 450 Q150 420 300 440 T600 430 T800 450 L800 600 L0 600 Z" fill="#047857" opacity="0.7" />
|
||||
<path d="M0 460 Q200 440 400 455 T800 460 L800 600 L0 600 Z" fill="url(#grassGrad)" />
|
||||
|
||||
{/* Academic Building Facade (Modern RIT Blocks) */}
|
||||
<rect x="50" y="200" width="300" height="260" fill="url(#buildGrad)" rx="8" />
|
||||
{/* Windows Grid */}
|
||||
<g fill="#475569" opacity="0.3">
|
||||
{/* Row 1 */}
|
||||
<rect x="75" y="230" width="35" height="40" rx="3" />
|
||||
<rect x="135" y="230" width="35" height="40" rx="3" />
|
||||
<rect x="195" y="230" width="35" height="40" rx="3" />
|
||||
<rect x="255" y="230" width="35" height="40" rx="3" />
|
||||
{/* Row 2 */}
|
||||
<rect x="75" y="300" width="35" height="40" rx="3" />
|
||||
<rect x="135" y="300" width="35" height="40" rx="3" />
|
||||
<rect x="195" y="300" width="35" height="40" rx="3" />
|
||||
<rect x="255" y="300" width="35" height="40" rx="3" />
|
||||
{/* Row 3 */}
|
||||
<rect x="75" y="370" width="35" height="40" rx="3" />
|
||||
<rect x="135" y="370" width="35" height="40" rx="3" />
|
||||
<rect x="195" y="370" width="35" height="40" rx="3" />
|
||||
<rect x="255" y="370" width="35" height="40" rx="3" />
|
||||
</g>
|
||||
|
||||
{/* Main Entrance Pillars & Arch (RIT Architecture) */}
|
||||
<rect x="380" y="150" width="380" height="310" fill="#F1F5F9" rx="16" opacity="0.95" />
|
||||
<rect x="400" y="170" width="340" height="290" fill="#E2E8F0" rx="12" />
|
||||
|
||||
{/* Premium Canopy Truss Structure (RIT Event Canopy Representation) */}
|
||||
<polygon points="360,150 780,120 780,180 360,210" fill="url(#canopyGrad)" opacity="0.9" />
|
||||
<g stroke="white" strokeWidth="3" opacity="0.8">
|
||||
<line x1="380" y1="155" x2="380" y2="200" />
|
||||
<line x1="450" y1="150" x2="450" y2="195" />
|
||||
<line x1="520" y1="145" x2="520" y2="190" />
|
||||
<line x1="590" y1="140" x2="590" y2="185" />
|
||||
<line x1="660" y1="135" x2="660" y2="180" />
|
||||
<line x1="730" y1="130" x2="730" y2="175" />
|
||||
|
||||
{/* Cross trusses */}
|
||||
<line x1="380" y1="155" x2="450" y2="195" />
|
||||
<line x1="450" y1="150" x2="520" y2="190" />
|
||||
<line x1="520" y1="145" x2="590" y2="185" />
|
||||
<line x1="590" y1="140" x2="660" y2="180" />
|
||||
<line x1="660" y1="135" x2="730" y2="175" />
|
||||
</g>
|
||||
|
||||
{/* Columns supporting Canopy */}
|
||||
<rect x="400" y="195" width="25" height="265" fill="#64748B" rx="4" />
|
||||
<rect x="520" y="190" width="25" height="270" fill="#64748B" rx="4" />
|
||||
<rect x="640" y="180" width="25" height="280" fill="#64748B" rx="4" />
|
||||
<rect x="730" y="175" width="25" height="285" fill="#64748B" rx="4" />
|
||||
|
||||
{/* Campus Pathway leading to building */}
|
||||
<polygon points="350,460 550,460 700,600 100,600" fill="url(#pathGrad)" />
|
||||
<line x1="400" y1="460" x2="250" y2="600" stroke="#CBD5E1" strokeWidth="4" />
|
||||
<line x1="500" y1="460" x2="550" y2="600" stroke="#CBD5E1" strokeWidth="4" />
|
||||
|
||||
{/* Trees & Foliage in Foreground */}
|
||||
<g fill="#059669">
|
||||
<circle cx="80" cy="480" r="45" />
|
||||
<circle cx="120" cy="510" r="35" />
|
||||
<circle cx="720" cy="500" r="50" />
|
||||
<circle cx="760" cy="480" r="40" />
|
||||
</g>
|
||||
<g fill="#10B981">
|
||||
<circle cx="75" cy="465" r="35" />
|
||||
<circle cx="715" cy="485" r="40" />
|
||||
</g>
|
||||
</svg>
|
||||
{/* Subtle dark gradient overlay to ensure text contrast */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-black/10 pointer-events-none" />
|
||||
|
||||
{/* Text on top of the image (RIT Events Hub Style) */}
|
||||
<div className="absolute bottom-6 left-6 z-10 text-left pointer-events-none">
|
||||
<h2 className="text-white font-extrabold text-2xl tracking-wide leading-tight" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
Rajalakshmi Institute
|
||||
</h2>
|
||||
<p className="text-orange-400 font-bold text-sm mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
of Technology, Chennai
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-1.5 mt-3.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-sm border border-white/20">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
|
||||
<span className="text-[10px] text-slate-300 font-bold uppercase tracking-wider">Est. 2008 • NBA & NAAC A+</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overlapping Dark Card (Innovation Card - Exact Match with Reference Image) */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="absolute -bottom-6 -right-4 md:-right-8 bg-[#1E293B] text-white p-6 rounded-2xl border border-white/10 max-w-[240px] z-20"
|
||||
style={{ boxShadow: '0 12px 40px rgba(0,0,0,0.25)' }}
|
||||
>
|
||||
<span className="text-[#F97316] text-[11px] font-bold tracking-wider uppercase mb-1.5 block" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
INNOVATION
|
||||
</span>
|
||||
<p className="text-[12px] text-slate-300 leading-relaxed font-semibold" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Where engineering meets guidance. Fueling the academic journey of our freshers.
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Right – Text Content */}
|
||||
<div className="lg:col-span-6 flex flex-col justify-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="mb-3"
|
||||
>
|
||||
<span className="text-[#F97316] text-xs font-bold uppercase tracking-widest" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
OUR LEGACY & FUTURE
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-4xl md:text-5xl xl:text-6xl font-bold leading-[1.1] mb-6"
|
||||
style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}
|
||||
>
|
||||
Welcome to <br />
|
||||
<span className="font-serif italic text-[#F97316] font-medium block mt-1">
|
||||
RIT Freshers Hub
|
||||
</span>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="text-base text-[#475569] leading-relaxed mb-8"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
>
|
||||
The RIT Freshers Hub is your centralized gateway to campus life, designed to simplify how you discover and participate in academic and extracurricular activities. With seamless access to notes, PYQs, AI assistant, and bus routes, we ensure you have everything needed to succeed.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 15 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="flex flex-wrap gap-4 mb-8"
|
||||
>
|
||||
<Link to="/notes">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03, boxShadow: '0 8px 25px rgba(249,115,22,0.3)' }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 px-6 py-3 rounded-xl text-white font-semibold text-sm cursor-pointer"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||
}}
|
||||
>
|
||||
<BookOpen className="w-4 h-4" />
|
||||
Explore Hub
|
||||
<ArrowRight className="w-3.5 h-3.5" />
|
||||
</motion.button>
|
||||
</Link>
|
||||
|
||||
<Link to="/ai-assistant">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03, borderColor: '#F97316', color: '#F97316', backgroundColor: '#FFF7ED' }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 px-6 py-3 rounded-xl font-semibold text-sm border-2 border-[#E5E7EB] text-[#1E293B] bg-white transition-all cursor-pointer"
|
||||
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
<Bot className="w-4 h-4" />
|
||||
Ask AI Assistant
|
||||
</motion.button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* Badges positioned cleanly below buttons with more breathing room */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="flex flex-wrap gap-6 text-[#94A3B8] border-t border-[#E5E7EB] pt-6"
|
||||
>
|
||||
{[
|
||||
{ icon: CheckCircle, text: 'Official Portal' },
|
||||
{ icon: Zap, text: 'Instant AI Support' },
|
||||
{ icon: Shield, text: 'Student Verified' },
|
||||
].map((badge, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-xs font-medium" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
<badge.icon className="w-4 h-4 text-[#F97316]/70" />
|
||||
<span>{badge.text}</span>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Stats Section ────────────────────────────────────────────────────── */}
|
||||
<section className="py-16 bg-white">
|
||||
<div className="container-custom">
|
||||
<StaggerContainer className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
|
||||
{STATS.map((stat, i) => (
|
||||
<StaggerItem key={i}>
|
||||
<StatsCard stat={stat} delay={i * 0.05} />
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Features Section ─────────────────────────────────────────────────── */}
|
||||
<section className="section-padding" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
<div className="container-custom">
|
||||
<SectionTitle
|
||||
tag="Everything You Need"
|
||||
title="Built for"
|
||||
highlight="RIT Freshers"
|
||||
subtitle="From notes to AI assistance — we've got everything you need to navigate campus life with confidence."
|
||||
/>
|
||||
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{FEATURES.map((feature) => (
|
||||
<StaggerItem key={feature.id}>
|
||||
<FeatureCard feature={feature} />
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── AI Section ───────────────────────────────────────────────────────── */}
|
||||
<section className="section-padding bg-white">
|
||||
<div className="container-custom">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
{/* Left – Text */}
|
||||
<AnimatedContainer direction="left">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-5"
|
||||
style={{ backgroundColor: '#F5F3FF', border: '1px solid #DDD6FE' }}>
|
||||
<Sparkles className="w-4 h-4 text-[#8B5CF6]" />
|
||||
<span className="text-xs font-semibold text-[#8B5CF6] uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
AI-Powered
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4" style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}>
|
||||
Your 24/7{' '}
|
||||
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
Campus Guide
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-[#475569] leading-relaxed mb-8" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Ask anything about RIT — from admission documents to canteen timings. Our AI assistant, powered by Google Gemini with campus-specific knowledge, gives you instant, accurate answers.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||
{[
|
||||
{ icon: 'Brain', label: 'Gemini AI', desc: 'State-of-the-art AI' },
|
||||
{ icon: 'Database', label: 'RAG System', desc: 'Campus knowledge base' },
|
||||
{ icon: 'Map', label: 'Campus Context', desc: 'RIT-specific answers' },
|
||||
{ icon: 'Zap', label: 'Instant Answers', desc: 'No wait time' },
|
||||
].map((item, i) => {
|
||||
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[item.icon];
|
||||
return (
|
||||
<div key={i} className="flex items-start gap-3 p-4 rounded-xl border border-[#E5E7EB] bg-[#FAFAFA]">
|
||||
<div className="w-9 h-9 rounded-lg flex items-center justify-center bg-[#FFF7ED] shrink-0">
|
||||
{Icon && <Icon className="w-4.5 h-4.5" style={{ color: '#F97316' } as React.CSSProperties} />}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>{item.label}</div>
|
||||
<div className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Link to="/ai-assistant">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 px-7 py-3.5 rounded-xl text-white font-semibold"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||
boxShadow: '0 6px 20px rgba(249,115,22,0.35)',
|
||||
}}
|
||||
>
|
||||
<Bot className="w-4.5 h-4.5" />
|
||||
Try AI Assistant
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</motion.button>
|
||||
</Link>
|
||||
</AnimatedContainer>
|
||||
|
||||
{/* Right – Chat Preview */}
|
||||
<AnimatedContainer direction="right" delay={0.2}>
|
||||
<ChatPreview />
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Campus Quick Nav ─────────────────────────────────────────────────── */}
|
||||
<section className="section-padding" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
<div className="container-custom">
|
||||
<SectionTitle
|
||||
tag="Navigate Campus"
|
||||
title="Explore"
|
||||
highlight="RIT Campus"
|
||||
subtitle="Find your way around campus — departments, labs, library, and all key locations."
|
||||
/>
|
||||
|
||||
<AnimatedContainer>
|
||||
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}>
|
||||
{/* Map placeholder */}
|
||||
<div
|
||||
className="h-56 flex items-center justify-center relative overflow-hidden"
|
||||
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 opacity-10"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)',
|
||||
backgroundSize: '30px 30px',
|
||||
}}
|
||||
/>
|
||||
{/* Location pins */}
|
||||
{[
|
||||
{ label: 'CSE Block', x: '25%', y: '30%' },
|
||||
{ label: 'Library', x: '55%', y: '45%' },
|
||||
{ label: 'Canteen', x: '70%', y: '65%' },
|
||||
{ label: 'Hostel', x: '20%', y: '65%' },
|
||||
].map((pin, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute flex flex-col items-center gap-1 cursor-pointer group"
|
||||
style={{ left: pin.x, top: pin.y, transform: 'translate(-50%, -50%)' }}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
>
|
||||
<div className="w-6 h-6 rounded-full flex items-center justify-center shadow-lg group-hover:scale-110 transition-all"
|
||||
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||
<div className="w-2 h-2 rounded-full bg-white" />
|
||||
</div>
|
||||
<span className="text-[10px] text-white font-medium bg-black/50 px-1.5 py-0.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
||||
{pin.label}
|
||||
</span>
|
||||
</motion.div>
|
||||
))}
|
||||
<div className="relative z-10 text-center">
|
||||
<div className="text-white/40 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>Interactive Campus Map</div>
|
||||
<Link to="/campus">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="mt-2 px-5 py-2 rounded-xl text-white text-xs font-semibold"
|
||||
style={{ background: 'rgba(249,115,22,0.8)', fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
Open Full Map
|
||||
</motion.button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick nav buttons */}
|
||||
<div className="p-5 grid grid-cols-4 sm:grid-cols-8 gap-3">
|
||||
{CAMPUS_LOCATIONS.map((loc) => {
|
||||
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
|
||||
return (
|
||||
<Link to="/campus" key={loc.id}>
|
||||
<motion.div
|
||||
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
|
||||
className="flex flex-col items-center gap-2 p-3 rounded-xl cursor-pointer transition-all border border-transparent hover:border-[#FED7AA]"
|
||||
>
|
||||
<div className="w-9 h-9 rounded-xl bg-[#F8FAFC] flex items-center justify-center">
|
||||
{Icon && <Icon className="w-4.5 h-4.5 text-[#F97316]" />}
|
||||
</div>
|
||||
<span className="text-[10px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{loc.name}
|
||||
</span>
|
||||
</motion.div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Community Teaser ─────────────────────────────────────────────────── */}
|
||||
<section className="section-padding bg-white">
|
||||
<div className="container-custom">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Q&A teaser */}
|
||||
<AnimatedContainer direction="left">
|
||||
<div className="rounded-3xl p-7 h-full border border-[#E5E7EB]"
|
||||
style={{ background: 'linear-gradient(135deg, #FAFAFA, #FFF7ED)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.07)' }}>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-11 h-11 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
|
||||
<LucideIcons.MessageCircle className="w-5 h-5 text-[#F97316]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Freshers Q&A</h3>
|
||||
<p className="text-xs text-[#94A3B8]">Get answers from seniors</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3 mb-5">
|
||||
{['How is hostel life at RIT?', 'Best clubs to join as a fresher?', 'Tips for first semester survival'].map((q, i) => (
|
||||
<div key={i} className="flex items-start gap-2.5 p-3 bg-white rounded-xl border border-[#E5E7EB]">
|
||||
<span className="text-[#F97316] text-xs font-bold mt-0.5">Q</span>
|
||||
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{q}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Link to="/community">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
className="w-full py-2.5 rounded-xl text-sm font-semibold text-[#F97316] border-2 border-[#F97316] hover:bg-[#FFF7ED] transition-all"
|
||||
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
Join the Discussion
|
||||
</motion.button>
|
||||
</Link>
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
|
||||
{/* Anonymous Confession Teaser */}
|
||||
<AnimatedContainer direction="right" delay={0.1}>
|
||||
<div className="rounded-3xl p-7 h-full border border-[#E5E7EB]"
|
||||
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.15)' }}>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-11 h-11 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249,115,22,0.2)' }}>
|
||||
<LucideIcons.Heart className="w-5 h-5 text-[#F97316]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-white" style={{ fontFamily: 'Poppins, sans-serif' }}>Anonymous Confessions</h3>
|
||||
<p className="text-xs text-slate-400">Share without revealing identity</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3 mb-5">
|
||||
{[
|
||||
{ text: "I went to the wrong department on my first day 😅", reactions: 87 },
|
||||
{ text: "The library WiFi is faster than my home connection 😂", reactions: 134 },
|
||||
].map((c, i) => (
|
||||
<div key={i} className="p-3 rounded-xl" style={{ backgroundColor: 'rgba(255,255,255,0.07)', border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||
<p className="text-xs text-slate-300 mb-2" style={{ fontFamily: 'Inter, sans-serif' }}>{c.text}</p>
|
||||
<div className="flex items-center gap-1 text-xs text-slate-500">
|
||||
<LucideIcons.Heart className="w-3 h-3 text-[#F97316]" />
|
||||
<span>{c.reactions}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 p-3 rounded-xl mb-4" style={{ backgroundColor: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.2)' }}>
|
||||
<LucideIcons.Shield className="w-4 h-4 text-[#F97316] shrink-0" />
|
||||
<span className="text-xs text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
100% Anonymous. No tracking. No identity revealed.
|
||||
</span>
|
||||
</div>
|
||||
<Link to="/community">
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
className="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all"
|
||||
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
>
|
||||
Confess Anonymously
|
||||
</motion.button>
|
||||
</Link>
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
212
src/pages/Notes/Notes.tsx
Normal file
212
src/pages/Notes/Notes.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight } from 'lucide-react';
|
||||
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { TOOLKIT_ITEMS, DEPARTMENTS } from '@/constants';
|
||||
|
||||
const SEMESTERS = ['All', '1st Sem', '2nd Sem', '3rd Sem', '4th Sem', '5th Sem', '6th Sem', '7th Sem', '8th Sem'];
|
||||
|
||||
const TYPE_COLORS: Record<string, { bg: string; text: string; icon: React.ComponentType<{ className?: string }> }> = {
|
||||
notes: { bg: '#EFF6FF', text: '#3B82F6', icon: BookOpen },
|
||||
pyq: { bg: '#FFF7ED', text: '#F97316', icon: FileText },
|
||||
syllabus: { bg: '#ECFDF5', text: '#10B981', icon: ScrollText },
|
||||
assignment: { bg: '#FDF2F8', text: '#EC4899', icon: BookMarked },
|
||||
};
|
||||
|
||||
const NOTES_DATA = [
|
||||
{ id: '1', title: 'Engineering Mathematics – Unit 1-5', subject: 'Mathematics', department: 'Computer Science & Engineering', semester: 1, type: 'notes', downloads: 348, fileSize: '4.2 MB', uploadedAt: '2025-06-01' },
|
||||
{ id: '2', title: 'Physics PYQ 2020-2024', subject: 'Physics', department: 'Computer Science & Engineering', semester: 1, type: 'pyq', downloads: 512, fileSize: '8.1 MB', uploadedAt: '2025-06-10' },
|
||||
{ id: '3', title: 'C Programming Complete Notes', subject: 'Programming', department: 'Computer Science & Engineering', semester: 1, type: 'notes', downloads: 734, fileSize: '6.3 MB', uploadedAt: '2025-06-15' },
|
||||
{ id: '4', title: 'Data Structures PYQ 2019-2024', subject: 'Data Structures', department: 'Computer Science & Engineering', semester: 3, type: 'pyq', downloads: 621, fileSize: '10.2 MB', uploadedAt: '2025-07-01' },
|
||||
{ id: '5', title: 'Circuit Theory Full Notes', subject: 'Circuit Theory', department: 'Electronics & Communication', semester: 2, type: 'notes', downloads: 289, fileSize: '5.7 MB', uploadedAt: '2025-06-20' },
|
||||
{ id: '6', title: 'Anna University Syllabus 2021', subject: 'Syllabus', department: 'All Departments', semester: 1, type: 'syllabus', downloads: 890, fileSize: '2.1 MB', uploadedAt: '2025-05-01' },
|
||||
{ id: '7', title: 'Thermodynamics Notes', subject: 'Thermodynamics', department: 'Mechanical Engineering', semester: 3, type: 'notes', downloads: 201, fileSize: '3.9 MB', uploadedAt: '2025-06-25' },
|
||||
{ id: '8', title: 'Digital Electronics PYQ', subject: 'Digital Electronics', department: 'Electronics & Communication', semester: 4, type: 'pyq', downloads: 445, fileSize: '7.3 MB', uploadedAt: '2025-07-05' },
|
||||
];
|
||||
|
||||
export default function Notes() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedSem, setSelectedSem] = useState('All');
|
||||
const [selectedDept, setSelectedDept] = useState('All Departments');
|
||||
const [selectedType, setSelectedType] = useState('all');
|
||||
|
||||
const filtered = NOTES_DATA.filter((note) => {
|
||||
const matchSearch = note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
note.subject.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchSem = selectedSem === 'All' || note.semester === parseInt(selectedSem);
|
||||
const matchDept = selectedDept === 'All Departments' || note.department === selectedDept || note.department === 'All Departments';
|
||||
const matchType = selectedType === 'all' || note.type === selectedType;
|
||||
return matchSearch && matchSem && matchDept && matchType;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||
<div className="container-custom">
|
||||
<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' }}>
|
||||
<span>Home</span>
|
||||
<ChevronRight className="w-3 h-3" />
|
||||
<span className="text-[#F97316]">Notes & PYQs</span>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
Notes &{' '}
|
||||
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
PYQs
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
Access semester-wise notes, previous year question papers, and study materials.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-custom py-10">
|
||||
{/* Search + Filters */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.1 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 mb-8"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search notes, subjects..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{/* Type Filter */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Filter className="w-4 h-4 text-[#94A3B8]" />
|
||||
{['all', 'notes', 'pyq', 'syllabus'].map((type) => (
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => setSelectedType(type)}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium capitalize transition-all"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
backgroundColor: selectedType === type ? '#F97316' : '#F8FAFC',
|
||||
color: selectedType === type ? 'white' : '#475569',
|
||||
border: `1px solid ${selectedType === type ? '#F97316' : '#E5E7EB'}`,
|
||||
}}
|
||||
>
|
||||
{type === 'all' ? 'All Types' : type.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Semester Filter */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{SEMESTERS.slice(0, 5).map((sem) => (
|
||||
<button
|
||||
key={sem}
|
||||
onClick={() => setSelectedSem(sem)}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium transition-all"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
backgroundColor: selectedSem === sem ? '#1E293B' : '#F8FAFC',
|
||||
color: selectedSem === sem ? 'white' : '#475569',
|
||||
border: `1px solid ${selectedSem === sem ? '#1E293B' : '#E5E7EB'}`,
|
||||
}}
|
||||
>
|
||||
{sem}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Results */}
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mb-12">
|
||||
{filtered.map((note) => {
|
||||
const typeConfig = TYPE_COLORS[note.type];
|
||||
const TypeIcon = typeConfig.icon;
|
||||
return (
|
||||
<StaggerItem key={note.id}>
|
||||
<motion.div
|
||||
whileHover={{ y: -4 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 flex flex-col gap-3"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0" style={{ backgroundColor: typeConfig.bg }}>
|
||||
<TypeIcon className="w-5 h-5" style={{ color: typeConfig.text } as React.CSSProperties} />
|
||||
</div>
|
||||
<span
|
||||
className="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase"
|
||||
style={{ backgroundColor: typeConfig.bg, color: typeConfig.text, fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
{note.type}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-[#1E293B] leading-snug mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
{note.title}
|
||||
</h3>
|
||||
<p className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{note.subject} · Sem {note.semester}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-[#94A3B8] pt-2 border-t border-[#E5E7EB]">
|
||||
<span style={{ fontFamily: 'Inter, sans-serif' }}>{note.downloads} downloads · {note.fileSize}</span>
|
||||
</div>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-sm font-semibold text-white"
|
||||
style={{
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||
}}
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
);
|
||||
})}
|
||||
</StaggerContainer>
|
||||
|
||||
{/* Toolkit Section */}
|
||||
<SectionTitle tag="Useful Downloads" title="Student" highlight="Toolkit" align="left" />
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{TOOLKIT_ITEMS.map((item) => (
|
||||
<StaggerItem key={item.id}>
|
||||
<motion.a
|
||||
href={item.url}
|
||||
whileHover={{ y: -3 }}
|
||||
className="flex items-center gap-4 p-4 bg-white rounded-2xl border border-[#E5E7EB] cursor-pointer group"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div className="w-10 h-10 rounded-xl flex items-center justify-center bg-[#FFF7ED] shrink-0">
|
||||
<Download className="w-4.5 h-4.5 text-[#F97316]" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-[#1E293B] group-hover:text-[#F97316] transition-colors" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
</motion.a>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
219
src/pages/Profile/Profile.tsx
Normal file
219
src/pages/Profile/Profile.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
User, Mail, Phone, GraduationCap, BookOpen,
|
||||
Settings, LogOut, Edit3, Camera, Bell,
|
||||
Shield, Star, Calendar, ChevronRight
|
||||
} from 'lucide-react';
|
||||
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
|
||||
const MOCK_USER = {
|
||||
name: 'Arjun Kumar',
|
||||
rollNo: 'CB.EN.U4CSE25001',
|
||||
department: 'Computer Science & Engineering',
|
||||
semester: '1st Semester',
|
||||
email: 'arjun.kumar@rit.ac.in',
|
||||
phone: '+91 98765 43210',
|
||||
joinedDate: 'August 2025',
|
||||
batch: '2025-2029',
|
||||
};
|
||||
|
||||
const ACTIVITY = [
|
||||
{ label: 'Notes Downloaded', value: '12', icon: BookOpen, color: '#F97316' },
|
||||
{ label: 'Questions Asked', value: '3', icon: Star, color: '#8B5CF6' },
|
||||
{ label: 'Events Registered', value: '2', icon: Calendar, color: '#10B981' },
|
||||
{ label: 'AI Queries', value: '48', icon: Shield, color: '#3B82F6' },
|
||||
];
|
||||
|
||||
const MENU_ITEMS = [
|
||||
{ icon: Bell, label: 'Notifications', badge: '3' },
|
||||
{ icon: BookOpen, label: 'My Downloads', badge: null },
|
||||
{ icon: Star, label: 'Bookmarks', badge: null },
|
||||
{ icon: Settings, label: 'Settings', badge: null },
|
||||
{ icon: Shield, label: 'Privacy & Security', badge: null },
|
||||
];
|
||||
|
||||
export default function Profile() {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{/* Header */}
|
||||
<div
|
||||
className="py-16 relative overflow-hidden"
|
||||
style={{ background: 'linear-gradient(135deg, #1E293B 0%, #334155 100%)' }}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 opacity-5"
|
||||
style={{
|
||||
backgroundImage: 'radial-gradient(circle, #F97316 1px, transparent 1px)',
|
||||
backgroundSize: '32px 32px',
|
||||
}}
|
||||
/>
|
||||
<div className="container-custom relative z-10">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-6">
|
||||
{/* Avatar */}
|
||||
<div className="relative">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="w-24 h-24 rounded-2xl flex items-center justify-center text-3xl font-bold text-white cursor-pointer"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||
fontFamily: 'Poppins, sans-serif',
|
||||
boxShadow: '0 8px 30px rgba(249,115,22,0.4)',
|
||||
}}
|
||||
>
|
||||
AK
|
||||
</motion.div>
|
||||
<button
|
||||
className="absolute -bottom-2 -right-2 w-8 h-8 rounded-xl flex items-center justify-center text-white"
|
||||
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||
aria-label="Change photo"
|
||||
>
|
||||
<Camera className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="flex-1">
|
||||
<h1 className="text-2xl font-bold text-white mb-1" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||
{MOCK_USER.name}
|
||||
</h1>
|
||||
<p className="text-orange-300 text-sm mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{MOCK_USER.rollNo}
|
||||
</p>
|
||||
<p className="text-slate-400 text-sm mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||
{MOCK_USER.department} · {MOCK_USER.batch}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-orange-500/20 text-orange-300 border border-orange-500/30" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
{MOCK_USER.semester}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
Active Student
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Edit button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-semibold border border-white/20 hover:bg-white/10 transition-all"
|
||||
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
<Edit3 className="w-4 h-4" />
|
||||
Edit Profile
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-custom py-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Left */}
|
||||
<div className="lg:col-span-2 flex flex-col gap-6">
|
||||
{/* Activity Stats */}
|
||||
<AnimatedContainer>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||
{ACTIVITY.map((item, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
whileHover={{ y: -3 }}
|
||||
className="bg-white rounded-2xl border border-[#E5E7EB] p-4 text-center"
|
||||
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||
>
|
||||
<div
|
||||
className="w-10 h-10 rounded-xl flex items-center justify-center mx-auto mb-2"
|
||||
style={{ backgroundColor: `${item.color}15` }}
|
||||
>
|
||||
<item.icon className="w-5 h-5" style={{ color: item.color } as React.CSSProperties} />
|
||||
</div>
|
||||
<div className="text-xl font-bold mb-0.5" style={{ color: item.color, fontFamily: 'Poppins, sans-serif' }}>
|
||||
{item.value}
|
||||
</div>
|
||||
<div className="text-[10px] text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
|
||||
{/* Personal Details */}
|
||||
<AnimatedContainer delay={0.1}>
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-6" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
<h3 className="text-sm font-semibold text-[#1E293B] mb-5" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
Personal Information
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
{[
|
||||
{ icon: User, label: 'Full Name', value: MOCK_USER.name },
|
||||
{ icon: GraduationCap, label: 'Roll Number', value: MOCK_USER.rollNo },
|
||||
{ icon: Mail, label: 'Email', value: MOCK_USER.email },
|
||||
{ icon: Phone, label: 'Phone', value: MOCK_USER.phone },
|
||||
{ icon: BookOpen, label: 'Department', value: MOCK_USER.department },
|
||||
{ icon: Calendar, label: 'Joined', value: MOCK_USER.joinedDate },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex items-center gap-3 p-3 rounded-xl bg-[#F8FAFC] border border-[#E5E7EB]">
|
||||
<div className="w-9 h-9 rounded-lg flex items-center justify-center bg-[#FFF7ED] shrink-0">
|
||||
<item.icon className="w-4.5 h-4.5 text-[#F97316]" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[10px] text-[#94A3B8] uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</div>
|
||||
<div className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.value}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div className="flex flex-col gap-5">
|
||||
{/* Menu */}
|
||||
<AnimatedContainer direction="right" delay={0.1}>
|
||||
<div className="bg-white rounded-2xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||
{MENU_ITEMS.map((item, i) => (
|
||||
<motion.button
|
||||
key={i}
|
||||
whileHover={{ backgroundColor: '#FFF7ED', x: 2 }}
|
||||
className="w-full flex items-center justify-between px-5 py-3.5 border-b border-[#E5E7EB] last:border-0 transition-all text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<item.icon className="w-4 h-4 text-[#94A3B8]" />
|
||||
<span className="text-sm text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{item.badge && (
|
||||
<span className="w-5 h-5 rounded-full flex items-center justify-center text-[10px] text-white font-bold"
|
||||
style={{ backgroundColor: '#F97316', fontFamily: 'Poppins, sans-serif' }}>
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
<ChevronRight className="w-3.5 h-3.5 text-[#94A3B8]" />
|
||||
</div>
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</AnimatedContainer>
|
||||
|
||||
{/* Logout */}
|
||||
<AnimatedContainer direction="right" delay={0.2}>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl border-2 border-red-100 text-red-400 hover:bg-red-50 text-sm font-semibold transition-all"
|
||||
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
Sign Out
|
||||
</motion.button>
|
||||
</AnimatedContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user