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

@@ -11,7 +11,7 @@ export default function SettingsPage() {
allow_new_registrations: true,
enable_free_trial: true,
trial_duration_days: 5,
trial_tier: "Al_Coach"
trial_tier: "AI Coach"
});
const [loading, setLoading] = useState(true);
@@ -117,7 +117,7 @@ export default function SettingsPage() {
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)]">
<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>
</div>
</div>

View File

@@ -13,11 +13,11 @@ import {
} from "lucide-react"
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",
"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) {
if (!iso) return "—"
@@ -164,7 +164,7 @@ export default function StudentKundliPage() {
>
<option value="Free">Free</option>
<option value="Pro">Pro</option>
<option value="Al_Coach">AI Coach</option>
<option value="AI Coach">AI Coach</option>
</select>
) : (
<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)}
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="JEE_Main">JEE Main</option>
<option value="JEE_Advanced">JEE Advanced</option>
<option value="JEE_NEET">JEE + NEET</option>
</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>

View File

@@ -9,13 +9,13 @@ import { useRouter } from "next/navigation"
import { useRef } from "react"
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",
"Free": "bg-secondary text-foreground border-border",
}
const TIER_LABEL: Record<string, string> = {
"Al_Coach": "AI Coach",
"AI Coach": "AI Coach",
"Pro": "Pro",
"Free": "Free",
}
@@ -27,12 +27,14 @@ export default function StudentsPage() {
const [search, setSearch] = useState("")
const [selectedIds, setSelectedIds] = useState<string[]>([])
const [deleting, setDeleting] = useState(false)
const [isUploading, setIsUploading] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const handleBulkUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
setIsUploading(true)
setLoading(true)
try {
const formData = new FormData()
@@ -68,6 +70,7 @@ export default function StudentsPage() {
} catch (err: any) {
alert("Error: " + err.message)
} finally {
setIsUploading(false)
setLoading(false)
if (fileInputRef.current) fileInputRef.current.value = ""
}
@@ -141,9 +144,10 @@ export default function StudentsPage() {
<Trash2 size={16} className="mr-2" /> Delete ({selectedIds.length})
</Button>
)}
<input type="file" accept=".csv" className="hidden" ref={fileInputRef} onChange={handleBulkUpload} />
<Button variant="outline" className="border-border hover:bg-secondary hover:text-white" onClick={() => fileInputRef.current?.click()}>
<Upload size={16} className="mr-2" /> Bulk Register CSV
<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" disabled={isUploading} onClick={() => fileInputRef.current?.click()}>
{isUploading ? <RefreshCw size={16} className="mr-2 animate-spin" /> : <Upload size={16} className="mr-2" />}
{isUploading ? "Uploading..." : "Bulk Register CSV"}
</Button>
<Button variant="outline" className="border-border hover:bg-secondary hover:text-white" onClick={downloadTemplate}>
<Download size={16} className="mr-2" /> Download Template
@@ -236,7 +240,9 @@ export default function StudentsPage() {
{TIER_LABEL[s.tier] || s.tier}
</Badge>
</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">
<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])
const amount = tier === "Al_Coach" ? 11999 : 3499
const amount = tier === "AI Coach" ? 11999 : 3499
const handlePayment = async () => {
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">
<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 === "Pro" ? "Pro" : "Free") : "Free"} />
<TierBadge tier={user ? (user.tier === "AI Coach" ? "AI Coach" : user.tier === "Pro" ? "Pro" : "Free") : "Free"} />
</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 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 date = new Intl.DateTimeFormat('en-IN', {
dateStyle: 'medium',

View File

@@ -33,9 +33,9 @@ const AI_COACH_FEATURES = [
export default function OnboardingPage() {
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);
// Save chosen plan preference to localStorage so dashboard can read it
if (typeof window !== "undefined") {
@@ -165,7 +165,7 @@ export default function OnboardingPage() {
background:
"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 */}
<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
onClick={(e) => {
e.stopPropagation();
if (!selecting) handleSelect("Al_Coach");
if (!selecting) handleSelect("AI Coach");
}}
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"
>
{selecting === "Al_Coach" ? (
{selecting === "AI Coach" ? (
<>
<span className="size-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />
Starting Trial…

View File

@@ -87,7 +87,7 @@ export default function UpgradePage() {
</div>
<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"
onClick={() => handleUpgrade("Al_Coach")}
onClick={() => handleUpgrade("AI Coach")}
disabled={userLoading}
>
{userLoading ? <Loader2 className="animate-spin" /> : "Get AI Coach"}

View File

@@ -15,7 +15,7 @@ export function ProgressShare() {
const accuracy = "85"
const name = user?.name || "Student"
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 handleDownload = async () => {
@@ -97,7 +97,7 @@ export function ProgressShare() {
<div className="mt-auto flex justify-between items-end w-full pt-4">
<div className="text-left">
<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>
<div className="text-right">

View File

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