feat: setup admin JSON generation and pdf viewer for free plan DPPs
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user