Fix AI Coach typo, fix bulk register UI, and update exam tiers

This commit is contained in:
2026-09-24 21:59:04 +05:30
parent 6ba69a05ea
commit ec4849ccce
13 changed files with 87 additions and 34 deletions

View File

@@ -1,7 +1,7 @@
from fastapi import FastAPI, Depends, HTTPException, status, Request, UploadFile, File, Form, BackgroundTasks from fastapi import FastAPI, Depends, HTTPException, status, Request, UploadFile, File, Form, BackgroundTasks
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlmodel import Session, select from sqlmodel import Session, select, delete
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from uuid import UUID from uuid import UUID
@@ -286,6 +286,21 @@ async def bulk_register_students(file: UploadFile = File(...), session: Session
session.commit() session.commit()
return results return results
class DeleteUsersRequest(BaseModel):
user_ids: List[UUID]
@app.delete("/api/admin/students/bulk", tags=["Admin"])
def bulk_delete_students(payload: DeleteUsersRequest, session: Session = Depends(get_session)):
for uid in payload.user_ids:
user = session.get(models.User, uid)
if user:
# Delete associated records manually since foreign key cascade might not be configured in sqlmodel by default
session.exec(delete(models.Attempt).where(models.Attempt.user_id == uid))
session.exec(delete(models.Mistake).where(models.Mistake.user_id == uid))
session.delete(user)
session.commit()
return {"success": True}
@app.post("/api/auth/register", tags=["Authentication"]) @app.post("/api/auth/register", tags=["Authentication"])
def register(payload: RegisterPayload, session: Session = Depends(get_session)): def register(payload: RegisterPayload, session: Session = Depends(get_session)):
import bcrypt import bcrypt

View File

@@ -24,7 +24,7 @@ class MediumEnum(str, Enum):
class TierEnum(str, Enum): class TierEnum(str, Enum):
FREE = "Free" FREE = "Free"
PRO = "Pro" PRO = "Pro"
AI_COACH = "Al_Coach" AI_COACH = "AI Coach"
class QuestionTypeEnum(str, Enum): class QuestionTypeEnum(str, Enum):
MCQ_SINGLE = "MCQ_Single" MCQ_SINGLE = "MCQ_Single"

31
demo_30_users.csv Normal file
View File

@@ -0,0 +1,31 @@
name,email,phone,target_exam,tier
Aarav Sharma,aarav.sharma@example.com,9876543201,JEE + NEET,Pro
Vivaan Gupta,vivaan.gupta@example.com,9876543202,NEET,Free
Aditya Patel,aditya.patel@example.com,9876543203,JEE,Pro
Vihaan Kumar,vihaan.kumar@example.com,9876543204,JEE + NEET,Pro
Arjun Singh,arjun.singh@example.com,9876543205,JEE,Free
Sai Reddy,sai.reddy@example.com,9876543206,JEE + NEET,AI Coach
Reyansh Das,reyansh.das@example.com,9876543207,NEET,Pro
Ayaan Joshi,ayaan.joshi@example.com,9876543208,JEE + NEET,Free
Krishna Iyer,krishna.iyer@example.com,9876543209,NEET,Pro
Ishaan Verma,ishaan.verma@example.com,9876543210,JEE,Free
Shaurya Rajput,shaurya.rajput@example.com,9876543211,JEE,AI Coach
Atharv Nair,atharv.nair@example.com,9876543212,NEET,Free
Ananya Menon,ananya.menon@example.com,9876543213,JEE + NEET,Pro
Diya Agarwal,diya.agarwal@example.com,9876543214,JEE + NEET,AI Coach
Pari Kapoor,pari.kapoor@example.com,9876543215,JEE + NEET,Free
Navya Jain,navya.jain@example.com,9876543216,JEE,Free
Kavya Desai,kavya.desai@example.com,9876543217,JEE + NEET,Free
Myra Choudhury,myra.choudhury@example.com,9876543218,JEE,Pro
Ira Sengupta,ira.sengupta@example.com,9876543219,JEE,Free
Saanvi Bhatt,saanvi.bhatt@example.com,9876543220,JEE + NEET,Pro
Rudra Tiwari,rudra.tiwari@example.com,9876543221,NEET,Pro
Kabir Mehra,kabir.mehra@example.com,9876543222,NEET,Free
Madhav Rao,madhav.rao@example.com,9876543223,JEE + NEET,AI Coach
Dhruv Menon,dhruv.menon@example.com,9876543224,JEE,Free
Aryan Dutta,aryan.dutta@example.com,9876543225,JEE + NEET,Free
Prisha Soni,prisha.soni@example.com,9876543226,JEE,Pro
Avni Khatri,avni.khatri@example.com,9876543227,JEE + NEET,Pro
Zara Sheikh,zara.sheikh@example.com,9876543228,JEE,Free
Riya Mishra,riya.mishra@example.com,9876543229,JEE,Free
Sneha Chawla,sneha.chawla@example.com,9876543230,JEE,Free
1 name email phone target_exam tier
2 Aarav Sharma aarav.sharma@example.com 9876543201 JEE + NEET Pro
3 Vivaan Gupta vivaan.gupta@example.com 9876543202 NEET Free
4 Aditya Patel aditya.patel@example.com 9876543203 JEE Pro
5 Vihaan Kumar vihaan.kumar@example.com 9876543204 JEE + NEET Pro
6 Arjun Singh arjun.singh@example.com 9876543205 JEE Free
7 Sai Reddy sai.reddy@example.com 9876543206 JEE + NEET AI Coach
8 Reyansh Das reyansh.das@example.com 9876543207 NEET Pro
9 Ayaan Joshi ayaan.joshi@example.com 9876543208 JEE + NEET Free
10 Krishna Iyer krishna.iyer@example.com 9876543209 NEET Pro
11 Ishaan Verma ishaan.verma@example.com 9876543210 JEE Free
12 Shaurya Rajput shaurya.rajput@example.com 9876543211 JEE AI Coach
13 Atharv Nair atharv.nair@example.com 9876543212 NEET Free
14 Ananya Menon ananya.menon@example.com 9876543213 JEE + NEET Pro
15 Diya Agarwal diya.agarwal@example.com 9876543214 JEE + NEET AI Coach
16 Pari Kapoor pari.kapoor@example.com 9876543215 JEE + NEET Free
17 Navya Jain navya.jain@example.com 9876543216 JEE Free
18 Kavya Desai kavya.desai@example.com 9876543217 JEE + NEET Free
19 Myra Choudhury myra.choudhury@example.com 9876543218 JEE Pro
20 Ira Sengupta ira.sengupta@example.com 9876543219 JEE Free
21 Saanvi Bhatt saanvi.bhatt@example.com 9876543220 JEE + NEET Pro
22 Rudra Tiwari rudra.tiwari@example.com 9876543221 NEET Pro
23 Kabir Mehra kabir.mehra@example.com 9876543222 NEET Free
24 Madhav Rao madhav.rao@example.com 9876543223 JEE + NEET AI Coach
25 Dhruv Menon dhruv.menon@example.com 9876543224 JEE Free
26 Aryan Dutta aryan.dutta@example.com 9876543225 JEE + NEET Free
27 Prisha Soni prisha.soni@example.com 9876543226 JEE Pro
28 Avni Khatri avni.khatri@example.com 9876543227 JEE + NEET Pro
29 Zara Sheikh zara.sheikh@example.com 9876543228 JEE Free
30 Riya Mishra riya.mishra@example.com 9876543229 JEE Free
31 Sneha Chawla sneha.chawla@example.com 9876543230 JEE Free

View File

@@ -11,7 +11,7 @@ export default function SettingsPage() {
allow_new_registrations: true, allow_new_registrations: true,
enable_free_trial: true, enable_free_trial: true,
trial_duration_days: 5, trial_duration_days: 5,
trial_tier: "Al_Coach" trial_tier: "AI Coach"
}); });
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -117,7 +117,7 @@ export default function SettingsPage() {
onChange={(e) => setSettings(prev => ({ ...prev, trial_tier: e.target.value }))} onChange={(e) => setSettings(prev => ({ ...prev, trial_tier: e.target.value }))}
className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--cyan-ai)]"> className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--cyan-ai)]">
<option value="Pro">Pro Tier</option> <option value="Pro">Pro Tier</option>
<option value="Al_Coach">AI Coach (All Features)</option> <option value="AI Coach">AI Coach (All Features)</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -13,11 +13,11 @@ import {
} from "lucide-react" } from "lucide-react"
const TIER_STYLE: Record<string, string> = { const TIER_STYLE: Record<string, string> = {
"Al_Coach": "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)]", "AI Coach": "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)]",
"Pro": "bg-yellow-500/10 text-yellow-500 border-yellow-500/20", "Pro": "bg-yellow-500/10 text-yellow-500 border-yellow-500/20",
"Free": "bg-secondary text-foreground border-border", "Free": "bg-secondary text-foreground border-border",
} }
const TIER_LABEL: Record<string, string> = { "Al_Coach": "AI Coach", "Pro": "Pro", "Free": "Free" } const TIER_LABEL: Record<string, string> = { "AI Coach": "AI Coach", "Pro": "Pro", "Free": "Free" }
function formatDate(iso?: string | null) { function formatDate(iso?: string | null) {
if (!iso) return "—" if (!iso) return "—"
@@ -164,7 +164,7 @@ export default function StudentKundliPage() {
> >
<option value="Free">Free</option> <option value="Free">Free</option>
<option value="Pro">Pro</option> <option value="Pro">Pro</option>
<option value="Al_Coach">AI Coach</option> <option value="AI Coach">AI Coach</option>
</select> </select>
) : ( ) : (
<Badge className={TIER_STYLE[data.tier] || TIER_STYLE["Free"]} variant="outline"> <Badge className={TIER_STYLE[data.tier] || TIER_STYLE["Free"]} variant="outline">
@@ -178,13 +178,14 @@ export default function StudentKundliPage() {
onChange={e => setEditExam(e.target.value)} onChange={e => setEditExam(e.target.value)}
className="text-sm bg-secondary border border-border rounded-lg px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary" className="text-sm bg-secondary border border-border rounded-lg px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
> >
<option value="JEE_Main">JEE</option>
<option value="NEET">NEET</option> <option value="NEET">NEET</option>
<option value="JEE_Main">JEE Main</option>
<option value="JEE_Advanced">JEE Advanced</option>
<option value="JEE_NEET">JEE + NEET</option> <option value="JEE_NEET">JEE + NEET</option>
</select> </select>
) : ( ) : (
<Badge variant="outline" className="border-border">{data.target_exam.replace("_", " ")}</Badge> <Badge variant="outline" className="border-border">
{data.target_exam === "JEE_Main" ? "JEE" : data.target_exam.replace("_", " ")}
</Badge>
)} )}
</div> </div>

View File

@@ -9,13 +9,13 @@ import { useRouter } from "next/navigation"
import { useRef } from "react" import { useRef } from "react"
const TIER_STYLE: Record<string, string> = { const TIER_STYLE: Record<string, string> = {
"Al_Coach": "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)] font-bold", "AI Coach": "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)] font-bold",
"Pro": "bg-yellow-500/10 text-yellow-500 border-yellow-500/20 font-bold", "Pro": "bg-yellow-500/10 text-yellow-500 border-yellow-500/20 font-bold",
"Free": "bg-secondary text-foreground border-border", "Free": "bg-secondary text-foreground border-border",
} }
const TIER_LABEL: Record<string, string> = { const TIER_LABEL: Record<string, string> = {
"Al_Coach": "AI Coach", "AI Coach": "AI Coach",
"Pro": "Pro", "Pro": "Pro",
"Free": "Free", "Free": "Free",
} }
@@ -27,12 +27,14 @@ export default function StudentsPage() {
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [selectedIds, setSelectedIds] = useState<string[]>([]) const [selectedIds, setSelectedIds] = useState<string[]>([])
const [deleting, setDeleting] = useState(false) const [deleting, setDeleting] = useState(false)
const [isUploading, setIsUploading] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
const handleBulkUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleBulkUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] const file = e.target.files?.[0]
if (!file) return if (!file) return
setIsUploading(true)
setLoading(true) setLoading(true)
try { try {
const formData = new FormData() const formData = new FormData()
@@ -68,6 +70,7 @@ export default function StudentsPage() {
} catch (err: any) { } catch (err: any) {
alert("Error: " + err.message) alert("Error: " + err.message)
} finally { } finally {
setIsUploading(false)
setLoading(false) setLoading(false)
if (fileInputRef.current) fileInputRef.current.value = "" if (fileInputRef.current) fileInputRef.current.value = ""
} }
@@ -141,9 +144,10 @@ export default function StudentsPage() {
<Trash2 size={16} className="mr-2" /> Delete ({selectedIds.length}) <Trash2 size={16} className="mr-2" /> Delete ({selectedIds.length})
</Button> </Button>
)} )}
<input type="file" accept=".csv" className="hidden" ref={fileInputRef} onChange={handleBulkUpload} /> <input type="file" accept=".csv" className="hidden" ref={fileInputRef} onChange={handleBulkUpload} disabled={isUploading} />
<Button variant="outline" className="border-border hover:bg-secondary hover:text-white" onClick={() => fileInputRef.current?.click()}> <Button variant="outline" className="border-border hover:bg-secondary hover:text-white" disabled={isUploading} onClick={() => fileInputRef.current?.click()}>
<Upload size={16} className="mr-2" /> Bulk Register CSV {isUploading ? <RefreshCw size={16} className="mr-2 animate-spin" /> : <Upload size={16} className="mr-2" />}
{isUploading ? "Uploading..." : "Bulk Register CSV"}
</Button> </Button>
<Button variant="outline" className="border-border hover:bg-secondary hover:text-white" onClick={downloadTemplate}> <Button variant="outline" className="border-border hover:bg-secondary hover:text-white" onClick={downloadTemplate}>
<Download size={16} className="mr-2" /> Download Template <Download size={16} className="mr-2" /> Download Template
@@ -236,7 +240,9 @@ export default function StudentsPage() {
{TIER_LABEL[s.tier] || s.tier} {TIER_LABEL[s.tier] || s.tier}
</Badge> </Badge>
</td> </td>
<td className="px-6 py-4 font-medium">{s.target_exam.replace("_", " ")}</td> <td className="px-6 py-4 font-medium">
{s.target_exam === "JEE_Main" ? "JEE" : s.target_exam.replace("_", " ")}
</td>
<td className="px-6 py-4 font-mono">{s.total_tests}</td> <td className="px-6 py-4 font-mono">{s.total_tests}</td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<span className={`font-bold ${s.avg_accuracy >= 80 ? "text-green-500" : s.avg_accuracy >= 60 ? "text-yellow-500" : "text-muted-foreground"}`}> <span className={`font-bold ${s.avg_accuracy >= 80 ? "text-green-500" : s.avg_accuracy >= 60 ? "text-yellow-500" : "text-muted-foreground"}`}>

View File

@@ -64,7 +64,7 @@ function CheckoutContent() {
} }
}, [userId, userLoading, router, tier]) }, [userId, userLoading, router, tier])
const amount = tier === "Al_Coach" ? 11999 : 3499 const amount = tier === "AI Coach" ? 11999 : 3499
const handlePayment = async () => { const handlePayment = async () => {
if (!address.trim() || !phone.trim() || phone.length < 10) { if (!address.trim() || !phone.trim() || phone.length < 10) {

View File

@@ -350,7 +350,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-bold leading-none truncate">{user ? cleanDisplayName(user.name).split(" ")[0] : "Loading..."}</p> <p className="text-sm font-bold leading-none truncate">{user ? cleanDisplayName(user.name).split(" ")[0] : "Loading..."}</p>
<div className="mt-1"> <div className="mt-1">
<TierBadge tier={user ? (user.tier === "Al_Coach" ? "AI Coach" : user.tier === "Pro" ? "Pro" : "Free") : "Free"} /> <TierBadge tier={user ? (user.tier === "AI Coach" ? "AI Coach" : user.tier === "Pro" ? "Pro" : "Free") : "Free"} />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -48,7 +48,7 @@ export default function DashboardPage() {
const rank = isNew ? "#--" : `#${userStats?.city_rank || 1} / ${userStats?.city_total || 50}` const rank = isNew ? "#--" : `#${userStats?.city_rank || 1} / ${userStats?.city_total || 50}`
const target = user?.target_exam === "NEET" ? "NEET" : "JEE" const target = user?.target_exam === "NEET" ? "NEET" : "JEE"
const plan = user?.tier === "Al_Coach" ? "AI Coach" : user?.tier || "Free" const plan = user?.tier === "AI Coach" ? "AI Coach" : user?.tier || "Free"
const name = user?.name || "Student" const name = user?.name || "Student"
const date = new Intl.DateTimeFormat('en-IN', { const date = new Intl.DateTimeFormat('en-IN', {
dateStyle: 'medium', dateStyle: 'medium',

View File

@@ -33,9 +33,9 @@ const AI_COACH_FEATURES = [
export default function OnboardingPage() { export default function OnboardingPage() {
const router = useRouter(); const router = useRouter();
const [selecting, setSelecting] = useState<"Pro" | "Al_Coach" | null>(null); const [selecting, setSelecting] = useState<"Pro" | "AI Coach" | null>(null);
const handleSelect = async (plan: "Pro" | "Al_Coach") => { const handleSelect = async (plan: "Pro" | "AI Coach") => {
setSelecting(plan); setSelecting(plan);
// Save chosen plan preference to localStorage so dashboard can read it // Save chosen plan preference to localStorage so dashboard can read it
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
@@ -165,7 +165,7 @@ export default function OnboardingPage() {
background: background:
"linear-gradient(135deg, rgba(12,96,96,0.5) 0%, rgba(12,96,96,0.08) 100%)", "linear-gradient(135deg, rgba(12,96,96,0.5) 0%, rgba(12,96,96,0.08) 100%)",
}} }}
onClick={() => !selecting && handleSelect("Al_Coach")} onClick={() => !selecting && handleSelect("AI Coach")}
> >
{/* Most Popular Badge */} {/* Most Popular Badge */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 z-10 inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full bg-[var(--cyan-ai)] text-black text-xs font-black uppercase tracking-wider shadow-[0_0_20px_-3px_var(--cyan-ai)]"> <div className="absolute -top-4 left-1/2 -translate-x-1/2 z-10 inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full bg-[var(--cyan-ai)] text-black text-xs font-black uppercase tracking-wider shadow-[0_0_20px_-3px_var(--cyan-ai)]">
@@ -201,12 +201,12 @@ export default function OnboardingPage() {
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (!selecting) handleSelect("Al_Coach"); if (!selecting) handleSelect("AI Coach");
}} }}
disabled={!!selecting} disabled={!!selecting}
className="w-full h-12 rounded-xl bg-[var(--cyan-ai)] text-black font-bold text-base hover:bg-[var(--cyan-ai-80)] transition-all duration-300 shadow-[0_0_20px_-5px_rgba(12,96,96,0.5)] hover:shadow-[0_0_30px_-3px_rgba(12,96,96,0.8)] hover:-translate-y-0.5 disabled:opacity-70 disabled:cursor-not-allowed flex items-center justify-center gap-2" className="w-full h-12 rounded-xl bg-[var(--cyan-ai)] text-black font-bold text-base hover:bg-[var(--cyan-ai-80)] transition-all duration-300 shadow-[0_0_20px_-5px_rgba(12,96,96,0.5)] hover:shadow-[0_0_30px_-3px_rgba(12,96,96,0.8)] hover:-translate-y-0.5 disabled:opacity-70 disabled:cursor-not-allowed flex items-center justify-center gap-2"
> >
{selecting === "Al_Coach" ? ( {selecting === "AI Coach" ? (
<> <>
<span className="size-4 border-2 border-black/30 border-t-black rounded-full animate-spin" /> <span className="size-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />
Starting Trial… Starting Trial…

View File

@@ -87,7 +87,7 @@ export default function UpgradePage() {
</div> </div>
<Button <Button
className="w-full font-bold text-lg rounded-2xl h-12 bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] transition-all" className="w-full font-bold text-lg rounded-2xl h-12 bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] transition-all"
onClick={() => handleUpgrade("Al_Coach")} onClick={() => handleUpgrade("AI Coach")}
disabled={userLoading} disabled={userLoading}
> >
{userLoading ? <Loader2 className="animate-spin" /> : "Get AI Coach"} {userLoading ? <Loader2 className="animate-spin" /> : "Get AI Coach"}

View File

@@ -15,7 +15,7 @@ export function ProgressShare() {
const accuracy = "85" const accuracy = "85"
const name = user?.name || "Student" const name = user?.name || "Student"
const target = user?.target_exam === "NEET" ? "NEET" : "JEE" const target = user?.target_exam === "NEET" ? "NEET" : "JEE"
const plan = user?.tier === "Al_Coach" ? "AI Coach" : user?.tier || "Free" const plan = user?.tier === "AI Coach" ? "AI Coach" : user?.tier || "Free"
const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${encodeURIComponent(name)}&target=${target}&plan=${encodeURIComponent(plan)}` const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${encodeURIComponent(name)}&target=${target}&plan=${encodeURIComponent(plan)}`
const handleDownload = async () => { const handleDownload = async () => {
@@ -97,7 +97,7 @@ export function ProgressShare() {
<div className="mt-auto flex justify-between items-end w-full pt-4"> <div className="mt-auto flex justify-between items-end w-full pt-4">
<div className="text-left"> <div className="text-left">
<div className="text-sm font-bold text-white">{name}</div> <div className="text-sm font-bold text-white">{name}</div>
<div className="text-[10px] text-white/70 mt-0.5">{user?.tier === "Al_Coach" ? "AI Coach Plan" : `${user?.tier || "Free"} Plan`}</div> <div className="text-[10px] text-white/70 mt-0.5">{user?.tier === "AI Coach" ? "AI Coach Plan" : `${user?.tier || "Free"} Plan`}</div>
<div className="text-[10px] text-[var(--cyan-ai)]">{user?.target_exam === "NEET" ? "NEET Aspirant" : "JEE Aspirant"}</div> <div className="text-[10px] text-[var(--cyan-ai)]">{user?.target_exam === "NEET" ? "NEET Aspirant" : "JEE Aspirant"}</div>
</div> </div>
<div className="text-right"> <div className="text-right">

View File

@@ -14,7 +14,7 @@ export interface UserProfile {
school: string | null; school: string | null;
city: string; city: string;
state: string; state: string;
tier: "Free" | "Pro" | "Al_Coach"; tier: "Free" | "Pro" | "AI Coach";
streak_count: number; streak_count: number;
longest_streak: number; longest_streak: number;
active_session_token?: string; active_session_token?: string;
@@ -48,14 +48,14 @@ export function computeTrialInfo(user: UserProfile | null, globalSettings: any):
trialDayNumber: number; // 1-based current day in trial trialDayNumber: number; // 1-based current day in trial
trialDaysRemaining: number; // days left (0 if expired) trialDaysRemaining: number; // days left (0 if expired)
trialTotalDays: number; trialTotalDays: number;
effectiveTier: "Free" | "Pro" | "Al_Coach"; effectiveTier: "Free" | "Pro" | "AI Coach";
} { } {
const defaultResult = { const defaultResult = {
isTrialActive: false, isTrialActive: false,
trialDayNumber: 0, trialDayNumber: 0,
trialDaysRemaining: 0, trialDaysRemaining: 0,
trialTotalDays: 0, trialTotalDays: 0,
effectiveTier: (user?.tier ?? "Free") as "Free" | "Pro" | "Al_Coach", effectiveTier: (user?.tier ?? "Free") as "Free" | "Pro" | "AI Coach",
}; };
if (!user || !globalSettings) return defaultResult; if (!user || !globalSettings) return defaultResult;
@@ -106,7 +106,7 @@ export function computeTrialInfo(user: UserProfile | null, globalSettings: any):
trialDaysRemaining, trialDaysRemaining,
trialTotalDays: trialDays, trialTotalDays: trialDays,
effectiveTier: isTrialActive effectiveTier: isTrialActive
? ((globalSettings.trial_tier as "Pro" | "Al_Coach") ?? "Al_Coach") ? ((globalSettings.trial_tier as "Pro" | "AI Coach") ?? "AI Coach")
: "Free", : "Free",
}; };
} }
@@ -311,14 +311,14 @@ export function useActiveUser() {
case "weekly-test-series": case "weekly-test-series":
case "full-length-mock-tests": case "full-length-mock-tests":
case "previous-year-papers": case "previous-year-papers":
case "detailed-performance-analytics":
case "chapter-wise-progress-tracking": case "chapter-wise-progress-tracking":
case "doubt-resolution-community": case "doubt-resolution-community":
case "parent-dashboard": case "parent-dashboard":
case "offline-mode": case "offline-mode":
case "pdf-export": case "pdf-export":
return tier === "Pro" || tier === "Al_Coach"; return tier === "Pro" || tier === "AI Coach";
case "detailed-performance-analytics":
case "ai-coach-custom-schedules": case "ai-coach-custom-schedules":
case "personalized-study-plan": case "personalized-study-plan":
case "ai-tutor-doubt-resolution": case "ai-tutor-doubt-resolution":
@@ -327,7 +327,7 @@ export function useActiveUser() {
case "ai-tutor-chat": case "ai-tutor-chat":
case "college-predictor": case "college-predictor":
case "exam-day-strategy": case "exam-day-strategy":
return tier === "Al_Coach"; return tier === "AI Coach";
default: default:
// Free tier features: DPS (1/day), basic dashboard, basic streak tracking, leaderboard // Free tier features: DPS (1/day), basic dashboard, basic streak tracking, leaderboard