chore: Secure routes with auth middleware, update text and fix minor bugs
This commit is contained in:
@@ -115,7 +115,7 @@ export default function AIComCBTDPPPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-xs font-black uppercase tracking-widest text-[#00F0FF] bg-[#00F0FF]/10 px-2.5 py-0.5 rounded-full border border-[#00F0FF]/30">
|
||||
AI CBT DPP Simulator
|
||||
PrepPilot AI
|
||||
</span>
|
||||
<span className="text-xs font-bold text-muted-foreground">• {user?.target_exam?.replace("_", " ") || "JEE/NEET"}</span>
|
||||
</div>
|
||||
@@ -180,7 +180,7 @@ export default function AIComCBTDPPPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-lg font-bold leading-relaxed text-foreground">
|
||||
<p className="text-xl md:text-2xl font-black leading-relaxed text-foreground">
|
||||
{sampleQuestions[activeQuestion].question}
|
||||
</p>
|
||||
|
||||
@@ -203,13 +203,13 @@ export default function AIComCBTDPPPage() {
|
||||
<div
|
||||
key={optIdx}
|
||||
onClick={() => handleOptionSelect(activeQuestion, optIdx)}
|
||||
className={`flex items-center gap-4 p-4 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
|
||||
className={`flex items-center gap-4 p-4.5 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
|
||||
>
|
||||
<span className="w-8 h-8 rounded-xl bg-background/80 border border-border flex items-center justify-center font-bold text-sm shrink-0">
|
||||
<span className="w-10 h-10 rounded-xl bg-background/80 border border-border flex items-center justify-center font-black text-base shrink-0">
|
||||
{letter}
|
||||
</span>
|
||||
<span className="text-base font-medium flex-1">{opt}</span>
|
||||
{submitted && isCorrect && <CheckCircle2 size={20} className="text-emerald-400" />}
|
||||
<span className="text-lg md:text-xl font-bold flex-1">{opt}</span>
|
||||
{submitted && isCorrect && <CheckCircle2 size={22} className="text-emerald-400" />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import AttemptHistoryPage from "@/app/dashboard/cbt/history/[attemptId]/page";
|
||||
|
||||
export default function AIHistoryPage({ params }: { params: Promise<{ attemptId: string }> }) {
|
||||
return <AttemptHistoryPage params={params} />;
|
||||
}
|
||||
@@ -2,19 +2,21 @@
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles } from "lucide-react"
|
||||
import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles, Share2, Maximize2, Minimize2 } from "lucide-react"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
|
||||
export default function PrepPilotAIPage() {
|
||||
export default function AIDailyPracticePage() {
|
||||
const { user, userId } = useActiveUser()
|
||||
const [dpps, setDpps] = useState<any[]>([])
|
||||
const [attempts, setAttempts] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [dateFilter, setDateFilter] = useState("")
|
||||
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(null)
|
||||
const [isFullScreen, setIsFullScreen] = useState(false)
|
||||
const [copiedShare, setCopiedShare] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDpps() {
|
||||
@@ -90,13 +92,26 @@ export default function PrepPilotAIPage() {
|
||||
const cleanTitle = (title: string) => title.replace(/\s*\(ExamEnum\.[^)]+\)/, '');
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2">PrepPilot AI <span className="text-[#00F0FF] text-xl">&</span> CBT Simulator</h1>
|
||||
<p className="text-muted-foreground font-medium max-w-xl">
|
||||
Select an interactive Target DPP to launch the AI-powered CBT examination simulator.
|
||||
</p>
|
||||
<div className="p-6 md:p-8 space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="border-b border-border/50 pb-6">
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-black tracking-tight flex items-center gap-3 mb-1">
|
||||
<span className="p-2.5 rounded-xl bg-[#00F0FF]/10 border border-[#00F0FF]/20">
|
||||
<Sparkles className="text-[#00F0FF]" size={28} />
|
||||
</span>
|
||||
AI Daily Practice (DPP)
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||
Launch AI-generated Daily Practice Problems (DPPs) tailored to your exam pattern and weak areas.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF] bg-[#00F0FF]/10 border border-[#00F0FF]/20 px-3 py-1.5 rounded-full flex items-center gap-1.5">
|
||||
<BrainCircuit size={12} /> AI Powered
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -114,12 +129,9 @@ export default function PrepPilotAIPage() {
|
||||
{diff}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-center gap-1 ml-2 text-xs text-muted-foreground bg-secondary/50 px-2 py-1 rounded-md">
|
||||
<Lock size={12}/> Pro
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
|
||||
<Download size={16} /> Export as PDF <span className="ml-1 text-[10px] uppercase tracking-wider bg-primary/10 text-primary px-1.5 py-0.5 rounded-md">Pro</span>
|
||||
<Download size={16} /> Export as PDF
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -200,7 +212,7 @@ export default function PrepPilotAIPage() {
|
||||
{!isExpired && (
|
||||
<Link href={`/cbt/${dpp.id}`}>
|
||||
<Button variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20 gap-2">
|
||||
<Play size={14} className="fill-current" /> Retake CBT
|
||||
<Play size={14} className="fill-current" /> Retake Test
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@@ -208,7 +220,7 @@ export default function PrepPilotAIPage() {
|
||||
) : (
|
||||
<Link href={`/cbt/${dpp.id}`}>
|
||||
<Button variant="secondary" size="sm" className="rounded-xl bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(0,240,255,0.2)]">
|
||||
<Play size={14} className="fill-black" /> Launch CBT DPP
|
||||
<Play size={14} className="fill-black" /> Launch Practice Test
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@@ -220,18 +232,18 @@ export default function PrepPilotAIPage() {
|
||||
|
||||
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
|
||||
{selectedPdfDpp && (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
|
||||
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
|
||||
<div className={`fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex items-center justify-center ${isFullScreen ? 'p-0' : 'p-4'} animate-in fade-in duration-300`}>
|
||||
<div className={`bg-background border border-border ${isFullScreen ? 'w-screen h-screen max-w-none max-h-none rounded-none border-0' : 'rounded-3xl w-full max-w-7xl max-h-[95vh]'} flex flex-col overflow-hidden shadow-2xl transition-all duration-300`}>
|
||||
{/* Modal Header */}
|
||||
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="p-4 md:p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
|
||||
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary hidden sm:flex">
|
||||
<FileText size={22} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-black text-lg flex items-center gap-2">
|
||||
<h3 className="font-black text-base sm:text-lg flex items-center gap-2">
|
||||
{selectedPdfDpp.title}
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
|
||||
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold hidden md:inline-block">
|
||||
Protected In-App PDF View
|
||||
</span>
|
||||
</h3>
|
||||
@@ -240,24 +252,46 @@ export default function PrepPilotAIPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setSelectedPdfDpp(null)}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="rounded-full border-border hover:bg-secondary"
|
||||
>
|
||||
<X size={18} />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(window.location.origin + '/dashboard/ai-coach/cbt-dpp');
|
||||
setCopiedShare(true);
|
||||
setTimeout(() => setCopiedShare(false), 2000);
|
||||
}}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3"
|
||||
>
|
||||
{copiedShare ? <CheckCircle2 size={14} className="text-green-500" /> : <Share2 size={14} />}
|
||||
<span>{copiedShare ? "Copied" : "Share PDF"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsFullScreen(!isFullScreen)}
|
||||
className="rounded-xl font-bold text-xs gap-1.5 h-9 px-3 bg-primary/10 text-primary border-primary/20 hover:bg-primary/20"
|
||||
>
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }}
|
||||
className="ml-1 flex-shrink-0 w-9 h-9 rounded-full border border-border bg-secondary flex items-center justify-center text-foreground hover:bg-destructive hover:border-destructive hover:text-white transition-all"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security Notice Banner */}
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2">
|
||||
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
|
||||
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 sm:px-6 py-2 flex items-center justify-between text-xs text-amber-400 font-bold">
|
||||
<span className="flex items-center gap-2 truncate">
|
||||
<ShieldAlert size={16} className="shrink-0" /> Read-Only Mode (Free Tier) — Downloads & direct copying are restricted.
|
||||
</span>
|
||||
<Link href="/upgrade">
|
||||
<Link href="/upgrade" className="hidden sm:block shrink-0 ml-2">
|
||||
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
|
||||
Upgrade for Interactive CBT Mode →
|
||||
Upgrade for Interactive Practice Mode →
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -265,27 +299,33 @@ export default function PrepPilotAIPage() {
|
||||
{/* Protected PDF Document Sheet Content (select-none) */}
|
||||
<div
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]"
|
||||
className="flex-1 w-full select-none bg-[#525659] overflow-hidden"
|
||||
style={{minHeight: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
>
|
||||
<embed
|
||||
src={getPdfUrl(selectedPdfDpp.title)}
|
||||
type="application/pdf"
|
||||
className="w-full h-full rounded-b-lg"
|
||||
className="w-full h-full"
|
||||
style={{height: isFullScreen ? 'calc(100vh - 120px)' : '75vh'}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
|
||||
<span className="text-xs text-muted-foreground font-medium">
|
||||
<div className="p-3.5 sm:p-4 border-t border-border bg-secondary/30 flex items-center justify-between flex-wrap gap-2">
|
||||
<span className="text-xs text-muted-foreground font-medium flex items-center gap-2">
|
||||
📄 PragatiVeer Protected PDF Reader v2.0
|
||||
</span>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
|
||||
Close PDF Reader
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button onClick={() => setIsFullScreen(!isFullScreen)} variant="secondary" className="rounded-xl font-bold text-xs h-9 bg-primary/10 text-primary hover:bg-primary/20 flex items-center gap-1.5">
|
||||
{isFullScreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||
<span>{isFullScreen ? "Exit Fullscreen" : "Fullscreen"}</span>
|
||||
</Button>
|
||||
<Button onClick={() => { setSelectedPdfDpp(null); setIsFullScreen(false); }} variant="outline" className="rounded-xl font-bold text-xs h-9">
|
||||
Close Reader
|
||||
</Button>
|
||||
<Link href="/upgrade">
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
|
||||
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
|
||||
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)] text-xs h-9">
|
||||
<Sparkles size={14} className="mr-1.5" /> Upgrade to AI Coach
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user