feat: apply various dashboard and auth fixes including demo trials, settings UI, and signup options
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { ReactNode, useState, useEffect, useRef } from "react"
|
||||
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare, MessageSquare } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { usePathname, useRouter } from "next/navigation"
|
||||
import { LanguageToggle } from "@/components/LanguageToggle"
|
||||
import { Logo } from "@/components/Logo"
|
||||
import { ThemeToggle } from "@/components/ThemeToggle"
|
||||
@@ -15,6 +15,8 @@ import { StreakOnboardingModal } from "@/components/Dashboard/StreakOnboardingMo
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import MaintenanceScreen from "@/components/MaintenanceScreen"
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
@@ -54,14 +56,27 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
const cleanDisplayName = (name: string) => name.replace(/\s*\([^)]*\)\s*$/, '').trim();
|
||||
|
||||
const userInitials = user ? cleanDisplayName(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" : user?.target_exam === "JEE_Advanced" ? "JEE Advanced" : user?.target_exam === "NEET" ? "NEET" : "NEET/JEE"
|
||||
const targetExamLabel = user?.target_exam === "JEE_Main" ? "JEE" : user?.target_exam === "JEE_Advanced" ? "JEE Advanced" : user?.target_exam === "NEET" ? "NEET" : "JEE + NEET"
|
||||
|
||||
// Trial logic removed as requested
|
||||
const router = useRouter();
|
||||
|
||||
if (maintenance) {
|
||||
return <MaintenanceScreen />;
|
||||
}
|
||||
|
||||
// Redirect to login (or admin) if no user is found after loading
|
||||
if (!loading && !switching && !user) {
|
||||
if (typeof window !== 'undefined') {
|
||||
const activeId = localStorage.getItem("activeUserId");
|
||||
if (activeId === "admin") {
|
||||
router.replace("/admin");
|
||||
} else {
|
||||
router.replace("/login");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (loading || !user) {
|
||||
return (
|
||||
<div className="h-screen w-screen flex flex-col items-center justify-center bg-background">
|
||||
@@ -103,20 +118,32 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
<nav className="flex-1 min-h-0 px-3 py-3 overflow-y-auto custom-scrollbar space-y-0.5">
|
||||
|
||||
{/* PRO TIER Section */}
|
||||
<div className="px-2 pt-2 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-yellow-500/40 to-transparent" />
|
||||
<span className="text-[10px] font-black uppercase tracking-[0.15em] text-yellow-600 dark:text-yellow-400 flex items-center gap-1.5 shrink-0">
|
||||
<span className="w-1 h-1 rounded-full bg-yellow-500 shadow-[0_0_4px_#EAB308]"></span>
|
||||
Pro Tier
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-gradient-to-l from-transparent via-yellow-500/40 to-transparent" />
|
||||
{/* Section divider — only shows 'Pro Tier' label for users who have Pro or above */}
|
||||
{hasAccessTo("chapter-wise-progress-tracking") ? (
|
||||
<div className="px-2 pt-2 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-yellow-500/40 to-transparent" />
|
||||
<span className="text-[10px] font-black uppercase tracking-[0.15em] text-yellow-600 dark:text-yellow-400 flex items-center gap-1.5 shrink-0">
|
||||
<span className="w-1 h-1 rounded-full bg-yellow-500 shadow-[0_0_4px_#EAB308]"></span>
|
||||
Pro Tier
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-gradient-to-l from-transparent via-yellow-500/40 to-transparent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 pt-2 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-border to-transparent" />
|
||||
<span className="text-[10px] font-black uppercase tracking-[0.15em] text-muted-foreground flex items-center gap-1.5 shrink-0">
|
||||
Free Plan
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-gradient-to-l from-transparent via-border to-transparent" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<NavItem section="pro" href="/dashboard" icon={<LayoutDashboard size={17} />} label="Overview" active={pathname === "/dashboard"} />
|
||||
<NavItem section="pro" href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
|
||||
<NavItem section="pro" href="/dashboard/leaderboard" icon={<Trophy size={17} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} isLocked={!hasAccessTo("chapter-wise-progress-tracking")} />
|
||||
<NavItem
|
||||
section="pro"
|
||||
href="/dashboard/progress"
|
||||
@@ -133,14 +160,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
active={pathname.startsWith("/dashboard/cbt")}
|
||||
isLocked={!hasAccessTo("weekly-test-series")}
|
||||
/>
|
||||
{/* <NavItem
|
||||
section="pro"
|
||||
href="/dashboard/materials"
|
||||
icon={<BookOpen size={17} />}
|
||||
label="Quick Notes"
|
||||
active={pathname.startsWith("/dashboard/materials")}
|
||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||
/> */}
|
||||
|
||||
<NavItem
|
||||
section="pro"
|
||||
@@ -151,7 +170,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
isLocked={!hasAccessTo("doubt-resolution-community")}
|
||||
/>
|
||||
|
||||
{/* AI COACH Section */}
|
||||
{/* AI COACH Section always visible */}
|
||||
<div className="px-2 pt-4 pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-cyan-500/50 to-transparent" />
|
||||
@@ -244,6 +263,26 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
isLocked={!hasAccessTo("detailed-performance-analytics")}
|
||||
/>
|
||||
|
||||
{(!hasAccessTo("ai-tutor-chat") || !hasAccessTo("chapter-wise-progress-tracking")) && (
|
||||
<div className="px-3 pt-6 pb-2">
|
||||
<div className="rounded-xl bg-gradient-to-br from-cyan-500/10 to-yellow-500/10 border border-border/50 p-4 text-center">
|
||||
<div className="w-10 h-10 mx-auto bg-background rounded-full shadow-sm flex items-center justify-center mb-3">
|
||||
<Lock size={18} className="text-muted-foreground" />
|
||||
</div>
|
||||
<h4 className="text-sm font-bold text-foreground mb-1">More Features Locked</h4>
|
||||
<p className="text-xs text-muted-foreground mb-3">
|
||||
Upgrade your plan to unlock the AI Coach and advanced Pro tools.
|
||||
</p>
|
||||
<Link
|
||||
href="/dashboard/settings"
|
||||
className="block w-full py-2 px-3 bg-primary text-primary-foreground text-xs font-bold rounded-lg shadow-sm hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Upgrade Now
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</nav>
|
||||
|
||||
{/* Bottom Settings + Profile */}
|
||||
@@ -275,10 +314,10 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
</Link>
|
||||
{isDemoUser(userId) && (
|
||||
<button
|
||||
onClick={() => switchUser(DEMO_USERS.AICOACH_JEE.id)}
|
||||
onClick={() => switchUser(userId, true)}
|
||||
className="w-full text-left flex items-center gap-2 px-3 py-2 text-sm text-foreground hover:bg-secondary rounded-lg transition-colors"
|
||||
>
|
||||
Reset Demo
|
||||
Reset Current Demo
|
||||
</button>
|
||||
)}
|
||||
<div className="h-px bg-border my-1" />
|
||||
@@ -303,7 +342,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-bold leading-none truncate">{user ? cleanDisplayName(user.name).split(" ")[0] : "Loading..."}</p>
|
||||
<div className="mt-1">
|
||||
<TierBadge tier={user ? (user.tier === "Al_Coach" ? "AI Coach" : user.tier) : "Free"} />
|
||||
<TierBadge tier={user ? (user.tier === "Al_Coach" ? "AI Coach" : user.tier === "Pro" ? "Pro" : "Free") : "Free"} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -326,7 +365,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
{targetExamLabel} Target
|
||||
</Badge>
|
||||
)}
|
||||
{/* Trial countdown badge removed from top header */}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -396,6 +434,8 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
function NavItem({ href, icon, label, active, isLocked, section }: { href: string, icon: React.ReactNode, label: string, active?: boolean, isLocked?: boolean, section?: "pro" | "ai" }) {
|
||||
// We no longer return null for isLocked. We render it with a disabled appearance.
|
||||
|
||||
// Section-aware active color tokens (light mode only, dark mode falls back to cyan)
|
||||
const activeClasses = active
|
||||
? section === "pro"
|
||||
@@ -403,8 +443,6 @@ function NavItem({ href, icon, label, active, isLocked, section }: { href: strin
|
||||
: section === "ai"
|
||||
? "bg-cyan-50 dark:bg-[var(--cyan-ai-10)] text-cyan-800 dark:text-[var(--cyan-ai)] font-semibold border border-cyan-300/60 dark:border-[var(--cyan-ai-20)] shadow-[0_0_12px_-4px_rgba(6,182,212,0.2)] dark:shadow-[0_0_12px_-4px_rgba(12,96,96,0.3)]"
|
||||
: "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] font-semibold border border-[var(--cyan-ai-20)] shadow-[0_0_12px_-4px_rgba(12,96,96,0.3)]"
|
||||
: isLocked
|
||||
? "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"
|
||||
|
||||
const iconActiveClass = active
|
||||
@@ -413,8 +451,6 @@ function NavItem({ href, icon, label, active, isLocked, section }: { href: strin
|
||||
: section === "ai"
|
||||
? "text-cyan-700 dark:text-[var(--cyan-ai)]"
|
||||
: "text-[var(--cyan-ai)]"
|
||||
: isLocked
|
||||
? "text-muted-foreground/30"
|
||||
: "text-muted-foreground/60 group-hover:text-foreground/80"
|
||||
|
||||
const activeBarClass = active
|
||||
@@ -427,8 +463,8 @@ function NavItem({ href, icon, label, active, isLocked, section }: { href: strin
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={isLocked ? "#" : href}
|
||||
className={`group flex items-center justify-between px-3 py-2 rounded-xl transition-all duration-200 ${activeClasses}`}
|
||||
href={isLocked ? "/dashboard/settings" : href}
|
||||
className={`group flex items-center justify-between px-3 py-2 rounded-xl transition-all duration-200 ${isLocked ? 'opacity-60 grayscale-[0.5]' : ''} ${activeClasses}`}
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className={`transition-all duration-200 ${iconActiveClass}`}>
|
||||
@@ -436,8 +472,10 @@ function NavItem({ href, icon, label, active, isLocked, section }: { href: strin
|
||||
</span>
|
||||
<span className="text-[13px] font-medium tracking-[-0.01em]">{label}</span>
|
||||
</div>
|
||||
{isLocked && <Lock size={11} className="text-muted-foreground/30 shrink-0" />}
|
||||
{active && <span className={activeBarClass}></span>}
|
||||
<div className="flex items-center gap-1.5">
|
||||
{isLocked && <Lock size={12} className="text-muted-foreground/70" />}
|
||||
{!isLocked && active && <span className={activeBarClass}></span>}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user