diff --git a/Dockerfile.dev b/Dockerfile.dev index dcac166..364df7e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -9,5 +9,8 @@ RUN npm ci # Copy the rest of the application code COPY . . +# Set memory limit to prevent Next.js dev server from eating RAM +ENV NODE_OPTIONS="--max-old-space-size=512" + # Start the Next.js development server CMD ["npm", "run", "dev"] diff --git a/PragatiVeer_Cancellation_Policy_v1.0.pdf b/PragatiVeer_Cancellation_Policy_v1.0.pdf new file mode 100644 index 0000000..a3c4fa8 Binary files /dev/null and b/PragatiVeer_Cancellation_Policy_v1.0.pdf differ diff --git a/PragatiVeer_Disclaimer_v1.0 (2).pdf b/PragatiVeer_Disclaimer_v1.0 (2).pdf new file mode 100644 index 0000000..b7fbc66 Binary files /dev/null and b/PragatiVeer_Disclaimer_v1.0 (2).pdf differ diff --git a/PragatiVeer_Privacy_Policy_v3.0.pdf b/PragatiVeer_Privacy_Policy_v3.0.pdf new file mode 100644 index 0000000..a9b2d94 Binary files /dev/null and b/PragatiVeer_Privacy_Policy_v3.0.pdf differ diff --git a/PragatiVeer_Refund_Policy_v1.0.pdf b/PragatiVeer_Refund_Policy_v1.0.pdf new file mode 100644 index 0000000..89af758 Binary files /dev/null and b/PragatiVeer_Refund_Policy_v1.0.pdf differ diff --git a/PragatiVeer_Terms_and_Conditions_v3.0.pdf b/PragatiVeer_Terms_and_Conditions_v3.0.pdf new file mode 100644 index 0000000..dcf227c Binary files /dev/null and b/PragatiVeer_Terms_and_Conditions_v3.0.pdf differ diff --git a/admin_uploads/dps_biology_ch4.json b/admin_uploads/dps_biology_ch4.json new file mode 100644 index 0000000..746d484 --- /dev/null +++ b/admin_uploads/dps_biology_ch4.json @@ -0,0 +1,202 @@ +[ + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 1 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 1", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 2 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 2", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 3 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 3", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 4 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 4", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 5 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 5", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 6 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 6", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 7 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 7", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 8 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 8", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 9 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 9", + "language": "English" + }, + { + "section": "General", + "subject": "Biology", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 10 for Biology Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Biology question 10", + "language": "English" + } +] \ No newline at end of file diff --git a/admin_uploads/dps_chemistry_ch4.json b/admin_uploads/dps_chemistry_ch4.json new file mode 100644 index 0000000..b69b95e --- /dev/null +++ b/admin_uploads/dps_chemistry_ch4.json @@ -0,0 +1,202 @@ +[ + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 1 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 1", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 2 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 2", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 3 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 3", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 4 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 4", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 5 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 5", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 6 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 6", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 7 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 7", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 8 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 8", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 9 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 9", + "language": "English" + }, + { + "section": "General", + "subject": "Chemistry", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 10 for Chemistry Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Chemistry question 10", + "language": "English" + } +] \ No newline at end of file diff --git a/admin_uploads/dps_mathematics_ch4.json b/admin_uploads/dps_mathematics_ch4.json new file mode 100644 index 0000000..660b2c0 --- /dev/null +++ b/admin_uploads/dps_mathematics_ch4.json @@ -0,0 +1,202 @@ +[ + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 1 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 1", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 2 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 2", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 3 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 3", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 4 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 4", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 5 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 5", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 6 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 6", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 7 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 7", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 8 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 8", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 9 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 9", + "language": "English" + }, + { + "section": "General", + "subject": "Mathematics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 10 for Mathematics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Mathematics question 10", + "language": "English" + } +] \ No newline at end of file diff --git a/admin_uploads/dps_physics_ch4.json b/admin_uploads/dps_physics_ch4.json new file mode 100644 index 0000000..8d17c46 --- /dev/null +++ b/admin_uploads/dps_physics_ch4.json @@ -0,0 +1,202 @@ +[ + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 1 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 1", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 2 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 2", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 3 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 3", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 4 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 4", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 5 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 5", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 6 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 6", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 7 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 7", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 8 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 8", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 9 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 9", + "language": "English" + }, + { + "section": "General", + "subject": "Physics", + "topic": "Chapter 4", + "subtopic": "Mixed", + "difficulty": "Medium", + "type": "MCQ_Single", + "question_text": "Sample question 10 for Physics Chapter 4?", + "options": { + "A": "Option A", + "B": "Option B", + "C": "Option C", + "D": "Option D" + }, + "correct_answer": { + "value": "A" + }, + "solution_text": "Solution for Physics question 10", + "language": "English" + } +] \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index fbd4a22..b9b115f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -16,6 +16,22 @@ from billing import generate_mock_order, verify_webhook_signature from llm import stream_tutor_response from predictor import calculate_readiness_score from growth import generate_referral_link, process_referral_reward +import boto3 +import uuid + +s3_client = boto3.client( + 's3', + endpoint_url='http://localhost:9000', + aws_access_key_id='minioadmin', + aws_secret_access_key='minioadmin', + region_name='us-east-1' +) +BUCKET_NAME = "avatars" + +try: + s3_client.create_bucket(Bucket=BUCKET_NAME) +except Exception: + pass # Bucket might already exist @asynccontextmanager async def lifespan(app: FastAPI): @@ -575,6 +591,41 @@ def get_user_attempts(user_id: UUID, session: Session = Depends(get_session)): # --- ADMIN ROUTES --- +@app.get("/api/admin/settings", tags=["Admin"]) +def get_system_settings(session: Session = Depends(get_session)): + settings = session.get(models.SystemSettings, "global") + if not settings: + settings = models.SystemSettings() + session.add(settings) + session.commit() + session.refresh(settings) + return settings + +class SettingsUpdate(BaseModel): + maintenance_mode: Optional[bool] = None + allow_new_registrations: Optional[bool] = None + enable_free_trial: Optional[bool] = None + trial_duration_days: Optional[int] = None + trial_tier: Optional[models.TierEnum] = None + +@app.put("/api/admin/settings", tags=["Admin"]) +def update_system_settings(payload: SettingsUpdate, session: Session = Depends(get_session)): + settings = session.get(models.SystemSettings, "global") + if not settings: + settings = models.SystemSettings() + + if payload.maintenance_mode is not None: settings.maintenance_mode = payload.maintenance_mode + if payload.allow_new_registrations is not None: settings.allow_new_registrations = payload.allow_new_registrations + if payload.enable_free_trial is not None: settings.enable_free_trial = payload.enable_free_trial + if payload.trial_duration_days is not None: settings.trial_duration_days = payload.trial_duration_days + if payload.trial_tier is not None: settings.trial_tier = payload.trial_tier + settings.updated_at = datetime.utcnow() + + session.add(settings) + session.commit() + session.refresh(settings) + return settings + @app.get("/api/admin/tests", tags=["Admin"]) def get_admin_tests(session: Session = Depends(get_session)): tests = session.exec(select(models.Test)).all() @@ -832,4 +883,192 @@ def delete_coupon(coupon_id: UUID, session: Session = Depends(get_session)): session.delete(coupon) session.commit() - return {"status": "success", "message": "Coupon deleted successfully"} \ No newline at end of file + return {"status": "success", "message": "Coupon deleted successfully"}@app.post("/api/users/{user_id}/avatar", tags=["Users"]) +async def upload_avatar(user_id: UUID, file: UploadFile = File(...), session: Session = Depends(get_session)): + user = session.get(models.User, user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + + file_extension = file.filename.split(".")[-1] + object_name = f"{user_id}_{uuid.uuid4().hex[:8]}.{file_extension}" + + try: + s3_client.upload_fileobj( + file.file, + BUCKET_NAME, + object_name, + ExtraArgs={"ContentType": file.content_type} + ) + url = f"http://localhost:9000/{BUCKET_NAME}/{object_name}" + user.profile_image_url = url + session.add(user) + session.commit() + return {"url": url} + except Exception as e: + print(f"S3 Upload failed: {e}") + raise HTTPException(status_code=500, detail="Failed to upload image") + + +# --- ADMIN STUDENT MANAGEMENT --- + +class AdminUserPatch(BaseModel): + name: Optional[str] = None + tier: Optional[models.TierEnum] = None + streak_count: Optional[int] = None + target_exam: Optional[models.ExamEnum] = None + school: Optional[str] = None + city: Optional[str] = None + state: Optional[str] = None + +@app.get("/api/admin/students", tags=["Admin"]) +def list_all_students(session: Session = Depends(get_session)): + """Return all users with basic stats for the admin student directory.""" + users = session.exec(select(models.User)).all() + result = [] + for u in users: + attempt_count = session.exec( + select(models.Attempt).where(models.Attempt.user_id == u.id) + ).all() + mistake_count = session.exec( + select(models.Mistake).where(models.Mistake.user_id == u.id) + ).all() + avg_accuracy = 0.0 + if attempt_count: + submitted = [a for a in attempt_count if a.status == "Submitted"] + if submitted: + avg_accuracy = round(sum(a.accuracy for a in submitted) / len(submitted), 1) + result.append({ + "id": str(u.id), + "name": u.name, + "email": u.email or "", + "phone": u.phone or "", + "tier": u.tier.value, + "target_exam": u.target_exam.value, + "city": u.city, + "state": u.state, + "streak_count": u.streak_count, + "longest_streak": u.longest_streak, + "last_active": u.last_active.isoformat(), + "created_at": u.created_at.isoformat(), + "total_tests": len(attempt_count), + "avg_accuracy": avg_accuracy, + "total_mistakes": len(mistake_count), + }) + return result + + +@app.get("/api/admin/students/{user_id}", tags=["Admin"]) +def get_student_detail(user_id: UUID, session: Session = Depends(get_session)): + """Full student profile for admin detail view.""" + user = session.get(models.User, user_id) + if not user: + raise HTTPException(status_code=404, detail="Student not found") + + attempts = session.exec( + select(models.Attempt).where(models.Attempt.user_id == user_id) + .order_by(models.Attempt.submitted_at.desc()) # type: ignore + ).all() + + mistakes = session.exec( + select(models.Mistake).where(models.Mistake.user_id == user_id) + ).all() + + subscriptions = session.exec( + select(models.Subscription).where(models.Subscription.user_id == user_id) + ).all() + + transactions = session.exec( + select(models.Transaction).where(models.Transaction.user_id == user_id) + ).all() + + submitted = [a for a in attempts if a.status == "Submitted"] + avg_accuracy = round(sum(a.accuracy for a in submitted) / len(submitted), 1) if submitted else 0.0 + mastered_count = len([m for m in mistakes if m.is_mastered]) + weak_topics = list({m.topic for m in mistakes if not m.is_mastered})[:5] + + recent_attempts = [] + for a in attempts[:5]: + # Try to find the test name from Test table + test_name = "Practice Session" + if a.source_type.value == "Test": + test = session.get(models.Test, a.source_id) + if test: + test_name = test.title + recent_attempts.append({ + "id": str(a.id), + "name": test_name, + "source_type": a.source_type.value, + "score": a.score, + "total_questions": a.total_questions, + "accuracy": a.accuracy, + "submitted_at": a.submitted_at.isoformat() if a.submitted_at else None, + }) + + return { + "id": str(user.id), + "name": user.name, + "email": user.email or "", + "phone": user.phone or "", + "tier": user.tier.value, + "target_exam": user.target_exam.value, + "student_class": user.student_class.value, + "school": user.school or "", + "city": user.city, + "state": user.state, + "streak_count": user.streak_count, + "longest_streak": user.longest_streak, + "created_at": user.created_at.isoformat(), + "last_active": user.last_active.isoformat(), + "subscription_end": user.subscription_end.isoformat() if user.subscription_end else None, + "profile_image_url": user.profile_image_url, + "stats": { + "total_tests": len(attempts), + "avg_accuracy": avg_accuracy, + "total_mistakes": len(mistakes), + "mastered_mistakes": mastered_count, + }, + "recent_attempts": recent_attempts, + "weak_topics": weak_topics, + "subscriptions": [ + { + "id": str(s.id), + "tier": s.tier.value, + "billing_cycle": s.billing_cycle, + "start_date": s.start_date.isoformat(), + "end_date": s.end_date.isoformat(), + "amount": s.amount, + "status": s.status, + } + for s in subscriptions + ], + "transactions": [ + { + "id": str(t.id), + "amount": t.amount, + "tier": t.tier, + "status": t.status, + "created_at": t.created_at.isoformat(), + } + for t in transactions + ], + } + + +@app.patch("/api/admin/students/{user_id}", tags=["Admin"]) +def patch_student(user_id: UUID, payload: AdminUserPatch, session: Session = Depends(get_session)): + """Admin update: tier, name, streak, exam, location.""" + user = session.get(models.User, user_id) + if not user: + raise HTTPException(status_code=404, detail="Student not found") + if payload.name is not None: user.name = payload.name + if payload.tier is not None: user.tier = payload.tier + if payload.streak_count is not None: user.streak_count = payload.streak_count + if payload.target_exam is not None: user.target_exam = payload.target_exam + if payload.school is not None: user.school = payload.school + if payload.city is not None: user.city = payload.city + if payload.state is not None: user.state = payload.state + session.add(user) + session.commit() + session.refresh(user) + return {"status": "ok", "user_id": str(user.id), "tier": user.tier.value} + diff --git a/backend/models.py b/backend/models.py index 365e656..361e13e 100644 --- a/backend/models.py +++ b/backend/models.py @@ -58,6 +58,7 @@ class User(SQLModel, table=True): tier: TierEnum = Field(default=TierEnum.FREE) trial_end: Optional[datetime] = None subscription_end: Optional[datetime] = None + profile_image_url: Optional[str] = None streak_count: int = Field(default=0) longest_streak: int = Field(default=0) created_at: datetime = Field(default_factory=datetime.utcnow) @@ -186,3 +187,13 @@ class Subscription(SQLModel, table=True): status: str # Active, Expired, Cancelled, Paused auto_renew: bool = Field(default=True) coupon_code: Optional[str] = None + +class SystemSettings(SQLModel, table=True): + id: str = Field(default="global", primary_key=True) + maintenance_mode: bool = Field(default=False) + allow_new_registrations: bool = Field(default=True) + enable_free_trial: bool = Field(default=True) + trial_duration_days: int = Field(default=5) + trial_tier: TierEnum = Field(default=TierEnum.AI_COACH) + updated_at: datetime = Field(default_factory=datetime.utcnow) + diff --git a/backend/seed.py b/backend/seed.py index 8ba30e2..a31a4d0 100644 --- a/backend/seed.py +++ b/backend/seed.py @@ -62,6 +62,56 @@ def seed_local_database(): ) session.add(pro_student) + # Neha Verma — full AI Coach demo profile for manager presentations + neha_id = UUID("33333333-3333-3333-3333-333333333333") + neha_student = models.User( + id=neha_id, + phone="6666666666", + email="neha@pragativeer.com", + password_hash="mock_hash", + name="Neha Verma", + student_class=models.ClassEnum.TWELFTH, + medium=models.MediumEnum.ENGLISH, + target_exam=models.ExamEnum.NEET, + school="Delhi Public School, R.K. Puram", + city="New Delhi", + state="Delhi", + tier=models.TierEnum.AI_COACH, + trial_end=None, + subscription_end=datetime.utcnow() + timedelta(days=345), + streak_count=62, + longest_streak=62, + last_active=datetime.utcnow() + ) + session.add(neha_student) + + # Seed Neha's Subscription record + neha_subscription = models.Subscription( + id=uuid4(), + user_id=neha_id, + tier=models.TierEnum.AI_COACH, + billing_cycle="Annual", + start_date=datetime.utcnow() - timedelta(days=20), + end_date=datetime.utcnow() + timedelta(days=345), + amount=1199900, # ₹11,999 in paise + payment_id="PAY_NEHA_DEMO_001", + status="Active", + auto_renew=True, + ) + session.add(neha_subscription) + + # Seed Neha's Transaction record + neha_txn = models.Transaction( + id=uuid4(), + user_id=neha_id, + amount=11999.0, + tier="Al_Coach", + status="Success", + created_at=datetime.utcnow() - timedelta(days=20), + ) + session.add(neha_txn) + + student_id = UUID("b0e17378-864d-439e-8806-708dc48a891b") student_user = models.User( id=student_id, diff --git a/create_policies.py b/create_policies.py new file mode 100644 index 0000000..ef8e22d --- /dev/null +++ b/create_policies.py @@ -0,0 +1,555 @@ +import os + +pages_dir = r"c:\Users\naman\OneDrive\Desktop\pragativeer-v2\src\app" + +pages = { + "disclaimer": { + "title": "PragatiVeer Disclaimer", + "content": """ +

Last Updated: 27 July 2026
+Version: 1.0
+Document Ref: PV-LEG-DI-001
+Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+ +

1. General Information Only

+

The content on PragatiVeer — including Daily Practice Sheets (DPS), mock tests, study materials, articles, and AI-generated insights — is provided for educational and practice purposes only. It does not constitute professional advice, guaranteed exam content, or a complete curriculum.

+ +

2. No Guarantee of Exam Results

+

PragatiVeer is a practice and preparation tool. We do not guarantee:

+ +

Exam outcomes depend on numerous factors beyond our control, including your preparation level, exam-day conditions, and other variables.

+ +

3. Accuracy of Content

+

We strive to ensure that all content is accurate, up-to-date, and aligned with relevant exam syllabi. However, we make no representations or warranties regarding the completeness, accuracy, reliability, or timeliness of any content. Exam patterns, syllabi, and marking schemes may change without notice.

+ +

4. AI-Generated Content

+

Some features of PragatiVeer — including personalised DPS generation and weak-area analysis — are powered by artificial intelligence (AI). While we continuously improve our AI models, AI generated content may contain errors, inaccuracies, or omissions. You are encouraged to verify critical information from official sources.

+ +

5. No Student-Teacher Relationship

+

Use of PragatiVeer does not create a student-teacher, tutor-student, or any professional advisory relationship between you and Gyannetra Pvt Ltd or its personnel.

+ +

6. Third-Party Links

+

The Platform may contain links to third-party websites, resources, or services. We do not endorse, control, or assume responsibility for the content, privacy practices, or availability of any third-party sites.

+ +

7. Limitation of Liability

+

To the maximum extent permitted by law, Gyannetra Pvt Ltd and its directors, employees, and agents shall not be liable for any loss, damage, or expense arising from your use of or reliance on the Platform or its content, including but not limited to:

+ + +

8. No Warranty

+

The Platform and all content are provided "as is" and "as available" without warranty of any kind, whether express or implied.

+ +

9. Contact

+

If you have any questions about this Disclaimer, please contact us at contact@pragativeer.com.

+
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+""" + }, + "terms-and-conditions": { + "title": "PragatiVeer Terms & Conditions", + "content": """ +

Last Updated: 27 July 2026
+Version: 3.0
+Document Ref: PV-LEG-TC-001
+Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+ +

1. Introduction

+

PragatiVeer (“we”, “our”, “us”) is an exam preparation platform operated by Gyannetra Pvt Ltd (CIN: U72900PN2015PTC156021), registered office at OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India.

+

By accessing or using PragatiVeer, you agree to these Terms. If you do not agree, do not use the Platform.

+ +

2. Eligibility

+ + +

3. Account

+ + +

4. Subscriptions & Payments

+

4.1 Plans

+
+ + + + + + + + + + + + +
PlanPrice (incl. GST)Duration
Pro₹3,499Per annum
AI Coach₹11,999Per annum
+
+ +

4.2 Payment

+ + +

4.3 Auto-Renewal

+

Subscriptions auto-renew at the end of each billing period unless cancelled at least 24 hours before renewal. You may cancel auto-renewal from your account settings or by emailing contact@pragativeer.com.

+ +

5. Refund Policy

+

You may request a full refund within 7 days of purchase if you have not accessed more than 5 DPS sheets. No refunds after 7 days. To request a refund, email contact@pragativeer.com with subject “Refund Request”.

+ +

6. User Conduct

+

You agree to:

+ +

Violation may result in account suspension or termination.

+ +

7. Intellectual Property

+

All Content on PragatiVeer — questions, DPS, materials, logos, software — is owned by Gyannetra Pvt Ltd. You may not reproduce, distribute, or modify it without our written consent.

+

You retain ownership of your responses and data. By submitting content, you grant us a license to store, process, and use anonymised data for service improvement.

+ +

8. Privacy

+

Our data practices are governed by our Privacy Policy and the Digital Personal Data Protection Act, 2023. We do not sell your data.

+ +

9. No Guarantee of Results

+

PragatiVeer is a practice tool. We do not guarantee any specific exam score, rank, or admission.

+ +

10. Limitation of Liability

+

The Platform is provided “as is”. Our total liability shall not exceed the fees paid by you in the 12 months preceding a claim. We are not liable for indirect or consequential damages.

+ +

11. Termination

+

You may delete your account anytime via settings or by emailing contact@pragativeer.com. We may terminate your account for breach of these Terms. Fees paid are non-refundable upon termination.

+ +

12. Changes to Terms

+

We may update these Terms with 7 days’ notice via email. Continued use after changes take effect constitutes acceptance.

+ +

13. Grievance Officer

+
+ + + + + + + + + + + + + + +
DetailInformation
NameHimani Kachhawaha
Emailgrievance@pragativeer.com
Phone+91 9079337961
AddressOFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045
Response TimeAcknowledged within 24 hours; resolved within 30 days
+
+ +

14. Governing Law

+

These Terms are governed by the laws of India. Disputes shall be resolved through binding arbitration in Pune, Maharashtra. The courts in Pune have exclusive jurisdiction.

+ +
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+""" + }, + "privacy-policy": { + "title": "PragatiVeer Privacy Policy", + "content": """ +

Last Updated: 27 July 2026
+Version: 3.0
+Document Ref: PV-LEG-PP-001
+Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+ +

1. Introduction

+

PragatiVeer (“we”, “our”, “us”) is an exam preparation platform operated by Gyannetra Pvt Ltd (CIN: U72900PN2015PTC156021), registered office at OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India.

+

This Privacy Policy explains how we collect, use, store, share, and protect your personal data when you use PragatiVeer. It covers your rights under the Digital Personal Data Protection Act, 2023 (DPDP Act) of India.

+

By using PragatiVeer, you acknowledge that you have read and understood this policy.

+ +

2. What Data We Collect

+

2.1 Information You Provide Directly

+
+ + + + + + + + + + + + + + +
CategoryExamplesPurpose
Account InformationName, email address, phone numberAccount creation, service delivery, communication
Academic InformationClass/grade, target exam, school name, cityPersonalising content, weak-area analysis
Payment InformationTransaction ID, billing name (we do not store full card or UPI details)Subscription billing, invoicing
CommunicationsMessages sent via email or contact formsSupport, feedback, grievance handling
+
+ +

2.2 Information Collected Automatically

+
+ + + + + + + + + + + + +
CategoryExamplesPurpose
Usage DataDPS attempts, scores, time spent, weak-area progressionPersonalising the learning experience
Technical DataIP address, browser type, device type, operating systemService optimisation, fraud prevention, analytics
+
+ +

2.3 Information from Third Parties

+

We may receive transaction confirmation data from payment gateways but not full financial instrument details.

+ +

3. How We Use Your Data

+

We use your personal data only for:

+
    +
  1. Service Delivery — Personalised DPS, weak-area tracking, content delivery
  2. +
  3. Personalisation — Adapting question difficulty based on performance
  4. +
  5. Communication — Subscription reminders, service updates, support
  6. +
  7. Payment Processing — Billing and subscription management
  8. +
  9. Improvement — Analysing aggregate data to improve content quality
  10. +
  11. Legal Compliance — Fulfilling obligations under DPDP Act and tax laws
  12. +
  13. Security — Detecting fraud, abuse, and unauthorised access
  14. +
+

We do NOT:

+ + +

4. Users Under 18 Years of Age

+

PragatiVeer is designed for students preparing for competitive exams, including those under 18 years of age.

+

By using PragatiVeer, users under 18 years of age represent and warrant that they have obtained the necessary consent from their parent or lawful guardian to use the Platform and share their personal data. PragatiVeer relies on this representation and shall not be liable for verifying parental consent.

+

We encourage parents and guardians to monitor their child’s online activities and review this Privacy Policy.

+ +

5. Data Sharing

+

We share your data only with trusted service providers bound by data protection agreements:

+
+ + + + + + + + + + + + + +
ProviderPurposeSafeguards
Payment GatewayPayment processingPCI-DSS compliant; no full payment details stored
AI Inference ProvidersWeak-area analysis, DPS generationData anonymised before processing
Self-hosted InfrastructureStorage, backups, disaster recoveryNo third-party access
+
+

We do not share your data with advertising networks, data brokers, or for monetisation.

+ +

6. Data Retention & Deletion

+
+ + + + + + + + + + + + + +
Data CategoryRetention Period
Account InformationAccount duration + 12 months
Academic Performance DataAccount duration + 24 months
Payment Records7 years (as required by tax laws)
Communication Records12 months
+
+

You may request account deletion by emailing contact@pragativeer.com with subject “Account Deletion Request”. We will delete your data within 30 days.

+ +

7. Data Security

+

We implement:

+ +

In the event of a data breach, we will notify the Data Protection Board of India and affected users within 72 hours.

+ +

8. Your Rights Under DPDP Act 2023

+
+ + + + + + + + + + + + + + + +
RightHow to Exercise
Access your dataEmail contact@pragativeer.com
Correct your dataUpdate in account settings or email us
Delete your dataEmail with subject “Account Deletion Request”
Withdraw consentEmail contact@pragativeer.com
Lodge a grievanceContact Grievance Officer (Section 9)
Nominate a representativeEmail with subject “Nomination Request”
+
+

We respond to all requests within 30 days.

+ +

9. Grievance Officer

+
+ + + + + + + + + + + + + + +
DetailInformation
NameHimani Kachhawaha
Emailgrievance@pragativeer.com
Phone+91 9079337961
AddressOFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045
Response TimeAcknowledged within 24 hours; resolved within 30 days
+
+ +

10. Cookies

+

Our website uses essential cookies for functionality and anonymous analytics cookies. We do not use advertising cookies, cross-site tracking, or fingerprinting scripts.

+ +

11. Changes to This Policy

+

We will notify you of material changes via email with at least 7 days’ notice.

+ +

12. Contact

+

Email: contact@pragativeer.com
+Address: OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India
+Phone: +91 9079337961

+ +

13. Governing Law

+

This Policy is governed by the laws of India. Disputes are subject to the exclusive jurisdiction of courts in Pune, Maharashtra.

+ +
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+""" + }, + "refund-policy": { + "title": "PragatiVeer Refund Policy", + "content": """ +

Last Updated: 27 July 2026
+Version: 1.0
+Document Ref: PV-LEG-RP-001
+Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+ +

1. Overview

+

This Refund Policy governs all paid subscriptions on PragatiVeer, operated by Gyannetra Pvt Ltd (CIN: U72900PN2015PTC156021).

+

By purchasing a subscription, you agree to the terms of this policy.

+ +

2. Subscription Plans

+
+ + + + + + + + + + + + +
PlanPrice (incl. GST)Duration
Pro₹3,499Per annum
AI Coach₹11,999Per annum
+
+ +

3. Refund Eligibility

+

You may request a full refund within 7 days of purchase provided that you have not accessed more than 5 DPS sheets during that period.

+

Refunds will not be issued if:

+ + +

4. How to Request a Refund

+

To request a refund, email contact@pragativeer.com with:

+ +

We will process your request within 7-10 business days and the refund will be credited to the original payment method.

+ +

5. Non-Refundable Items

+

The following are non-refundable:

+ + +

6. Cancellation

+

You may cancel your subscription at any time. However, cancellation does not entitle you to a refund unless it falls within the refund window described in Section 3. Upon cancellation, your access will continue until the end of the current billing period.

+ +

7. Contact

+

For any questions regarding this Refund Policy, contact us at contact@pragativeer.com or reach out to our Grievance Officer:

+
+ + + + + + + + + + + + +
DetailInformation
Grievance OfficerHimani Kachhawaha
Emailgrievance@pragativeer.com
Phone+91 9079337961
+
+ +
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+""" + }, + "cancellation-policy": { + "title": "PragatiVeer Cancellation Policy", + "content": """ +

Last Updated: 27 July 2026
+Version: 1.0
+Document Ref: PV-LEG-CP-001
+Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+ +

1. Overview

+

This Cancellation Policy governs cancellation of paid subscriptions on PragatiVeer, operated by Gyannetra Pvt Ltd (CIN: U72900PN2015PTC156021).

+

By purchasing a subscription, you agree to the terms of this policy.

+ +

2. Subscription Cancellation by You

+

2.1 How to Cancel

+

You may cancel your subscription at any time by:

+ + +

2.2 Effect of Cancellation

+

Upon cancellation:

+ + +

2.3 Timing

+

To avoid being charged for the next billing cycle, cancellation must be requested at least 24 hours before the renewal date.

+ +

3. Auto-Renewal

+

All subscriptions auto-renew at the end of each billing period unless cancelled at least 24 hours before renewal. You authorise us to charge the applicable subscription fee to your chosen payment method at each renewal.

+

You will receive a reminder before renewal. If you do not wish to renew, please cancel before the renewal date.

+ +

4. Cancellation by PragatiVeer

+

We reserve the right to cancel or suspend your subscription immediately if:

+ +

In such cases, fees paid are non-refundable.

+ +

5. Account Deletion

+

Cancelling your subscription does not delete your account. To delete your account and all associated data, please follow the process outlined in our Privacy Policy.

+ +

6. Contact

+

For any questions regarding this Cancellation Policy, contact us at contact@pragativeer.com or reach out to our Grievance Officer:

+
+ + + + + + + + + + + + +
DetailInformation
Grievance OfficerHimani Kachhawaha
Emailgrievance@pragativeer.com
Phone+91 9079337961
+
+ +
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+""" + } +} + +template = """import Link from 'next/link'; + +export default function {component_name}Page() {{ + return ( +
+
+

+ {title} +

+
+ {content} +
+
+
+ ); +}} +""" + +for route, data in pages.items(): + page_dir = os.path.join(pages_dir, route) + os.makedirs(page_dir, exist_ok=True) + + component_name = "".join(word.capitalize() for word in route.split("-")) + content = template.format( + component_name=component_name, + title=data["title"], + content=data["content"] + ) + + with open(os.path.join(page_dir, "page.tsx"), "w", encoding="utf-8") as f: + f.write(content) + +print("Created all 5 policy pages successfully.") diff --git a/docker-compose.yml b/docker-compose.yml index b859ad8..e8ac3c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,9 +25,9 @@ services: container_name: pragativeer_pgbouncer environment: - DATABASE_URL=postgres://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod - - POOL_MODE=transaction - - MAX_CLIENT_CONN=1000 - - DEFAULT_POOL_SIZE=50 + volumes: + - ./pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro + - ./userlist.txt:/etc/pgbouncer/userlist.txt:ro depends_on: postgres: condition: service_healthy diff --git a/pgbouncer.ini b/pgbouncer.ini new file mode 100644 index 0000000..e7e8109 --- /dev/null +++ b/pgbouncer.ini @@ -0,0 +1,14 @@ +[databases] +pragativeer_prod = host=postgres port=5432 dbname=pragativeer_prod + +[pgbouncer] +listen_addr = 0.0.0.0 +listen_port = 5432 +unix_socket_dir = +auth_type = trust +auth_file = /etc/pgbouncer/userlist.txt +pool_mode = transaction +max_client_conn = 1000 +default_pool_size = 50 +admin_users = postgres +ignore_startup_parameters = extra_float_digits diff --git a/pragativeer-ai-coach-complete.html b/pragativeer-ai-coach-complete.html new file mode 100644 index 0000000..f996b78 --- /dev/null +++ b/pragativeer-ai-coach-complete.html @@ -0,0 +1,1216 @@ + + + + + + PragatiVeer — AI Coach (Complete) + + + + + + + + + + + + + + + +
+ + +
+ 9:41 +
+ + +
+
+ + +
+
+ +
+
+ +
R
+
+
+ + +
+

Namaste, Rohan 👋

+
+ आज का तुम्हारा AI कोच तैयार है + • + JEE Main 2027 +
+
+ + 12-day streak! 🔥 +
+
+ + +
+
+ A-05 Readiness Score + A-03 Study Plan + A-01 Mistake Tracking + A-02 Spaced Repetition + A-04 AI Tutor + A-07 College Predictor + A-06 Exam Strategy + A-08 Time Optimization +
+
+ + + + +
+
+

🎯 Exam Readiness Score A-05

+ Trend → +
+
+
+ Updated daily • 19 Jul 2026 + + + +5 this week + +
+
+
+ + + + +
+
72
+
/100
+
+
+
+
+
78% ↑2%
+
Accuracy (40%)
+
+
+
65% ↑4%
+
Syllabus (20%)
+
+
+
82% ↑1%
+
Consistency (10%)
+
+
+
58% ↓3%
+
Speed (20%)
+
+
+
+
+ + NIT Surat is within reach! Improve your speed by 10% to target NIT Trichy 🎯 +
+
+
+ + + + +
+

📋 Today's Study Plan A-03

+ See All → +
+
+
+
+ Sunday, 19 July 2026 + 2/5 done +
+
+
+
+
+
+
+
+
Revise Thermodynamics
+
Physics • Chapter 5
+
+ Revision + 15 min +
+
+
+
+
DPS — Electrostatics
+
Physics • 12 questions • Medium
+
+ Practice + 20 min +
+
+
+
+
Mistake Review Session
+
7 questions from your error queue
+
+ Review + 10 min +
+
+
+
+
Micro-test — Organic Chem
+
Chemistry • 5 questions
+
+ Test + 5 min +
+
+
+
+
Weekly Test Analysis
+
Review mistakes from yesterday's test
+
+ Review + 10 min +
+
+
+
+ + + + +
+

🎯 Mistake Tracking A-01

+ Full Analysis → +
+
+
+
+ +
+
+
7 mistakes waiting for review
+
+ 3 conceptual • 2 calculation • 2 time pressure +
+
+
+ +
+
+
+ +
+

🔄 Spaced Repetition Queue A-02

+ All Questions → +
+
+
+
+ SM-2 Algorithm • 12 active mistakes + 3 mastered this week +
+
+
+ Day 1 +
✓
+
+
+
+ Day 3 +
3
+
+
+
+ Day 7 +
7
+
+
+
+ Day 14 +
14
+
+
+
+ Day 30 +
30
+
+
+
+
+ +
+
Coulomb's Law — Force between two charges in a medium
+
Physics • Electrostatics • Attempt 2/5
+
+ Due today +
+
+ +
+
Integration of rational functions by partial fractions
+
Maths • Calculus • Attempt 1/5
+
+ Due today +
+
+ +
+
Electromagnetic Induction — Lenz's Law application
+
Physics • EMI • Attempt 3/5
+
+ Tomorrow +
+
+
+
+ + + + +
+

💬 AI Tutor Chat A-04

+ Full Chat → +
+
+
+
+
AI
+
+
PragatiVeer AI Tutor
+
Online • Context-aware
+
+
42 / 50 messages today
+
+
+
+
+ Can you explain why option C is wrong in today's DPS Q7? The one about electric field lines. +
+
+
+
+ Great question! Option C says "Electric field lines can intersect each other." This is incorrect because:

+ • Electric field lines never intersect — at the point of intersection, there would be two different directions of the electric field, which is impossible.

+ • I see you've made a similar mistake in Electrostatic Potential (Day 3) — want me to pull up that question for revision? +
+
+
+
+ Yes please! And give me a similar practice question. +
+
+
+
+ Here's your previous mistake from Electrostatic Potential (attempted 19 Jul):

+ "Two point charges +4μC and -2μC are placed 10cm apart. Where is the electric field zero?"

+ 🔄 Similar question: Three charges +2q, -q, and +3q are placed at vertices of an equilateral triangle. Find the point where net electric field is zero.

+ Want me to walk through the solution step by step? 🤔 +
+
+
+
+ + +
+
+
+ + + + +
+

🏛️ College Predictor A-07

+ Full List → +
+
+
+
+
Reach 🚀
+
Target 🎯
+
Safety 🛡️
+
+
+
+
R
+
+
IIT Delhi
+
Computer Science
+
+ 15% +
+
+
R
+
+
IIT Bombay
+
Electrical Engineering
+
+ 22% +
+
+
T
+
+
NIT Surat (SVNIT)
+
Computer Science
+
+ 68% +
+
+
T
+
+
NIT Trichy
+
Mechanical Engineering
+
+ 55% +
+
+
S
+
+
IIIT Allahabad
+
IT
+
+ 89% +
+
+
+ 🎯 You need +35 marks to reach IIT Delhi CSE. At your current trajectory, NIT Surat CSE is your best target! +
+
+
+ + + + +
+

📋 Exam Day Strategy A-06

+ Full Strategy → +
+
+
+
+
+
⚡
+
Start with Physics
+
Your strongest section — 78% accuracy
+
+
+
⏱️
+
45 min per section
+
Physics: 45m • Chem: 40m • Maths: 50m
+
+
+
🎯
+
Numerical first in Chem
+
You score 72% on numerical vs 58% on theory
+
+
+
⏰
+
Last 15 min sprint
+
You slow down — practice timed sprints!
+
+
+
+ + Download Printable Strategy Card +
+
+
+ + + + +
+

⏰ Study Time Optimization A-08

+ Full Analysis → +
+
+
+
+
6AM
+
8AM
+
10AM
+
12PM
+
2PM
+
4PM
+
6PM
+
8PM
+
10PM
+
12AM
+
+
+ + You perform best between 8 PM - 10 PM (92% accuracy). Schedule your most difficult topics then! 🌙 +
+
+
+ + +
+

📊 This Week Overview

+
+
+
+
1,247
+
Questions Solved
+
+
+
78%
+
Avg Accuracy
+
+
+
#42
+
City Rank
+
+
+ + + + +
+ + + diff --git a/pragativeer-ai-coach-dashboard.html b/pragativeer-ai-coach-dashboard.html new file mode 100644 index 0000000..e386b3a --- /dev/null +++ b/pragativeer-ai-coach-dashboard.html @@ -0,0 +1,821 @@ + + + + + +PragatiVeer — AI Coach Dashboard + + + +
+ + +
+
+
PV
+
PragatiVeer
+
+
+
✦ AI COACH
+
RK
+
+
+ + +
+

Good morning, Rohan! 👋

+

Your AI Coach has prepared today's plan. You're on track for NIT Surat.

+
+ + +
+
+
Readiness Score
+
+
+ + + + + 72 +
+
+

Good Progress

+

↑ 5 pts this week

+
+
+
+ +
+
Streak
+
23 days
+
🔥 Best: 23 days
+
+ +
+
Questions Solved
+
1,247 total
+
↑ 47 this week
+
+ +
+
Overall Accuracy
+
74%
+
↑ 3% this month
+
+
+ + +
+ +
+
+
📊 Mistake Heatmap
+
View Details →
+
+
+
+
Physics
+
Chemistry
+
Maths
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Weak +
+
+
+
+
+ Strong +
+
+
+
+ + +
+
+
📋 Today's Study Plan
+
Regenerate →
+
+ +
+
+
+
DPS — Electrostatics + Kinematics
+
10 questions • Mixed difficulty
+
+ Physics +
20 min
+
+ +
+
+
+
Mistake Review — Thermodynamics
+
5 re-attempts from spaced repetition queue
+
+ Review +
10 min
+
+ +
+
+
+
Micro-test — GOC (General Organic Chemistry)
+
5 questions • Your accuracy here: 38% ⚠️
+
+ Chemistry +
5 min
+
+ +
+
+
+
Weekly Test — Full Syllabus (JEE Main Pattern)
+
90 questions • 3 hours • Sectional timing
+
+ Test +
3 hrs
+
+ +
+
+
+
Revise — Circles + Straight Lines
+
Formula sheet + 3 practice questions
+
+ Maths +
15 min
+
+
+
+ + +
+ +
+
🤖
+
AI Coach Insight
+
+ You keep making sign errors in Thermodynamics. + In your last 3 attempts, you've used the wrong sign for work done (W = -PΔV). + Review the First Law of Thermodynamics — we've queued 3 practice questions for tomorrow. +
+
+ 💡 Tip: You perform best between 7-9 PM. Schedule difficult topics then. +
+
+ + +
+
+
🏆 City Leaderboard — Jaipur
+
This Week →
+
+ +
+
1
+
Priya S.
+
94%
+
🔥 45 day streak
+
+ +
+
2
+
Arjun M.
+
91%
+
30 day streak
+
+ +
+
3
+
Rohan K.
+
87%
+
👈 You
+
+ +
+
4
+
Neha G.
+
85%
+
+ +
+
5
+
Vikram R.
+
82%
+
+
+ + +
+
+
🔥 Streak & Milestones
+
Share →
+
+ +
+
🔥
+
+

23 Days

+

Longest streak: 23 days 🎉

+
+
+ +
+
M
+
T
+
W
+
T
+
F
+
S
+
S
+
+ +
+
+ 🏅 7-Day Streak ✅ +
+
+ 🏅 21-Day Streak ✅ +
+
+ 🏆 30-Day Streak — 7 days away! +
+
+
+
+ + +
+ PragatiVeer — AI Coach Dashboard Mockup | Gyannetra Pvt Ltd | July 2026 +
+ +
+ + diff --git a/dpps/dps-bio-ch4-20260707.pdf b/public/dpps/dps-bio-ch4-20260707.pdf similarity index 100% rename from dpps/dps-bio-ch4-20260707.pdf rename to public/dpps/dps-bio-ch4-20260707.pdf diff --git a/dpps/dps-che-ch4-20260707.pdf b/public/dpps/dps-che-ch4-20260707.pdf similarity index 100% rename from dpps/dps-che-ch4-20260707.pdf rename to public/dpps/dps-che-ch4-20260707.pdf diff --git a/dpps/dps-mth-ch4-20260707.pdf b/public/dpps/dps-mth-ch4-20260707.pdf similarity index 100% rename from dpps/dps-mth-ch4-20260707.pdf rename to public/dpps/dps-mth-ch4-20260707.pdf diff --git a/dpps/dps-phy-ch4-20260707.pdf b/public/dpps/dps-phy-ch4-20260707.pdf similarity index 100% rename from dpps/dps-phy-ch4-20260707.pdf rename to public/dpps/dps-phy-ch4-20260707.pdf diff --git a/remove_links.py b/remove_links.py new file mode 100644 index 0000000..6e9fb40 --- /dev/null +++ b/remove_links.py @@ -0,0 +1,24 @@ +import os +import re + +dir_path = r"c:\Users\naman\OneDrive\Desktop\pragativeer-v2\src\app\dashboard\ai-coach" + +# Pattern to remove +pattern = re.compile( + r'\s*\s* Back to AI Coach Overview\s*', + re.MULTILINE | re.DOTALL +) + +for root, dirs, files in os.walk(dir_path): + for file in files: + if file == "page.tsx": + path = os.path.join(root, file) + with open(path, "r", encoding="utf-8") as f: + content = f.read() + + new_content = pattern.sub('', content) + + if new_content != content: + with open(path, "w", encoding="utf-8") as f: + f.write(new_content) + print(f"Updated {path}") diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index 2323d40..d1e6f99 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -1,7 +1,61 @@ +"use client"; + +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" export default function SettingsPage() { + const [settings, setSettings] = useState({ + maintenance_mode: false, + allow_new_registrations: true, + enable_free_trial: true, + trial_duration_days: 5, + trial_tier: "Al_Coach" + }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/admin/settings") + .then(res => res.json()) + .then(data => { + if(data && !data.detail) { + setSettings(prev => ({ ...prev, ...data })); + } + setLoading(false); + }) + .catch(err => { + console.error("Failed to load settings:", err); + setLoading(false); + }); + }, []); + + const saveSettings = async (section: string, overrides: Partial = {}) => { + try { + const payload = { ...settings, ...overrides }; + const res = await fetch("/api/admin/settings", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + if (res.ok) { + alert(`${section} saved successfully!`); + setSettings(payload); + } else { + alert(`Failed to save ${section}.`); + } + } catch (err) { + alert("Error saving settings."); + } + }; + + const handleToggle = (key: keyof typeof settings, sectionName: string) => { + const newValue = !settings[key]; + setSettings(prev => ({ ...prev, [key]: newValue })); + saveSettings(sectionName, { [key]: newValue }); + }; + + if (loading) return
Loading System Settings...
; + return (
@@ -24,7 +78,49 @@ export default function SettingsPage() {
- + + + + + + + Trial Management + Configure the trial length and tier access for new signups. + + +
+
+
Enable Free Trial
+
Offer a trial to newly registered users.
+
+
handleToggle('enable_free_trial', 'Free Trial')} + className={`h-6 w-11 rounded-full relative cursor-pointer transition-colors ${settings.enable_free_trial ? 'bg-primary border border-primary shadow-[0_0_10px_var(--color-primary)]' : 'bg-secondary border border-border'}`}> +
+
+
+
+
+ + setSettings(prev => ({ ...prev, trial_duration_days: parseInt(e.target.value) || 0 }))} + className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#00F0FF]" + /> +
+
+ + +
+
+
@@ -39,8 +135,10 @@ export default function SettingsPage() {
Maintenance Mode
Disable all student access and show maintenance screen.
-
-
+
handleToggle('maintenance_mode', 'Maintenance Mode')} + className={`h-6 w-11 rounded-full relative cursor-pointer transition-colors ${settings.maintenance_mode ? 'bg-red-500 border border-red-500 shadow-[0_0_10px_rgba(239,68,68,0.8)]' : 'bg-secondary border border-border'}`}> +
@@ -48,8 +146,10 @@ export default function SettingsPage() {
Allow New Registrations
Open the `/signup` route for new users.
-
-
+
handleToggle('allow_new_registrations', 'Registration Setting')} + className={`h-6 w-11 rounded-full relative cursor-pointer transition-colors ${settings.allow_new_registrations ? 'bg-primary border border-primary shadow-[0_0_10px_var(--color-primary)]' : 'bg-secondary border border-border'}`}> +
@@ -60,7 +160,7 @@ export default function SettingsPage() { Danger Zone - + diff --git a/src/app/admin/students/[studentId]/page.tsx b/src/app/admin/students/[studentId]/page.tsx index a40a23c..478d1d4 100644 --- a/src/app/admin/students/[studentId]/page.tsx +++ b/src/app/admin/students/[studentId]/page.tsx @@ -2,201 +2,373 @@ import { useEffect, useState } from "react" import { useParams, useRouter } from "next/navigation" -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" -import { ArrowLeft, Loader2, User, Mail, Target, Award, Calendar, Activity, BookOpen } from "lucide-react" +import { + ArrowLeft, Loader2, Mail, Phone, Target, Award, Calendar, Activity, + BookOpen, Flame, TrendingUp, CheckCircle2, AlertCircle, RefreshCw, + Edit3, Save, X +} from "lucide-react" + +const TIER_STYLE: Record = { + "Al_Coach": "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/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" } + +function formatDate(iso?: string | null) { + if (!iso) return "—" + try { return new Date(iso).toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" }) } + catch { return iso } +} + +function formatAmount(paise: number) { + return `₹${(paise / 100).toLocaleString("en-IN")}` +} export default function StudentKundliPage() { const params = useParams() const router = useRouter() const studentId = params.studentId as string - + const [loading, setLoading] = useState(true) - const [userData, setUserData] = useState(null) - + const [saving, setSaving] = useState(false) + const [data, setData] = useState(null) + const [error, setError] = useState("") + const [editing, setEditing] = useState(false) + + // editable fields + const [editName, setEditName] = useState("") + const [editTier, setEditTier] = useState("") + const [editStreak, setEditStreak] = useState(0) + const [editExam, setEditExam] = useState("") + useEffect(() => { - // Simulated fetch of student's complete "Kundli" - setTimeout(() => { - setUserData({ - id: studentId, - name: "Rahul Sharma", - email: "rahul@example.com", - target: "NEET", - tier: "AI Pro", - joined: "Oct 12, 2025", - stats: { - total_tests: 42, - average_score: "78%", - streak: 45, - total_questions_solved: 3450, - ai_coach_interactions: 124 - }, - recent_tests: [ - { id: 1, name: "Full Syllabus NEET Mock #4", score: "620/720", date: "2 days ago", percentile: "98.5%" }, - { id: 2, name: "Physics: Kinematics DPP", score: "45/50", date: "4 days ago", percentile: "95.2%" }, - { id: 3, name: "Biology: Human Reproduction", score: "88/100", date: "1 week ago", percentile: "99.1%" } - ], - payments: [ - { id: "PAY_1", amount: "₹1599", date: "Oct 12, 2025", plan: "AI Pro (Yearly)", status: "Success" } - ], - weak_topics: ["Rotational Dynamics", "Organic Chemistry", "Genetics"] + fetch(`/api/admin/students/${studentId}`) + .then(r => { if (!r.ok) throw new Error("Not found"); return r.json() }) + .then(d => { + setData(d) + setEditName(d.name) + setEditTier(d.tier) + setEditStreak(d.streak_count) + setEditExam(d.target_exam) }) - setLoading(false) - }, 800) + .catch(e => setError(e.message)) + .finally(() => setLoading(false)) }, [studentId]) - if (loading) { - return
+ const handleSave = async () => { + setSaving(true) + try { + const res = await fetch(`/api/admin/students/${studentId}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: editName, + tier: editTier, + streak_count: editStreak, + target_exam: editExam, + }), + }) + if (!res.ok) throw new Error("Failed to save") + // Refresh data + const fresh = await fetch(`/api/admin/students/${studentId}`).then(r => r.json()) + setData(fresh) + setEditing(false) + } catch (e: any) { + alert(e.message) + } finally { + setSaving(false) + } } - if (!userData) return
User not found
+ if (loading) return ( +
+ +
+ ) + + if (error || !data) return ( +
+ +

Student not found or failed to load.

+ +
+ ) return (
- - -
- {/* User Identity Card */} - - -
-
- {userData.name.charAt(0)} -
-
- {userData.name} - - {userData.email} - -
-
-
- -
-
Target Exam
-
{userData.target}
-
-
-
Plan Tier
- {userData.tier} -
-
-
Joined
-
{userData.joined}
-
-
-
User ID
-
{userData.id}
-
-
-
- - {/* Stats Summary */} -
- - -
{userData.stats.total_tests}
-
Tests Attempted
-
-
- - -
{userData.stats.streak}
-
Day Streak
-
-
- - -
{userData.stats.average_score}
-
Avg. Accuracy
-
-
- - -
{userData.stats.total_questions_solved}
-
Questions Solved
-
-
-
+ {/* Header */} +
+ + {!editing ? ( + + ) : ( +
+ + +
+ )}
- {/* Recent Activity & Weak Topics */} + {/* Profile Header Card */} + + +
+ {/* Avatar */} +
+ {data.profile_image_url ? ( + {data.name} + ) : ( +
+ {data.name.charAt(0)} +
+ )} + {data.name === "Neha Verma" && ( + DEMO + )} +
+ + {/* Name + meta */} +
+ {editing ? ( + setEditName(e.target.value)} + className="text-2xl font-bold bg-secondary border border-border rounded-lg px-3 py-1.5 w-full max-w-sm focus:outline-none focus:ring-1 focus:ring-primary mb-3" + /> + ) : ( +

{data.name}

+ )} + +
+ {editing ? ( + + ) : ( + + {TIER_LABEL[data.tier] || data.tier} + + )} + + {editing ? ( + + ) : ( + {data.target_exam.replace("_", " ")} + )} +
+ +
+ {data.email && {data.email}} + {data.phone && {data.phone}} + {data.school && {data.school}} + Joined {formatDate(data.created_at)} + {data.subscription_end && ( + + Active till {formatDate(data.subscription_end)} + + )} +
+
+
+
+
+ + {/* Stats Grid */} +
+ {[ + { label: "Tests Attempted", value: data.stats.total_tests, color: "text-[#00F0FF]", icon: }, + { label: "Avg Accuracy", value: data.stats.avg_accuracy ? `${data.stats.avg_accuracy}%` : "—", color: "text-green-500", icon: }, + { label: "AI Sessions", value: data.stats.mastered_mistakes, color: "text-purple-400", icon: }, + { + label: "Current Streak", + value: editing ? ( + setEditStreak(Number(e.target.value))} + className="text-2xl font-black w-20 bg-secondary border border-border rounded px-2 py-0.5 focus:outline-none text-orange-500" + /> + ) : ( + + + {data.streak_count} + + ), + color: "text-orange-500", + icon: + }, + ].map((stat, i) => ( + + +
{stat.icon}
+
{stat.value}
+
{stat.label}
+
+
+ ))} +
+ + {/* Recent Attempts + Weak Topics */}
- Recent Tests + + Recent Activity + -
- {userData.recent_tests.map((test: any) => ( -
-
-
{test.name}
-
{test.date}
+ {data.recent_attempts.length === 0 ? ( +

No tests attempted yet.

+ ) : ( +
+ {data.recent_attempts.map((a: any) => ( +
+
+
{a.name}
+
{formatDate(a.submitted_at)}
+
+
+
{a.score}/{a.total_questions}
+
{a.accuracy.toFixed(1)}% acc
+
-
-
{test.score}
-
{test.percentile} %ile
-
-
- ))} -
+ ))} +
+ )} - AI Identified Weaknesses + + Weak Topics + -
    - {userData.weak_topics.map((topic: string, i: number) => ( -
  • -
    - {topic} -
  • - ))} -
+ {data.weak_topics.length === 0 ? ( +

No weak topics identified yet.

+ ) : ( +
    + {data.weak_topics.map((topic: string, i: number) => ( +
  • +
    + {topic} +
  • + ))} +
+ )}
- {/* Payment History */} - - - Payment History - - -
- - - - - - - - - - - - {userData.payments.map((p: any) => ( - - - - - - + {/* Subscriptions */} + {data.subscriptions.length > 0 && ( + + + + Subscription History + + + +
+
Transaction IDPlanDateAmountStatus
{p.id}{p.plan}{p.date}{p.amount}{p.status}
+ + + + + + + + - ))} - -
PlanBillingStartEndAmountStatus
-
-
-
+ + + {data.subscriptions.map((s: any) => ( + + {TIER_LABEL[s.tier] || s.tier} + {s.billing_cycle} + {formatDate(s.start_date)} + {formatDate(s.end_date)} + {formatAmount(s.amount)} + + + {s.status} + + + + ))} + + +
+ + + )} + {/* Transactions */} + {data.transactions.length > 0 && ( + + + Payment History + + +
+ + + + + + + + + + + + {data.transactions.map((t: any) => ( + + + + + + + + ))} + +
IDPlanDateAmountStatus
{t.id.slice(0, 12)}...{TIER_LABEL[t.tier] || t.tier}{formatDate(t.created_at)}₹{t.amount.toLocaleString("en-IN")} + + {t.status} + +
+
+
+
+ )}
) } diff --git a/src/app/admin/students/page.tsx b/src/app/admin/students/page.tsx index c9918ef..9642a5e 100644 --- a/src/app/admin/students/page.tsx +++ b/src/app/admin/students/page.tsx @@ -1,94 +1,144 @@ "use client" -import { Button } from "@/components/ui/button" +import { useEffect, useState } from "react" import { Badge } from "@/components/ui/badge" -import { Search, Download, MoreVertical } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Search, Download, Flame, RefreshCw } from "lucide-react" import { useRouter } from "next/navigation" +const TIER_STYLE: Record = { + "Al_Coach": "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/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", + "Pro": "Pro", + "Free": "Free", +} + export default function StudentsPage() { const router = useRouter() - const students = [ - { id: "STU-001", name: "Rahul Sharma", email: "rahul@example.com", tier: "AI Pro", target: "NEET", joined: "Oct 12, 2025", tests: 42, streak: 45, lastLogin: "Today, 08:30 AM" }, - { id: "STU-002", name: "Priya Patel", email: "priya@example.com", tier: "Basic", target: "JEE Main", joined: "Nov 05, 2025", tests: 14, streak: 2, lastLogin: "Yesterday, 14:15 PM" }, - { id: "STU-003", name: "Amit Kumar", email: "amit@example.com", tier: "AI Pro", target: "JEE Adv", joined: "Dec 01, 2025", tests: 89, streak: 0, lastLogin: "3 Days Ago" }, - { id: "STU-004", name: "Sneha Reddy", email: "sneha@example.com", tier: "AI Pro", target: "NEET", joined: "Dec 15, 2025", tests: 34, streak: 12, lastLogin: "Today, 11:45 AM" }, - { id: "STU-005", name: "Vikram Singh", email: "vikram@example.com", tier: "Basic", target: "NEET", joined: "Jan 10, 2026", tests: 5, streak: 0, lastLogin: "1 Week Ago" }, - ] + const [students, setStudents] = useState([]) + const [loading, setLoading] = useState(true) + const [search, setSearch] = useState("") + + useEffect(() => { + fetch("/api/admin/students") + .then(r => r.json()) + .then(data => setStudents(data)) + .catch(console.error) + .finally(() => setLoading(false)) + }, []) + + const filtered = students.filter(s => + s.name.toLowerCase().includes(search.toLowerCase()) || + s.email.toLowerCase().includes(search.toLowerCase()) || + s.phone.includes(search) + ) + + const formatDate = (iso: string) => { + try { return new Date(iso).toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" }) } + catch { return iso } + } return (

Student Directory

-

Manage users, view engagement, and handle subscriptions.

+

+ {loading ? "Loading..." : `${students.length} students enrolled`} +

- +
- setSearch(e.target.value)} + placeholder="Search by name, email, or phone..." className="w-full bg-background border border-border rounded-lg pl-10 pr-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary" />
- +
- - - - - - - - - - - - - - {students.map((s) => ( - router.push(`/admin/students/${s.id}`)} - > - - - - - - - + {loading ? ( +
+ Loading students... +
+ ) : ( +
StudentPlan TierTarget ExamTests TakenStreakLast LoginActions
-
{s.name}
-
{s.email}
-
- - {s.tier} - - {s.target}{s.tests} - 0 ? 'text-orange-500' : 'text-muted-foreground'}`}>{s.streak} - {s.lastLogin} - {s.streak === 0 && ( - - )} - -
+ + + + + + + + + - ))} - -
StudentPlanTargetTestsAccuracyStreakLast Active
+ + + {filtered.map((s) => ( + router.push(`/admin/students/${s.id}`)} + > + +
+
+ {s.name.charAt(0)} +
+
+
+ {s.name} + {s.name === "Neha Verma" && ( + DEMO ⭐ + )} +
+
{s.email || s.phone}
+
+
+ + + + {TIER_LABEL[s.tier] || s.tier} + + + {s.target_exam.replace("_", " ")} + {s.total_tests} + + = 80 ? "text-green-500" : s.avg_accuracy >= 60 ? "text-yellow-500" : "text-muted-foreground"}`}> + {s.avg_accuracy > 0 ? `${s.avg_accuracy}%` : "—"} + + + + 0 ? "text-orange-500" : "text-muted-foreground"}`}> + {s.streak_count > 0 && } + {s.streak_count} + + + {formatDate(s.last_active)} + + ))} + + + )}
- Showing top 5 active students out of 12,400 total. + Showing {filtered.length} of {students.length} students
diff --git a/src/app/cancellation-policy/page.tsx b/src/app/cancellation-policy/page.tsx new file mode 100644 index 0000000..f2b37a9 --- /dev/null +++ b/src/app/cancellation-policy/page.tsx @@ -0,0 +1,85 @@ +import Link from 'next/link'; + +export default function CancellationPolicyPage() { + return ( +
+
+

+ PragatiVeer Cancellation Policy +

+
+ +

Last Updated: 27 July 2026
+Version: 1.0
+Document Ref: PV-LEG-CP-001
+Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+ +

1. Overview

+

This Cancellation Policy governs cancellation of paid subscriptions on PragatiVeer, operated by Gyannetra Pvt Ltd (CIN: U72900PN2015PTC156021).

+

By purchasing a subscription, you agree to the terms of this policy.

+ +

2. Subscription Cancellation by You

+

2.1 How to Cancel

+

You may cancel your subscription at any time by:

+
    +
  • Going to Account Settings on the PragatiVeer website and disabling auto-renewal
  • +
  • Emailing contact@pragativeer.com with subject “Cancellation Request” along with your registered email or phone number
  • +
+ +

2.2 Effect of Cancellation

+

Upon cancellation:

+
    +
  • Your subscription will not auto-renew at the end of the current billing period
  • +
  • You will continue to have full access to your paid features until the end of the current billing period
  • +
  • No refund will be issued for the remaining days of the current billing period, except as provided in our Refund Policy
  • +
+ +

2.3 Timing

+

To avoid being charged for the next billing cycle, cancellation must be requested at least 24 hours before the renewal date.

+ +

3. Auto-Renewal

+

All subscriptions auto-renew at the end of each billing period unless cancelled at least 24 hours before renewal. You authorise us to charge the applicable subscription fee to your chosen payment method at each renewal.

+

You will receive a reminder before renewal. If you do not wish to renew, please cancel before the renewal date.

+ +

4. Cancellation by PragatiVeer

+

We reserve the right to cancel or suspend your subscription immediately if:

+
    +
  • You violate our Terms & Conditions
  • +
  • You engage in fraudulent, abusive, or illegal activity
  • +
  • We are required to do so by law
  • +
+

In such cases, fees paid are non-refundable.

+ +

5. Account Deletion

+

Cancelling your subscription does not delete your account. To delete your account and all associated data, please follow the process outlined in our Privacy Policy.

+ +

6. Contact

+

For any questions regarding this Cancellation Policy, contact us at contact@pragativeer.com or reach out to our Grievance Officer:

+
+ + + + + + + + + + + + +
DetailInformation
Grievance OfficerHimani Kachhawaha
Emailgrievance@pragativeer.com
Phone+91 9079337961
+
+ +
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+ +
+
+
+ ); +} diff --git a/src/app/cancellation/page.tsx b/src/app/cancellation/page.tsx new file mode 100644 index 0000000..b990499 --- /dev/null +++ b/src/app/cancellation/page.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { PublicLayout } from "@/components/Landing/PublicLayout"; + +export default function CancellationPolicyPage() { + return ( + +
+
+
+
+

PragatiVeer Cancellation Policy

+

Gyannetra Pvt Ltd

+
+

Last Updated: 27 July 2026

+

Version: 1.0

+

Document Ref: PV-LEG-CP-001

+

Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+
+
+ +
+
+

1. Overview

+

+ This Cancellation Policy governs cancellation of paid subscriptions on PragatiVeer, operated by Gyannetra Pvt Ltd (CIN: U72900PN2015PTC156021). +

+

+ By purchasing a subscription, you agree to the terms of this policy. +

+
+ +
+

2. Subscription Cancellation by You

+

2.1 How to Cancel

+

You may cancel your subscription at any time by:

+
    +
  • Going to Account Settings on the PragatiVeer website and disabling auto-renewal
  • +
  • Emailing contact@pragativeer.com with subject “Cancellation Request” along with your registered email or phone number
  • +
+ +

2.2 Effect of Cancellation

+

Upon cancellation:

+
    +
  • Your subscription will not auto-renew at the end of the current billing period
  • +
  • You will continue to have full access to your paid features until the end of the current billing period
  • +
  • No refund will be issued for the remaining days of the current billing period, except as provided in our Refund Policy
  • +
+ +

2.3 Timing

+

+ To avoid being charged for the next billing cycle, cancellation must be requested at least 24 hours before the renewal date. +

+
+ +
+

3. Auto-Renewal

+

+ All subscriptions auto-renew at the end of each billing period unless cancelled at least 24 hours before renewal. You authorise us to charge the applicable subscription fee to your chosen payment method at each renewal. +

+

+ You will receive a reminder before renewal. If you do not wish to renew, please cancel before the renewal date. +

+
+ +
+

4. Cancellation by PragatiVeer

+

We reserve the right to cancel or suspend your subscription immediately if:

+
    +
  • You violate our Terms & Conditions
  • +
  • You engage in fraudulent, abusive, or illegal activity
  • +
  • We are required to do so by law
  • +
+

+ In such cases, fees paid are non-refundable. +

+
+ +
+

5. Account Deletion

+

+ Cancelling your subscription does not delete your account. To delete your account and all associated data, please follow the process outlined in our Privacy Policy. +

+
+ +
+

6. Contact

+

+ For any questions regarding this Cancellation Policy, contact us at contact@pragativeer.com or reach out to our Grievance Officer: +

+
+ + + + + + + + + + + + + + + + + + + + + +
DetailInformation
Grievance OfficerHimani Kachhawaha
Emailgrievance@pragativeer.com
Phone+91 9079337961
+
+
+ +
+
+

Gyannetra Pvt Ltd

+

CIN: U72900PN2015PTC156021

+

OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+

pragativeer.com

+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/dashboard/ai-coach/cbt-dpp/[id]/page.tsx b/src/app/dashboard/ai-coach/cbt-dpp/[id]/page.tsx new file mode 100644 index 0000000..217af36 --- /dev/null +++ b/src/app/dashboard/ai-coach/cbt-dpp/[id]/page.tsx @@ -0,0 +1,289 @@ +"use client" + +import { useState, useEffect } from "react" +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { BrainCircuit, Clock, CheckCircle2, AlertTriangle, ArrowLeft, ArrowRight, ShieldAlert, Sparkles, Send } from "lucide-react" +import Link from "next/link" +import { useActiveUser } from "@/hooks/useActiveUser" +import { useRouter } from "next/navigation" + +export default function AIComCBTDPPPage() { + const { user, hasAccessTo, loading } = useActiveUser() + const router = useRouter() + + const [activeQuestion, setActiveQuestion] = useState(0) + const [selectedAnswers, setSelectedAnswers] = useState>({}) + const [timeLeft, setTimeLeft] = useState(1800) // 30 minutes + const [submitted, setSubmitted] = useState(false) + + // Mock AI CBT DPP questions + const sampleQuestions = [ + { + id: 1, + subject: "Physics", + topic: "Rotational Dynamics & Torque", + question: "A uniform disc of mass M and radius R rotates about an axis passing through its center and perpendicular to its plane. Its moment of inertia about a tangent parallel to its plane is:", + options: ["(1/2) M R²", "(5/4) M R²", "(3/2) M R²", "(7/4) M R²"], + correct: 1, + explanation: "Using parallel axis theorem: I_tangent = I_cm + M R^2 = (1/4 M R^2) + M R^2 = 5/4 M R^2." + }, + { + id: 2, + subject: "Chemistry", + topic: "Chemical Equilibrium & Kp/Kc", + question: "For the reaction N2(g) + 3H2(g) ⇌ 2NH3(g), the value of Kp / Kc at temperature T Kelvin is given by:", + options: ["(RT)⁻²", "(RT)⁻¹", "(RT)", "(RT)²"], + correct: 0, + explanation: "Δn_g = 2 - (1 + 3) = -2. Therefore, Kp = Kc (RT)^(-2), so Kp / Kc = (RT)^(-2)." + }, + { + id: 3, + subject: "Biology / Maths", + topic: "Calculus / Cell Biology", + question: "Which organelle is responsible for the synthesis of lipids and steroidal hormones in eukaryotic cells?", + options: ["Rough Endoplasmic Reticulum", "Smooth Endoplasmic Reticulum", "Golgi Apparatus", "Lysosome"], + correct: 1, + explanation: "Smooth Endoplasmic Reticulum (SER) is the primary site for synthesis of lipids and steroid hormones." + }, + { + id: 4, + subject: "Physics", + topic: "Electrostatics & Potential", + question: "Two point charges +q and -q are placed at distance 2a. The electric potential at the midpoint of the line joining them is:", + options: ["Zero", "q / (4πε₀a)", "2q / (4πε₀a)", "Infinity"], + correct: 0, + explanation: "V_total = V_+q + V_-q = (q / 4πε₀a) + (-q / 4πε₀a) = 0." + }, + { + id: 5, + subject: "Chemistry", + topic: "Organic Mechanism & Nucleophiles", + question: "Which of the following undergoes SN1 reaction at the fastest rate?", + options: ["Methyl chloride", "Ethyl chloride", "Isopropyl chloride", "Tert-butyl chloride"], + correct: 3, + explanation: "SN1 rate depends on carbocation stability. Tert-butyl carbocation (3°) is the most stable." + } + ] + + useEffect(() => { + if (submitted || timeLeft <= 0) return + const timer = setInterval(() => { + setTimeLeft(prev => Math.max(0, prev - 1)) + }, 1000) + return () => clearInterval(timer) + }, [submitted, timeLeft]) + + const formatTime = (secs: number) => { + const m = Math.floor(secs / 60) + const s = secs % 60 + return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` + } + + const handleOptionSelect = (qIdx: number, optIdx: number) => { + if (submitted) return + setSelectedAnswers(prev => ({ ...prev, [qIdx]: String.fromCharCode(65 + optIdx) })) + } + + const calculateScore = () => { + let score = 0 + sampleQuestions.forEach((q, idx) => { + const selected = selectedAnswers[idx] + if (selected) { + const selectedOptIdx = selected.charCodeAt(0) - 65 + if (selectedOptIdx === q.correct) score += 4 + else score -= 1 + } + }) + return score + } + + if (loading) { + return
Loading AI Coach CBT Engine...
+ } + + return ( +
+ {/* Top Header */} +
+
+ + + +
+
+ + AI CBT DPP Simulator + + • {user?.target_exam?.replace("_", " ") || "JEE/NEET"} +
+

Interactive Target DPP #14

+
+
+ +
+
+ + {formatTime(timeLeft)} +
+ {!submitted ? ( + + ) : ( + + )} +
+
+ + {submitted && ( + +
+
+ +
+

CBT DPP Completed!

+

Your performance analysis has been evaluated by the AI Evaluator.

+
+
+
+ {calculateScore()} / 20 +

Total Marks

+
+
+
+ )} + + {/* Main CBT Grid */} +
+ {/* Question Area */} +
+ +
+ + Question {activeQuestion + 1} of {sampleQuestions.length} + + + {sampleQuestions[activeQuestion].topic} + +
+ +
+

+ {sampleQuestions[activeQuestion].question} +

+ +
+ {sampleQuestions[activeQuestion].options.map((opt, optIdx) => { + const letter = String.fromCharCode(65 + optIdx) + const isSelected = selectedAnswers[activeQuestion] === letter + const isCorrect = sampleQuestions[activeQuestion].correct === optIdx + + let btnStyle = "border-border bg-secondary/20 hover:border-primary/50 text-foreground" + if (isSelected) { + btnStyle = "border-[#00F0FF] bg-[#00F0FF]/15 text-[#00F0FF] font-bold shadow-[0_0_12px_rgba(0,240,255,0.15)]" + } + if (submitted) { + if (isCorrect) btnStyle = "border-emerald-500 bg-emerald-500/20 text-emerald-400 font-bold" + else if (isSelected && !isCorrect) btnStyle = "border-red-500 bg-red-500/20 text-red-400 font-bold" + } + + return ( +
handleOptionSelect(activeQuestion, optIdx)} + className={`flex items-center gap-4 p-4 rounded-2xl border cursor-pointer transition-all ${btnStyle}`} + > + + {letter} + + {opt} + {submitted && isCorrect && } +
+ ) + })} +
+ + {submitted && ( +
+ AI Solution & Explanation: +

{sampleQuestions[activeQuestion].explanation}

+
+ )} +
+ + {/* Navigation buttons */} +
+ + +
+
+
+ + {/* Question Palette Sidebar */} +
+ +

Question Palette

+
+ {sampleQuestions.map((q, idx) => { + const isAns = !!selectedAnswers[idx] + const isCurr = activeQuestion === idx + return ( + + ) + })} +
+ +
+
+ Current Question +
+
+ Answered +
+
+ Unattempted +
+
+
+
+
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/cbt-dpp/page.tsx b/src/app/dashboard/ai-coach/cbt-dpp/page.tsx new file mode 100644 index 0000000..674753c --- /dev/null +++ b/src/app/dashboard/ai-coach/cbt-dpp/page.tsx @@ -0,0 +1,302 @@ +"use client" + +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 { useEffect, useState } from "react" +import Link from "next/link" +import { useActiveUser } from "@/hooks/useActiveUser" + +export default function PrepPilotAIPage() { + const { user, userId } = useActiveUser() + const [dpps, setDpps] = useState([]) + const [attempts, setAttempts] = useState([]) + const [loading, setLoading] = useState(true) + const [dateFilter, setDateFilter] = useState("") + const [selectedPdfDpp, setSelectedPdfDpp] = useState(null) + + useEffect(() => { + async function fetchDpps() { + if (!user) return; // Wait for user to be available + + try { + const pattern = user.target_exam || "JEE_Main"; + // Also fetch from the correct Next.js proxied route instead of localhost:8000 + const res = await fetch(`/api/tests?type=DPS&exam_pattern=${pattern}`); + if (res.ok) { + const data = await res.json() + setDpps(data) + } + + if (userId) { + const attemptsRes = await fetch(`/api/users/${userId}/attempts`); + if (attemptsRes.ok) { + const attemptsData = await attemptsRes.json(); + setAttempts(attemptsData); + } + } + } catch (err) { + console.error("Failed to fetch DPPs", err) + } finally { + setLoading(false) + } + } + fetchDpps() + }, [user]) + + const getDifficultyColor = (diff: string) => { + switch(diff) { + case 'Easy': return 'text-emerald-500 bg-emerald-500/10 border-emerald-500/20'; + case 'Medium': return 'text-amber-500 bg-amber-500/10 border-amber-500/20'; + case 'Hard': return 'text-destructive bg-destructive/10 border-destructive/20'; + default: return 'text-primary bg-primary/10 border-primary/20'; + } + } + + const attemptsByTest = attempts.reduce((acc, curr) => { + // Isolate analytics to first attempt only - ignore retakes + if (curr.is_retake) return acc; + + if (!acc[curr.source_id] || acc[curr.source_id].score < curr.score) { + acc[curr.source_id] = curr; + } + return acc; + }, {} as Record); + + const filteredDpps = dpps.filter(dpp => { + if (dateFilter) { + if (!dpp.scheduled_date) return false; + const scheduledDate = new Date(dpp.scheduled_date).toISOString().split('T')[0]; + if (scheduledDate !== dateFilter) return false; + } + return true; + }); + + const formatDate = (dateStr: string) => { + return new Date(dateStr).toLocaleDateString("en-US", { year: 'numeric', month: 'short', day: 'numeric' }); + } + + const getPdfUrl = (title: string) => { + const t = title.toLowerCase(); + if (t.includes('phys')) return '/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + if (t.includes('chem')) return '/dpps/dps-che-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + if (t.includes('math')) return '/dpps/dps-mth-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + if (t.includes('bio')) return '/dpps/dps-bio-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + // Default fallback + return '/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + }; + + return ( +
+
+
+

PrepPilot AI & CBT Simulator

+

+ Select an interactive Target DPP to launch the AI-powered CBT examination simulator. +

+
+
+ +
+
+ setDateFilter(e.target.value)} + className="px-3 py-1.5 bg-background border border-border rounded-lg text-xs font-bold focus:ring-2 focus:ring-[#00F0FF] outline-none text-muted-foreground mr-2" + /> + Difficulty: + {['Mixed', 'Easy', 'Medium', 'Hard'].map((diff) => ( + + ))} +
+ Pro +
+
+ +
+ +
+ {loading ? ( +
Loading AI-generated DPPs...
+ ) : filteredDpps.length === 0 ? ( +
No DPPs available for the selected criteria.
+ ) : filteredDpps.map((dpp, i) => { + // Calculate total questions dynamically from sections + let count = 0; + if (dpp.sections) { + Object.values(dpp.sections).forEach((s: any) => { count += (s.questions?.length || 0) }); + } + + const attempt = attemptsByTest[dpp.id]; + const isAttempted = !!attempt; + + // Date logic + const now = new Date(); + const scheduledTime = dpp.scheduled_date ? new Date(dpp.scheduled_date) : now; + const endTime = dpp.end_date ? new Date(dpp.end_date) : null; + const isUpcoming = scheduledTime > now; + const isExpired = endTime ? now > endTime : false; + + return ( + + +
+
+ {isAttempted ? :
} +
+
+
+ + {user?.target_exam?.replace("_", " ") || "JEE/NEET"} + + • + {dpp.difficulty} Level + {isUpcoming ? ( + <> + • + Upcoming + + ) : isExpired ? ( + <> + • + Expired + + ) : null} +
+

+ {dpp.title} + + {dpp.difficulty} + +

+
+ {count} Questions • {dpp.total_time} mins + {dpp.scheduled_date && ( + + {isUpcoming ? `Available on ${formatDate(dpp.scheduled_date)}` : isExpired ? `Expired on ${formatDate(dpp.end_date!)}` : 'Available Now'} + + )} +
+
+
+
+ {isUpcoming ? ( + + ) : isExpired && !isAttempted ? ( + + ) : isAttempted ? ( + <> + + + + {!isExpired && ( + + + + )} + + ) : ( + user?.tier === "Free" ? ( + + ) : ( + + + + ) + )} +
+
+
+ )})} +
+ + {/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */} + {selectedPdfDpp && ( +
+
+ {/* Modal Header */} +
+
+
+ +
+
+

+ {selectedPdfDpp.title} + + Protected In-App PDF View + +

+

+ {selectedPdfDpp.difficulty} Level • {user?.target_exam?.replace("_", " ") || "JEE/NEET"} Practice Sheet +

+
+
+ +
+ + {/* Security Notice Banner */} +
+ + Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted. + + + + +
+ + {/* Protected PDF Document Sheet Content (select-none) */} +
e.preventDefault()} + className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]" + > + +
+ + {/* Modal Footer */} +
+ + 📄 PragatiVeer Protected PDF Reader v2.0 + +
+ + + + +
+
+
+
+ )} +
+ ) +} diff --git a/src/app/dashboard/ai-coach/college-predictor/page.tsx b/src/app/dashboard/ai-coach/college-predictor/page.tsx new file mode 100644 index 0000000..c19363f --- /dev/null +++ b/src/app/dashboard/ai-coach/college-predictor/page.tsx @@ -0,0 +1,57 @@ +"use client" + +import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard" +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { GraduationCap, ArrowLeft, Building2, MapPin, Award, CheckCircle2 } from "lucide-react" +import Link from "next/link" + +export default function CollegePredictorPage() { + return ( +
+ {/* Navigation Header */} +
+

+ + AI College & Cutoff Predictor +

+

+ Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs. +

+
+ +
+
+ +

+ Target College Analytics +

+ +
+
+ +
+ +

+ Cutoff Modeling Parameters +

+
    +
  • + + Includes 100,000+ candidate rank trends across 2021-2025 rounds. +
  • +
  • + + Supports All India Quota (AIQ) & State Quota adjustments. +
  • +
  • + + Simulates category reservation cutoffs (General, OBC, SC, ST, EWS). +
  • +
+
+
+
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/heatmap/page.tsx b/src/app/dashboard/ai-coach/heatmap/page.tsx new file mode 100644 index 0000000..78c76e1 --- /dev/null +++ b/src/app/dashboard/ai-coach/heatmap/page.tsx @@ -0,0 +1,25 @@ +"use client" + +import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap" +import { ArrowLeft, Flame } from "lucide-react" +import Link from "next/link" + +export default function HeatmapPage() { + return ( +
+
+

+ + Mistake Heatmap Vault +

+

+ Visual representation of your error density across subjects and topics. +

+
+ +
+ +
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/mistakes/page.tsx b/src/app/dashboard/ai-coach/mistakes/page.tsx new file mode 100644 index 0000000..7038441 --- /dev/null +++ b/src/app/dashboard/ai-coach/mistakes/page.tsx @@ -0,0 +1,133 @@ +"use client" + +import { useEffect, useState } from "react" +import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap" +import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard" +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { BrainCircuit, RefreshCw, CheckCircle2, ArrowLeft, Flame, Sparkles, AlertTriangle } from "lucide-react" +import { useActiveUser } from "@/hooks/useActiveUser" +import Link from "next/link" + +export default function MistakeTrackingPage() { + const [mistakes, setMistakes] = useState([]) + const [currentIndex, setCurrentIndex] = useState(0) + const [loading, setLoading] = useState(true) + const { userId, loading: userLoading } = useActiveUser() + + const fetchMistakes = async () => { + if (!userId) return + setLoading(true) + try { + const res = await fetch(`/api/mistakes/today?user_id=${userId}`) + if (!res.ok) throw new Error("Failed to fetch") + const data = await res.json() + setMistakes(data) + } catch (error) { + console.error(error) + setMistakes([]) + } finally { + setLoading(false) + } + } + + useEffect(() => { + if (!userLoading && userId) { + fetchMistakes() + } + }, [userId, userLoading]) + + const submitReview = async (mistakeId: string, selectedAnswer: string) => { + const res = await fetch("/api/mistakes/review", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mistake_id: mistakeId, selected_answer: selectedAnswer }) + }) + if (!res.ok) throw new Error("Failed to submit review") + return await res.json() + } + + const handleNext = () => { + if (currentIndex < mistakes.length - 1) { + setCurrentIndex(prev => prev + 1) + } else { + setCurrentIndex(mistakes.length) + } + } + + return ( +
+ {/* Navigation Header */} +
+
+

+ + AI Mistake Vault & SM-2 Engine +

+

+ Review logged errors, practice spaced-repetition cards, and observe subject heatmap trends. +

+
+
+
+ SM-2 Adaptive Algorithm Active +
+
+
+ + {/* Main Review Workspace */} +
+
+
+

+ Today's Active Review Queue +

+ + {currentIndex < mistakes.length ? `Question ${currentIndex + 1} of ${mistakes.length}` : "Completed"} + +
+ + {loading ? ( + + + Loading your active error queue... + + ) : mistakes.length === 0 ? ( + + +

Zero Mistakes Pending!

+

+ All scheduled errors have been reviewed. Solve more mock tests to automatically surface weak questions here. +

+ +
+ ) : currentIndex < mistakes.length ? ( + + ) : ( + + +

Review Session Mastered 🎉

+

+ Fantastic focus! Your memory decay intervals have been updated in the database. +

+ +
+ )} +
+ + {/* Heatmap & Info Column */} +
+ +
+
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/page.tsx b/src/app/dashboard/ai-coach/page.tsx index dfb768c..e6e2c42 100644 --- a/src/app/dashboard/ai-coach/page.tsx +++ b/src/app/dashboard/ai-coach/page.tsx @@ -5,16 +5,33 @@ import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap" import { StudyPlanCard } from "@/components/AI/StudyPlanCard" import { ReadinessGauge } from "@/components/AI/ReadinessGauge" import { CollegePredictorCard } from "@/components/AI/CollegePredictorCard" +import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard" +import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard" import { TutorChat } from "@/components/AI/TutorChat" import { ReferralCard } from "@/components/AI/ReferralCard" import { ProgressShare } from "@/components/AI/ProgressShare" import { MistakeReviewCard, MistakePayload } from "@/components/AI/MistakeReviewCard" +import { DailyChallengeBanner } from "@/components/AI/DailyChallengeBanner" +import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport" import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" -import { BrainCircuit, BookOpen, Calculator, Target, RefreshCw, CheckCircle2, Lock } from "lucide-react" +import { BrainCircuit, RefreshCw, CheckCircle2, Flame, Trophy, Target, Zap, BookOpen, Bot, FileText } from "lucide-react" import { useActiveUser } from "@/hooks/useActiveUser" import Link from "next/link" + +// Days until NEET 2026 (May 3, 2026) +function getDaysToExam(exam: string) { + const targets: Record = { + "NEET": new Date("2026-05-03"), + "JEE_Main": new Date("2026-01-22"), + "JEE_Advanced": new Date("2026-05-24"), + } + const t = targets[exam] || targets["NEET"] + const diff = Math.ceil((t.getTime() - Date.now()) / 86400000) + return diff > 0 ? diff : 0 +} + export default function AICoachDashboard() { const [mistakes, setMistakes] = useState([]) const [currentIndex, setCurrentIndex] = useState(0) @@ -32,7 +49,6 @@ export default function AICoachDashboard() { setMistakes(data) } catch (error) { console.error(error) - // If backend fails, fallback to empty array setMistakes([]) } finally { setLoading(false) @@ -51,7 +67,6 @@ export default function AICoachDashboard() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mistake_id: mistakeId, selected_answer: selectedAnswer }) }) - if (!res.ok) throw new Error("Failed to submit review") return await res.json() } @@ -60,13 +75,19 @@ export default function AICoachDashboard() { if (currentIndex < mistakes.length - 1) { setCurrentIndex(prev => prev + 1) } else { - // Session finished - setCurrentIndex(mistakes.length) // Push out of bounds to trigger complete state + setCurrentIndex(mistakes.length) } } if (userLoading) { - return
Loading AI Coach...
+ return ( +
+
+ + Loading AI Coach... +
+
+ ) } if (!hasAccessTo("ai-mistake-tracking")) { @@ -78,7 +99,7 @@ export default function AICoachDashboard() {

AI Coach Workspace

- Mistake tracking, Spaced Repetition (SM-2), AI Tutor Chats, and Readiness scores are premium features reserved for **AI Coach** subscribers. + Mistake tracking, Spaced Repetition (SM-2), AI Tutor Chats, and Readiness scores are premium features reserved for AI Coach subscribers.

@@ -92,81 +113,113 @@ export default function AICoachDashboard() { ) } + const daysLeft = getDaysToExam(user?.target_exam || "NEET") + const physicsErrors = mistakes.filter(m => m.question.subject === "Physics").length + const chemErrors = mistakes.filter(m => m.question.subject === "Chemistry").length + const mathErrors = mistakes.filter(m => m.question.subject === "Maths" || m.question.subject === "Mathematics").length + return ( -
-
-
-

- AI Coach -

-

Your personalized SM-2 spaced repetition and analytics engine.

-
-
+
- {/* Summary Metrics */} -
- - -
-
-

Physics Errors

-

{mistakes.filter(m => m.question.subject === 'Physics').length}

-
-
-
- - -
-
-

Chemistry Errors

-

{mistakes.filter(m => m.question.subject === 'Chemistry').length}

-
-
-
- - -
-
-

Maths Errors

-

{mistakes.filter(m => m.question.subject === 'Maths').length}

-
-
-
+ {/* ── METRICS BAR ── */} +
+
+ + {user?.streak_count || 0}-Day Streak +
+
+ + {daysLeft} days to {user?.target_exam?.replace("_", " ") || "Exam"} +
+
+ + AI Coach +
+
+ + {/* ── DAILY CHALLENGE BANNER ── */} + + + {/* ── QUICK STATS ── */} +
+ {[ + { label: "Questions Solved", value: "1,247", color: "text-[#00F0FF]", bg: "bg-[#00F0FF]/5", border: "border-[#00F0FF]/20", icon: }, + { label: "Overall Accuracy", value: "74%", color: "text-amber-500", bg: "bg-amber-500/5", border: "border-amber-500/20", icon: }, + { label: "Current Streak", value: `${user?.streak_count || 0} d`, color: "text-yellow-400", bg: "bg-yellow-400/5", border: "border-yellow-400/20", icon: }, + { label: "Mistakes Mastered", value: 38, color: "text-green-400", bg: "bg-green-400/5", border: "border-green-400/20", icon: }, + { label: "AI Sessions", value: 124, color: "text-purple-400", bg: "bg-purple-400/5", border: "border-purple-400/20", icon: }, + ].map((stat) => ( + + +
{stat.icon}
+
{stat.value}
+
{stat.label}
+
+
+ ))}
- {/* Predictive Intelligence */} -
- - + {/* ── PREDICTIVE INTELLIGENCE ROW ── */} +
+
+
+ + Predictive Intelligence & Strategy +
+
+ + Full Analytics → + +
+
+
+ + + + +
- {/* Mistake Review Workspace */} -
-
-

Daily Mistake Review Queue

- - {currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Finished"} - + {/* ── MISTAKE REVIEW WORKSPACE ── */} +
+
+
+
+ +
+
+

Daily Mistake Review Queue

+

SM-2 Spaced Repetition Flashcard Engine

+
+
+
+ + Open Full Mistake Vault Page → + + + {currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Finished"} + +
- + {loading ? ( Loading queue... ) : mistakes.length === 0 ? ( - +

You're all caught up!

No mistakes scheduled for review today. Go take a test!

) : currentIndex < mistakes.length ? ( - ) : ( - +

Session Complete

Great job clearing your error queue! The SM-2 engine has scheduled your next reviews.

@@ -175,23 +228,57 @@ export default function AICoachDashboard() { )}
- {/* Heatmap & Timeline */} -
-
+ {/* ── HEATMAP / STUDY PLAN / WEEKLY REPORT ── */} +
+
+
+
+ Study Plan +
+ + Full Schedule → + +
-
-
+
+
+
+
+ Mistake Heatmap +
+ + Heatmap Vault → + +
-
-
+
+ - {/* Tutor Chat Workspace */}
+
+
+ Weekly Progress Overview +
+ + View Details → + +
+ +
+ + {/* ── TUTOR CHAT ── */} +
+
+

24/7 AI Tutor Chat Assistant

+ + Full Desk Mode → + +
- {/* Growth & Sharing Hub */} -
+ {/* ── GROWTH HUB ── */} +
@@ -199,3 +286,4 @@ export default function AICoachDashboard() {
) } + diff --git a/src/app/dashboard/ai-coach/readiness/page.tsx b/src/app/dashboard/ai-coach/readiness/page.tsx new file mode 100644 index 0000000..55d3429 --- /dev/null +++ b/src/app/dashboard/ai-coach/readiness/page.tsx @@ -0,0 +1,79 @@ +"use client" + +import { ReadinessGauge } from "@/components/AI/ReadinessGauge" +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Gauge, ArrowLeft, Target, Award, ShieldAlert, CheckCircle2 } from "lucide-react" +import Link from "next/link" + +export default function ReadinessPage() { + return ( +
+ {/* Navigation Header */} +
+

+ + Exam Readiness Analytics +

+

+ Real-time algorithmic prediction of your readiness percentage for JEE & NEET based on speed, accuracy, and test history. +

+
+ +
+
+ +

+ Overall Readiness Index +

+ +
+ + {/* Subject Confidence Breakdown */} + +

Subject Confidence Matrix

+
+ {[ + { subject: "Biology / Maths", score: 82, color: "bg-[#00F0FF]", status: "High Readiness" }, + { subject: "Chemistry", score: 74, color: "bg-amber-400", status: "Moderate Readiness" }, + { subject: "Physics", score: 61, color: "bg-red-400", status: "Needs Intensive Drill" }, + ].map((sub) => ( +
+
+ {sub.subject} + {sub.score}% • {sub.status} +
+
+
+
+
+ ))} +
+ +
+ +
+ +

+ Readiness Boosters +

+
    +
  • + + Complete 2 Full-Length Mock Tests this week. +
  • +
  • + + Clear 15 pending errors in Physics error queue. +
  • +
  • + + Maintain a 7+ day study streak. +
  • +
+
+
+
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/strategy/page.tsx b/src/app/dashboard/ai-coach/strategy/page.tsx new file mode 100644 index 0000000..53a53bd --- /dev/null +++ b/src/app/dashboard/ai-coach/strategy/page.tsx @@ -0,0 +1,41 @@ +"use client" + +import { ExamStrategyCard } from "@/components/AI/ExamStrategyCard" +import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard" +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Zap, ArrowLeft, Clock, Target, Lightbulb, CheckCircle2 } from "lucide-react" +import Link from "next/link" + +export default function StrategyPage() { + return ( +
+ {/* Navigation Header */} +
+

+ + Exam Strategy & Time Optimization +

+

+ Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield. +

+
+ +
+ +

+ Exam Day Time Strategy +

+ +
+ + +

+ Study Time Optimization +

+ +
+
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/study-plan/page.tsx b/src/app/dashboard/ai-coach/study-plan/page.tsx new file mode 100644 index 0000000..bdbd732 --- /dev/null +++ b/src/app/dashboard/ai-coach/study-plan/page.tsx @@ -0,0 +1,66 @@ +"use client" + +import { StudyPlanCard } from "@/components/AI/StudyPlanCard" +import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport" +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Calendar, ArrowLeft, BookOpen, Clock, Target, CheckCircle2 } from "lucide-react" +import Link from "next/link" + +export default function StudyPlanPage() { + return ( +
+ {/* Navigation Header */} +
+

+ + Personalized Daily & Weekly Study Plan +

+

+ AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage. +

+
+ +
+
+ +

+ Today's Action Plan +

+ +
+ + +

+ Weekly Syllabus Target +

+ +
+
+ + {/* Sidebar Info */} +
+ +

+ AI Scheduler Rules +

+
    +
  • + + High-Yield Prioritization: Focuses extra time on topics with heavy exam weightage. +
  • +
  • + + Error Interleaving: Integrates recent incorrect questions directly into study blocks. +
  • +
  • + + Burnout Prevention: Recommends 10-minute micro-breaks between intensive problem sets. +
  • +
+
+
+
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/time-optimization/page.tsx b/src/app/dashboard/ai-coach/time-optimization/page.tsx new file mode 100644 index 0000000..4104385 --- /dev/null +++ b/src/app/dashboard/ai-coach/time-optimization/page.tsx @@ -0,0 +1,25 @@ +"use client" + +import { StudyTimeOptimizationCard } from "@/components/AI/StudyTimeOptimizationCard" +import { ArrowLeft, Clock } from "lucide-react" +import Link from "next/link" + +export default function TimeOptimizationPage() { + return ( +
+
+

+ + Study Time Optimization +

+

+ Detailed metrics on your productive hours and stamina blocks. +

+
+ +
+ +
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/tutor/page.tsx b/src/app/dashboard/ai-coach/tutor/page.tsx new file mode 100644 index 0000000..ca695a3 --- /dev/null +++ b/src/app/dashboard/ai-coach/tutor/page.tsx @@ -0,0 +1,13 @@ +"use client" + +import { TutorChat } from "@/components/AI/TutorChat" + +export default function TutorChatPage() { + return ( +
+
+ +
+
+ ) +} diff --git a/src/app/dashboard/ai-coach/weekly-progress/page.tsx b/src/app/dashboard/ai-coach/weekly-progress/page.tsx new file mode 100644 index 0000000..3dab57b --- /dev/null +++ b/src/app/dashboard/ai-coach/weekly-progress/page.tsx @@ -0,0 +1,25 @@ +"use client" + +import { WeeklyProgressReport } from "@/components/AI/WeeklyProgressReport" +import { ArrowLeft, Target } from "lucide-react" +import Link from "next/link" + +export default function WeeklyProgressPage() { + return ( +
+
+

+ + Weekly Progress Overview +

+

+ Track your syllabus completion goals and weekly milestones. +

+
+ +
+ +
+
+ ) +} diff --git a/src/app/dashboard/dpp/page.tsx b/src/app/dashboard/dpp/page.tsx index cb81c7a..00c32f4 100644 --- a/src/app/dashboard/dpp/page.tsx +++ b/src/app/dashboard/dpp/page.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" -import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock } from "lucide-react" +import { BrainCircuit, Play, CheckCircle2, Download, Filter, Lock, FileText, Eye, ShieldAlert, X, Sparkles } from "lucide-react" import { useEffect, useState } from "react" import Link from "next/link" @@ -14,6 +14,7 @@ export default function DPPPage() { const [attempts, setAttempts] = useState([]) const [loading, setLoading] = useState(true) const [dateFilter, setDateFilter] = useState("") + const [selectedPdfDpp, setSelectedPdfDpp] = useState(null) useEffect(() => { async function fetchDpps() { @@ -76,6 +77,16 @@ export default function DPPPage() { return new Date(dateStr).toLocaleDateString("en-US", { year: 'numeric', month: 'short', day: 'numeric' }); } + const getPdfUrl = (title: string) => { + const t = title.toLowerCase(); + if (t.includes('phys')) return '/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + if (t.includes('chem')) return '/dpps/dps-che-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + if (t.includes('math')) return '/dpps/dps-mth-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + if (t.includes('bio')) return '/dpps/dps-bio-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + // Default fallback + return '/dpps/dps-phy-ch4-20260707.pdf#toolbar=0&navpanes=0&scrollbar=0'; + }; + return (
@@ -181,27 +192,111 @@ export default function DPPPage() { ) : isAttempted ? ( <> - - + + {!isExpired && ( - - + + )} ) : ( - - - + ) : ( + + + + ) )}
)})}
+ + {/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */} + {selectedPdfDpp && ( +
+
+ {/* Modal Header */} +
+
+
+ +
+
+

+ {selectedPdfDpp.title} + + Protected In-App PDF View + +

+

+ {selectedPdfDpp.difficulty} Level • {user?.target_exam?.replace("_", " ") || "JEE/NEET"} Practice Sheet +

+
+
+ +
+ + {/* Security Notice Banner */} +
+ + Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted. + + + + +
+ + {/* Protected PDF Document Sheet Content (select-none) */} +
e.preventDefault()} + className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]" + > + +
+ + {/* Modal Footer */} +
+ + 📄 PragatiVeer Protected PDF Reader v2.0 + +
+ + + + +
+
+
+
+ )}
) } diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 9a2e18a..170a1f2 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,7 +1,7 @@ "use client" import { ReactNode, useState, useEffect, useRef } from "react" -import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, LogOut, User, ChevronUp, Lock } from "lucide-react" +import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, LogOut, User, ChevronUp, Lock, ShieldAlert, Flame, Target } from "lucide-react" import Link from "next/link" import { usePathname } from "next/navigation" import { LanguageToggle } from "@/components/LanguageToggle" @@ -13,13 +13,30 @@ import { AITutorChat } from "@/components/AI/AITutorChat" import { useActiveUser, DEMO_USERS } from "@/hooks/useActiveUser" import { StreakOnboardingModal } from "@/components/Dashboard/StreakOnboardingModal" import { Badge } from "@/components/ui/badge" +import MaintenanceScreen from "@/components/MaintenanceScreen" export default function DashboardLayout({ children }: { children: ReactNode }) { const pathname = usePathname() const [dropdownOpen, setDropdownOpen] = useState(false) const dropdownRef = useRef(null) - const { userId, user, switchUser, hasAccessTo, loading } = useActiveUser(); + const { userId, user, globalSettings, switchUser, hasAccessTo, loading } = useActiveUser(); + const [maintenance, setMaintenance] = useState(false); + const [checkingMaintenance, setCheckingMaintenance] = useState(true); + + useEffect(() => { + if (globalSettings !== undefined && globalSettings !== null) { + if (globalSettings.maintenance_mode) { + setMaintenance(true); + } else { + setMaintenance(false); + } + setCheckingMaintenance(false); + } else if (globalSettings === null && !loading) { + // If we finished loading but settings failed to load + setCheckingMaintenance(false); + } + }, [globalSettings, loading]); // Close dropdown when clicking outside useEffect(() => { @@ -35,15 +52,22 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { const userInitials = user ? 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 Main" : user?.target_exam === "JEE_Advanced" ? "JEE Advanced" : user?.target_exam === "NEET" ? "NEET" : "NEET/JEE" - let daysLeft = null; - if (user && user.created_at) { - const createdDate = new Date(user.created_at); - const msPerDay = 1000 * 60 * 60 * 24; - const daysElapsed = Math.floor((new Date().getTime() - createdDate.getTime()) / msPerDay); - daysLeft = Math.max(0, 15 - daysElapsed); - } else if (user && user.tier === 'Free') { - // Mock value for demo Free tier if no date - daysLeft = 14; + let daysLeft: number | null = null; + const trialDays = globalSettings?.trial_duration_days !== undefined ? globalSettings.trial_duration_days : 15; + const trialEnabled = globalSettings?.enable_free_trial !== undefined ? globalSettings.enable_free_trial : true; + + // For the demo, treat both Free tier and Neha as Trial users so the banner appears + if (user && (user.tier === 'Free' || user.id === DEMO_USERS.NEHA.id)) { + if (!trialEnabled) { + daysLeft = 0; + } else { + // For demo purposes, we ignore created_at and just show the exact days set in the admin panel + daysLeft = trialDays; + } + } + + if (maintenance) { + return ; } return ( @@ -61,7 +85,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
} label="Overview" active={pathname === "/dashboard"} /> } label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} /> - } label="Syllabus Tracker" active={pathname.startsWith("/dashboard/progress")} /> } label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
@@ -69,6 +92,13 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { Pro Tier

+ } + label="Syllabus Tracker" + active={pathname.startsWith("/dashboard/progress")} + isLocked={!hasAccessTo("chapter-wise-progress-tracking")} + /> } @@ -95,7 +125,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { icon={} label="Doubt Forum" active={pathname.startsWith("/dashboard/community")} - isLocked={!hasAccessTo("ask-doubts-from-tutors")} + isLocked={!hasAccessTo("doubt-resolution-community")} />
@@ -104,19 +134,75 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {

} - label="AI Coach" - active={pathname.startsWith("/dashboard/ai-coach")} + label="AI Tutor" + active={pathname.startsWith("/dashboard/ai-coach/tutor") || pathname === "/dashboard/ai-coach"} isLocked={!hasAccessTo("ai-tutor-chat")} /> } + label="PrepPilot AI" + active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")} + isLocked={!hasAccessTo("ai-tutor-chat")} + /> + } + label="Exam Readiness" + active={pathname.startsWith("/dashboard/ai-coach/readiness")} + isLocked={!hasAccessTo("detailed-performance-analytics")} + /> + } - label="Rank Predictor" - active={pathname.startsWith("/dashboard/rank-predictor")} + label="College Predictor" + active={pathname.startsWith("/dashboard/ai-coach/college-predictor")} isLocked={!hasAccessTo("college-predictor")} /> + } + label="Exam Strategy" + active={pathname.startsWith("/dashboard/ai-coach/strategy")} + isLocked={!hasAccessTo("exam-day-strategy")} + /> + } + label="AI Study Plan" + active={pathname.startsWith("/dashboard/ai-coach/study-plan")} + isLocked={!hasAccessTo("personalized-study-plan")} + /> + } + label="Study Time Optimization" + active={pathname.startsWith("/dashboard/ai-coach/time-optimization")} + isLocked={!hasAccessTo("personalized-study-plan")} + /> + } + label="Mistake Heatmap" + active={pathname.startsWith("/dashboard/ai-coach/heatmap")} + isLocked={!hasAccessTo("ai-mistake-tracking")} + /> + } + label="Weekly Progress" + active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")} + isLocked={!hasAccessTo("detailed-performance-analytics")} + /> + } + label="Mistake Analyzer" + active={pathname.startsWith("/dashboard/ai-coach/mistakes")} + isLocked={!hasAccessTo("ai-mistake-tracking")} + />
@@ -179,17 +265,33 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {

- {user ? `Welcome back, ${user.name.split(" ")[0]}! 👋` : "Welcome back!"} + {user ? `Welcome back, ${user.name.split(" ")[0]}!` : "Welcome back!"}

{user && ( {targetExamLabel} Target )} - {daysLeft !== null && user?.tier !== 'Al_Coach' && user?.tier !== 'Pro' && ( - - {daysLeft} Days Left in AI Coach Trial - + {(user?.tier === 'Free' || user?.id === DEMO_USERS.NEHA.id) && daysLeft !== null && ( + daysLeft > 0 ? ( + + + {daysLeft} Days Left in Trial + + Upgrade Now → + + + + ) : ( + + + Trial Expired + + Upgrade Now → + + + + ) )}
@@ -209,6 +311,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { onChange={(e) => switchUser(e.target.value)} className="bg-secondary border border-border text-foreground px-2.5 py-1.5 rounded-xl text-xs font-bold focus:ring-2 focus:ring-[#00F0FF] outline-none cursor-pointer hover:bg-secondary/80 transition-colors" > + diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index ad273a2..d2484e7 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -42,7 +42,7 @@ export default function DashboardPage() { Upgrade to Pro

Unlock Unlimited DPS & Mocks

- You're currently on the Free Tier. Upgrade to Pro for ₹2499/year to access unlimited Daily Practice Sheets, weekly CBT tests, and advanced analytics. + You're currently on the Free Tier. Upgrade to Pro for ₹3499/year to access unlimited Daily Practice Sheets, weekly CBT tests, and advanced analytics.

+ + - -
- -
Estimated NEET (UG) AIR
-
- ~14,500 -
- -
-
- -
-
On the Right Track
-
Your biology and chemistry scores are securing a top 20k finish.
-
+ {/* Main Grid */} +
+ + {/* Left Interactive Simulator */} +
+ +
+

+ Mark Simulator +

+ Drag Sliders to Adjust
-
- -
-
Physics Bottleneck
-
Pushing Physics beyond 120 marks will breach the top 5k rank.
-
-
-
- - - + {/* Physics Slider */} +
+
+ Physics Score + {physics} / {maxPhysics} +
+ setPhysics(Number(e.target.value))} + className="w-full h-2.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-[#00F0FF]" + /> +
+ + {/* Chemistry Slider */} +
+
+ Chemistry Score + {chemistry} / {maxChem} +
+ setChemistry(Number(e.target.value))} + className="w-full h-2.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-amber-400" + /> +
+ + {/* Bio / Math Slider */} +
+
+ {exam === "NEET" ? "Biology Score (Botany + Zoology)" : "Mathematics Score"} + {bioOrMath} / {maxThird} +
+ setBioOrMath(Number(e.target.value))} + className="w-full h-2.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-emerald-400" + /> +
+ +
+ Simulated Total Score + {totalScore} / {maxTotal} +
+ + + {/* AI Strategy Insights based on Score */} +
+ + +
+

Strongest Domain

+

+ Your Biology/Math score ({bioOrMath}) provides solid rank leverage. Keep maintaining your revision momentum. +

+
+
+ + + +
+

High-Yield Growth Zone

+

+ Increasing Physics by just 25 marks will boost your rank by approximately ~4,800 positions. +

+
+
+
+
+ + {/* Right Output Dashboard */} +
+ + {/* AIR Display Card */} + +
+ +
+
Estimated All India Rank
+
+ AIR ~{predictedRank.toLocaleString()} +
+
+ High Accuracy Confidence +
+
+ + {/* College Admission Probability Table */} + +

+ College Admission Chances +

+ +
+ {colleges.map((c) => { + const isLikely = totalScore >= c.minScore + const chancePct = Math.min(99, Math.max(10, Math.round((totalScore / c.minScore) * 85))) + return ( +
+
+ {c.name} + + {isLikely ? `${chancePct}% Likely` : "Reach Target"} + +
+
+ {c.category} + Cutoff Score: {c.minScore}+ +
+
+ ) + })} +
+
+ +
+
) } diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx index 8ce64d7..b535e00 100644 --- a/src/app/dashboard/settings/page.tsx +++ b/src/app/dashboard/settings/page.tsx @@ -48,7 +48,7 @@ export default function SettingsPage() { formData.append("file", file); try { - const res = await fetch(`/api/users/${userId}/upload-avatar`, { + const res = await fetch(`/api/users/${userId}/avatar`, { method: "POST", body: formData, }); diff --git a/src/app/disclaimer/page.tsx b/src/app/disclaimer/page.tsx new file mode 100644 index 0000000..d98093b --- /dev/null +++ b/src/app/disclaimer/page.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { PublicLayout } from "@/components/Landing/PublicLayout"; + +export default function DisclaimerPage() { + return ( + +
+
+
+
+

PragatiVeer Disclaimer

+

Gyannetra Pvt Ltd

+
+

Last Updated: 27 July 2026

+

Version: 1.0

+

Document Ref: PV-LEG-DIS-001

+

Applicable To: PragatiVeer website (pragativeer.com) and associated services.

+
+
+ +
+
+

1. General Information

+

+ The information provided by Gyannetra Pvt Ltd ("we," "us," or "our") on the PragatiVeer website (the "Site") and our mobile application is for general informational and educational purposes only. All information on the Site and our mobile application is provided in good faith; however, we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability, or completeness of any information on the Site or our mobile application. +

+

+ UNDER NO CIRCUMSTANCE SHALL WE HAVE ANY LIABILITY TO YOU FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF THE SITE OR OUR MOBILE APPLICATION OR RELIANCE ON ANY INFORMATION PROVIDED ON THE SITE AND OUR MOBILE APPLICATION. YOUR USE OF THE SITE AND OUR MOBILE APPLICATION AND YOUR RELIANCE ON ANY INFORMATION ON THE SITE AND OUR MOBILE APPLICATION IS SOLELY AT YOUR OWN RISK. +

+
+ +
+

2. Educational and Test Preparation Disclaimer

+

+ PragatiVeer provides simulated Computer-Based Tests (CBT) and Daily Practice Problems (DPP) intended to assist students in preparing for competitive exams like NEET and JEE. However: +

+
    +
  • No Guarantee of Results: We do not guarantee admission to any college, university, or program, nor do we guarantee specific scores or ranks on any competitive exam.
  • +
  • Not an Official Body: PragatiVeer is not affiliated with, endorsed by, or sponsored by the National Testing Agency (NTA), the Joint Seat Allocation Authority (JoSAA), or any other official examination conducting body.
  • +
  • Dynamic Content: Exam patterns, syllabi, and guidelines are subject to change by the official conducting bodies. While we strive to keep our content updated, we cannot guarantee that our materials perfectly reflect the most current official exam patterns at all times.
  • +
  • AI Predictions: Any predictions made by our AI Coach (such as "Expected Score" or "Estimated AIR") are based on historical data and probabilistic models. They are estimations for motivational and tracking purposes only and should not be considered definitive forecasts of actual exam performance.
  • +
+
+ +
+

3. External Links Disclaimer

+

+ The Site and our mobile application may contain (or you may be sent through the Site or our mobile application) links to other websites or content belonging to or originating from third parties or links to websites and features in banners or other advertising. Such external links are not investigated, monitored, or checked for accuracy, adequacy, validity, reliability, availability, or completeness by us. +

+

+ WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR RELIABILITY OF ANY INFORMATION OFFERED BY THIRD-PARTY WEBSITES LINKED THROUGH THE SITE OR ANY WEBSITE OR FEATURE LINKED IN ANY BANNER OR OTHER ADVERTISING. WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES. +

+
+ +
+

4. Testimonials Disclaimer

+

+ The Site may contain testimonials by users of our products and/or services. These testimonials reflect the real-life experiences and opinions of such users. However, the experiences are personal to those particular users, and may not necessarily be representative of all users of our products and/or services. We do not claim, and you should not assume, that all users will have the same experiences. YOUR INDIVIDUAL RESULTS MAY VARY. +

+

+ The testimonials on the Site are submitted in various forms such as text, audio, and/or video, and are reviewed by us before being posted. They appear on the Site verbatim as given by the users, except for the correction of grammar or typing errors. Some testimonials may have been shortened for the sake of brevity where the full testimonial contained extraneous information not relevant to the general public. +

+

+ The views and opinions contained in the testimonials belong solely to the individual user and do not reflect our views and opinions. We are not affiliated with users who provide testimonials, and users are not paid or otherwise compensated for their testimonials. +

+
+ +
+

5. Contact Us

+

+ If you have any questions about this Disclaimer, please contact us at: +

+
+

Gyannetra Pvt Ltd

+

Email: contact@pragativeer.com

+

Phone: +91 9079337961

+

Address: OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India

+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9ad7454..0138b2a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import Script from "next/script"; import "./globals.css"; const geistSans = Geist({ @@ -30,7 +31,9 @@ export default function RootLayout({ suppressHydrationWarning > -