diff --git a/backend/main.py b/backend/main.py index 8afe9cf..19345e0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,7 +1,7 @@ from fastapi import FastAPI, Depends, HTTPException, status, Request, UploadFile, File, Form, BackgroundTasks from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware -from sqlmodel import Session, select +from sqlmodel import Session, select, delete from contextlib import asynccontextmanager from typing import List, Dict, Any, Optional from uuid import UUID @@ -286,6 +286,21 @@ async def bulk_register_students(file: UploadFile = File(...), session: Session session.commit() 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"]) def register(payload: RegisterPayload, session: Session = Depends(get_session)): import bcrypt diff --git a/backend/models.py b/backend/models.py index 91d1888..b9879d8 100644 --- a/backend/models.py +++ b/backend/models.py @@ -24,7 +24,7 @@ class MediumEnum(str, Enum): class TierEnum(str, Enum): FREE = "Free" PRO = "Pro" - AI_COACH = "Al_Coach" + AI_COACH = "AI Coach" class QuestionTypeEnum(str, Enum): MCQ_SINGLE = "MCQ_Single" diff --git a/demo_30_users.csv b/demo_30_users.csv new file mode 100644 index 0000000..9ee4fa6 --- /dev/null +++ b/demo_30_users.csv @@ -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 diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index 0c0fb4a..6124f2c 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -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)]"> - + diff --git a/src/app/admin/students/[studentId]/page.tsx b/src/app/admin/students/[studentId]/page.tsx index e825714..1261dce 100644 --- a/src/app/admin/students/[studentId]/page.tsx +++ b/src/app/admin/students/[studentId]/page.tsx @@ -13,11 +13,11 @@ import { } from "lucide-react" const TIER_STYLE: Record = { - "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 = { "Al_Coach": "AI Coach", "Pro": "Pro", "Free": "Free" } +const TIER_LABEL: Record = { "AI Coach": "AI Coach", "Pro": "Pro", "Free": "Free" } function formatDate(iso?: string | null) { if (!iso) return "—" @@ -164,7 +164,7 @@ export default function StudentKundliPage() { > - + ) : ( @@ -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" > + - - ) : ( - {data.target_exam.replace("_", " ")} + + {data.target_exam === "JEE_Main" ? "JEE" : data.target_exam.replace("_", " ")} + )} diff --git a/src/app/admin/students/page.tsx b/src/app/admin/students/page.tsx index eb23412..60bdf3a 100644 --- a/src/app/admin/students/page.tsx +++ b/src/app/admin/students/page.tsx @@ -9,13 +9,13 @@ import { useRouter } from "next/navigation" import { useRef } from "react" const TIER_STYLE: Record = { - "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 = { - "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([]) const [deleting, setDeleting] = useState(false) + const [isUploading, setIsUploading] = useState(false) const fileInputRef = useRef(null) const handleBulkUpload = async (e: React.ChangeEvent) => { 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() { Delete ({selectedIds.length}) )} - -