chore: Secure routes with auth middleware, update text and fix minor bugs

This commit is contained in:
2026-08-03 17:55:20 +05:30
parent 62d15c85f6
commit c2fab50749
68 changed files with 2515 additions and 6808 deletions

View File

@@ -1,7 +1,7 @@
"use client"
import { ReactNode, useState, useEffect, useRef } from "react"
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, LogOut, User, ChevronUp, Lock, ShieldAlert, Flame, Target } from "lucide-react"
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LanguageToggle } from "@/components/LanguageToggle"
@@ -52,24 +52,23 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
const userInitials = user ? user.name.split(" ").filter(n => /^[a-zA-Z]/.test(n)).slice(0, 2).map(n => n[0]).join("").toUpperCase() : "DS"
const targetExamLabel = user?.target_exam === "JEE_Main" ? "JEE Main" : user?.target_exam === "JEE_Advanced" ? "JEE Advanced" : user?.target_exam === "NEET" ? "NEET" : "NEET/JEE"
let daysLeft: number | null = null;
const trialDays = globalSettings?.trial_duration_days !== undefined ? globalSettings.trial_duration_days : 15;
const trialEnabled = globalSettings?.enable_free_trial !== undefined ? globalSettings.enable_free_trial : true;
// For the demo, treat both Free tier and Neha as Trial users so the banner appears
if (user && (user.tier === 'Free' || user.id === DEMO_USERS.NEHA.id)) {
if (!trialEnabled) {
daysLeft = 0;
} else {
// For demo purposes, we ignore created_at and just show the exact days set in the admin panel
daysLeft = trialDays;
}
}
// Trial logic removed as requested
if (maintenance) {
return <MaintenanceScreen />;
}
if (loading || !user) {
return (
<div className="h-screen w-screen flex flex-col items-center justify-center bg-background">
<div className="animate-pulse flex flex-col items-center gap-4">
<Logo height={56} className="mb-4" />
<div className="h-6 w-6 border-4 border-[#00F0FF] border-t-transparent rounded-full animate-spin"></div>
</div>
</div>
);
}
return (
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-primary/20">
{/* Sidebar */}
@@ -78,59 +77,64 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<Logo href="/dashboard" className="w-full" imageClassName="object-contain object-left scale-110 origin-left" height={56} />
</div>
<nav className="flex-1 px-4 space-y-1.5 mt-4 overflow-y-auto">
<div className="pt-2 pb-2">
{/* <div className="pt-2 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-white"></span> Free Tier
</p>
</div>
<NavItem href="/dashboard" icon={<LayoutDashboard size={18} />} label="Overview" active={pathname === "/dashboard"} />
<NavItem href="/dashboard/dpp" icon={<FileText size={18} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} />
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={18} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
</div> */}
<div className="pt-4 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_var(--color-yellow-500)]"></span> Pro Tier
<div className="pt-3 pb-1">
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 flex items-center gap-2">
<span className="flex-1 h-px bg-border/60"></span>
<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_rgba(234,179,8,0.8)]"></span>Pro Tier</span>
<span className="flex-1 h-px bg-border/60"></span>
</p>
</div>
<NavItem href="/dashboard" icon={<LayoutDashboard size={17} />} label="Overview" active={pathname === "/dashboard"} />
{/* <NavItem href="/dashboard/dpp" icon={<FileText size={17} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} /> */}
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
<NavItem
href="/dashboard/progress"
icon={<Activity size={18} />}
icon={<BarChart3 size={17} />}
label="Syllabus Tracker"
active={pathname.startsWith("/dashboard/progress")}
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
/>
<NavItem
href="/dashboard/cbt"
icon={<Activity size={18} />}
icon={<CheckSquare size={17} />}
label="Mock Tests (CBT)"
active={pathname.startsWith("/dashboard/cbt")}
isLocked={!hasAccessTo("weekly-test-series")}
/>
<NavItem
href="/dashboard/materials"
icon={<BookOpen size={18} />}
icon={<BookOpen size={17} />}
label="Quick Notes"
active={pathname.startsWith("/dashboard/materials")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/analytics"
icon={<LineChart size={18} />}
icon={<LineChart size={17} />}
label="Performance Analytics"
active={pathname.startsWith("/dashboard/analytics")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/community"
icon={<BookOpen size={18} />}
icon={<School size={17} />}
label="Doubt Forum"
active={pathname.startsWith("/dashboard/community")}
isLocked={!hasAccessTo("doubt-resolution-community")}
/>
<div className="pt-4 pb-2">
<p className="px-3 text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-[#00F0FF] shadow-[0_0_8px_-2px_#00F0FF]"></span> AI Coach
<div className="pt-4 pb-1">
<p className="px-3 text-[10px] font-black uppercase tracking-widest text-muted-foreground/60 flex items-center gap-2">
<span className="flex-1 h-px bg-border/60"></span>
<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-[#00F0FF] shadow-[0_0_8px_-2px_#00F0FF]"></span>AI Coach</span>
<span className="flex-1 h-px bg-border/60"></span>
</p>
</div>
<NavItem
@@ -142,63 +146,63 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
/>
<NavItem
href="/dashboard/ai-coach/cbt-dpp"
icon={<FileText size={18} />}
icon={<Zap size={17} />}
label="PrepPilot AI"
active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")}
isLocked={!hasAccessTo("ai-tutor-chat")}
/>
<NavItem
href="/dashboard/ai-coach/readiness"
icon={<Activity size={18} />}
icon={<Gauge size={17} />}
label="Exam Readiness"
active={pathname.startsWith("/dashboard/ai-coach/readiness")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/ai-coach/college-predictor"
icon={<Trophy size={18} />}
icon={<School size={17} />}
label="College Predictor"
active={pathname.startsWith("/dashboard/ai-coach/college-predictor")}
isLocked={!hasAccessTo("college-predictor")}
/>
<NavItem
href="/dashboard/ai-coach/strategy"
icon={<BookOpen size={18} />}
icon={<Target size={17} />}
label="Exam Strategy"
active={pathname.startsWith("/dashboard/ai-coach/strategy")}
isLocked={!hasAccessTo("exam-day-strategy")}
/>
<NavItem
href="/dashboard/ai-coach/study-plan"
icon={<LineChart size={18} />}
icon={<Calendar size={17} />}
label="AI Study Plan"
active={pathname.startsWith("/dashboard/ai-coach/study-plan")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/time-optimization"
icon={<Activity size={18} />}
label="Study Time Optimization"
icon={<Clock size={17} />}
label="Time Optimization"
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/heatmap"
icon={<Flame size={18} />}
icon={<Flame size={17} />}
label="Mistake Heatmap"
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
/>
<NavItem
href="/dashboard/ai-coach/weekly-progress"
icon={<Target size={18} />}
icon={<Activity size={17} />}
label="Weekly Progress"
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/ai-coach/mistakes"
icon={<Activity size={18} />}
icon={<BrainCircuit size={17} />}
label="Mistake Analyzer"
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
@@ -272,27 +276,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
{targetExamLabel} Target
</Badge>
)}
{(user?.tier === 'Free' || user?.id === DEMO_USERS.NEHA.id) && daysLeft !== null && (
daysLeft > 0 ? (
<Link href="/upgrade" className="group">
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/30 font-bold px-3 py-1 cursor-pointer hover:bg-yellow-500/20 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_12px_rgba(234,179,8,0.15)]">
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-yellow-500" /> {daysLeft} Days Left in Trial</span>
<span className="text-[11px] bg-yellow-500 text-black px-2 py-0.5 rounded-full font-black group-hover:bg-yellow-400 transition-colors shadow-sm">
Upgrade Now →
</span>
</Badge>
</Link>
) : (
<Link href="/upgrade" className="group">
<Badge variant="outline" className="bg-red-500/15 text-red-400 border-red-500/40 font-black px-3.5 py-1 cursor-pointer hover:bg-red-500/30 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_15px_rgba(239,68,68,0.25)] animate-pulse">
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-red-400" /> Trial Expired</span>
<span className="text-[11px] bg-red-500 text-white px-2 py-0.5 rounded-full font-black shadow-sm group-hover:bg-red-600 transition-colors">
Upgrade Now →
</span>
</Badge>
</Link>
)
)}
</div>
<div className="flex items-center gap-4">
@@ -314,7 +297,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<option value={DEMO_USERS.NEHA.id}>Neha Verma (AI Coach)</option>
<option value={DEMO_USERS.AI_COACH.id}>Rahul (AI Coach)</option>
<option value={DEMO_USERS.PRO.id}>Priya (Pro Plan)</option>
<option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option>
{/* <option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option> */}
</select>
</div>
@@ -344,19 +327,24 @@ function NavItem({ href, icon, label, active, isLocked }: { href: string, icon:
return (
<Link
href={isLocked ? "#" : href}
className={`flex items-center justify-between px-3 py-2.5 rounded-xl transition-all duration-300 ${
className={`group flex items-center justify-between px-3 py-2 rounded-xl transition-all duration-200 ${
active
? "bg-secondary text-foreground font-semibold border border-border"
? "bg-[#00F0FF]/10 text-[#00F0FF] font-semibold border border-[#00F0FF]/20 shadow-[0_0_12px_-4px_rgba(0,240,255,0.3)]"
: isLocked
? "text-muted-foreground/60 cursor-not-allowed border border-transparent"
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground border border-transparent"
? "text-muted-foreground/40 cursor-not-allowed border border-transparent"
: "text-muted-foreground/80 hover:bg-secondary/60 hover:text-foreground border border-transparent hover:border-border/40"
}`}
>
<div className="flex items-center gap-3">
{icon}
<span className="text-sm">{label}</span>
<div className="flex items-center gap-2.5">
<span className={`transition-all duration-200 ${
active ? "text-[#00F0FF]" : isLocked ? "text-muted-foreground/30" : "text-muted-foreground/60 group-hover:text-foreground/80"
}`}>
{icon}
</span>
<span className="text-[13px] font-medium tracking-[-0.01em]">{label}</span>
</div>
{isLocked && <Lock size={12} className="text-muted-foreground/50" />}
{isLocked && <Lock size={11} className="text-muted-foreground/30 shrink-0" />}
{active && <span className="w-1 h-4 rounded-full bg-[#00F0FF] shrink-0 shadow-[0_0_6px_rgba(0,240,255,0.8)]"></span>}
</Link>
)
}