fix: QA report bugs BUG-01 through BUG-11 resolved - streak modal close btn, calculator crash guard, AI chat auto-scroll, leaderboard zero-streak fix, name annotation cleanup everywhere, CBT score display colors, PrepPilot difficulty badge layout

This commit is contained in:
2026-09-19 22:34:45 +05:30
parent afab06d33c
commit 45f2f82eaf
11 changed files with 88 additions and 26 deletions

View File

@@ -185,10 +185,12 @@ export default function AIDailyPracticePage() {
</div>
<h3 className="font-bold text-lg leading-tight group-hover:text-[#00F0FF] transition-colors">
{cleanTitle(dpp.title)}
<span className={`ml-2 text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
</h3>
<div className="mt-1">
<span className={`text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
{dpp.difficulty}
</span>
</h3>
</div>
<div className="text-sm text-muted-foreground mt-2 font-medium">
{count} Questions • {dpp.total_time} mins
{dpp.scheduled_date && (

View File

@@ -411,7 +411,14 @@ export default function CBTPage() {
</div>
) : attempts.filter(a => a.source_type === "Test" && exams.some(e => e.id === a.source_id)).map((attempt, i) => {
const exam = exams.find(e => e.id === attempt.source_id);
const scoreCalc = (attempt.score * 4) - ((attempt.total_questions - attempt.score) * 1); // rough calc for display
// BUG-07 fix: use actual final_score from backend if available, else derive correctly
// backend stores final_score = (correct*4) - (incorrect*1)
const correctCount = attempt.answers ? attempt.answers.filter((a: any) => a.is_correct).length : attempt.score;
const incorrectCount = attempt.answers ? (attempt.answers.filter((a: any) => a.selected_answer !== null).length - correctCount) : 0;
const scoreCalc = attempt.final_score ?? ((correctCount * 4) - (incorrectCount * 1));
const maxScore = attempt.total_questions * 4;
const scorePercent = maxScore > 0 ? (scoreCalc / maxScore) * 100 : 0;
const scoreColor = scorePercent >= 60 ? 'text-green-500' : scorePercent >= 35 ? 'text-amber-500' : 'text-red-500';
return (
<Card key={i} className="bg-secondary/20">
@@ -424,8 +431,8 @@ export default function CBTPage() {
<div className="text-right">
<div className="text-xs md:text-sm text-muted-foreground font-bold uppercase tracking-wider">Score</div>
<div className="font-bold text-base md:text-lg">
<span className="text-foreground">{scoreCalc}</span>
<span className="text-muted-foreground">/{attempt.total_questions * 4}</span>
<span className={scoreColor}>{scoreCalc}</span>
<span className="text-muted-foreground">/{maxScore}</span>
</div>
</div>
<div className="text-right">

View File

@@ -49,7 +49,11 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
return () => document.removeEventListener("mousedown", handleClickOutside)
}, [])
const userInitials = user ? user.name.split(" ").filter(n => /^[a-zA-Z]/.test(n)).slice(0, 2).map(n => n[0]).join("").toUpperCase() : "DS"
// BUG-05 fix: strip any parenthetical role annotations from display names
// e.g. "Rahul (AI Coach)" → "Rahul", "Priya (NEET Aspirant)" → "Priya"
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"
// Trial logic removed as requested
@@ -251,7 +255,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
{dropdownOpen && user && (
<div className="absolute bottom-full left-0 w-full mb-2 bg-background border border-border rounded-xl shadow-xl shadow-black/20 overflow-hidden animate-in fade-in slide-in-from-bottom-2 duration-200 z-50">
<div className="px-4 py-3 border-b border-border bg-secondary/30">
<p className="text-sm font-bold text-foreground truncate">{user.name}</p>
<p className="text-sm font-bold text-foreground truncate">{cleanDisplayName(user.name)}</p>
<p className="text-xs text-muted-foreground truncate">{user.email || user.phone}</p>
{/* Trial info in dropdown */}
{trialInfo.isTrialActive && (
@@ -295,7 +299,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
{userInitials}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-bold leading-none truncate">{user ? 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">
{trialInfo.isTrialActive ? (
<span className="inline-flex items-center text-[10px] font-bold text-primary">
@@ -319,7 +323,7 @@ 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, ${cleanDisplayName(user.name).split(" ")[0]}!` : "Welcome back!"}
</h2>
{user && (
<Badge variant="outline" className="bg-cyan-500/10 text-cyan-700 dark:text-cyan-400 border-cyan-500/20 font-bold tracking-wide uppercase px-2.5 py-0.5 animate-pulse shadow-none dark:shadow-[0_0_10px_-2px_#00F0FF]">

View File

@@ -14,6 +14,9 @@ export default function LeaderboardPage() {
const [rankings, setRankings] = useState<any[]>([])
const [loadingRankings, setLoadingRankings] = useState(true)
// Strip parenthetical role annotations from user names (e.g. "Rahul (AI Coach)" → "Rahul")
const cleanDisplayName = (name: string) => name.replace(/\s*\([^)]*\)\s*$/, '').trim();
useEffect(() => {
async function fetchLeaderboard() {
setLoadingRankings(true)
@@ -39,7 +42,7 @@ export default function LeaderboardPage() {
rank: rankings.length + 1,
score: 0,
streak: user?.streak_count || 1,
name: user?.name || "You"
name: user?.name ? cleanDisplayName(user.name) : "You"
}
return (
@@ -121,7 +124,7 @@ export default function LeaderboardPage() {
</div>
<div>
<p className={`font-bold ${student.isCurrentUser ? 'text-[#00F0FF]' : 'text-foreground'}`}>
{student.isCurrentUser ? (isAnonymous ? "Anonymous User" : `${student.name} (You)`) : student.name}
{student.isCurrentUser ? (isAnonymous ? "Anonymous User" : `${cleanDisplayName(student.name)} (You)`) : cleanDisplayName(student.name)}
</p>
<p className="text-xs text-muted-foreground flex items-center gap-1">
<Shield size={12} /> Accuracy: {student.accuracy}

View File

@@ -85,7 +85,7 @@ export default function RankPredictorPage() {
</p>
{user && (
<div className="inline-flex items-center gap-2 px-3 py-1 bg-emerald-500/10 text-emerald-400 border border-emerald-500/30 rounded-xl text-xs font-bold mt-1">
<UserCheck size={14} /> Profile Connected: {user.name} ({user.target_exam?.replace("_", " ")})
<UserCheck size={14} /> Profile Connected: {user.name.replace(/\s*\([^)]*\)\s*$/, '').trim()} ({user.target_exam?.replace("_", " ")})
</div>
)}

View File

@@ -32,7 +32,9 @@ export default function SettingsPage() {
setAvatarUrl(localStorage.getItem(`user_avatar_${user.id}`) || null);
if (user.name) {
const parts = user.name.split(" ");
// BUG-06 fix: strip parenthetical role annotations before splitting into first/last
const cleanName = user.name.replace(/\s*\([^)]*\)\s*$/, '').trim();
const parts = cleanName.split(" ");
setFirstName(parts[0] || "");
setLastName(parts.slice(1).join(" ") || "");
}