feat: setup admin JSON generation and pdf viewer for free plan DPPs

This commit is contained in:
2026-07-27 14:56:25 +05:30
parent c214ca41b6
commit a5bb2d867c
74 changed files with 8546 additions and 915 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 } from "lucide-react"
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, LogOut, User, ChevronUp, Lock, ShieldAlert, Flame, Target } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LanguageToggle } from "@/components/LanguageToggle"
@@ -13,13 +13,30 @@ import { AITutorChat } from "@/components/AI/AITutorChat"
import { useActiveUser, DEMO_USERS } from "@/hooks/useActiveUser"
import { StreakOnboardingModal } from "@/components/Dashboard/StreakOnboardingModal"
import { Badge } from "@/components/ui/badge"
import MaintenanceScreen from "@/components/MaintenanceScreen"
export default function DashboardLayout({ children }: { children: ReactNode }) {
const pathname = usePathname()
const [dropdownOpen, setDropdownOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
const { userId, user, switchUser, hasAccessTo, loading } = useActiveUser();
const { userId, user, globalSettings, switchUser, hasAccessTo, loading } = useActiveUser();
const [maintenance, setMaintenance] = useState(false);
const [checkingMaintenance, setCheckingMaintenance] = useState(true);
useEffect(() => {
if (globalSettings !== undefined && globalSettings !== null) {
if (globalSettings.maintenance_mode) {
setMaintenance(true);
} else {
setMaintenance(false);
}
setCheckingMaintenance(false);
} else if (globalSettings === null && !loading) {
// If we finished loading but settings failed to load
setCheckingMaintenance(false);
}
}, [globalSettings, loading]);
// Close dropdown when clicking outside
useEffect(() => {
@@ -35,15 +52,22 @@ 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 = null;
if (user && user.created_at) {
const createdDate = new Date(user.created_at);
const msPerDay = 1000 * 60 * 60 * 24;
const daysElapsed = Math.floor((new Date().getTime() - createdDate.getTime()) / msPerDay);
daysLeft = Math.max(0, 15 - daysElapsed);
} else if (user && user.tier === 'Free') {
// Mock value for demo Free tier if no date
daysLeft = 14;
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;
}
}
if (maintenance) {
return <MaintenanceScreen />;
}
return (
@@ -61,7 +85,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</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/progress" icon={<Activity size={18} />} label="Syllabus Tracker" active={pathname.startsWith("/dashboard/progress")} />
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={18} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
<div className="pt-4 pb-2">
@@ -69,6 +92,13 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<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
</p>
</div>
<NavItem
href="/dashboard/progress"
icon={<Activity size={18} />}
label="Syllabus Tracker"
active={pathname.startsWith("/dashboard/progress")}
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
/>
<NavItem
href="/dashboard/cbt"
icon={<Activity size={18} />}
@@ -95,7 +125,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
icon={<BookOpen size={18} />}
label="Doubt Forum"
active={pathname.startsWith("/dashboard/community")}
isLocked={!hasAccessTo("ask-doubts-from-tutors")}
isLocked={!hasAccessTo("doubt-resolution-community")}
/>
<div className="pt-4 pb-2">
@@ -104,19 +134,75 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</p>
</div>
<NavItem
href="/dashboard/ai-coach"
href="/dashboard/ai-coach/tutor"
icon={<BrainCircuitIcon />}
label="AI Coach"
active={pathname.startsWith("/dashboard/ai-coach")}
label="AI Tutor"
active={pathname.startsWith("/dashboard/ai-coach/tutor") || pathname === "/dashboard/ai-coach"}
isLocked={!hasAccessTo("ai-tutor-chat")}
/>
<NavItem
href="/dashboard/rank-predictor"
href="/dashboard/ai-coach/cbt-dpp"
icon={<FileText size={18} />}
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} />}
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} />}
label="Rank Predictor"
active={pathname.startsWith("/dashboard/rank-predictor")}
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} />}
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} />}
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"
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/heatmap"
icon={<Flame size={18} />}
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} />}
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} />}
label="Mistake Analyzer"
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
/>
</nav>
<div className="p-4 border-t border-border space-y-2 bg-secondary/30">
@@ -179,17 +265,33 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<header className="h-16 border-b border-border bg-background/95 backdrop-blur-md flex items-center justify-between px-8 shrink-0 z-40">
<div className="flex items-center gap-4">
<h2 className="font-bold text-lg hidden sm:block">
{user ? `Welcome back, ${user.name.split(" ")[0]}! 👋` : "Welcome back!"}
{user ? `Welcome back, ${user.name.split(" ")[0]}!` : "Welcome back!"}
</h2>
{user && (
<Badge variant="outline" className="bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20 font-bold tracking-wide uppercase px-2.5 py-0.5 animate-pulse">
{targetExamLabel} Target
</Badge>
)}
{daysLeft !== null && user?.tier !== 'Al_Coach' && user?.tier !== 'Pro' && (
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20 font-bold px-2.5 py-0.5">
{daysLeft} Days Left in AI Coach Trial
</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>
@@ -209,6 +311,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
onChange={(e) => switchUser(e.target.value)}
className="bg-secondary border border-border text-foreground px-2.5 py-1.5 rounded-xl text-xs font-bold focus:ring-2 focus:ring-[#00F0FF] outline-none cursor-pointer hover:bg-secondary/80 transition-colors"
>
<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>