feat(auth): protect Collab, Community Q&A, and Leetcode routes behind Google Sign-In
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled

This commit is contained in:
Shanmuga Krishnan S M
2026-08-06 13:39:08 +05:30
parent 1b698d7cca
commit b852018f24
2 changed files with 137 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'; import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider } from '@/context/AuthContext'; import { AuthProvider } from '@/context/AuthContext';
import ProtectedRoute from '@/components/Auth/ProtectedRoute';
import MainLayout from '@/layouts/MainLayout'; import MainLayout from '@/layouts/MainLayout';
import Home from '@/pages/Home/Home'; import Home from '@/pages/Home/Home';
import Notes from '@/pages/Notes/Notes'; import Notes from '@/pages/Notes/Notes';
@@ -48,9 +49,42 @@ function App() {
<Route path="/bus-routes" element={<BusRoutes />} /> <Route path="/bus-routes" element={<BusRoutes />} />
<Route path="/faculty" element={<Faculty />} /> <Route path="/faculty" element={<Faculty />} />
<Route path="/events" element={<Events />} /> <Route path="/events" element={<Events />} />
<Route path="/community" element={<Community />} />
<Route path="/collab" element={<DevCollab />} /> {/* Protected Routes — Require Google Sign-In */}
<Route path="/leetcode" element={<LeetcodeLeaderboard />} /> <Route
path="/community"
element={
<ProtectedRoute
title="RIT Q&A Community"
description="Connect with fellow RIT students and ask senior helpers any question about campus life and academics."
>
<Community />
</ProtectedRoute>
}
/>
<Route
path="/collab"
element={
<ProtectedRoute
title="Developer Collab Hub"
description="Post open-source projects, search for co-developers, and connect with fellow programmers."
requireVerifiedStudent={true}
>
<DevCollab />
</ProtectedRoute>
}
/>
<Route
path="/leetcode"
element={
<ProtectedRoute
title="RIT LeetCode Leaderboard"
description="Track campus competitive programming rankings and see top RIT coders."
>
<LeetcodeLeaderboard />
</ProtectedRoute>
}
/>
</Route> </Route>
</Routes> </Routes>
</AuthProvider> </AuthProvider>

View File

@@ -0,0 +1,100 @@
import { type ReactNode } from 'react';
import { motion } from 'framer-motion';
import { ShieldAlert, LogIn, Sparkles, ShieldCheck } from 'lucide-react';
import { useAuth } from '@/context/AuthContext';
interface ProtectedRouteProps {
children: ReactNode;
title: string;
description: string;
requireVerifiedStudent?: boolean;
}
export default function ProtectedRoute({
children,
title,
description,
requireVerifiedStudent = false,
}: ProtectedRouteProps) {
const { isAuthenticated, isVerifiedStudent, loginWithGoogle, user } = useAuth();
// If user is authenticated (and meets verified student criteria if required), render the actual page!
const isAllowed = isAuthenticated && (!requireVerifiedStudent || isVerifiedStudent);
if (isAllowed) {
return <>{children}</>;
}
return (
<div className="min-h-[80vh] flex items-center justify-center p-4 relative overflow-hidden bg-[#FAFAFA]">
{/* Background ambient light */}
<div className="absolute inset-0 pointer-events-none overflow-hidden">
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-96 h-96 bg-orange-200/30 rounded-full blur-3xl opacity-60" />
</div>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="relative z-10 w-full max-w-lg bg-white border border-[#E5E7EB] rounded-3xl shadow-xl p-8 sm:p-10 text-center"
>
{/* Lock / Shield Icon */}
<div className="w-20 h-20 rounded-3xl bg-orange-50 border border-orange-200 flex items-center justify-center mx-auto mb-6 shadow-sm">
{requireVerifiedStudent ? (
<ShieldCheck className="w-10 h-10 text-[#F97316]" />
) : (
<ShieldAlert className="w-10 h-10 text-[#F97316]" />
)}
</div>
{/* Feature Title */}
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-orange-50 text-[#F97316] text-xs font-semibold mb-3 border border-orange-200">
<Sparkles className="w-3.5 h-3.5" /> Authentication Required
</div>
<h1
className="text-2xl sm:text-3xl font-bold text-[#1E293B] mb-3"
style={{ fontFamily: 'Playfair Display, serif' }}
>
{title}
</h1>
<p
className="text-slate-600 text-sm mb-8 leading-relaxed max-w-md mx-auto"
style={{ fontFamily: 'Inter, sans-serif' }}
>
{!isAuthenticated ? (
<>
{description} Sign in with your Google account to access this feature.
</>
) : (
<>
You are signed in as <strong>{user?.email}</strong>. This feature is restricted to verified students with an <strong className="text-[#F97316]">@ritchennai.edu.in</strong> college email.
</>
)}
</p>
{/* Action Button */}
<div className="space-y-3">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={loginWithGoogle}
className="w-full flex items-center justify-center gap-2.5 px-6 py-4 rounded-2xl text-white font-semibold text-sm shadow-lg shadow-orange-500/25 cursor-pointer"
style={{
background: 'linear-gradient(135deg, #F97316, #FB923C)',
fontFamily: 'Poppins, sans-serif',
}}
>
<LogIn className="w-5 h-5" />
{!isAuthenticated ? 'Sign In with Google to Unlock' : 'Switch to @ritchennai.edu.in Account'}
</motion.button>
<p className="text-[11px] text-slate-400">
Sign-in is quick, secure, and preserves your student privacy.
</p>
</div>
</motion.div>
</div>
);
}