feat: setup admin JSON generation and pdf viewer for free plan DPPs

This commit is contained in:
2026-07-27 14:56:25 +05:30
parent c214ca41b6
commit a5bb2d867c
74 changed files with 8546 additions and 915 deletions

View File

@@ -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"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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"}
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}

View File

@@ -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)

View File

@@ -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,

555
create_policies.py Normal file
View File

@@ -0,0 +1,555 @@
import os
pages_dir = r"c:\Users\naman\OneDrive\Desktop\pragativeer-v2\src\app"
pages = {
"disclaimer": {
"title": "PragatiVeer Disclaimer",
"content": """
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 1.0<br/>
<strong>Document Ref:</strong> PV-LEG-DI-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. General Information Only</h2>
<p>The content on PragatiVeer — including Daily Practice Sheets (DPS), mock tests, study materials, articles, and AI-generated insights — is provided for <strong>educational and practice purposes only</strong>. It does not constitute professional advice, guaranteed exam content, or a complete curriculum.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. No Guarantee of Exam Results</h2>
<p>PragatiVeer is a practice and preparation tool. We do <strong>not</strong> guarantee:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Any specific score, rank, percentile, or result in any examination</li>
<li>Admission to any educational institution</li>
<li>That our content covers every topic that may appear in any exam</li>
<li>That your performance on our platform will translate to equivalent performance in actual exams</li>
</ul>
<p>Exam outcomes depend on numerous factors beyond our control, including your preparation level, exam-day conditions, and other variables.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Accuracy of Content</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">4. AI-Generated Content</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. No Student-Teacher Relationship</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Third-Party Links</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Limitation of Liability</h2>
<p>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:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Loss of exam opportunities or academic standing</li>
<li>Loss of data or account access</li>
<li>Indirect, incidental, or consequential damages</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">8. No Warranty</h2>
<p>The Platform and all content are provided <strong>"as is"</strong> and <strong>"as available"</strong> without warranty of any kind, whether express or implied.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">9. Contact</h2>
<p>If you have any questions about this Disclaimer, please contact us at <strong>contact@pragativeer.com</strong>.</p>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
"""
},
"terms-and-conditions": {
"title": "PragatiVeer Terms & Conditions",
"content": """
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 3.0<br/>
<strong>Document Ref:</strong> PV-LEG-TC-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Introduction</h2>
<p>PragatiVeer (“we”, “our”, “us”) is an exam preparation platform operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021), registered office at OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India.</p>
<p>By accessing or using PragatiVeer, you agree to these Terms. If you do not agree, do not use the Platform.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. Eligibility</h2>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You must be at least <strong>13 years of age</strong>.</li>
<li>If you are between <strong>13 and 18 years</strong>, by using the Platform you represent and warrant that you have obtained the necessary consent from your parent or lawful guardian. PragatiVeer is not liable for verifying such consent.</li>
<li>Parents or guardians are encouraged to supervise their child’s use of the Platform.</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Account</h2>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You are responsible for your account credentials and all activity under your account.</li>
<li>Notify us immediately at <strong>contact@pragativeer.com</strong> of any unauthorised use.</li>
<li>We may suspend or terminate your account if you violate these Terms or engage in harmful, fraudulent, or illegal activity.</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">4. Subscriptions & Payments</h2>
<h3 className="text-xl font-bold mt-6 mb-3">4.1 Plans</h3>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Plan</th>
<th className="p-3 border">Price (incl. GST)</th>
<th className="p-3 border">Duration</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Pro</td><td className="p-3 border">₹3,499</td><td className="p-3 border">Per annum</td></tr>
<tr><td className="p-3 border font-bold">AI Coach</td><td className="p-3 border">₹11,999</td><td className="p-3 border">Per annum</td></tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-bold mt-6 mb-3">4.2 Payment</h3>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>All payments are processed through our <strong>payment gateway partner</strong>. We do not store your full card or UPI details.</li>
<li>Fees are non-refundable except as stated in our Refund Policy (Section 5).</li>
<li>Prices may change with 30 days’ notice.</li>
</ul>
<h3 className="text-xl font-bold mt-6 mb-3">4.3 Auto-Renewal</h3>
<p>Subscriptions auto-renew at the end of each billing period unless cancelled at least <strong>24 hours</strong> before renewal. You may cancel auto-renewal from your account settings or by emailing <strong>contact@pragativeer.com</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Refund Policy</h2>
<p>You may request a full refund within <strong>7 days</strong> of purchase if you have not accessed more than 5 DPS sheets. No refunds after 7 days. To request a refund, email <strong>contact@pragativeer.com</strong> with subject “Refund Request”.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. User Conduct</h2>
<p>You agree to:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Use the Platform only for lawful purposes</li>
<li>Not harass, abuse, or harm other users</li>
<li>Not use bots, scrapers, or automated tools to access the Platform</li>
<li>Not attempt to hack, reverse engineer, or disrupt the Platform</li>
</ul>
<p>Violation may result in account suspension or termination.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Intellectual Property</h2>
<p>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.</p>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">8. Privacy</h2>
<p>Our data practices are governed by our <strong>Privacy Policy</strong> and the <strong>Digital Personal Data Protection Act, 2023</strong>. We do not sell your data.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">9. No Guarantee of Results</h2>
<p>PragatiVeer is a practice tool. We do <strong>not</strong> guarantee any specific exam score, rank, or admission.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">10. Limitation of Liability</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">11. Termination</h2>
<p>You may delete your account anytime via settings or by emailing <strong>contact@pragativeer.com</strong>. We may terminate your account for breach of these Terms. Fees paid are non-refundable upon termination.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">12. Changes to Terms</h2>
<p>We may update these Terms with <strong>7 days’ notice</strong> via email. Continued use after changes take effect constitutes acceptance.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">13. Grievance Officer</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Name</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
<tr><td className="p-3 border font-bold">Address</td><td className="p-3 border">OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045</td></tr>
<tr><td className="p-3 border font-bold">Response Time</td><td className="p-3 border">Acknowledged within 24 hours; resolved within 30 days</td></tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mt-8 mb-4">14. Governing Law</h2>
<p>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.</p>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
"""
},
"privacy-policy": {
"title": "PragatiVeer Privacy Policy",
"content": """
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 3.0<br/>
<strong>Document Ref:</strong> PV-LEG-PP-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Introduction</h2>
<p>PragatiVeer (“we”, “our”, “us”) is an exam preparation platform operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021), registered office at OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India.</p>
<p>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 <strong>Digital Personal Data Protection Act, 2023 (DPDP Act)</strong> of India.</p>
<p>By using PragatiVeer, you acknowledge that you have read and understood this policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. What Data We Collect</h2>
<h3 className="text-xl font-bold mt-6 mb-3">2.1 Information You Provide Directly</h3>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Category</th>
<th className="p-3 border">Examples</th>
<th className="p-3 border">Purpose</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Account Information</td><td className="p-3 border">Name, email address, phone number</td><td className="p-3 border">Account creation, service delivery, communication</td></tr>
<tr><td className="p-3 border font-bold">Academic Information</td><td className="p-3 border">Class/grade, target exam, school name, city</td><td className="p-3 border">Personalising content, weak-area analysis</td></tr>
<tr><td className="p-3 border font-bold">Payment Information</td><td className="p-3 border">Transaction ID, billing name (we do <strong>not</strong> store full card or UPI details)</td><td className="p-3 border">Subscription billing, invoicing</td></tr>
<tr><td className="p-3 border font-bold">Communications</td><td className="p-3 border">Messages sent via email or contact forms</td><td className="p-3 border">Support, feedback, grievance handling</td></tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-bold mt-6 mb-3">2.2 Information Collected Automatically</h3>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Category</th>
<th className="p-3 border">Examples</th>
<th className="p-3 border">Purpose</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Usage Data</td><td className="p-3 border">DPS attempts, scores, time spent, weak-area progression</td><td className="p-3 border">Personalising the learning experience</td></tr>
<tr><td className="p-3 border font-bold">Technical Data</td><td className="p-3 border">IP address, browser type, device type, operating system</td><td className="p-3 border">Service optimisation, fraud prevention, analytics</td></tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-bold mt-6 mb-3">2.3 Information from Third Parties</h3>
<p>We may receive transaction confirmation data from payment gateways but <strong>not</strong> full financial instrument details.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">3. How We Use Your Data</h2>
<p>We use your personal data only for:</p>
<ol className="list-decimal pl-6 mb-4 space-y-2">
<li><strong>Service Delivery</strong> — Personalised DPS, weak-area tracking, content delivery</li>
<li><strong>Personalisation</strong> — Adapting question difficulty based on performance</li>
<li><strong>Communication</strong> — Subscription reminders, service updates, support</li>
<li><strong>Payment Processing</strong> — Billing and subscription management</li>
<li><strong>Improvement</strong> — Analysing aggregate data to improve content quality</li>
<li><strong>Legal Compliance</strong> — Fulfilling obligations under DPDP Act and tax laws</li>
<li><strong>Security</strong> — Detecting fraud, abuse, and unauthorised access</li>
</ol>
<p>We do <strong>NOT</strong>:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Sell your personal data</li>
<li>Use your data to train AI models without explicit consent</li>
<li>Share your data for advertising</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">4. Users Under 18 Years of Age</h2>
<p>PragatiVeer is designed for students preparing for competitive exams, including those under 18 years of age.</p>
<p>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.</p>
<p>We encourage parents and guardians to monitor their child’s online activities and review this Privacy Policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Data Sharing</h2>
<p>We share your data only with trusted service providers bound by data protection agreements:</p>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Provider</th>
<th className="p-3 border">Purpose</th>
<th className="p-3 border">Safeguards</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Payment Gateway</td><td className="p-3 border">Payment processing</td><td className="p-3 border">PCI-DSS compliant; no full payment details stored</td></tr>
<tr><td className="p-3 border font-bold">AI Inference Providers</td><td className="p-3 border">Weak-area analysis, DPS generation</td><td className="p-3 border">Data anonymised before processing</td></tr>
<tr><td className="p-3 border font-bold">Self-hosted Infrastructure</td><td className="p-3 border">Storage, backups, disaster recovery</td><td className="p-3 border">No third-party access</td></tr>
</tbody>
</table>
</div>
<p>We do <strong>not</strong> share your data with advertising networks, data brokers, or for monetisation.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Data Retention & Deletion</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Data Category</th>
<th className="p-3 border">Retention Period</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Account Information</td><td className="p-3 border">Account duration + 12 months</td></tr>
<tr><td className="p-3 border font-bold">Academic Performance Data</td><td className="p-3 border">Account duration + 24 months</td></tr>
<tr><td className="p-3 border font-bold">Payment Records</td><td className="p-3 border">7 years (as required by tax laws)</td></tr>
<tr><td className="p-3 border font-bold">Communication Records</td><td className="p-3 border">12 months</td></tr>
</tbody>
</table>
</div>
<p>You may request account deletion by emailing <strong>contact@pragativeer.com</strong> with subject “Account Deletion Request”. We will delete your data within <strong>30 days</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Data Security</h2>
<p>We implement:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Encryption in transit (TLS 1.2+)</li>
<li>Encryption at rest</li>
<li>Role-based access controls</li>
<li>Self-hosted infrastructure</li>
<li>Documented breach notification process</li>
</ul>
<p>In the event of a data breach, we will notify the Data Protection Board of India and affected users within <strong>72 hours</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">8. Your Rights Under DPDP Act 2023</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Right</th>
<th className="p-3 border">How to Exercise</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Access your data</td><td className="p-3 border">Email contact@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Correct your data</td><td className="p-3 border">Update in account settings or email us</td></tr>
<tr><td className="p-3 border font-bold">Delete your data</td><td className="p-3 border">Email with subject “Account Deletion Request”</td></tr>
<tr><td className="p-3 border font-bold">Withdraw consent</td><td className="p-3 border">Email contact@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Lodge a grievance</td><td className="p-3 border">Contact Grievance Officer (Section 9)</td></tr>
<tr><td className="p-3 border font-bold">Nominate a representative</td><td className="p-3 border">Email with subject “Nomination Request”</td></tr>
</tbody>
</table>
</div>
<p>We respond to all requests within <strong>30 days</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">9. Grievance Officer</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Name</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
<tr><td className="p-3 border font-bold">Address</td><td className="p-3 border">OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045</td></tr>
<tr><td className="p-3 border font-bold">Response Time</td><td className="p-3 border">Acknowledged within 24 hours; resolved within 30 days</td></tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mt-8 mb-4">10. Cookies</h2>
<p>Our website uses essential cookies for functionality and anonymous analytics cookies. We do <strong>not</strong> use advertising cookies, cross-site tracking, or fingerprinting scripts.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">11. Changes to This Policy</h2>
<p>We will notify you of material changes via email with at least <strong>7 days’ notice</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">12. Contact</h2>
<p><strong>Email:</strong> contact@pragativeer.com<br/>
<strong>Address:</strong> OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India<br/>
<strong>Phone:</strong> +91 9079337961</p>
<h2 className="text-2xl font-bold mt-8 mb-4">13. Governing Law</h2>
<p>This Policy is governed by the laws of India. Disputes are subject to the exclusive jurisdiction of courts in Pune, Maharashtra.</p>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
"""
},
"refund-policy": {
"title": "PragatiVeer Refund Policy",
"content": """
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 1.0<br/>
<strong>Document Ref:</strong> PV-LEG-RP-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Overview</h2>
<p>This Refund Policy governs all paid subscriptions on PragatiVeer, operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021).</p>
<p>By purchasing a subscription, you agree to the terms of this policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. Subscription Plans</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Plan</th>
<th className="p-3 border">Price (incl. GST)</th>
<th className="p-3 border">Duration</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Pro</td><td className="p-3 border">₹3,499</td><td className="p-3 border">Per annum</td></tr>
<tr><td className="p-3 border font-bold">AI Coach</td><td className="p-3 border">₹11,999</td><td className="p-3 border">Per annum</td></tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Refund Eligibility</h2>
<p>You may request a full refund within <strong>7 days</strong> of purchase provided that you have <strong>not accessed more than 5 DPS sheets</strong> during that period.</p>
<p>Refunds will <strong>not</strong> be issued if:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>More than 7 days have passed since purchase</li>
<li>You have accessed more than 5 DPS sheets</li>
<li>The request is made after the subscription has been active beyond the refund window</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">4. How to Request a Refund</h2>
<p>To request a refund, email <strong>contact@pragativeer.com</strong> with:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li><strong>Subject:</strong> “Refund Request”</li>
<li><strong>Your registered email address or phone number</strong></li>
<li><strong>Order/transaction ID</strong> (if available)</li>
</ul>
<p>We will process your request within <strong>7-10 business days</strong> and the refund will be credited to the original payment method.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Non-Refundable Items</h2>
<p>The following are non-refundable:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Subscription fees beyond the 7-day refund window</li>
<li>Discounted or promotional purchases (unless otherwise stated)</li>
<li>Any add-on services purchased separately</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Cancellation</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Contact</h2>
<p>For any questions regarding this Refund Policy, contact us at <strong>contact@pragativeer.com</strong> or reach out to our Grievance Officer:</p>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Grievance Officer</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
</tbody>
</table>
</div>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
"""
},
"cancellation-policy": {
"title": "PragatiVeer Cancellation Policy",
"content": """
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 1.0<br/>
<strong>Document Ref:</strong> PV-LEG-CP-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Overview</h2>
<p>This Cancellation Policy governs cancellation of paid subscriptions on PragatiVeer, operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021).</p>
<p>By purchasing a subscription, you agree to the terms of this policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. Subscription Cancellation by You</h2>
<h3 className="text-xl font-bold mt-6 mb-3">2.1 How to Cancel</h3>
<p>You may cancel your subscription at any time by:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Going to <strong>Account Settings</strong> on the PragatiVeer website and disabling auto-renewal</li>
<li>Emailing <strong>contact@pragativeer.com</strong> with subject “Cancellation Request” along with your registered email or phone number</li>
</ul>
<h3 className="text-xl font-bold mt-6 mb-3">2.2 Effect of Cancellation</h3>
<p>Upon cancellation:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Your subscription will <strong>not</strong> auto-renew at the end of the current billing period</li>
<li>You will continue to have full access to your paid features until the end of the current billing period</li>
<li>No refund will be issued for the remaining days of the current billing period, except as provided in our <a href="/refund-policy" className="text-[#00F0FF] hover:underline font-semibold">Refund Policy</a></li>
</ul>
<h3 className="text-xl font-bold mt-6 mb-3">2.3 Timing</h3>
<p>To avoid being charged for the next billing cycle, cancellation must be requested at least <strong>24 hours</strong> before the renewal date.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Auto-Renewal</h2>
<p>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.</p>
<p>You will receive a reminder before renewal. If you do not wish to renew, please cancel before the renewal date.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">4. Cancellation by PragatiVeer</h2>
<p>We reserve the right to cancel or suspend your subscription immediately if:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You violate our <a href="/terms-and-conditions" className="text-[#00F0FF] hover:underline font-semibold">Terms & Conditions</a></li>
<li>You engage in fraudulent, abusive, or illegal activity</li>
<li>We are required to do so by law</li>
</ul>
<p>In such cases, fees paid are non-refundable.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Account Deletion</h2>
<p>Cancelling your subscription does <strong>not</strong> delete your account. To delete your account and all associated data, please follow the process outlined in our <a href="/privacy-policy" className="text-[#00F0FF] hover:underline font-semibold">Privacy Policy</a>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Contact</h2>
<p>For any questions regarding this Cancellation Policy, contact us at <strong>contact@pragativeer.com</strong> or reach out to our Grievance Officer:</p>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Grievance Officer</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
</tbody>
</table>
</div>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
"""
}
}
template = """import Link from 'next/link';
export default function {component_name}Page() {{
return (
<div className="min-h-screen bg-background pt-24 pb-16">
<div className="max-w-4xl mx-auto px-6">
<h1 className="text-4xl md:text-5xl font-black tracking-tight mb-12 text-foreground">
{title}
</h1>
<div className="prose prose-invert prose-lg max-w-none text-muted-foreground">
{content}
</div>
</div>
</div>
);
}}
"""
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.")

View File

@@ -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

14
pgbouncer.ini Normal file
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,821 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PragatiVeer — AI Coach Dashboard</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, sans-serif;
background: #0f0f1a;
color: #e0e0e0;
display: flex;
justify-content: center;
padding: 40px 20px;
min-height: 100vh;
}
.dashboard {
max-width: 1200px;
width: 100%;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #2a2a3e;
}
.logo-area {
display: flex;
align-items: center;
gap: 12px;
}
.logo-icon {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #7c3aed, #a855f7);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 18px;
color: white;
}
.logo-text {
font-size: 20px;
font-weight: 700;
color: #fff;
}
.logo-text span {
color: #a855f7;
}
.header-right {
display: flex;
align-items: center;
gap: 20px;
}
.tier-badge {
background: linear-gradient(135deg, #7c3aed, #a855f7);
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
color: white;
letter-spacing: 0.5px;
}
.user-avatar {
width: 36px;
height: 36px;
background: #2a2a3e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
color: #a855f7;
border: 2px solid #a855f7;
}
/* Greeting */
.greeting {
margin-bottom: 25px;
}
.greeting h1 {
font-size: 24px;
font-weight: 700;
color: #fff;
margin-bottom: 4px;
}
.greeting p {
font-size: 14px;
color: #888;
}
.greeting p span {
color: #a855f7;
font-weight: 500;
}
/* Stats Row */
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: #1a1a2e;
border-radius: 16px;
padding: 20px;
border: 1px solid #2a2a3e;
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
border-color: #7c3aed40;
}
.stat-label {
font-size: 12px;
color: #888;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #fff;
}
.stat-value .unit {
font-size: 14px;
font-weight: 400;
color: #888;
}
.stat-change {
font-size: 12px;
margin-top: 6px;
font-weight: 500;
}
.stat-change.up { color: #22c55e; }
.stat-change.down { color: #ef4444; }
/* Readiness Score - Special */
.readiness-ring {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.readiness-ring svg {
width: 60px;
height: 60px;
transform: rotate(-90deg);
}
.readiness-ring .bg { fill: none; stroke: #2a2a3e; stroke-width: 4; }
.readiness-ring .progress { fill: none; stroke: #a855f7; stroke-width: 4; stroke-linecap: round; }
.readiness-value {
position: absolute;
font-size: 18px;
font-weight: 700;
color: #fff;
}
.stat-card.readiness {
display: flex;
align-items: center;
gap: 16px;
}
.readiness-text h3 {
font-size: 16px;
font-weight: 600;
color: #fff;
margin-bottom: 2px;
}
.readiness-text p {
font-size: 12px;
color: #888;
}
/* Main Grid */
.main-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 24px;
}
.card {
background: #1a1a2e;
border-radius: 16px;
padding: 24px;
border: 1px solid #2a2a3e;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #fff;
}
.card-action {
font-size: 12px;
color: #a855f7;
cursor: pointer;
font-weight: 500;
}
.card-action:hover { text-decoration: underline; }
/* Mistake Heatmap */
.heatmap-grid {
display: grid;
grid-template-columns: 120px 1fr;
gap: 8px;
}
.heatmap-subjects {
display: flex;
flex-direction: column;
gap: 6px;
}
.heatmap-subject {
height: 28px;
display: flex;
align-items: center;
font-size: 12px;
font-weight: 500;
color: #888;
}
.heatmap-cells {
display: flex;
flex-direction: column;
gap: 6px;
}
.heatmap-row {
display: flex;
gap: 4px;
}
.heatmap-cell {
width: 28px;
height: 28px;
border-radius: 4px;
cursor: pointer;
transition: transform 0.15s;
}
.heatmap-cell:hover {
transform: scale(1.15);
}
.heatmap-cell.level-0 { background: #1a1a2e; border: 1px solid #2a2a3e; }
.heatmap-cell.level-1 { background: #2d1b4e; }
.heatmap-cell.level-2 { background: #4a1d6e; }
.heatmap-cell.level-3 { background: #7c3aed; }
.heatmap-cell.level-4 { background: #a855f7; }
.heatmap-cell.level-5 { background: #22c55e; }
.heatmap-legend {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 10px;
font-size: 10px;
color: #666;
align-items: center;
}
.heatmap-legend .dot {
width: 10px;
height: 10px;
border-radius: 2px;
}
/* Daily Plan */
.plan-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid #2a2a3e;
}
.plan-item:last-child { border-bottom: none; }
.plan-check {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #3a3a5e;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
transition: all 0.2s;
}
.plan-check.done {
background: #22c55e;
border-color: #22c55e;
}
.plan-check.done::after {
content: '✓';
color: white;
font-size: 11px;
font-weight: 700;
}
.plan-info {
flex: 1;
}
.plan-name {
font-size: 13px;
font-weight: 500;
color: #e0e0e0;
}
.plan-name.done-text {
text-decoration: line-through;
color: #666;
}
.plan-meta {
font-size: 11px;
color: #666;
margin-top: 2px;
}
.plan-time {
font-size: 12px;
color: #888;
font-weight: 500;
white-space: nowrap;
}
.plan-tag {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 500;
}
.plan-tag.physics { background: #7c3aed30; color: #a855f7; }
.plan-tag.chemistry { background: #22c55e30; color: #22c55e; }
.plan-tag.maths { background: #f59e0b30; color: #f59e0b; }
.plan-tag.review { background: #ef444430; color: #ef4444; }
/* Bottom Row */
.bottom-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
/* AI Insight */
.insight-card {
background: linear-gradient(135deg, #1a1a2e, #2d1b4e);
border: 1px solid #7c3aed30;
}
.insight-icon {
font-size: 24px;
margin-bottom: 12px;
}
.insight-text {
font-size: 13px;
line-height: 1.6;
color: #ccc;
}
.insight-text strong {
color: #a855f7;
}
/* Leaderboard Mini */
.lb-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 0;
border-bottom: 1px solid #2a2a3e;
}
.lb-item:last-child { border-bottom: none; }
.lb-rank {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
}
.lb-rank.gold { background: #f59e0b30; color: #f59e0b; }
.lb-rank.silver { background: #94a3b830; color: #94a3b8; }
.lb-rank.bronze { background: #b4530930; color: #b45309; }
.lb-rank.other { background: #2a2a3e; color: #666; }
.lb-name {
flex: 1;
font-size: 13px;
font-weight: 500;
}
.lb-score {
font-size: 12px;
color: #888;
font-weight: 500;
}
.lb-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 8px;
background: #7c3aed20;
color: #a855f7;
}
/* Streak */
.streak-display {
display: flex;
align-items: center;
gap: 16px;
}
.streak-fire {
font-size: 40px;
line-height: 1;
}
.streak-info h3 {
font-size: 28px;
font-weight: 700;
color: #f59e0b;
}
.streak-info p {
font-size: 12px;
color: #888;
}
.streak-days {
display: flex;
gap: 6px;
margin-top: 12px;
}
.streak-day {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
}
.streak-day.active {
background: #22c55e;
color: white;
}
.streak-day.inactive {
background: #2a2a3e;
color: #555;
}
.streak-day.today {
border: 2px solid #f59e0b;
background: #f59e0b20;
color: #f59e0b;
}
/* Responsive */
@media (max-width: 900px) {
.stats-row { grid-template-columns: repeat(2, 1fr); }
.main-grid { grid-template-columns: 1fr; }
.bottom-grid { grid-template-columns: 1fr; }
}
@media (max-width: 500px) {
.stats-row { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="dashboard">
<!-- Header -->
<div class="header">
<div class="logo-area">
<div class="logo-icon">PV</div>
<div class="logo-text">Pragati<span>Veer</span></div>
</div>
<div class="header-right">
<div class="tier-badge">✦ AI COACH</div>
<div class="user-avatar">RK</div>
</div>
</div>
<!-- Greeting -->
<div class="greeting">
<h1>Good morning, Rohan! 👋</h1>
<p>Your AI Coach has prepared today's plan. <span>You're on track for NIT Surat.</span></p>
</div>
<!-- Stats Row -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-label">Readiness Score</div>
<div class="stat-card readiness" style="padding:0; border:none; background:transparent;">
<div class="readiness-ring">
<svg viewBox="0 0 60 60">
<circle class="bg" cx="30" cy="30" r="24"/>
<circle class="progress" cx="30" cy="30" r="24" stroke-dasharray="150.8" stroke-dashoffset="45.2"/>
</svg>
<span class="readiness-value">72</span>
</div>
<div class="readiness-text">
<h3>Good Progress</h3>
<p>↑ 5 pts this week</p>
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-label">Streak</div>
<div class="stat-value">23 <span class="unit">days</span></div>
<div class="stat-change up">🔥 Best: 23 days</div>
</div>
<div class="stat-card">
<div class="stat-label">Questions Solved</div>
<div class="stat-value">1,247 <span class="unit">total</span></div>
<div class="stat-change up">↑ 47 this week</div>
</div>
<div class="stat-card">
<div class="stat-label">Overall Accuracy</div>
<div class="stat-value">74<span class="unit">%</span></div>
<div class="stat-change up">↑ 3% this month</div>
</div>
</div>
<!-- Main Grid: Heatmap + Daily Plan -->
<div class="main-grid">
<!-- Mistake Heatmap -->
<div class="card">
<div class="card-header">
<div class="card-title">📊 Mistake Heatmap</div>
<div class="card-action">View Details →</div>
</div>
<div class="heatmap-grid">
<div class="heatmap-subjects">
<div class="heatmap-subject">Physics</div>
<div class="heatmap-subject">Chemistry</div>
<div class="heatmap-subject">Maths</div>
</div>
<div>
<div class="heatmap-row">
<div class="heatmap-cell level-5" title="Electrostatics - 92%"></div>
<div class="heatmap-cell level-4" title="Kinematics - 85%"></div>
<div class="heatmap-cell level-2" title="Thermodynamics - 55%"></div>
<div class="heatmap-cell level-3" title="Optics - 68%"></div>
<div class="heatmap-cell level-1" title="SHM - 40%"></div>
<div class="heatmap-cell level-4" title="Laws of Motion - 82%"></div>
<div class="heatmap-cell level-3" title="Work Energy - 65%"></div>
<div class="heatmap-cell level-5" title="Gravitation - 90%"></div>
<div class="heatmap-cell level-2" title="Fluids - 50%"></div>
<div class="heatmap-cell level-0" title="Waves - Not started"></div>
</div>
<div class="heatmap-row">
<div class="heatmap-cell level-3" title="Mole Concept - 70%"></div>
<div class="heatmap-cell level-5" title="Atomic Structure - 95%"></div>
<div class="heatmap-cell level-1" title="GOC - 38%"></div>
<div class="heatmap-cell level-2" title="Chemical Bonding - 52%"></div>
<div class="heatmap-cell level-4" title="Thermodynamics - 80%"></div>
<div class="heatmap-cell level-3" title="Equilibrium - 72%"></div>
<div class="heatmap-cell level-5" title="Redox - 88%"></div>
<div class="heatmap-cell level-2" title="Hydrocarbons - 48%"></div>
<div class="heatmap-cell level-1" title="Periodic Table - 35%"></div>
<div class="heatmap-cell level-0" title="Coordination - Not started"></div>
</div>
<div class="heatmap-row">
<div class="heatmap-cell level-4" title="Sets & Relations - 83%"></div>
<div class="heatmap-cell level-3" title="Functions - 67%"></div>
<div class="heatmap-cell level-5" title="Trigonometry - 91%"></div>
<div class="heatmap-cell level-2" title="Straight Lines - 45%"></div>
<div class="heatmap-cell level-1" title="Circles - 30%"></div>
<div class="heatmap-cell level-3" title="Limits - 62%"></div>
<div class="heatmap-cell level-4" title="Differentiation - 78%"></div>
<div class="heatmap-cell level-2" title="Integration - 55%"></div>
<div class="heatmap-cell level-0" title="Vectors - Not started"></div>
<div class="heatmap-cell level-0" title="3D Geometry - Not started"></div>
</div>
<div class="heatmap-legend">
<span>Weak</span>
<div class="dot" style="background:#2d1b4e"></div>
<div class="dot" style="background:#4a1d6e"></div>
<div class="dot" style="background:#7c3aed"></div>
<div class="dot" style="background:#a855f7"></div>
<div class="dot" style="background:#22c55e"></div>
<span>Strong</span>
</div>
</div>
</div>
</div>
<!-- Today's Plan -->
<div class="card">
<div class="card-header">
<div class="card-title">📋 Today's Study Plan</div>
<div class="card-action">Regenerate →</div>
</div>
<div class="plan-item">
<div class="plan-check done"></div>
<div class="plan-info">
<div class="plan-name done-text">DPS — Electrostatics + Kinematics</div>
<div class="plan-meta">10 questions • Mixed difficulty</div>
</div>
<span class="plan-tag physics">Physics</span>
<div class="plan-time">20 min</div>
</div>
<div class="plan-item">
<div class="plan-check done"></div>
<div class="plan-info">
<div class="plan-name done-text">Mistake Review — Thermodynamics</div>
<div class="plan-meta">5 re-attempts from spaced repetition queue</div>
</div>
<span class="plan-tag review">Review</span>
<div class="plan-time">10 min</div>
</div>
<div class="plan-item">
<div class="plan-check"></div>
<div class="plan-info">
<div class="plan-name">Micro-test — GOC (General Organic Chemistry)</div>
<div class="plan-meta">5 questions • Your accuracy here: 38% ⚠️</div>
</div>
<span class="plan-tag chemistry">Chemistry</span>
<div class="plan-time">5 min</div>
</div>
<div class="plan-item">
<div class="plan-check"></div>
<div class="plan-info">
<div class="plan-name">Weekly Test — Full Syllabus (JEE Main Pattern)</div>
<div class="plan-meta">90 questions • 3 hours • Sectional timing</div>
</div>
<span class="plan-tag maths">Test</span>
<div class="plan-time">3 hrs</div>
</div>
<div class="plan-item">
<div class="plan-check"></div>
<div class="plan-info">
<div class="plan-name">Revise — Circles + Straight Lines</div>
<div class="plan-meta">Formula sheet + 3 practice questions</div>
</div>
<span class="plan-tag maths">Maths</span>
<div class="plan-time">15 min</div>
</div>
</div>
</div>
<!-- Bottom Grid -->
<div class="bottom-grid">
<!-- AI Insight -->
<div class="card insight-card">
<div class="insight-icon">🤖</div>
<div class="card-title" style="margin-bottom:10px;">AI Coach Insight</div>
<div class="insight-text">
<strong>You keep making sign errors in Thermodynamics.</strong>
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.
</div>
<div style="margin-top:14px; padding:10px; background:#2a2a3e; border-radius:10px; font-size:12px; color:#888;">
💡 <strong style="color:#a855f7;">Tip:</strong> You perform best between 7-9 PM. Schedule difficult topics then.
</div>
</div>
<!-- Leaderboard -->
<div class="card">
<div class="card-header">
<div class="card-title">🏆 City Leaderboard — Jaipur</div>
<div class="card-action">This Week →</div>
</div>
<div class="lb-item">
<div class="lb-rank gold">1</div>
<div class="lb-name">Priya S.</div>
<div class="lb-score">94%</div>
<div class="lb-badge">🔥 45 day streak</div>
</div>
<div class="lb-item">
<div class="lb-rank silver">2</div>
<div class="lb-name">Arjun M.</div>
<div class="lb-score">91%</div>
<div class="lb-badge">30 day streak</div>
</div>
<div class="lb-item">
<div class="lb-rank bronze">3</div>
<div class="lb-name">Rohan K.</div>
<div class="lb-score">87%</div>
<div class="lb-badge" style="background:#f59e0b30; color:#f59e0b;">👈 You</div>
</div>
<div class="lb-item">
<div class="lb-rank other">4</div>
<div class="lb-name">Neha G.</div>
<div class="lb-score">85%</div>
</div>
<div class="lb-item">
<div class="lb-rank other">5</div>
<div class="lb-name">Vikram R.</div>
<div class="lb-score">82%</div>
</div>
</div>
<!-- Streak + Milestones -->
<div class="card">
<div class="card-header">
<div class="card-title">🔥 Streak & Milestones</div>
<div class="card-action">Share →</div>
</div>
<div class="streak-display">
<div class="streak-fire">🔥</div>
<div class="streak-info">
<h3>23 Days</h3>
<p>Longest streak: 23 days 🎉</p>
</div>
</div>
<div class="streak-days">
<div class="streak-day active">M</div>
<div class="streak-day active">T</div>
<div class="streak-day active">W</div>
<div class="streak-day active">T</div>
<div class="streak-day active">F</div>
<div class="streak-day active">S</div>
<div class="streak-day today">S</div>
</div>
<div style="margin-top:16px; display:flex; gap:8px; flex-wrap:wrap;">
<div style="padding:6px 12px; background:#2a2a3e; border-radius:20px; font-size:11px; color:#888;">
🏅 7-Day Streak ✅
</div>
<div style="padding:6px 12px; background:#2a2a3e; border-radius:20px; font-size:11px; color:#888;">
🏅 21-Day Streak ✅
</div>
<div style="padding:6px 12px; background:#7c3aed20; border-radius:20px; font-size:11px; color:#a855f7; border:1px solid #7c3aed40;">
🏆 30-Day Streak — 7 days away!
</div>
</div>
</div>
</div>
<!-- Footer -->
<div style="margin-top:30px; text-align:center; font-size:11px; color:#444; padding:20px 0; border-top:1px solid #2a2a3e;">
PragatiVeer — AI Coach Dashboard Mockup | Gyannetra Pvt Ltd | July 2026
</div>
</div>
</body>
</html>

24
remove_links.py Normal file
View File

@@ -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*<Link href="/dashboard/ai-coach" className="inline-flex items-center gap-2 text-sm font-bold text-\[#00F0FF\] hover:underline mb-2">\s*<ArrowLeft size=\{16\} /> Back to AI Coach Overview\s*</Link>',
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}")

View File

@@ -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<typeof settings> = {}) => {
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 <div className="p-8 animate-pulse text-muted-foreground font-bold">Loading System Settings...</div>;
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-4xl">
<div>
@@ -24,7 +78,49 @@ export default function SettingsPage() {
<label className="text-sm font-bold">OpenAI API Key (Fallback)</label>
<input type="password" defaultValue="sk-........................" 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]" />
</div>
<Button className="bg-[#00F0FF] text-background hover:bg-[#00F0FF]/90 font-bold">Save Configuration</Button>
<Button onClick={() => alert("Configuration Saved via Admin Panel")} className="bg-[#00F0FF] text-background hover:bg-[#00F0FF]/90 font-bold">Save Configuration</Button>
</CardContent>
</Card>
<Card className="border-border bg-secondary/10">
<CardHeader>
<CardTitle>Trial Management</CardTitle>
<CardDescription>Configure the trial length and tier access for new signups.</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
<div>
<div className="font-bold">Enable Free Trial</div>
<div className="text-sm text-muted-foreground">Offer a trial to newly registered users.</div>
</div>
<div
onClick={() => 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'}`}>
<div className={`absolute top-1 size-4 rounded-full transition-all ${settings.enable_free_trial ? 'right-1 bg-primary-foreground' : 'left-1 bg-muted-foreground'}`}></div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold">Trial Duration (Days)</label>
<input
type="number"
value={settings.trial_duration_days}
onChange={(e) => 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]"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold">Trial Access Tier</label>
<select
value={settings.trial_tier}
onChange={(e) => setSettings(prev => ({ ...prev, trial_tier: e.target.value }))}
className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#00F0FF]">
<option value="Pro">Pro Tier</option>
<option value="Al_Coach">AI Coach (All Features)</option>
</select>
</div>
</div>
<Button onClick={() => saveSettings("Trial Settings")} className="bg-[#00F0FF] text-background hover:bg-[#00F0FF]/90 font-bold">Update Trial Settings</Button>
</CardContent>
</Card>
@@ -39,8 +135,10 @@ export default function SettingsPage() {
<div className="font-bold">Maintenance Mode</div>
<div className="text-sm text-muted-foreground">Disable all student access and show maintenance screen.</div>
</div>
<div className="h-6 w-11 rounded-full bg-secondary border border-border relative cursor-pointer">
<div className="absolute left-1 top-1 size-4 bg-muted-foreground rounded-full"></div>
<div
onClick={() => 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'}`}>
<div className={`absolute top-1 size-4 rounded-full transition-all ${settings.maintenance_mode ? 'right-1 bg-white' : 'left-1 bg-muted-foreground'}`}></div>
</div>
</div>
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
@@ -48,8 +146,10 @@ export default function SettingsPage() {
<div className="font-bold">Allow New Registrations</div>
<div className="text-sm text-muted-foreground">Open the `/signup` route for new users.</div>
</div>
<div className="h-6 w-11 rounded-full bg-primary border border-primary relative cursor-pointer shadow-[0_0_10px_var(--color-primary)]">
<div className="absolute right-1 top-1 size-4 bg-primary-foreground rounded-full"></div>
<div
onClick={() => 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'}`}>
<div className={`absolute top-1 size-4 rounded-full transition-all ${settings.allow_new_registrations ? 'right-1 bg-primary-foreground' : 'left-1 bg-muted-foreground'}`}></div>
</div>
</div>
</CardContent>
@@ -60,7 +160,7 @@ export default function SettingsPage() {
<CardTitle className="text-red-500">Danger Zone</CardTitle>
</CardHeader>
<CardContent>
<Button className="bg-red-500 text-white hover:bg-red-600 font-bold">Purge Generated Cache</Button>
<Button onClick={() => alert("Cache Purged.")} className="bg-red-500 text-white hover:bg-red-600 font-bold">Purge Generated Cache</Button>
</CardContent>
</Card>
</div>

View File

@@ -2,10 +2,31 @@
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<string, string> = {
"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<string, string> = { "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()
@@ -13,190 +34,341 @@ export default function StudentKundliPage() {
const studentId = params.studentId as string
const [loading, setLoading] = useState(true)
const [userData, setUserData] = useState<any>(null)
const [saving, setSaving] = useState(false)
const [data, setData] = useState<any>(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 <div className="h-full flex items-center justify-center"><Loader2 className="animate-spin text-muted-foreground w-8 h-8" /></div>
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 <div>User not found</div>
if (loading) return (
<div className="h-full flex items-center justify-center">
<Loader2 className="animate-spin text-muted-foreground w-8 h-8" />
</div>
)
if (error || !data) return (
<div className="p-8 text-center text-muted-foreground">
<AlertCircle className="mx-auto mb-3 text-red-500" size={32} />
<p>Student not found or failed to load.</p>
<Button variant="ghost" onClick={() => router.back()} className="mt-4">← Back</Button>
</div>
)
return (
<div className="p-8 space-y-6 max-w-6xl mx-auto animate-in fade-in duration-500">
<Button variant="ghost" onClick={() => router.back()} className="text-muted-foreground hover:text-foreground mb-4">
<ArrowLeft size={16} className="mr-2" /> Back to Students
</Button>
<div className="flex flex-col md:flex-row gap-6">
{/* User Identity Card */}
<Card className="flex-1 border-border shadow-sm">
<CardHeader className="pb-4 border-b border-border">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-full bg-secondary flex items-center justify-center text-2xl font-bold">
{userData.name.charAt(0)}
</div>
<div>
<CardTitle className="text-2xl">{userData.name}</CardTitle>
<CardDescription className="flex items-center gap-2 mt-1">
<Mail size={14} /> {userData.email}
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="pt-6 grid grid-cols-2 gap-y-4 gap-x-8">
<div>
<div className="text-sm text-muted-foreground flex items-center gap-2 mb-1"><Target size={14}/> Target Exam</div>
<div className="font-medium">{userData.target}</div>
</div>
<div>
<div className="text-sm text-muted-foreground flex items-center gap-2 mb-1"><Award size={14}/> Plan Tier</div>
<Badge className="bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20">{userData.tier}</Badge>
</div>
<div>
<div className="text-sm text-muted-foreground flex items-center gap-2 mb-1"><Calendar size={14}/> Joined</div>
<div className="font-medium">{userData.joined}</div>
</div>
<div>
<div className="text-sm text-muted-foreground flex items-center gap-2 mb-1"><User size={14}/> User ID</div>
<div className="font-mono text-xs">{userData.id}</div>
</div>
</CardContent>
</Card>
{/* Stats Summary */}
<div className="grid grid-cols-2 gap-4 flex-1">
<Card className="bg-secondary/20 border-border">
<CardContent className="p-4 flex flex-col items-center justify-center h-full text-center">
<div className="text-3xl font-black text-[#00F0FF]">{userData.stats.total_tests}</div>
<div className="text-sm text-muted-foreground mt-1">Tests Attempted</div>
</CardContent>
</Card>
<Card className="bg-secondary/20 border-border">
<CardContent className="p-4 flex flex-col items-center justify-center h-full text-center">
<div className="text-3xl font-black text-orange-500">{userData.stats.streak}</div>
<div className="text-sm text-muted-foreground mt-1">Day Streak</div>
</CardContent>
</Card>
<Card className="bg-secondary/20 border-border">
<CardContent className="p-4 flex flex-col items-center justify-center h-full text-center">
<div className="text-3xl font-black">{userData.stats.average_score}</div>
<div className="text-sm text-muted-foreground mt-1">Avg. Accuracy</div>
</CardContent>
</Card>
<Card className="bg-secondary/20 border-border">
<CardContent className="p-4 flex flex-col items-center justify-center h-full text-center">
<div className="text-3xl font-black">{userData.stats.total_questions_solved}</div>
<div className="text-sm text-muted-foreground mt-1">Questions Solved</div>
</CardContent>
</Card>
</div>
{/* Header */}
<div className="flex items-center justify-between">
<Button variant="ghost" onClick={() => router.back()} className="text-muted-foreground hover:text-foreground">
<ArrowLeft size={16} className="mr-2" /> Back to Students
</Button>
{!editing ? (
<Button variant="outline" onClick={() => setEditing(true)} className="gap-2">
<Edit3 size={14} /> Edit Profile
</Button>
) : (
<div className="flex gap-2">
<Button variant="ghost" onClick={() => setEditing(false)} className="gap-2">
<X size={14} /> Cancel
</Button>
<Button onClick={handleSave} disabled={saving} className="gap-2 bg-[#00F0FF] text-background hover:bg-[#00F0FF]/90">
{saving ? <RefreshCw size={14} className="animate-spin" /> : <Save size={14} />}
Save Changes
</Button>
</div>
)}
</div>
{/* Recent Activity & Weak Topics */}
{/* Profile Header Card */}
<Card className="border-border shadow-sm">
<CardContent className="p-6">
<div className="flex flex-col md:flex-row gap-6 items-start">
{/* Avatar */}
<div className="relative shrink-0">
{data.profile_image_url ? (
<img src={data.profile_image_url} alt={data.name} className="w-20 h-20 rounded-2xl object-cover" />
) : (
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-[#00F0FF]/20 to-[#00F0FF]/5 border border-[#00F0FF]/20 flex items-center justify-center text-3xl font-black text-[#00F0FF]">
{data.name.charAt(0)}
</div>
)}
{data.name === "Neha Verma" && (
<span className="absolute -top-2 -right-2 text-[9px] font-black px-1.5 py-0.5 rounded bg-yellow-500 text-background">DEMO</span>
)}
</div>
{/* Name + meta */}
<div className="flex-1">
{editing ? (
<input
value={editName}
onChange={e => 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"
/>
) : (
<h2 className="text-2xl font-bold mb-1">{data.name}</h2>
)}
<div className="flex flex-wrap gap-2 mt-2">
{editing ? (
<select
value={editTier}
onChange={e => setEditTier(e.target.value)}
className="text-sm bg-secondary border border-border rounded-lg px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
>
<option value="Free">Free</option>
<option value="Pro">Pro</option>
<option value="Al_Coach">AI Coach</option>
</select>
) : (
<Badge className={TIER_STYLE[data.tier] || TIER_STYLE["Free"]} variant="outline">
{TIER_LABEL[data.tier] || data.tier}
</Badge>
)}
{editing ? (
<select
value={editExam}
onChange={e => setEditExam(e.target.value)}
className="text-sm bg-secondary border border-border rounded-lg px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
>
<option value="NEET">NEET</option>
<option value="JEE_Main">JEE Main</option>
<option value="JEE_Advanced">JEE Advanced</option>
</select>
) : (
<Badge variant="outline" className="border-border">{data.target_exam.replace("_", " ")}</Badge>
)}
</div>
<div className="flex flex-wrap gap-4 mt-3 text-sm text-muted-foreground">
{data.email && <span className="flex items-center gap-1.5"><Mail size={13}/>{data.email}</span>}
{data.phone && <span className="flex items-center gap-1.5"><Phone size={13}/>{data.phone}</span>}
{data.school && <span className="flex items-center gap-1.5"><BookOpen size={13}/>{data.school}</span>}
<span className="flex items-center gap-1.5"><Calendar size={13}/> Joined {formatDate(data.created_at)}</span>
{data.subscription_end && (
<span className="flex items-center gap-1.5 text-green-500 font-medium">
<CheckCircle2 size={13}/> Active till {formatDate(data.subscription_end)}
</span>
)}
</div>
</div>
</div>
</CardContent>
</Card>
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ label: "Tests Attempted", value: data.stats.total_tests, color: "text-[#00F0FF]", icon: <Activity size={16}/> },
{ label: "Avg Accuracy", value: data.stats.avg_accuracy ? `${data.stats.avg_accuracy}%` : "—", color: "text-green-500", icon: <TrendingUp size={16}/> },
{ label: "AI Sessions", value: data.stats.mastered_mistakes, color: "text-purple-400", icon: <Award size={16}/> },
{
label: "Current Streak",
value: editing ? (
<input
type="number"
value={editStreak}
onChange={e => 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"
/>
) : (
<span className="flex items-center gap-1">
<Flame size={20} className="fill-orange-500 text-orange-500" />
{data.streak_count}
</span>
),
color: "text-orange-500",
icon: <Flame size={16}/>
},
].map((stat, i) => (
<Card key={i} className="border-border bg-card shadow-sm">
<CardContent className="p-5 flex flex-col items-center text-center">
<div className={`mb-2 ${stat.color}`}>{stat.icon}</div>
<div className={`text-2xl font-black mb-1 ${stat.color}`}>{stat.value}</div>
<div className="text-xs text-muted-foreground font-medium">{stat.label}</div>
</CardContent>
</Card>
))}
</div>
{/* Recent Attempts + Weak Topics */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<Card className="lg:col-span-2 border-border shadow-sm">
<CardHeader>
<CardTitle className="flex items-center gap-2"><Activity size={18} className="text-[#00F0FF]"/> Recent Tests</CardTitle>
<CardTitle className="flex items-center gap-2 text-base">
<Activity size={16} className="text-[#00F0FF]" /> Recent Activity
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{userData.recent_tests.map((test: any) => (
<div key={test.id} className="flex items-center justify-between p-3 rounded-lg border border-border bg-secondary/10">
<div>
<div className="font-bold">{test.name}</div>
<div className="text-xs text-muted-foreground mt-1">{test.date}</div>
{data.recent_attempts.length === 0 ? (
<p className="text-muted-foreground text-sm text-center py-6">No tests attempted yet.</p>
) : (
<div className="space-y-3">
{data.recent_attempts.map((a: any) => (
<div key={a.id} className="flex items-center justify-between p-3 rounded-lg border border-border bg-secondary/10">
<div>
<div className="font-bold text-sm">{a.name}</div>
<div className="text-xs text-muted-foreground mt-0.5">{formatDate(a.submitted_at)}</div>
</div>
<div className="text-right">
<div className="font-mono font-bold text-[#00F0FF] text-sm">{a.score}/{a.total_questions}</div>
<div className="text-xs text-muted-foreground">{a.accuracy.toFixed(1)}% acc</div>
</div>
</div>
<div className="text-right">
<div className="font-mono font-bold text-[#00F0FF]">{test.score}</div>
<div className="text-xs text-muted-foreground mt-1">{test.percentile} %ile</div>
</div>
</div>
))}
</div>
))}
</div>
)}
</CardContent>
</Card>
<Card className="border-border shadow-sm">
<CardHeader>
<CardTitle className="flex items-center gap-2"><BookOpen size={18} className="text-red-500"/> AI Identified Weaknesses</CardTitle>
<CardTitle className="flex items-center gap-2 text-base">
<BookOpen size={16} className="text-red-500" /> Weak Topics
</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-3">
{userData.weak_topics.map((topic: string, i: number) => (
<li key={i} className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full bg-red-500" />
<span className="font-medium text-sm">{topic}</span>
</li>
))}
</ul>
{data.weak_topics.length === 0 ? (
<p className="text-muted-foreground text-sm text-center py-6">No weak topics identified yet.</p>
) : (
<ul className="space-y-3">
{data.weak_topics.map((topic: string, i: number) => (
<li key={i} className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full bg-red-500 shrink-0" />
<span className="font-medium text-sm">{topic}</span>
</li>
))}
</ul>
)}
</CardContent>
</Card>
</div>
{/* Payment History */}
<Card className="border-border shadow-sm">
<CardHeader>
<CardTitle>Payment History</CardTitle>
</CardHeader>
<CardContent>
<div className="border border-border rounded-lg overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-secondary/30">
<tr>
<th className="px-4 py-3 font-semibold">Transaction ID</th>
<th className="px-4 py-3 font-semibold">Plan</th>
<th className="px-4 py-3 font-semibold">Date</th>
<th className="px-4 py-3 font-semibold">Amount</th>
<th className="px-4 py-3 font-semibold">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{userData.payments.map((p: any) => (
<tr key={p.id}>
<td className="px-4 py-3 font-mono text-xs">{p.id}</td>
<td className="px-4 py-3 font-medium">{p.plan}</td>
<td className="px-4 py-3 text-muted-foreground">{p.date}</td>
<td className="px-4 py-3 font-bold">{p.amount}</td>
<td className="px-4 py-3"><Badge className="bg-green-500/20 text-green-500 border-green-500/30">{p.status}</Badge></td>
{/* Subscriptions */}
{data.subscriptions.length > 0 && (
<Card className="border-border shadow-sm">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">
<Award size={16} className="text-[#00F0FF]" /> Subscription History
</CardTitle>
</CardHeader>
<CardContent>
<div className="border border-border rounded-lg overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-secondary/30 border-b border-border">
<tr>
<th className="px-4 py-3 font-semibold">Plan</th>
<th className="px-4 py-3 font-semibold">Billing</th>
<th className="px-4 py-3 font-semibold">Start</th>
<th className="px-4 py-3 font-semibold">End</th>
<th className="px-4 py-3 font-semibold">Amount</th>
<th className="px-4 py-3 font-semibold">Status</th>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</thead>
<tbody className="divide-y divide-border">
{data.subscriptions.map((s: any) => (
<tr key={s.id}>
<td className="px-4 py-3 font-medium">{TIER_LABEL[s.tier] || s.tier}</td>
<td className="px-4 py-3 text-muted-foreground">{s.billing_cycle}</td>
<td className="px-4 py-3 text-muted-foreground">{formatDate(s.start_date)}</td>
<td className="px-4 py-3 text-muted-foreground">{formatDate(s.end_date)}</td>
<td className="px-4 py-3 font-bold">{formatAmount(s.amount)}</td>
<td className="px-4 py-3">
<Badge className={s.status === "Active" ? "bg-green-500/20 text-green-500 border-green-500/30" : "bg-secondary text-muted-foreground border-border"}>
{s.status}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
)}
{/* Transactions */}
{data.transactions.length > 0 && (
<Card className="border-border shadow-sm">
<CardHeader>
<CardTitle className="text-base">Payment History</CardTitle>
</CardHeader>
<CardContent>
<div className="border border-border rounded-lg overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-secondary/30 border-b border-border">
<tr>
<th className="px-4 py-3 font-semibold">ID</th>
<th className="px-4 py-3 font-semibold">Plan</th>
<th className="px-4 py-3 font-semibold">Date</th>
<th className="px-4 py-3 font-semibold">Amount</th>
<th className="px-4 py-3 font-semibold">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{data.transactions.map((t: any) => (
<tr key={t.id}>
<td className="px-4 py-3 font-mono text-xs text-muted-foreground">{t.id.slice(0, 12)}...</td>
<td className="px-4 py-3 font-medium">{TIER_LABEL[t.tier] || t.tier}</td>
<td className="px-4 py-3 text-muted-foreground">{formatDate(t.created_at)}</td>
<td className="px-4 py-3 font-bold">₹{t.amount.toLocaleString("en-IN")}</td>
<td className="px-4 py-3">
<Badge className={t.status === "Success" ? "bg-green-500/20 text-green-500 border-green-500/30" : "bg-red-500/20 text-red-500 border-red-500/30"}>
{t.status}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
)}
</div>
)
}

View File

@@ -1,28 +1,60 @@
"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<string, string> = {
"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<string, string> = {
"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<any[]>([])
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 (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Student Directory</h1>
<p className="text-muted-foreground mt-1">Manage users, view engagement, and handle subscriptions.</p>
<p className="text-muted-foreground mt-1">
{loading ? "Loading..." : `${students.length} students enrolled`}
</p>
</div>
<Button variant="outline" className="border-border hover:bg-secondary"><Download size={16} className="mr-2"/> Export CSV</Button>
<Button variant="outline" className="border-border hover:bg-secondary">
<Download size={16} className="mr-2" /> Export CSV
</Button>
</div>
<div className="bg-secondary/10 border border-border rounded-xl overflow-hidden backdrop-blur-sm">
@@ -31,64 +63,82 @@ export default function StudentsPage() {
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
<input
type="text"
placeholder="Search by name, email, or ID..."
value={search}
onChange={e => 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"
/>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm whitespace-nowrap">
<thead className="bg-secondary/30 text-muted-foreground border-b border-border">
<tr>
<th className="px-6 py-4 font-semibold">Student</th>
<th className="px-6 py-4 font-semibold">Plan Tier</th>
<th className="px-6 py-4 font-semibold">Target Exam</th>
<th className="px-6 py-4 font-semibold">Tests Taken</th>
<th className="px-6 py-4 font-semibold text-center">Streak</th>
<th className="px-6 py-4 font-semibold">Last Login</th>
<th className="px-6 py-4 font-semibold text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{students.map((s) => (
<tr
key={s.id}
className="hover:bg-secondary/20 transition-colors cursor-pointer"
onClick={() => router.push(`/admin/students/${s.id}`)}
>
<td className="px-6 py-4">
<div className="font-bold">{s.name}</div>
<div className="text-muted-foreground text-xs">{s.email}</div>
</td>
<td className="px-6 py-4">
<Badge className={
s.tier === "AI Pro" ? "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20 font-bold" : "bg-secondary text-foreground border-border"
} variant="outline">
{s.tier}
</Badge>
</td>
<td className="px-6 py-4 font-medium">{s.target}</td>
<td className="px-6 py-4 font-mono">{s.tests}</td>
<td className="px-6 py-4 text-center">
<span className={`font-bold ${s.streak > 0 ? 'text-orange-500' : 'text-muted-foreground'}`}>{s.streak}</span>
</td>
<td className="px-6 py-4 text-muted-foreground">{s.lastLogin}</td>
<td className="px-6 py-4 text-right flex items-center justify-end gap-2">
{s.streak === 0 && (
<Button variant="outline" size="sm" className="h-7 text-[10px] border-red-500/50 text-red-500 hover:bg-red-500/10" onClick={() => window.alert(`Alert sent to ${s.name} regarding broken streak.`)}>
Send Alert
</Button>
)}
<button className="p-1.5 hover:text-foreground hover:bg-secondary rounded"><MoreVertical size={16} /></button>
</td>
{loading ? (
<div className="flex items-center justify-center py-20 text-muted-foreground gap-3">
<RefreshCw size={18} className="animate-spin" /> Loading students...
</div>
) : (
<table className="w-full text-left text-sm whitespace-nowrap">
<thead className="bg-secondary/30 text-muted-foreground border-b border-border">
<tr>
<th className="px-6 py-4 font-semibold">Student</th>
<th className="px-6 py-4 font-semibold">Plan</th>
<th className="px-6 py-4 font-semibold">Target</th>
<th className="px-6 py-4 font-semibold">Tests</th>
<th className="px-6 py-4 font-semibold">Accuracy</th>
<th className="px-6 py-4 font-semibold text-center">Streak</th>
<th className="px-6 py-4 font-semibold">Last Active</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody className="divide-y divide-border">
{filtered.map((s) => (
<tr
key={s.id}
className="hover:bg-secondary/20 transition-colors cursor-pointer"
onClick={() => router.push(`/admin/students/${s.id}`)}
>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-secondary flex items-center justify-center text-xs font-bold shrink-0">
{s.name.charAt(0)}
</div>
<div>
<div className="font-bold flex items-center gap-2">
{s.name}
{s.name === "Neha Verma" && (
<span className="text-[9px] font-black px-1.5 py-0.5 rounded bg-yellow-500/20 text-yellow-400 border border-yellow-500/30">DEMO ⭐</span>
)}
</div>
<div className="text-muted-foreground text-xs">{s.email || s.phone}</div>
</div>
</div>
</td>
<td className="px-6 py-4">
<Badge className={TIER_STYLE[s.tier] || TIER_STYLE["Free"]} variant="outline">
{TIER_LABEL[s.tier] || s.tier}
</Badge>
</td>
<td className="px-6 py-4 font-medium">{s.target_exam.replace("_", " ")}</td>
<td className="px-6 py-4 font-mono">{s.total_tests}</td>
<td className="px-6 py-4">
<span className={`font-bold ${s.avg_accuracy >= 80 ? "text-green-500" : s.avg_accuracy >= 60 ? "text-yellow-500" : "text-muted-foreground"}`}>
{s.avg_accuracy > 0 ? `${s.avg_accuracy}%` : "—"}
</span>
</td>
<td className="px-6 py-4 text-center">
<span className={`font-bold flex items-center justify-center gap-1 ${s.streak_count > 0 ? "text-orange-500" : "text-muted-foreground"}`}>
{s.streak_count > 0 && <Flame size={12} className="fill-orange-500" />}
{s.streak_count}
</span>
</td>
<td className="px-6 py-4 text-muted-foreground text-xs">{formatDate(s.last_active)}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
<div className="p-4 border-t border-border text-sm text-muted-foreground text-center">
Showing top 5 active students out of 12,400 total.
Showing {filtered.length} of {students.length} students
</div>
</div>
</div>

View File

@@ -0,0 +1,85 @@
import Link from 'next/link';
export default function CancellationPolicyPage() {
return (
<div className="min-h-screen bg-background pt-24 pb-16">
<div className="max-w-4xl mx-auto px-6">
<h1 className="text-4xl md:text-5xl font-black tracking-tight mb-12 text-foreground">
PragatiVeer Cancellation Policy
</h1>
<div className="prose prose-invert prose-lg max-w-none text-muted-foreground">
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 1.0<br/>
<strong>Document Ref:</strong> PV-LEG-CP-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Overview</h2>
<p>This Cancellation Policy governs cancellation of paid subscriptions on PragatiVeer, operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021).</p>
<p>By purchasing a subscription, you agree to the terms of this policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. Subscription Cancellation by You</h2>
<h3 className="text-xl font-bold mt-6 mb-3">2.1 How to Cancel</h3>
<p>You may cancel your subscription at any time by:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Going to <strong>Account Settings</strong> on the PragatiVeer website and disabling auto-renewal</li>
<li>Emailing <strong>contact@pragativeer.com</strong> with subject “Cancellation Request” along with your registered email or phone number</li>
</ul>
<h3 className="text-xl font-bold mt-6 mb-3">2.2 Effect of Cancellation</h3>
<p>Upon cancellation:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Your subscription will <strong>not</strong> auto-renew at the end of the current billing period</li>
<li>You will continue to have full access to your paid features until the end of the current billing period</li>
<li>No refund will be issued for the remaining days of the current billing period, except as provided in our <a href="/refund-policy" className="text-[#00F0FF] hover:underline font-semibold">Refund Policy</a></li>
</ul>
<h3 className="text-xl font-bold mt-6 mb-3">2.3 Timing</h3>
<p>To avoid being charged for the next billing cycle, cancellation must be requested at least <strong>24 hours</strong> before the renewal date.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Auto-Renewal</h2>
<p>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.</p>
<p>You will receive a reminder before renewal. If you do not wish to renew, please cancel before the renewal date.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">4. Cancellation by PragatiVeer</h2>
<p>We reserve the right to cancel or suspend your subscription immediately if:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You violate our <a href="/terms-and-conditions" className="text-[#00F0FF] hover:underline font-semibold">Terms & Conditions</a></li>
<li>You engage in fraudulent, abusive, or illegal activity</li>
<li>We are required to do so by law</li>
</ul>
<p>In such cases, fees paid are non-refundable.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Account Deletion</h2>
<p>Cancelling your subscription does <strong>not</strong> delete your account. To delete your account and all associated data, please follow the process outlined in our <a href="/privacy-policy" className="text-[#00F0FF] hover:underline font-semibold">Privacy Policy</a>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Contact</h2>
<p>For any questions regarding this Cancellation Policy, contact us at <strong>contact@pragativeer.com</strong> or reach out to our Grievance Officer:</p>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Grievance Officer</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
</tbody>
</table>
</div>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,131 @@
"use client";
import { PublicLayout } from "@/components/Landing/PublicLayout";
export default function CancellationPolicyPage() {
return (
<PublicLayout>
<div className="bg-background min-h-screen pt-24 pb-16">
<div className="container max-w-4xl mx-auto px-4 md:px-8">
<div className="bg-secondary/20 p-8 md:p-12 rounded-3xl border border-border shadow-sm">
<div className="mb-10 border-b border-border pb-6">
<h1 className="text-3xl md:text-5xl font-black mb-4">PragatiVeer Cancellation Policy</h1>
<p className="text-muted-foreground font-medium">Gyannetra Pvt Ltd</p>
<div className="mt-4 space-y-1 text-sm text-muted-foreground/80">
<p><span className="font-semibold text-muted-foreground">Last Updated:</span> 27 July 2026</p>
<p><span className="font-semibold text-muted-foreground">Version:</span> 1.0</p>
<p><span className="font-semibold text-muted-foreground">Document Ref:</span> PV-LEG-CP-001</p>
<p><span className="font-semibold text-muted-foreground">Applicable To:</span> PragatiVeer website (pragativeer.com) and associated services.</p>
</div>
</div>
<div className="space-y-8 prose prose-invert max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-li:text-muted-foreground">
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">1. Overview</h2>
<p>
This Cancellation Policy governs cancellation of paid subscriptions on PragatiVeer, operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021).
</p>
<p className="mt-2">
By purchasing a subscription, you agree to the terms of this policy.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">2. Subscription Cancellation by You</h2>
<h3 className="text-lg font-semibold mt-4 mb-2">2.1 How to Cancel</h3>
<p>You may cancel your subscription at any time by:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Going to <strong>Account Settings</strong> on the PragatiVeer website and disabling auto-renewal</li>
<li>Emailing <strong>contact@pragativeer.com</strong> with subject &ldquo;Cancellation Request&rdquo; along with your registered email or phone number</li>
</ul>
<h3 className="text-lg font-semibold mt-6 mb-2">2.2 Effect of Cancellation</h3>
<p>Upon cancellation:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Your subscription will <strong>not</strong> auto-renew at the end of the current billing period</li>
<li>You will continue to have full access to your paid features until the end of the current billing period</li>
<li>No refund will be issued for the remaining days of the current billing period, except as provided in our <strong>Refund Policy</strong></li>
</ul>
<h3 className="text-lg font-semibold mt-6 mb-2">2.3 Timing</h3>
<p>
To avoid being charged for the next billing cycle, cancellation must be requested at least <strong>24 hours</strong> before the renewal date.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">3. Auto-Renewal</h2>
<p>
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.
</p>
<p className="mt-2">
You will receive a reminder before renewal. If you do not wish to renew, please cancel before the renewal date.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">4. Cancellation by PragatiVeer</h2>
<p>We reserve the right to cancel or suspend your subscription immediately if:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>You violate our <strong>Terms &amp; Conditions</strong></li>
<li>You engage in fraudulent, abusive, or illegal activity</li>
<li>We are required to do so by law</li>
</ul>
<p className="mt-4">
In such cases, fees paid are non-refundable.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">5. Account Deletion</h2>
<p>
Cancelling your subscription does <strong>not</strong> delete your account. To delete your account and all associated data, please follow the process outlined in our <strong>Privacy Policy</strong>.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">6. Contact</h2>
<p>
For any questions regarding this Cancellation Policy, contact us at <strong>contact@pragativeer.com</strong> or reach out to our Grievance Officer:
</p>
<div className="overflow-x-auto my-4">
<table className="min-w-full border-collapse border border-border text-sm">
<thead>
<tr className="bg-secondary/50">
<th className="border border-border px-4 py-2 text-left">Detail</th>
<th className="border border-border px-4 py-2 text-left">Information</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border border-border px-4 py-2 font-medium">Grievance Officer</td>
<td className="border border-border px-4 py-2">Himani Kachhawaha</td>
</tr>
<tr>
<td className="border border-border px-4 py-2 font-medium">Email</td>
<td className="border border-border px-4 py-2">grievance@pragativeer.com</td>
</tr>
<tr>
<td className="border border-border px-4 py-2 font-medium">Phone</td>
<td className="border border-border px-4 py-2">+91 9079337961</td>
</tr>
</tbody>
</table>
</div>
</section>
<section>
<div className="mt-8 p-4 bg-background rounded-xl border border-border text-sm">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
</section>
</div>
</div>
</div>
</div>
</PublicLayout>
);
}

View File

@@ -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<Record<number, string>>({})
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 <div className="p-8 text-center text-muted-foreground">Loading AI Coach CBT Engine...</div>
}
return (
<div className="p-6 md:p-8 space-y-6 max-w-6xl mx-auto min-h-screen animate-in fade-in duration-500">
{/* Top Header */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-secondary/20 border border-border p-6 rounded-3xl backdrop-blur-xl">
<div className="flex items-center gap-4">
<Link href="/dashboard/dpp">
<Button variant="outline" size="icon" className="rounded-xl border-border hover:bg-secondary">
<ArrowLeft size={18} />
</Button>
</Link>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-black uppercase tracking-widest text-[#00F0FF] bg-[#00F0FF]/10 px-2.5 py-0.5 rounded-full border border-[#00F0FF]/30">
AI CBT DPP Simulator
</span>
<span className="text-xs font-bold text-muted-foreground">• {user?.target_exam?.replace("_", " ") || "JEE/NEET"}</span>
</div>
<h1 className="text-2xl font-black tracking-tight">Interactive Target DPP #14</h1>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 px-4 py-2 bg-background border border-border rounded-2xl font-mono text-lg font-bold text-[#00F0FF] shadow-inner">
<Clock size={18} className="text-[#00F0FF] animate-pulse" />
{formatTime(timeLeft)}
</div>
{!submitted ? (
<Button
onClick={() => setSubmitted(true)}
className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-bold px-6 rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]"
>
Submit Test <Send size={16} className="ml-2" />
</Button>
) : (
<Button
onClick={() => { setSubmitted(false); setSelectedAnswers({}); setTimeLeft(1800); setActiveQuestion(0) }}
variant="outline"
className="font-bold rounded-xl border-border"
>
Retake CBT DPP
</Button>
)}
</div>
</div>
{submitted && (
<Card className="border-emerald-500/30 bg-emerald-500/10 p-6 rounded-3xl space-y-4 animate-in zoom-in-95">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<CheckCircle2 size={32} className="text-emerald-400" />
<div>
<h3 className="text-xl font-black text-emerald-400">CBT DPP Completed!</h3>
<p className="text-xs text-muted-foreground">Your performance analysis has been evaluated by the AI Evaluator.</p>
</div>
</div>
<div className="text-right">
<span className="text-3xl font-black text-emerald-400">{calculateScore()} / 20</span>
<p className="text-xs text-muted-foreground font-bold">Total Marks</p>
</div>
</div>
</Card>
)}
{/* Main CBT Grid */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Question Area */}
<div className="lg:col-span-3">
<Card className="border-border bg-background/80 backdrop-blur-xl p-6 rounded-3xl shadow-xl space-y-6">
<div className="flex justify-between items-center border-b border-border pb-4">
<span className="text-sm font-bold text-[#00F0FF] uppercase tracking-wider">
Question {activeQuestion + 1} of {sampleQuestions.length}
</span>
<span className="text-xs font-bold bg-secondary px-3 py-1 rounded-full text-muted-foreground">
{sampleQuestions[activeQuestion].topic}
</span>
</div>
<div className="space-y-4">
<p className="text-lg font-bold leading-relaxed text-foreground">
{sampleQuestions[activeQuestion].question}
</p>
<div className="space-y-3 pt-2">
{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 (
<div
key={optIdx}
onClick={() => handleOptionSelect(activeQuestion, optIdx)}
className={`flex items-center gap-4 p-4 rounded-2xl border cursor-pointer transition-all ${btnStyle}`}
>
<span className="w-8 h-8 rounded-xl bg-background/80 border border-border flex items-center justify-center font-bold text-sm shrink-0">
{letter}
</span>
<span className="text-base font-medium flex-1">{opt}</span>
{submitted && isCorrect && <CheckCircle2 size={20} className="text-emerald-400" />}
</div>
)
})}
</div>
{submitted && (
<div className="mt-6 p-4 rounded-2xl bg-[#00F0FF]/10 border border-[#00F0FF]/30 text-sm text-[#00F0FF] space-y-1 animate-in fade-in">
<span className="font-extrabold uppercase text-xs tracking-wider block">AI Solution & Explanation:</span>
<p>{sampleQuestions[activeQuestion].explanation}</p>
</div>
)}
</div>
{/* Navigation buttons */}
<div className="flex justify-between items-center pt-4 border-t border-border">
<Button
variant="outline"
disabled={activeQuestion === 0}
onClick={() => setActiveQuestion(prev => Math.max(0, prev - 1))}
className="rounded-xl font-bold border-border"
>
<ArrowLeft size={16} className="mr-2" /> Previous
</Button>
<Button
disabled={activeQuestion === sampleQuestions.length - 1}
onClick={() => setActiveQuestion(prev => Math.min(sampleQuestions.length - 1, prev + 1))}
className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-bold rounded-xl"
>
Next Question <ArrowRight size={16} className="ml-2" />
</Button>
</div>
</Card>
</div>
{/* Question Palette Sidebar */}
<div className="space-y-4">
<Card className="border-border bg-background/80 backdrop-blur-xl p-5 rounded-3xl shadow-xl space-y-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-muted-foreground">Question Palette</h3>
<div className="grid grid-cols-5 gap-2">
{sampleQuestions.map((q, idx) => {
const isAns = !!selectedAnswers[idx]
const isCurr = activeQuestion === idx
return (
<button
key={q.id}
onClick={() => setActiveQuestion(idx)}
className={`h-10 rounded-xl text-xs font-extrabold transition-all border ${
isCurr
? "border-[#00F0FF] bg-[#00F0FF] text-black ring-2 ring-[#00F0FF]/50"
: isAns
? "bg-[#00F0FF]/20 border-[#00F0FF]/40 text-[#00F0FF]"
: "bg-secondary/40 border-border text-muted-foreground hover:border-primary"
}`}
>
{idx + 1}
</button>
)
})}
</div>
<div className="pt-4 border-t border-border space-y-2 text-xs font-semibold text-muted-foreground">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-md bg-[#00F0FF]" /> Current Question
</div>
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-md bg-[#00F0FF]/20 border border-[#00F0FF]/40" /> Answered
</div>
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-md bg-secondary/40 border border-border" /> Unattempted
</div>
</div>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -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<any[]>([])
const [attempts, setAttempts] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [dateFilter, setDateFilter] = useState("")
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(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<string, any>);
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 (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2">PrepPilot AI <span className="text-[#00F0FF] text-xl">&</span> CBT Simulator</h1>
<p className="text-muted-foreground font-medium max-w-xl">
Select an interactive Target DPP to launch the AI-powered CBT examination simulator.
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row justify-between items-center gap-4 border-b border-border pb-6">
<div className="flex items-center gap-2">
<input
type="date"
value={dateFilter}
onChange={(e) => 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"
/>
<span className="text-sm font-bold text-muted-foreground mr-2"><Filter size={16} className="inline mr-1"/> Difficulty:</span>
{['Mixed', 'Easy', 'Medium', 'Hard'].map((diff) => (
<button key={diff} className={`px-3 py-1.5 rounded-lg text-xs font-bold border transition-colors ${diff === 'Mixed' ? 'bg-primary text-primary-foreground border-primary' : 'bg-background text-muted-foreground border-border hover:border-primary/50'}`}>
{diff}
</button>
))}
<div className="flex items-center gap-1 ml-2 text-xs text-muted-foreground bg-secondary/50 px-2 py-1 rounded-md">
<Lock size={12}/> Pro
</div>
</div>
<Button variant="outline" className="gap-2 rounded-xl text-sm font-bold">
<Download size={16} /> Export as PDF <span className="ml-1 text-[10px] uppercase tracking-wider bg-primary/10 text-primary px-1.5 py-0.5 rounded-md">Pro</span>
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{loading ? (
<div className="col-span-2 text-center py-12 text-muted-foreground">Loading AI-generated DPPs...</div>
) : filteredDpps.length === 0 ? (
<div className="col-span-2 text-center py-12 text-muted-foreground border border-dashed rounded-xl">No DPPs available for the selected criteria.</div>
) : 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 (
<Card key={dpp.id} className={`group cursor-pointer transition-all hover:shadow-md ${isAttempted ? 'opacity-70 hover:opacity-100 bg-secondary/10 border-primary/20' : isExpired ? 'opacity-80' : 'hover:border-[#00F0FF]/50 bg-background'}`}>
<CardContent className="p-6 flex items-center justify-between">
<div className="flex gap-4">
<div className={`mt-1 flex-shrink-0 ${isAttempted ? 'text-green-500' : 'text-foreground'}`}>
{isAttempted ? <CheckCircle2 size={24} /> : <div className="size-6 rounded-full border-2 border-muted-foreground group-hover:border-[#00F0FF] transition-colors flex items-center justify-center"></div>}
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-bold uppercase tracking-wider text-[#00F0FF]">
{user?.target_exam?.replace("_", " ") || "JEE/NEET"}
</span>
<span className="text-muted-foreground text-xs font-bold">•</span>
<span className="text-xs font-bold uppercase tracking-wider text-muted-foreground">{dpp.difficulty} Level</span>
{isUpcoming ? (
<>
<span className="text-muted-foreground text-xs font-bold">•</span>
<span className="text-[10px] font-bold uppercase text-yellow-500 bg-yellow-500/10 px-1.5 py-0.5 rounded">Upcoming</span>
</>
) : isExpired ? (
<>
<span className="text-muted-foreground text-xs font-bold">•</span>
<span className="text-[10px] font-bold uppercase text-muted-foreground bg-secondary px-1.5 py-0.5 rounded">Expired</span>
</>
) : null}
</div>
<h3 className="font-bold text-lg leading-tight group-hover:text-[#00F0FF] transition-colors flex items-center gap-2">
{dpp.title}
<span className={`text-[10px] uppercase px-2 py-0.5 rounded-md border font-bold tracking-wider ${getDifficultyColor(dpp.difficulty)}`}>
{dpp.difficulty}
</span>
</h3>
<div className="text-sm text-muted-foreground mt-2 font-medium">
{count} Questions • {dpp.total_time} mins
{dpp.scheduled_date && (
<span className="ml-2 border-l border-border pl-2">
{isUpcoming ? `Available on ${formatDate(dpp.scheduled_date)}` : isExpired ? `Expired on ${formatDate(dpp.end_date!)}` : 'Available Now'}
</span>
)}
</div>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-2 shrink-0">
{isUpcoming ? (
<Button disabled variant="outline" size="sm" className="font-bold">Starts Soon</Button>
) : isExpired && !isAttempted ? (
<Button disabled variant="outline" size="sm" className="font-bold bg-secondary/50">Expired</Button>
) : isAttempted ? (
<>
<Link href={user?.tier === "Free" ? "#" : `/cbt/${dpp.id}/result`}>
<Button onClick={() => user?.tier === "Free" && setSelectedPdfDpp(dpp)} variant="outline" size="sm" className="font-bold border-border">Result</Button>
</Link>
{!isExpired && (
<Link href={user?.tier === "Free" ? "#" : `/dashboard/ai-coach/cbt-dpp/${dpp.id}`}>
<Button onClick={() => user?.tier === "Free" && setSelectedPdfDpp(dpp)} variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20">
{user?.tier === "Free" ? "Read PDF" : "Retake CBT"}
</Button>
</Link>
)}
</>
) : (
user?.tier === "Free" ? (
<Button onClick={() => setSelectedPdfDpp(dpp)} variant="secondary" className="rounded-xl bg-primary/10 text-primary hover:bg-primary/20 font-bold gap-2 text-xs">
<Eye size={14} /> Open PDF
</Button>
) : (
<Link href={`/dashboard/ai-coach/cbt-dpp/${dpp.id}`}>
<Button variant="secondary" size="sm" className="rounded-xl bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(0,240,255,0.2)]">
<Play size={14} className="fill-black" /> Launch CBT DPP
</Button>
</Link>
)
)}
</div>
</CardContent>
</Card>
)})}
</div>
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
{selectedPdfDpp && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
{/* Modal Header */}
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
<FileText size={22} />
</div>
<div>
<h3 className="font-black text-lg flex items-center gap-2">
{selectedPdfDpp.title}
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
Protected In-App PDF View
</span>
</h3>
<p className="text-xs text-muted-foreground font-medium">
{selectedPdfDpp.difficulty} Level • {user?.target_exam?.replace("_", " ") || "JEE/NEET"} Practice Sheet
</p>
</div>
</div>
<Button
onClick={() => setSelectedPdfDpp(null)}
variant="outline"
size="icon"
className="rounded-full border-border hover:bg-secondary"
>
<X size={18} />
</Button>
</div>
{/* Security Notice Banner */}
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2">
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
</span>
<Link href="/upgrade">
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
Upgrade for Interactive CBT Mode →
</Button>
</Link>
</div>
{/* Protected PDF Document Sheet Content (select-none) */}
<div
onContextMenu={(e) => e.preventDefault()}
className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]"
>
<embed
src={getPdfUrl(selectedPdfDpp.title)}
type="application/pdf"
className="w-full h-full rounded-b-lg"
/>
</div>
{/* Modal Footer */}
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
<span className="text-xs text-muted-foreground font-medium">
📄 PragatiVeer Protected PDF Reader v2.0
</span>
<div className="flex gap-3">
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
Close PDF Reader
</Button>
<Link href="/upgrade">
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
</Button>
</Link>
</div>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<GraduationCap className="text-[#00F0FF]" size={36} />
AI College & Cutoff Predictor
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Discover your probable target medical & engineering colleges based on current mock exam scores and historical cutoffs.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
<Building2 className="text-[#00F0FF]" size={24} /> Target College Analytics
</h2>
<CollegePredictorCard />
</Card>
</div>
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/30 rounded-3xl space-y-4">
<h3 className="text-xl font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
<Award size={20} /> Cutoff Modeling Parameters
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Includes 100,000+ candidate rank trends across 2021-2025 rounds.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Supports All India Quota (AIQ) & State Quota adjustments.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span>Simulates category reservation cutoffs (General, OBC, SC, ST, EWS).</span>
</li>
</ul>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Flame className="text-yellow-500" size={36} />
Mistake Heatmap Vault
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Visual representation of your error density across subjects and topics.
</p>
</div>
<div className="max-w-4xl mx-auto">
<MistakeHeatmap />
</div>
</div>
)
}

View File

@@ -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<MistakePayload[]>([])
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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/50 pb-6">
<div>
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<BrainCircuit className="text-[#00F0FF]" size={36} />
AI Mistake Vault & SM-2 Engine
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium">
Review logged errors, practice spaced-repetition cards, and observe subject heatmap trends.
</p>
</div>
<div className="flex items-center gap-3 shrink-0">
<div className="bg-[#00F0FF]/10 text-[#00F0FF] border border-[#00F0FF]/30 px-4 py-2 rounded-2xl font-bold text-sm flex items-center gap-2">
<Sparkles size={16} /> SM-2 Adaptive Algorithm Active
</div>
</div>
</div>
{/* Main Review Workspace */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-extrabold tracking-tight flex items-center gap-2">
<Flame className="text-amber-500" size={24} /> Today's Active Review Queue
</h2>
<span className="text-sm font-bold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/20">
{currentIndex < mistakes.length ? `Question ${currentIndex + 1} of ${mistakes.length}` : "Completed"}
</span>
</div>
{loading ? (
<Card className="p-16 flex items-center justify-center border-dashed">
<RefreshCw className="animate-spin text-[#00F0FF] mr-3" size={24} />
<span className="text-lg font-bold">Loading your active error queue...</span>
</Card>
) : mistakes.length === 0 ? (
<Card className="p-16 text-center border-dashed bg-secondary/10 rounded-3xl">
<CheckCircle2 size={56} className="mx-auto text-green-400 mb-4 opacity-80" />
<h3 className="text-2xl font-extrabold mb-2">Zero Mistakes Pending!</h3>
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
All scheduled errors have been reviewed. Solve more mock tests to automatically surface weak questions here.
</p>
<Button onClick={() => window.location.reload()} variant="outline" className="rounded-xl font-bold">
Refresh Queue
</Button>
</Card>
) : currentIndex < mistakes.length ? (
<MistakeReviewCard
data={mistakes[currentIndex]}
onNext={handleNext}
onSubmitReview={submitReview}
/>
) : (
<Card className="p-16 text-center border-dashed bg-[#00F0FF]/5 border-[#00F0FF]/30 rounded-3xl">
<BrainCircuit size={56} className="mx-auto text-[#00F0FF] mb-4" />
<h3 className="text-2xl font-extrabold mb-2 text-[#00F0FF]">Review Session Mastered 🎉</h3>
<p className="text-muted-foreground text-base max-w-md mx-auto mb-6">
Fantastic focus! Your memory decay intervals have been updated in the database.
</p>
<Button onClick={() => window.location.reload()} className="bg-[#00F0FF] text-black font-bold rounded-xl px-6">
Check Additional Pending Queues
</Button>
</Card>
)}
</div>
{/* Heatmap & Info Column */}
<div className="space-y-6">
<MistakeHeatmap />
</div>
</div>
</div>
)
}

View File

@@ -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<string, Date> = {
"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<MistakePayload[]>([])
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 <div className="p-8 text-center text-muted-foreground animate-pulse">Loading AI Coach...</div>
return (
<div className="h-screen flex items-center justify-center">
<div className="flex flex-col items-center gap-3 text-muted-foreground">
<BrainCircuit size={32} className="animate-pulse text-[#00F0FF]" />
<span className="text-sm font-medium">Loading AI Coach...</span>
</div>
</div>
)
}
if (!hasAccessTo("ai-mistake-tracking")) {
@@ -78,7 +99,7 @@ export default function AICoachDashboard() {
<div className="space-y-4">
<h1 className="text-4xl font-extrabold tracking-tight">AI Coach Workspace</h1>
<p className="text-muted-foreground text-lg max-w-xl mx-auto leading-relaxed">
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 <strong>AI Coach</strong> subscribers.
</p>
</div>
<div className="pt-4">
@@ -92,61 +113,93 @@ 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 (
<div className="p-8 max-w-6xl mx-auto space-y-10 animate-in fade-in duration-500">
<header className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-3">
<BrainCircuit className="text-primary" size={32}/> AI Coach
</h1>
<p className="text-muted-foreground mt-1 font-medium">Your personalized SM-2 spaced repetition and analytics engine.</p>
<div className="p-6 max-w-6xl mx-auto space-y-8 animate-in fade-in duration-500">
{/* ── METRICS BAR ── */}
<div className="flex flex-wrap items-center justify-end gap-2 pt-2">
<div className="inline-flex items-center gap-1.5 bg-yellow-500/10 text-yellow-500 px-3 py-1.5 rounded-xl text-xs font-bold border border-yellow-500/20">
<Flame size={14} className="fill-yellow-500" />
{user?.streak_count || 0}-Day Streak
</div>
</header>
<div className="inline-flex items-center gap-1.5 bg-red-500/10 text-red-400 px-3 py-1.5 rounded-xl text-xs font-bold border border-red-500/20">
<Target size={13} />
{daysLeft} days to {user?.target_exam?.replace("_", " ") || "Exam"}
</div>
<div className="inline-flex items-center gap-1.5 bg-purple-500/10 text-purple-400 px-3 py-1.5 rounded-xl text-xs font-bold border border-purple-500/20">
<Trophy size={13} />
AI Coach
</div>
</div>
{/* Summary Metrics */}
<section className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="border-border shadow-sm">
<CardContent className="p-5 flex items-center gap-4">
<div className="p-3 bg-secondary rounded-xl text-primary"><Calculator size={20}/></div>
<div>
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">Physics Errors</p>
<h3 className="text-2xl font-bold">{mistakes.filter(m => m.question.subject === 'Physics').length}</h3>
</div>
</CardContent>
</Card>
<Card className="border-border shadow-sm">
<CardContent className="p-5 flex items-center gap-4">
<div className="p-3 bg-secondary rounded-xl text-emerald-500"><BookOpen size={20}/></div>
<div>
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">Chemistry Errors</p>
<h3 className="text-2xl font-bold">{mistakes.filter(m => m.question.subject === 'Chemistry').length}</h3>
</div>
</CardContent>
</Card>
<Card className="border-border shadow-sm">
<CardContent className="p-5 flex items-center gap-4">
<div className="p-3 bg-secondary rounded-xl text-blue-500"><Target size={20}/></div>
<div>
<p className="text-sm font-bold text-muted-foreground uppercase tracking-wider">Maths Errors</p>
<h3 className="text-2xl font-bold">{mistakes.filter(m => m.question.subject === 'Maths').length}</h3>
</div>
</CardContent>
</Card>
{/* ── DAILY CHALLENGE BANNER ── */}
<DailyChallengeBanner />
{/* ── QUICK STATS ── */}
<section className="grid grid-cols-2 md:grid-cols-5 gap-4">
{[
{ label: "Questions Solved", value: "1,247", color: "text-[#00F0FF]", bg: "bg-[#00F0FF]/5", border: "border-[#00F0FF]/20", icon: <FileText size={32} /> },
{ label: "Overall Accuracy", value: "74%", color: "text-amber-500", bg: "bg-amber-500/5", border: "border-amber-500/20", icon: <Target size={32} /> },
{ label: "Current Streak", value: `${user?.streak_count || 0} d`, color: "text-yellow-400", bg: "bg-yellow-400/5", border: "border-yellow-400/20", icon: <Flame size={32} className="fill-yellow-400" /> },
{ label: "Mistakes Mastered", value: 38, color: "text-green-400", bg: "bg-green-400/5", border: "border-green-400/20", icon: <CheckCircle2 size={32} /> },
{ label: "AI Sessions", value: 124, color: "text-purple-400", bg: "bg-purple-400/5", border: "border-purple-400/20", icon: <Bot size={32} /> },
].map((stat) => (
<Card key={stat.label} className={`border ${stat.border} ${stat.bg} backdrop-blur-sm shadow-sm hover:shadow-[0_0_20px_rgba(0,240,255,0.2)] transition-all duration-300 transform hover:-translate-y-1 rounded-2xl`}>
<CardContent className="p-6 flex flex-col items-center justify-center text-center">
<div className="text-3xl mb-2 filter drop-shadow-md">{stat.icon}</div>
<div className={`text-4xl font-black ${stat.color} leading-none tracking-tight mb-2 filter drop-shadow-sm`}>{stat.value}</div>
<div className="text-xs font-extrabold text-muted-foreground uppercase tracking-wider">{stat.label}</div>
</CardContent>
</Card>
))}
</section>
{/* Predictive Intelligence */}
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<ReadinessGauge />
<CollegePredictorCard />
{/* ── PREDICTIVE INTELLIGENCE ROW ── */}
<section className="mt-10">
<div className="flex items-center justify-between gap-3 mb-6">
<div className="flex items-center gap-2 text-base md:text-lg font-black uppercase tracking-widest text-[#00F0FF]">
<Zap size={20} />
Predictive Intelligence & Strategy
</div>
<div className="flex items-center gap-3">
<Link href="/dashboard/ai-coach/readiness" className="text-xs font-bold text-[#00F0FF] hover:underline flex items-center gap-1">
Full Analytics →
</Link>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<ReadinessGauge />
<CollegePredictorCard />
<ExamStrategyCard />
<StudyTimeOptimizationCard />
</div>
</section>
{/* Mistake Review Workspace */}
<section>
<div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-semibold tracking-tight">Daily Mistake Review Queue</h2>
<span className="text-sm font-bold text-muted-foreground bg-secondary px-3 py-1 rounded-full">
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Finished"}
</span>
{/* ── MISTAKE REVIEW WORKSPACE ── */}
<section className="mt-12">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-[#00F0FF]/10 rounded-xl border border-[#00F0FF]/20">
<BrainCircuit size={24} className="text-[#00F0FF]" />
</div>
<div>
<h2 className="text-2xl md:text-3xl font-black tracking-tight">Daily Mistake Review Queue</h2>
<p className="text-sm text-muted-foreground font-medium">SM-2 Spaced Repetition Flashcard Engine</p>
</div>
</div>
<div className="flex items-center gap-3">
<Link href="/dashboard/ai-coach/mistakes" className="hidden sm:inline-flex text-xs font-extrabold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-2 rounded-xl border border-[#00F0FF]/30 hover:bg-[#00F0FF] hover:text-black transition-all">
Open Full Mistake Vault Page →
</Link>
<span className="text-sm font-extrabold text-[#00F0FF] bg-[#00F0FF]/10 px-4 py-1.5 rounded-full border border-[#00F0FF]/30 shadow-[0_0_10px_rgba(0,240,255,0.15)]">
{currentIndex < mistakes.length ? `${currentIndex + 1} / ${mistakes.length}` : "Finished"}
</span>
</div>
</div>
{loading ? (
@@ -154,7 +207,7 @@ export default function AICoachDashboard() {
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading queue...
</Card>
) : mistakes.length === 0 ? (
<Card className="p-12 text-center border-dashed bg-secondary/20">
<Card className="p-12 text-center border-dashed bg-secondary/20 rounded-3xl">
<CheckCircle2 size={48} className="mx-auto text-green-500 mb-4 opacity-50" />
<h3 className="text-xl font-bold mb-2">You're all caught up!</h3>
<p className="text-muted-foreground">No mistakes scheduled for review today. Go take a test!</p>
@@ -166,7 +219,7 @@ export default function AICoachDashboard() {
onSubmitReview={submitReview}
/>
) : (
<Card className="p-12 text-center border-dashed bg-primary/5 border-primary/20">
<Card className="p-12 text-center border-dashed bg-primary/5 border-primary/20 rounded-3xl">
<BrainCircuit size={48} className="mx-auto text-primary mb-4 opacity-80" />
<h3 className="text-xl font-bold mb-2 text-primary">Session Complete</h3>
<p className="text-muted-foreground mb-6">Great job clearing your error queue! The SM-2 engine has scheduled your next reviews.</p>
@@ -175,23 +228,57 @@ export default function AICoachDashboard() {
)}
</section>
{/* Heatmap & Timeline */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8 pt-8 border-t border-border">
<section>
{/* ── HEATMAP / STUDY PLAN / WEEKLY REPORT ── */}
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 pt-10 border-t border-border">
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2 text-base font-extrabold">
<BookOpen size={18} className="text-[#00F0FF]" /> Study Plan
</div>
<Link href="/dashboard/ai-coach/study-plan" className="text-xs font-bold text-[#00F0FF] hover:underline">
Full Schedule →
</Link>
</div>
<StudyPlanCard />
</section>
<section>
</div>
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2 text-base font-extrabold">
<Flame size={18} className="text-yellow-500" /> Mistake Heatmap
</div>
<Link href="/dashboard/ai-coach/mistakes" className="text-xs font-bold text-[#00F0FF] hover:underline">
Heatmap Vault →
</Link>
</div>
<MistakeHeatmap />
</section>
</div>
</div>
</section>
{/* Tutor Chat Workspace */}
<section className="pt-8 border-t border-border">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2 text-base font-extrabold">
<Target size={18} className="text-[#00F0FF]" /> Weekly Progress Overview
</div>
<Link href="/dashboard/ai-coach/study-plan" className="text-xs font-bold text-[#00F0FF] hover:underline">
View Details →
</Link>
</div>
<WeeklyProgressReport />
</section>
{/* ── TUTOR CHAT ── */}
<section className="pt-8 border-t border-border">
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-extrabold tracking-tight">24/7 AI Tutor Chat Assistant</h2>
<Link href="/dashboard/ai-coach/tutor" className="text-xs font-extrabold text-[#00F0FF] bg-[#00F0FF]/10 px-3.5 py-1.5 rounded-xl border border-[#00F0FF]/30 hover:bg-[#00F0FF] hover:text-black transition-all">
Full Desk Mode →
</Link>
</div>
<TutorChat />
</section>
{/* Growth & Sharing Hub */}
<section className="grid grid-cols-1 lg:grid-cols-2 gap-8 pt-8 border-t border-border mt-8">
{/* ── GROWTH HUB ── */}
<section className="grid grid-cols-1 lg:grid-cols-2 gap-6 pt-6 border-t border-border">
<ReferralCard />
<ProgressShare />
</section>
@@ -199,3 +286,4 @@ export default function AICoachDashboard() {
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Gauge className="text-[#00F0FF]" size={36} />
Exam Readiness Analytics
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Real-time algorithmic prediction of your readiness percentage for JEE & NEET based on speed, accuracy, and test history.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
<Card className="p-8 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-6 flex items-center gap-2">
<Award className="text-amber-400" size={24} /> Overall Readiness Index
</h2>
<ReadinessGauge />
</Card>
{/* Subject Confidence Breakdown */}
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl space-y-6">
<h3 className="text-xl font-extrabold tracking-tight">Subject Confidence Matrix</h3>
<div className="space-y-4">
{[
{ 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) => (
<div key={sub.subject} className="space-y-2">
<div className="flex justify-between items-center text-sm font-bold">
<span>{sub.subject}</span>
<span className="text-muted-foreground">{sub.score}% • {sub.status}</span>
</div>
<div className="h-3 w-full bg-secondary rounded-full overflow-hidden">
<div className={`h-full ${sub.color}`} style={{ width: `${sub.score}%` }} />
</div>
</div>
))}
</div>
</Card>
</div>
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-amber-500/10 via-secondary/20 to-background border border-amber-500/30 rounded-3xl space-y-4">
<h3 className="text-xl font-black tracking-tight text-amber-400 flex items-center gap-2">
<ShieldAlert size={20} /> Readiness Boosters
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Complete 2 Full-Length Mock Tests this week.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Clear 15 pending errors in Physics error queue.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-amber-400 shrink-0 mt-0.5" />
<span>Maintain a 7+ day study streak.</span>
</li>
</ul>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Zap className="text-[#00F0FF]" size={36} />
Exam Strategy & Time Optimization
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Master time allocation per question, eliminate negative marking traps, and fine-tune your subject sequence for maximum score yield.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Target className="text-[#00F0FF]" size={24} /> Exam Day Time Strategy
</h2>
<ExamStrategyCard />
</Card>
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Clock className="text-amber-400" size={24} /> Study Time Optimization
</h2>
<StudyTimeOptimizationCard />
</Card>
</div>
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
{/* Navigation Header */}
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Calendar className="text-[#00F0FF]" size={36} />
Personalized Daily & Weekly Study Plan
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
AI-generated task schedule dynamically tailored to fix your weak areas and maintain optimal syllabus coverage.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-8">
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Clock className="text-[#00F0FF]" size={24} /> Today's Action Plan
</h2>
<StudyPlanCard />
</Card>
<Card className="p-6 bg-secondary/10 border-border/60 rounded-3xl">
<h2 className="text-2xl font-extrabold tracking-tight mb-4 flex items-center gap-2">
<Target className="text-emerald-400" size={24} /> Weekly Syllabus Target
</h2>
<WeeklyProgressReport />
</Card>
</div>
{/* Sidebar Info */}
<div className="space-y-6">
<Card className="p-6 bg-gradient-to-br from-[#00F0FF]/10 via-secondary/20 to-background border border-[#00F0FF]/30 rounded-3xl space-y-4">
<h3 className="text-xl font-black tracking-tight text-[#00F0FF] flex items-center gap-2">
<BookOpen size={20} /> AI Scheduler Rules
</h3>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong>High-Yield Prioritization:</strong> Focuses extra time on topics with heavy exam weightage.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong>Error Interleaving:</strong> Integrates recent incorrect questions directly into study blocks.</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 size={16} className="text-[#00F0FF] shrink-0 mt-0.5" />
<span><strong>Burnout Prevention:</strong> Recommends 10-minute micro-breaks between intensive problem sets.</span>
</li>
</ul>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Clock className="text-[#00F0FF]" size={36} />
Study Time Optimization
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Detailed metrics on your productive hours and stamina blocks.
</p>
</div>
<div className="max-w-4xl mx-auto">
<StudyTimeOptimizationCard />
</div>
</div>
)
}

View File

@@ -0,0 +1,13 @@
"use client"
import { TutorChat } from "@/components/AI/TutorChat"
export default function TutorChatPage() {
return (
<div className="h-[calc(100vh-80px)] w-full flex flex-col p-4 md:p-6 animate-in fade-in duration-500">
<div className="flex-1 rounded-3xl overflow-hidden border border-border/50 shadow-xl bg-background">
<TutorChat />
</div>
</div>
)
}

View File

@@ -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 (
<div className="p-6 md:p-8 max-w-7xl mx-auto space-y-8 animate-in fade-in duration-500 min-h-screen">
<div className="border-b border-border/50 pb-6">
<h1 className="text-3xl md:text-5xl font-black tracking-tight flex items-center gap-3">
<Target className="text-[#00F0FF]" size={36} />
Weekly Progress Overview
</h1>
<p className="text-muted-foreground text-base md:text-lg mt-1 font-medium max-w-3xl">
Track your syllabus completion goals and weekly milestones.
</p>
</div>
<div className="max-w-4xl mx-auto">
<WeeklyProgressReport />
</div>
</div>
)
}

View File

@@ -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<any[]>([])
const [loading, setLoading] = useState(true)
const [dateFilter, setDateFilter] = useState("")
const [selectedPdfDpp, setSelectedPdfDpp] = useState<any | null>(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 (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-5xl mx-auto">
<div className="flex flex-col md:flex-row md:items-center justify-between p-8 bg-gradient-to-r from-secondary/50 to-background border border-border rounded-2xl shadow-sm gap-6">
@@ -181,27 +192,111 @@ export default function DPPPage() {
<Button disabled variant="outline" size="sm" className="font-bold bg-secondary/50">Expired</Button>
) : isAttempted ? (
<>
<Link href={`/cbt/${dpp.id}/result`}>
<Button variant="outline" size="sm" className="font-bold border-border">Result</Button>
<Link href={user?.tier === "Free" ? "#" : `/cbt/${dpp.id}/result`}>
<Button onClick={() => user?.tier === "Free" && setSelectedPdfDpp(dpp)} variant="outline" size="sm" className="font-bold border-border">Result</Button>
</Link>
{!isExpired && (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20">Retake</Button>
<Link href={user?.tier === "Free" ? "#" : `/dashboard/ai-coach/cbt-dpp/${dpp.id}`}>
<Button onClick={() => user?.tier === "Free" && setSelectedPdfDpp(dpp)} variant="secondary" size="sm" className="font-bold bg-[#00F0FF]/10 text-[#00F0FF] hover:bg-[#00F0FF]/20">
{user?.tier === "Free" ? "Read PDF" : "Retake CBT"}
</Button>
</Link>
)}
</>
) : (
<Link href={`/cbt/${dpp.id}`}>
<Button variant="secondary" size="icon" className="rounded-full group-hover:bg-primary group-hover:text-primary-foreground hover:bg-primary hover:text-primary-foreground shadow-sm transition-all duration-300">
<Play size={16} className="ml-1" />
user?.tier === "Free" ? (
<Button onClick={() => setSelectedPdfDpp(dpp)} variant="secondary" className="rounded-xl bg-primary/10 text-primary hover:bg-primary/20 font-bold gap-2 text-xs">
<Eye size={14} /> Open PDF
</Button>
</Link>
) : (
<Link href={`/dashboard/ai-coach/cbt-dpp/${dpp.id}`}>
<Button variant="secondary" size="sm" className="rounded-xl bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold gap-2 text-xs shadow-[0_0_12px_rgba(0,240,255,0.2)]">
<Play size={14} className="fill-black" /> Launch CBT DPP
</Button>
</Link>
)
)}
</div>
</CardContent>
</Card>
)})}
</div>
{/* ── PROTECTED PDF READER MODAL (FREE TIER) ── */}
{selectedPdfDpp && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 animate-in fade-in duration-300">
<div className="bg-background border border-border rounded-3xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden shadow-2xl">
{/* Modal Header */}
<div className="p-5 border-b border-border bg-secondary/30 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-primary/10 rounded-xl border border-primary/20 text-primary">
<FileText size={22} />
</div>
<div>
<h3 className="font-black text-lg flex items-center gap-2">
{selectedPdfDpp.title}
<span className="text-[10px] bg-yellow-500/10 text-yellow-500 border border-yellow-500/20 px-2 py-0.5 rounded-full uppercase tracking-wider font-extrabold">
Protected In-App PDF View
</span>
</h3>
<p className="text-xs text-muted-foreground font-medium">
{selectedPdfDpp.difficulty} Level • {user?.target_exam?.replace("_", " ") || "JEE/NEET"} Practice Sheet
</p>
</div>
</div>
<Button
onClick={() => setSelectedPdfDpp(null)}
variant="outline"
size="icon"
className="rounded-full border-border hover:bg-secondary"
>
<X size={18} />
</Button>
</div>
{/* Security Notice Banner */}
<div className="bg-amber-500/10 border-b border-amber-500/20 px-6 py-2.5 flex items-center justify-between text-xs text-amber-400 font-bold">
<span className="flex items-center gap-2">
<ShieldAlert size={16} /> Read-Only Mode (Free Tier) — Downloads, text copying, and sharing are restricted.
</span>
<Link href="/upgrade">
<Button size="sm" className="bg-amber-500 text-black hover:bg-amber-400 font-extrabold h-7 text-[11px] rounded-lg">
Upgrade for Interactive CBT Mode →
</Button>
</Link>
</div>
{/* Protected PDF Document Sheet Content (select-none) */}
<div
onContextMenu={(e) => e.preventDefault()}
className="flex-1 w-full h-full min-h-[60vh] select-none bg-[#525659]"
>
<embed
src={getPdfUrl(selectedPdfDpp.title)}
type="application/pdf"
className="w-full h-full rounded-b-lg"
/>
</div>
{/* Modal Footer */}
<div className="p-4 border-t border-border bg-secondary/30 flex items-center justify-between">
<span className="text-xs text-muted-foreground font-medium">
📄 PragatiVeer Protected PDF Reader v2.0
</span>
<div className="flex gap-3">
<Button onClick={() => setSelectedPdfDpp(null)} variant="outline" className="rounded-xl font-bold">
Close PDF Reader
</Button>
<Link href="/upgrade">
<Button className="bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 font-extrabold rounded-xl shadow-[0_0_15px_rgba(0,240,255,0.2)]">
<Sparkles size={16} className="mr-2" /> Upgrade to AI Coach for Interactive CBT
</Button>
</Link>
</div>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -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<HTMLDivElement>(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 <MaintenanceScreen />;
}
return (
@@ -61,7 +85,6 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</div>
<NavItem href="/dashboard" icon={<LayoutDashboard size={18} />} label="Overview" active={pathname === "/dashboard"} />
<NavItem href="/dashboard/dpp" icon={<FileText size={18} />} label="Target DPPs" active={pathname.startsWith("/dashboard/dpp")} />
<NavItem href="/dashboard/progress" icon={<Activity size={18} />} label="Syllabus Tracker" active={pathname.startsWith("/dashboard/progress")} />
<NavItem href="/dashboard/leaderboard" icon={<Trophy size={18} />} label="Leaderboard" active={pathname.startsWith("/dashboard/leaderboard")} />
<div className="pt-4 pb-2">
@@ -69,6 +92,13 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_-2px_var(--color-yellow-500)]"></span> Pro Tier
</p>
</div>
<NavItem
href="/dashboard/progress"
icon={<Activity size={18} />}
label="Syllabus Tracker"
active={pathname.startsWith("/dashboard/progress")}
isLocked={!hasAccessTo("chapter-wise-progress-tracking")}
/>
<NavItem
href="/dashboard/cbt"
icon={<Activity size={18} />}
@@ -95,7 +125,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
icon={<BookOpen size={18} />}
label="Doubt Forum"
active={pathname.startsWith("/dashboard/community")}
isLocked={!hasAccessTo("ask-doubts-from-tutors")}
isLocked={!hasAccessTo("doubt-resolution-community")}
/>
<div className="pt-4 pb-2">
@@ -104,19 +134,75 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</p>
</div>
<NavItem
href="/dashboard/ai-coach"
href="/dashboard/ai-coach/tutor"
icon={<BrainCircuitIcon />}
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")}
/>
<NavItem
href="/dashboard/rank-predictor"
href="/dashboard/ai-coach/cbt-dpp"
icon={<FileText size={18} />}
label="PrepPilot AI"
active={pathname.startsWith("/dashboard/ai-coach/cbt-dpp")}
isLocked={!hasAccessTo("ai-tutor-chat")}
/>
<NavItem
href="/dashboard/ai-coach/readiness"
icon={<Activity size={18} />}
label="Exam Readiness"
active={pathname.startsWith("/dashboard/ai-coach/readiness")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/ai-coach/college-predictor"
icon={<Trophy size={18} />}
label="Rank Predictor"
active={pathname.startsWith("/dashboard/rank-predictor")}
label="College Predictor"
active={pathname.startsWith("/dashboard/ai-coach/college-predictor")}
isLocked={!hasAccessTo("college-predictor")}
/>
<NavItem
href="/dashboard/ai-coach/strategy"
icon={<BookOpen size={18} />}
label="Exam Strategy"
active={pathname.startsWith("/dashboard/ai-coach/strategy")}
isLocked={!hasAccessTo("exam-day-strategy")}
/>
<NavItem
href="/dashboard/ai-coach/study-plan"
icon={<LineChart size={18} />}
label="AI Study Plan"
active={pathname.startsWith("/dashboard/ai-coach/study-plan")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/time-optimization"
icon={<Activity size={18} />}
label="Study Time Optimization"
active={pathname.startsWith("/dashboard/ai-coach/time-optimization")}
isLocked={!hasAccessTo("personalized-study-plan")}
/>
<NavItem
href="/dashboard/ai-coach/heatmap"
icon={<Flame size={18} />}
label="Mistake Heatmap"
active={pathname.startsWith("/dashboard/ai-coach/heatmap")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
/>
<NavItem
href="/dashboard/ai-coach/weekly-progress"
icon={<Target size={18} />}
label="Weekly Progress"
active={pathname.startsWith("/dashboard/ai-coach/weekly-progress")}
isLocked={!hasAccessTo("detailed-performance-analytics")}
/>
<NavItem
href="/dashboard/ai-coach/mistakes"
icon={<Activity size={18} />}
label="Mistake Analyzer"
active={pathname.startsWith("/dashboard/ai-coach/mistakes")}
isLocked={!hasAccessTo("ai-mistake-tracking")}
/>
</nav>
<div className="p-4 border-t border-border space-y-2 bg-secondary/30">
@@ -179,17 +265,33 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<header className="h-16 border-b border-border bg-background/95 backdrop-blur-md flex items-center justify-between px-8 shrink-0 z-40">
<div className="flex items-center gap-4">
<h2 className="font-bold text-lg hidden sm:block">
{user ? `Welcome back, ${user.name.split(" ")[0]}! 👋` : "Welcome back!"}
{user ? `Welcome back, ${user.name.split(" ")[0]}!` : "Welcome back!"}
</h2>
{user && (
<Badge variant="outline" className="bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20 font-bold tracking-wide uppercase px-2.5 py-0.5 animate-pulse">
{targetExamLabel} Target
</Badge>
)}
{daysLeft !== null && user?.tier !== 'Al_Coach' && user?.tier !== 'Pro' && (
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20 font-bold px-2.5 py-0.5">
{daysLeft} Days Left in AI Coach Trial
</Badge>
{(user?.tier === 'Free' || user?.id === DEMO_USERS.NEHA.id) && daysLeft !== null && (
daysLeft > 0 ? (
<Link href="/upgrade" className="group">
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/30 font-bold px-3 py-1 cursor-pointer hover:bg-yellow-500/20 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_12px_rgba(234,179,8,0.15)]">
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-yellow-500" /> {daysLeft} Days Left in Trial</span>
<span className="text-[11px] bg-yellow-500 text-black px-2 py-0.5 rounded-full font-black group-hover:bg-yellow-400 transition-colors shadow-sm">
Upgrade Now →
</span>
</Badge>
</Link>
) : (
<Link href="/upgrade" className="group">
<Badge variant="outline" className="bg-red-500/15 text-red-400 border-red-500/40 font-black px-3.5 py-1 cursor-pointer hover:bg-red-500/30 hover:scale-105 transition-all flex items-center gap-2 shadow-[0_0_15px_rgba(239,68,68,0.25)] animate-pulse">
<span className="flex items-center gap-1.5"><ShieldAlert size={14} className="text-red-400" /> Trial Expired</span>
<span className="text-[11px] bg-red-500 text-white px-2 py-0.5 rounded-full font-black shadow-sm group-hover:bg-red-600 transition-colors">
Upgrade Now →
</span>
</Badge>
</Link>
)
)}
</div>
@@ -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"
>
<option value={DEMO_USERS.NEHA.id}>Neha Verma (AI Coach)</option>
<option value={DEMO_USERS.AI_COACH.id}>Rahul (AI Coach)</option>
<option value={DEMO_USERS.PRO.id}>Priya (Pro Plan)</option>
<option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option>

View File

@@ -42,7 +42,7 @@ export default function DashboardPage() {
<Badge className="bg-primary text-primary-foreground font-bold tracking-widest uppercase mb-2">Upgrade to Pro</Badge>
<h2 className="text-2xl font-bold tracking-tight">Unlock Unlimited DPS & Mocks</h2>
<p className="text-muted-foreground max-w-xl font-medium">
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.
</p>
</div>
<Button size="lg" className="shrink-0 rounded-xl shadow-[0_0_20px_-5px_var(--color-primary)] font-bold">

View File

@@ -1,50 +1,244 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Trophy, TrendingUp, AlertTriangle } from "lucide-react"
import { Trophy, TrendingUp, AlertTriangle, Sparkles, Building2, Target, Sliders, CheckCircle2, ChevronRight, UserCheck } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
export default function RankPredictorPage() {
const { user } = useActiveUser()
const [exam, setExam] = useState<"NEET" | "JEE">("NEET")
const [physics, setPhysics] = useState(135)
const [chemistry, setChemistry] = useState(155)
const [bioOrMath, setBioOrMath] = useState(330) // Bio out of 360 or Math out of 100
const [saved, setSaved] = useState(false)
useEffect(() => {
if (user?.target_exam) {
if (user.target_exam.includes("NEET")) {
setExam("NEET")
setPhysics(140)
setChemistry(150)
setBioOrMath(320)
} else {
setExam("JEE")
setPhysics(65)
setChemistry(70)
setBioOrMath(75)
}
}
}, [user])
const maxTotal = exam === "NEET" ? 720 : 300
const maxPhysics = exam === "NEET" ? 180 : 100
const maxChem = exam === "NEET" ? 180 : 100
const maxThird = exam === "NEET" ? 360 : 100
const totalScore = physics + chemistry + bioOrMath
// Calculate predicted rank based on total score percentage
const calculateRank = (score: number, max: number) => {
const pct = score / max
if (pct >= 0.95) return Math.max(12, Math.round((1 - pct) * 2000))
if (pct >= 0.90) return Math.round(1500 + (0.95 - pct) * 50000)
if (pct >= 0.80) return Math.round(4000 + (0.90 - pct) * 80000)
if (pct >= 0.70) return Math.round(12000 + (0.80 - pct) * 120000)
if (pct >= 0.60) return Math.round(25000 + (0.70 - pct) * 200000)
return Math.round(60000 + (0.60 - pct) * 400000)
}
const predictedRank = calculateRank(totalScore, maxTotal)
const colleges = exam === "NEET" ? [
{ name: "AIIMS New Delhi", category: "Top Tier Medical", minScore: 690, color: "text-[#00F0FF]", border: "border-[#00F0FF]/30", bg: "bg-[#00F0FF]/10" },
{ name: "MAMC New Delhi", category: "Central GMC", minScore: 665, color: "text-amber-400", border: "border-amber-400/30", bg: "bg-amber-400/10" },
{ name: "KGMU Lucknow / VMMCH", category: "Premier State GMC", minScore: 635, color: "text-emerald-400", border: "border-emerald-400/30", bg: "bg-emerald-400/10" },
{ name: "Top State Govt Medical College", category: "State Quota", minScore: 590, color: "text-purple-400", border: "border-purple-400/30", bg: "bg-purple-400/10" },
] : [
{ name: "IIT Bombay / IIT Delhi (CS)", category: "Top 5 IITs", minScore: 260, color: "text-[#00F0FF]", border: "border-[#00F0FF]/30", bg: "bg-[#00F0FF]/10" },
{ name: "IIT Madras / Kharagpur", category: "Core Engineering", minScore: 220, color: "text-amber-400", border: "border-amber-400/30", bg: "bg-amber-400/10" },
{ name: "NIT Trichy / Surathkal", category: "Top NITs", minScore: 180, color: "text-emerald-400", border: "border-emerald-400/30", bg: "bg-emerald-400/10" },
{ name: "Top State Engineering Colleges", category: "State Govt", minScore: 140, color: "text-purple-400", border: "border-purple-400/30", bg: "bg-purple-400/10" },
]
return (
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-4xl mx-auto">
<div className="text-center max-w-2xl mx-auto mb-10">
<div className="inline-flex items-center justify-center p-4 bg-[#00F0FF]/10 rounded-full mb-6">
<Trophy size={40} className="text-[#00F0FF]" />
<div className="p-6 md:p-8 space-y-8 animate-in fade-in duration-500 max-w-5xl mx-auto min-h-screen">
{/* Header */}
<div className="text-center max-w-3xl mx-auto space-y-3">
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-[#00F0FF]/10 text-[#00F0FF] rounded-full border border-[#00F0FF]/30 text-xs font-black tracking-widest uppercase shadow-[0_0_15px_rgba(0,240,255,0.15)]">
<Sparkles size={14} /> AI Neural Prediction Engine
</div>
<h1 className="text-4xl font-bold tracking-tight mb-4">Rank Predictor AI</h1>
<p className="text-muted-foreground text-lg font-medium">
Our proprietary neural network analyzes your CBT scores against 100,000+ past successful candidates to predict your All India Rank.
<h1 className="text-4xl md:text-5xl font-black tracking-tight">AI Rank & College Predictor</h1>
<p className="text-muted-foreground text-base md:text-lg font-medium">
Simulate your subject marks to dynamically calculate your All India Rank (AIR) and college admission chances.
</p>
{user && (
<div className="inline-flex items-center gap-2 px-3 py-1 bg-emerald-500/10 text-emerald-400 border border-emerald-500/30 rounded-xl text-xs font-bold mt-1">
<UserCheck size={14} /> Profile Connected: {user.name} ({user.target_exam?.replace("_", " ")})
</div>
)}
{/* Exam Switcher */}
<div className="inline-flex p-1.5 bg-secondary/40 border border-border rounded-2xl gap-2 mt-4">
<button
onClick={() => { setExam("NEET"); setPhysics(135); setChemistry(155); setBioOrMath(330) }}
className={`px-6 py-2 rounded-xl text-sm font-extrabold transition-all ${
exam === "NEET" ? "bg-[#00F0FF] text-black shadow-[0_0_15px_#00F0FF]" : "text-muted-foreground hover:text-foreground"
}`}
>
NEET (UG) 720 Marks
</button>
<button
onClick={() => { setExam("JEE"); setPhysics(65); setChemistry(70); setBioOrMath(75) }}
className={`px-6 py-2 rounded-xl text-sm font-extrabold transition-all ${
exam === "JEE" ? "bg-[#00F0FF] text-black shadow-[0_0_15px_#00F0FF]" : "text-muted-foreground hover:text-foreground"
}`}
>
JEE (Main) 300 Marks
</button>
</div>
</div>
<Card className="border-border shadow-lg bg-background overflow-hidden relative">
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-[#00F0FF] to-blue-600"></div>
<CardContent className="p-10 text-center">
<div className="text-sm font-bold uppercase tracking-widest text-muted-foreground mb-4">Estimated NEET (UG) AIR</div>
<div className="text-7xl font-black tracking-tighter bg-clip-text text-transparent bg-gradient-to-br from-foreground to-muted-foreground mb-8">
~14,500
</div>
{/* Main Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-left">
<div className="p-4 bg-secondary/30 rounded-xl border border-border flex items-start gap-3">
<TrendingUp className="text-emerald-500 mt-0.5" size={20} />
<div>
<div className="font-bold">On the Right Track</div>
<div className="text-sm text-muted-foreground mt-1">Your biology and chemistry scores are securing a top 20k finish.</div>
</div>
{/* Left Interactive Simulator */}
<div className="lg:col-span-2 space-y-6">
<Card className="border-border/60 bg-secondary/10 backdrop-blur-xl rounded-3xl p-6 space-y-6 shadow-xl">
<div className="flex items-center justify-between border-b border-border/40 pb-4">
<h2 className="text-xl font-extrabold flex items-center gap-2">
<Sliders className="text-[#00F0FF]" size={20} /> Mark Simulator
</h2>
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Drag Sliders to Adjust</span>
</div>
<div className="p-4 bg-secondary/30 rounded-xl border border-border flex items-start gap-3">
<AlertTriangle className="text-amber-500 mt-0.5" size={20} />
<div>
<div className="font-bold">Physics Bottleneck</div>
<div className="text-sm text-muted-foreground mt-1">Pushing Physics beyond 120 marks will breach the top 5k rank.</div>
</div>
</div>
</div>
<Button size="lg" className="mt-8 bg-foreground text-background hover:bg-foreground/90 font-bold rounded-xl px-8">
Recalculate with Latest Mock
</Button>
</CardContent>
</Card>
{/* Physics Slider */}
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-extrabold">
<span>Physics Score</span>
<span className="text-[#00F0FF] text-base">{physics} / {maxPhysics}</span>
</div>
<input
type="range"
min={0}
max={maxPhysics}
value={physics}
onChange={(e) => setPhysics(Number(e.target.value))}
className="w-full h-2.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-[#00F0FF]"
/>
</div>
{/* Chemistry Slider */}
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-extrabold">
<span>Chemistry Score</span>
<span className="text-amber-400 text-base">{chemistry} / {maxChem}</span>
</div>
<input
type="range"
min={0}
max={maxChem}
value={chemistry}
onChange={(e) => setChemistry(Number(e.target.value))}
className="w-full h-2.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-amber-400"
/>
</div>
{/* Bio / Math Slider */}
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-extrabold">
<span>{exam === "NEET" ? "Biology Score (Botany + Zoology)" : "Mathematics Score"}</span>
<span className="text-emerald-400 text-base">{bioOrMath} / {maxThird}</span>
</div>
<input
type="range"
min={0}
max={maxThird}
value={bioOrMath}
onChange={(e) => setBioOrMath(Number(e.target.value))}
className="w-full h-2.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-emerald-400"
/>
</div>
<div className="p-4 bg-background/50 border border-border/50 rounded-2xl flex items-center justify-between">
<span className="text-sm font-bold">Simulated Total Score</span>
<span className="text-3xl font-black text-foreground">{totalScore} <span className="text-sm text-muted-foreground font-semibold">/ {maxTotal}</span></span>
</div>
</Card>
{/* AI Strategy Insights based on Score */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card className="p-5 bg-emerald-500/10 border-emerald-500/30 rounded-2xl flex items-start gap-3">
<TrendingUp className="text-emerald-400 shrink-0 mt-0.5" size={22} />
<div>
<h4 className="font-extrabold text-emerald-400">Strongest Domain</h4>
<p className="text-xs text-muted-foreground font-medium mt-1">
Your Biology/Math score ({bioOrMath}) provides solid rank leverage. Keep maintaining your revision momentum.
</p>
</div>
</Card>
<Card className="p-5 bg-amber-500/10 border-amber-500/30 rounded-2xl flex items-start gap-3">
<AlertTriangle className="text-amber-400 shrink-0 mt-0.5" size={22} />
<div>
<h4 className="font-extrabold text-amber-400">High-Yield Growth Zone</h4>
<p className="text-xs text-muted-foreground font-medium mt-1">
Increasing Physics by just 25 marks will boost your rank by approximately ~4,800 positions.
</p>
</div>
</Card>
</div>
</div>
{/* Right Output Dashboard */}
<div className="space-y-6">
{/* AIR Display Card */}
<Card className="border-2 border-[#00F0FF] bg-gradient-to-br from-[#00F0FF]/15 via-secondary/30 to-background rounded-3xl p-6 text-center shadow-[0_0_30px_rgba(0,240,255,0.15)] relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10">
<Trophy size={120} className="text-[#00F0FF]" />
</div>
<div className="text-xs font-black uppercase tracking-widest text-[#00F0FF] mb-2">Estimated All India Rank</div>
<div className="text-5xl md:text-6xl font-black tracking-tighter text-foreground my-3 filter drop-shadow-md">
AIR ~{predictedRank.toLocaleString()}
</div>
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-[#00F0FF]/20 text-[#00F0FF] text-xs font-bold border border-[#00F0FF]/40">
<CheckCircle2 size={13} /> High Accuracy Confidence
</div>
</Card>
{/* College Admission Probability Table */}
<Card className="border-border/60 bg-secondary/10 backdrop-blur-xl rounded-3xl p-6 space-y-4">
<h3 className="text-lg font-extrabold flex items-center gap-2">
<Building2 className="text-[#00F0FF]" size={20} /> College Admission Chances
</h3>
<div className="space-y-3">
{colleges.map((c) => {
const isLikely = totalScore >= c.minScore
const chancePct = Math.min(99, Math.max(10, Math.round((totalScore / c.minScore) * 85)))
return (
<div key={c.name} className={`p-3.5 rounded-2xl border ${c.border} ${c.bg} space-y-1.5`}>
<div className="flex justify-between items-center">
<span className="font-extrabold text-sm text-foreground">{c.name}</span>
<span className={`text-xs font-black ${isLikely ? "text-green-400" : "text-amber-400"}`}>
{isLikely ? `${chancePct}% Likely` : "Reach Target"}
</span>
</div>
<div className="flex justify-between text-xs text-muted-foreground font-medium">
<span>{c.category}</span>
<span>Cutoff Score: {c.minScore}+</span>
</div>
</div>
)
})}
</div>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -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,
});

View File

@@ -0,0 +1,87 @@
"use client";
import { PublicLayout } from "@/components/Landing/PublicLayout";
export default function DisclaimerPage() {
return (
<PublicLayout>
<div className="bg-background min-h-screen pt-24 pb-16">
<div className="container max-w-4xl mx-auto px-4 md:px-8">
<div className="bg-secondary/20 p-8 md:p-12 rounded-3xl border border-border shadow-sm">
<div className="mb-10 border-b border-border pb-6">
<h1 className="text-3xl md:text-5xl font-black mb-4">PragatiVeer Disclaimer</h1>
<p className="text-muted-foreground font-medium">Gyannetra Pvt Ltd</p>
<div className="mt-4 space-y-1 text-sm text-muted-foreground/80">
<p><span className="font-semibold text-muted-foreground">Last Updated:</span> 27 July 2026</p>
<p><span className="font-semibold text-muted-foreground">Version:</span> 1.0</p>
<p><span className="font-semibold text-muted-foreground">Document Ref:</span> PV-LEG-DIS-001</p>
<p><span className="font-semibold text-muted-foreground">Applicable To:</span> PragatiVeer website (pragativeer.com) and associated services.</p>
</div>
</div>
<div className="space-y-8 prose prose-invert max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-li:text-muted-foreground">
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">1. General Information</h2>
<p>
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.
</p>
<p className="mt-2">
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.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">2. Educational and Test Preparation Disclaimer</h2>
<p>
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:
</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li><strong>No Guarantee of Results:</strong> We do not guarantee admission to any college, university, or program, nor do we guarantee specific scores or ranks on any competitive exam.</li>
<li><strong>Not an Official Body:</strong> 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.</li>
<li><strong>Dynamic Content:</strong> 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.</li>
<li><strong>AI Predictions:</strong> 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.</li>
</ul>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">3. External Links Disclaimer</h2>
<p>
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.
</p>
<p className="mt-2">
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.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">4. Testimonials Disclaimer</h2>
<p>
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.
</p>
<p className="mt-2">
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.
</p>
<p className="mt-2">
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.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">5. Contact Us</h2>
<p>
If you have any questions about this Disclaimer, please contact us at:
</p>
<div className="mt-4 p-4 bg-background rounded-xl border border-border text-sm">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>Email: contact@pragativeer.com</p>
<p>Phone: +91 9079337961</p>
<p>Address: OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
</div>
</section>
</div>
</div>
</div>
</div>
</PublicLayout>
);
}

View File

@@ -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
>
<head>
<script
<Script
id="theme-script"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
try {

View File

@@ -5,10 +5,24 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { ArrowLeft, Eye, EyeOff } from "lucide-react"
import Link from "next/link"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Logo } from "@/components/Logo"
export default function LoginPage() {
const [showPassword, setShowPassword] = useState(false)
const [email, setEmail] = useState("demo@pragativeer.com")
const [password, setPassword] = useState("PragatiVeer@Secure2026!")
const [error, setError] = useState("")
const router = useRouter()
const handleLogin = (e: React.FormEvent) => {
e.preventDefault()
if (email === "demo@pragativeer.com" && password === "PragatiVeer@Secure2026!") {
router.push("/dashboard")
} else {
setError("Invalid credentials. Please use the demo account.")
}
}
return (
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-6 selection:bg-[#00F0FF]/20">
<Link href="/" className="absolute top-8 left-8 text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors font-medium">
@@ -26,12 +40,14 @@ export default function LoginPage() {
<CardDescription className="text-muted-foreground">Enter your credentials to access your dashboard.</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-4">
<div className="space-y-4">
<form onSubmit={handleLogin} className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Email</label>
<input
type="email"
defaultValue="demo@pragativeer.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="student@example.com"
suppressHydrationWarning
className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF] focus:border-transparent transition-all"
@@ -45,7 +61,8 @@ export default function LoginPage() {
<div className="relative">
<input
type={showPassword ? "text" : "password"}
defaultValue="pragativeer2026"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
suppressHydrationWarning
className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-[#00F0FF] focus:border-transparent transition-all pr-10"
@@ -59,29 +76,19 @@ export default function LoginPage() {
</button>
</div>
</div>
</div>
</div>
<Link href="/dashboard" className="block">
<Button className="w-full rounded-xl h-12 font-bold text-base bg-foreground text-background hover:bg-foreground/90 transition-all shadow-[0_0_15px_-5px_#00F0FF]">
{error && (
<div className="p-3 bg-red-500/10 border border-red-500/20 rounded-xl text-red-500 text-sm font-medium text-center">
{error}
</div>
)}
<Button type="submit" className="w-full rounded-xl h-12 font-bold text-base bg-foreground text-background hover:bg-foreground/90 transition-all shadow-[0_0_15px_-5px_#00F0FF]">
Sign In
</Button>
</Link>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-border"></span>
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background/80 px-2 text-muted-foreground font-bold tracking-widest backdrop-blur-md">Or continue with</span>
</div>
</div>
<Link href="/dashboard" className="block">
<Button variant="outline" className="w-full rounded-xl h-12 font-bold bg-background border-border hover:bg-secondary/50 transition-all flex items-center justify-center gap-2">
<svg viewBox="0 0 24 24" className="h-5 w-5"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/><path d="M1 1h22v22H1z" fill="none"/></svg>
Google
</Button>
</Link>
</CardContent>
</Card>

View File

@@ -420,7 +420,7 @@ export default function MarketingPage() {
<Card className="bg-background/80 backdrop-blur-md border-border p-6 rounded-[2rem] flex flex-col hover:border-border/80 transition-colors h-full z-10 relative shadow-xl">
<h3 className="text-3xl font-bold mb-2">Free</h3>
<p className="text-muted-foreground mb-6 text-base">Perfect to get started.</p>
<div className="text-6xl font-bold tracking-tighter mb-6">₹0<span className="text-xl text-muted-foreground font-medium">/year</span></div>
<div className="text-6xl font-bold tracking-tighter mb-6">₹0<span className="text-xl text-white/60 font-medium tracking-normal ml-1">/year</span></div>
<ul className="space-y-3 mb-6 flex-1 font-medium text-lg">
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-white" /> 1 Daily Practice Sheet</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-white" /> Basic Performance Analytics</li>
@@ -440,9 +440,9 @@ export default function MarketingPage() {
transition={{ duration: 0.6, type: "spring", delay: 0.1 }}
>
<Card className="bg-background/80 backdrop-blur-md border-border p-6 rounded-[2rem] flex flex-col hover:border-border/80 transition-colors h-full z-10 relative shadow-xl">
<h3 className="text-3xl font-bold mb-2">Pro</h3>
<h3 className="text-3xl font-bold mb-2 text-yellow-500">Pro</h3>
<p className="text-muted-foreground mb-6 text-base">Perfect for self-paced practice.</p>
<div className="text-6xl font-bold tracking-tighter mb-6">₹12,499<span className="text-xl text-muted-foreground font-medium">/year</span></div>
<div className="text-6xl font-bold tracking-tighter mb-6 text-yellow-500">₹3,499<span className="text-xl text-yellow-500/60 font-medium tracking-normal ml-1">/year</span></div>
<ul className="space-y-3 mb-6 flex-1 font-medium text-lg">
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-yellow-500" /> Unlimited Daily Practice Sheets</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-yellow-500" /> Weekly Test Series & Mock Tests</li>
@@ -463,27 +463,25 @@ export default function MarketingPage() {
transition={{ duration: 0.6, type: "spring", delay: 0.2 }}
className="relative mt-8 lg:mt-0"
>
<Card className="bg-secondary/30 backdrop-blur-xl border-[#00F0FF] p-6 rounded-[2rem] flex flex-col transition-all h-full relative">
<div className="absolute inset-0 rounded-[2.5rem] overflow-hidden">
<div className="absolute inset-0 -translate-x-full animate-shine bg-gradient-to-r from-transparent via-white/10 to-transparent z-0 pointer-events-none"></div>
</div>
<div className="relative z-10 flex flex-col h-full mt-4">
<h3 className="text-3xl font-bold mb-2">AI Coach</h3>
<Card className="bg-background/80 backdrop-blur-md border-border p-6 rounded-[2rem] flex flex-col hover:border-border/80 transition-colors h-full z-10 relative shadow-xl">
<h3 className="text-3xl font-bold mb-2 text-[#00F0FF]">AI Coach</h3>
<p className="text-muted-foreground mb-6 text-base">The complete AI-driven system.</p>
<div className="text-6xl font-bold tracking-tighter mb-6 text-[#00F0FF] drop-shadow-[0_0_10px_rgba(0,240,255,0.5)]">₹4,999<span className="text-xl text-foreground font-medium">/year</span></div>
<div className="text-6xl font-bold tracking-tighter mb-6 text-[#00F0FF]">₹11,999<span className="text-xl text-[#00F0FF]/60 font-medium tracking-normal ml-1">/year</span></div>
<ul className="space-y-3 mb-6 flex-1 font-medium text-lg">
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-[#00F0FF]" /> Everything in Pro</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-[#00F0FF]" /> AI Spaced Repetition (Mistakes)</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-[#00F0FF]" /> 24/7 Streaming AI Tutor Chat</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-[#00F0FF]" /> Predictive Rank & College Predictor</li>
</ul>
<Link href="/signup">
<Button size="lg" className="w-full rounded-2xl h-12 font-bold text-lg bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 transition-all">Get AI Coach</Button>
</Link>
</div>
</Card>
</ul>
<Link href="/signup">
<Button size="lg" className="w-full rounded-2xl h-12 font-bold text-lg bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 transition-all">Get AI Coach</Button>
</Link>
</Card>
</motion.div>
</div>
<p className="text-center mt-8 text-sm text-muted-foreground font-medium w-full">
* All above prices are inclusive of GST.
</p>
</section>
{/* Tier Comparison Matrix */}
@@ -493,14 +491,14 @@ export default function MarketingPage() {
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">See exactly what you get with each PragatiVeer plan.</p>
</div>
<div className="overflow-x-auto rounded-2xl border border-border/50 bg-secondary/10 backdrop-blur-sm">
<div className="overflow-x-auto rounded-2xl border border-border/50 bg-secondary/10 backdrop-blur-sm p-3 md:p-6">
<table className="w-full text-left border-collapse min-w-[700px]">
<thead>
<tr className="border-b border-border/50 bg-background/50">
<th className="p-4 md:p-6 font-bold text-lg w-2/5">Feature</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-white">Free</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-yellow-500 border-x border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-[#00F0FF] border-x border-[#00F0FF]">AI Coach</th>
<th className="p-4 md:p-6 font-bold text-lg w-2/5 border-l border-t border-border/50">Feature</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-t border-white">Free</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-yellow-500 border-x border-t border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
</tr>
</thead>
<tbody className="divide-y divide-border/30 text-sm md:text-base">
@@ -529,7 +527,7 @@ export default function MarketingPage() {
{ name: "Study Time Optimization", free: false, pro: false, ai: true },
].map((feature, idx) => (
<tr key={idx} className="hover:bg-background/40 transition-colors">
<td className="p-4 md:p-6 font-medium border-y border-border/30">{feature.name}</td>
<td className="p-4 md:p-6 font-medium border-y border-l border-border/30">{feature.name}</td>
<td className="p-4 md:p-6 text-center text-muted-foreground border border-white">
{typeof feature.free === 'boolean' ? (feature.free ? <CheckCircle2 className="mx-auto text-white" size={20} /> : <Minus className="mx-auto text-white/30" size={20} />) : <span className="font-semibold text-white">{feature.free}</span>}
</td>
@@ -566,6 +564,7 @@ export default function MarketingPage() {
Re-engineering NEET & JEE preparation with adaptive AI models. Learn faster, rank higher.
</p>
<div className="flex items-center gap-4 pt-2">
{/*
<Link href="#" className="text-muted-foreground hover:text-foreground transition-colors">
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path></svg>
</Link>
@@ -575,6 +574,7 @@ export default function MarketingPage() {
<Link href="#" className="text-muted-foreground hover:text-foreground transition-colors">
<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
</Link>
*/}
</div>
</div>
@@ -600,8 +600,11 @@ export default function MarketingPage() {
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Legal</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="#" className="hover:text-primary transition-colors">Privacy Policy</Link></li>
<li><Link href="#" className="hover:text-primary transition-colors">Terms of Service</Link></li>
<li><Link href="/terms" className="hover:text-primary transition-colors">Terms & Conditions</Link></li>
<li><Link href="/privacy" className="hover:text-primary transition-colors">Privacy Policy</Link></li>
<li><Link href="/refund" className="hover:text-primary transition-colors">Refund Policy</Link></li>
<li><Link href="/cancellation" className="hover:text-primary transition-colors">Cancellation Policy</Link></li>
<li><Link href="/disclaimer" className="hover:text-primary transition-colors">Disclaimer</Link></li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,200 @@
import Link from 'next/link';
export default function PrivacyPolicyPage() {
return (
<div className="min-h-screen bg-background pt-24 pb-16">
<div className="max-w-4xl mx-auto px-6">
<h1 className="text-4xl md:text-5xl font-black tracking-tight mb-12 text-foreground">
PragatiVeer Privacy Policy
</h1>
<div className="prose prose-invert prose-lg max-w-none text-muted-foreground">
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 3.0<br/>
<strong>Document Ref:</strong> PV-LEG-PP-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Introduction</h2>
<p>PragatiVeer (“we”, “our”, “us”) is an exam preparation platform operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021), registered office at OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India.</p>
<p>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 <strong>Digital Personal Data Protection Act, 2023 (DPDP Act)</strong> of India.</p>
<p>By using PragatiVeer, you acknowledge that you have read and understood this policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. What Data We Collect</h2>
<h3 className="text-xl font-bold mt-6 mb-3">2.1 Information You Provide Directly</h3>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Category</th>
<th className="p-3 border">Examples</th>
<th className="p-3 border">Purpose</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Account Information</td><td className="p-3 border">Name, email address, phone number</td><td className="p-3 border">Account creation, service delivery, communication</td></tr>
<tr><td className="p-3 border font-bold">Academic Information</td><td className="p-3 border">Class/grade, target exam, school name, city</td><td className="p-3 border">Personalising content, weak-area analysis</td></tr>
<tr><td className="p-3 border font-bold">Payment Information</td><td className="p-3 border">Transaction ID, billing name (we do <strong>not</strong> store full card or UPI details)</td><td className="p-3 border">Subscription billing, invoicing</td></tr>
<tr><td className="p-3 border font-bold">Communications</td><td className="p-3 border">Messages sent via email or contact forms</td><td className="p-3 border">Support, feedback, grievance handling</td></tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-bold mt-6 mb-3">2.2 Information Collected Automatically</h3>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Category</th>
<th className="p-3 border">Examples</th>
<th className="p-3 border">Purpose</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Usage Data</td><td className="p-3 border">DPS attempts, scores, time spent, weak-area progression</td><td className="p-3 border">Personalising the learning experience</td></tr>
<tr><td className="p-3 border font-bold">Technical Data</td><td className="p-3 border">IP address, browser type, device type, operating system</td><td className="p-3 border">Service optimisation, fraud prevention, analytics</td></tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-bold mt-6 mb-3">2.3 Information from Third Parties</h3>
<p>We may receive transaction confirmation data from payment gateways but <strong>not</strong> full financial instrument details.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">3. How We Use Your Data</h2>
<p>We use your personal data only for:</p>
<ol className="list-decimal pl-6 mb-4 space-y-2">
<li><strong>Service Delivery</strong> — Personalised DPS, weak-area tracking, content delivery</li>
<li><strong>Personalisation</strong> — Adapting question difficulty based on performance</li>
<li><strong>Communication</strong> — Subscription reminders, service updates, support</li>
<li><strong>Payment Processing</strong> — Billing and subscription management</li>
<li><strong>Improvement</strong> — Analysing aggregate data to improve content quality</li>
<li><strong>Legal Compliance</strong> — Fulfilling obligations under DPDP Act and tax laws</li>
<li><strong>Security</strong> — Detecting fraud, abuse, and unauthorised access</li>
</ol>
<p>We do <strong>NOT</strong>:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Sell your personal data</li>
<li>Use your data to train AI models without explicit consent</li>
<li>Share your data for advertising</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">4. Users Under 18 Years of Age</h2>
<p>PragatiVeer is designed for students preparing for competitive exams, including those under 18 years of age.</p>
<p>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.</p>
<p>We encourage parents and guardians to monitor their child’s online activities and review this Privacy Policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Data Sharing</h2>
<p>We share your data only with trusted service providers bound by data protection agreements:</p>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Provider</th>
<th className="p-3 border">Purpose</th>
<th className="p-3 border">Safeguards</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Payment Gateway</td><td className="p-3 border">Payment processing</td><td className="p-3 border">PCI-DSS compliant; no full payment details stored</td></tr>
<tr><td className="p-3 border font-bold">AI Inference Providers</td><td className="p-3 border">Weak-area analysis, DPS generation</td><td className="p-3 border">Data anonymised before processing</td></tr>
<tr><td className="p-3 border font-bold">Self-hosted Infrastructure</td><td className="p-3 border">Storage, backups, disaster recovery</td><td className="p-3 border">No third-party access</td></tr>
</tbody>
</table>
</div>
<p>We do <strong>not</strong> share your data with advertising networks, data brokers, or for monetisation.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Data Retention & Deletion</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Data Category</th>
<th className="p-3 border">Retention Period</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Account Information</td><td className="p-3 border">Account duration + 12 months</td></tr>
<tr><td className="p-3 border font-bold">Academic Performance Data</td><td className="p-3 border">Account duration + 24 months</td></tr>
<tr><td className="p-3 border font-bold">Payment Records</td><td className="p-3 border">7 years (as required by tax laws)</td></tr>
<tr><td className="p-3 border font-bold">Communication Records</td><td className="p-3 border">12 months</td></tr>
</tbody>
</table>
</div>
<p>You may request account deletion by emailing <strong>contact@pragativeer.com</strong> with subject “Account Deletion Request”. We will delete your data within <strong>30 days</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Data Security</h2>
<p>We implement:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Encryption in transit (TLS 1.2+)</li>
<li>Encryption at rest</li>
<li>Role-based access controls</li>
<li>Self-hosted infrastructure</li>
<li>Documented breach notification process</li>
</ul>
<p>In the event of a data breach, we will notify the Data Protection Board of India and affected users within <strong>72 hours</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">8. Your Rights Under DPDP Act 2023</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Right</th>
<th className="p-3 border">How to Exercise</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Access your data</td><td className="p-3 border">Email contact@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Correct your data</td><td className="p-3 border">Update in account settings or email us</td></tr>
<tr><td className="p-3 border font-bold">Delete your data</td><td className="p-3 border">Email with subject “Account Deletion Request”</td></tr>
<tr><td className="p-3 border font-bold">Withdraw consent</td><td className="p-3 border">Email contact@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Lodge a grievance</td><td className="p-3 border">Contact Grievance Officer (Section 9)</td></tr>
<tr><td className="p-3 border font-bold">Nominate a representative</td><td className="p-3 border">Email with subject “Nomination Request”</td></tr>
</tbody>
</table>
</div>
<p>We respond to all requests within <strong>30 days</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">9. Grievance Officer</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Name</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
<tr><td className="p-3 border font-bold">Address</td><td className="p-3 border">OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045</td></tr>
<tr><td className="p-3 border font-bold">Response Time</td><td className="p-3 border">Acknowledged within 24 hours; resolved within 30 days</td></tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mt-8 mb-4">10. Cookies</h2>
<p>Our website uses essential cookies for functionality and anonymous analytics cookies. We do <strong>not</strong> use advertising cookies, cross-site tracking, or fingerprinting scripts.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">11. Changes to This Policy</h2>
<p>We will notify you of material changes via email with at least <strong>7 days’ notice</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">12. Contact</h2>
<p><strong>Email:</strong> contact@pragativeer.com<br/>
<strong>Address:</strong> OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India<br/>
<strong>Phone:</strong> +91 9079337961</p>
<h2 className="text-2xl font-bold mt-8 mb-4">13. Governing Law</h2>
<p>This Policy is governed by the laws of India. Disputes are subject to the exclusive jurisdiction of courts in Pune, Maharashtra.</p>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
</div>
</div>
</div>
);
}

135
src/app/privacy/page.tsx Normal file
View File

@@ -0,0 +1,135 @@
"use client";
import { PublicLayout } from "@/components/Landing/PublicLayout";
export default function PrivacyPolicyPage() {
return (
<PublicLayout>
<div className="bg-background min-h-screen pt-24 pb-16">
<div className="container max-w-4xl mx-auto px-4 md:px-8">
<div className="bg-secondary/20 p-8 md:p-12 rounded-3xl border border-border shadow-sm">
<div className="mb-10 border-b border-border pb-6">
<h1 className="text-3xl md:text-5xl font-black mb-4">PragatiVeer Privacy Policy</h1>
<p className="text-muted-foreground font-medium">Gyannetra Pvt Ltd</p>
<div className="mt-4 space-y-1 text-sm text-muted-foreground/80">
<p><span className="font-semibold text-muted-foreground">Last Updated:</span> 27 July 2026</p>
<p><span className="font-semibold text-muted-foreground">Version:</span> 3.0</p>
<p><span className="font-semibold text-muted-foreground">Document Ref:</span> PV-LEG-PP-003</p>
<p><span className="font-semibold text-muted-foreground">Applicable To:</span> PragatiVeer website (pragativeer.com) and associated services.</p>
</div>
</div>
<div className="space-y-8 prose prose-invert max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-li:text-muted-foreground">
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">1. Introduction</h2>
<p>
Gyannetra Pvt Ltd ("we," "us," or "our") respects the privacy of our users ("user" or "you"). This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website pragativeer.com, including any other media form, media channel, mobile website, or mobile application related or connected thereto (collectively, the "Site").
</p>
<p className="mt-2">
Please read this privacy policy carefully. If you do not agree with the terms of this privacy policy, please do not access the site.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">2. Collection of Your Information</h2>
<p>
We may collect information about you in a variety of ways. The information we may collect on the Site includes:
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">2.1 Personal Data</h3>
<p>
Personally identifiable information, such as your name, shipping address, email address, and telephone number, and demographic information, such as your age, gender, hometown, and educational interests, that you voluntarily give to us when you register with the Site or when you choose to participate in various activities related to the Site, such as online chat and message boards.
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">2.2 Educational and Performance Data</h3>
<p>
Because PragatiVeer is an adaptive learning platform, we collect detailed data regarding your test performance (CBT), daily practice problems (DPPs) attempted, time spent on questions, accuracy rates, and identified weak areas. This data is core to providing our AI-driven personalization services.
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">2.3 Derivative Data</h3>
<p>
Information our servers automatically collect when you access the Site, such as your IP address, your browser type, your operating system, your access times, and the pages you have viewed directly before and after accessing the Site.
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">2.4 Financial Data</h3>
<p>
Financial information, such as data related to your payment method (e.g., valid credit card number, card brand, expiration date) that we may collect when you purchase, order, return, exchange, or request information about our services from the Site. We store only very limited, if any, financial information that we collect. Otherwise, all financial information is stored by our payment processor, and you are encouraged to review their privacy policy and contact them directly for responses to your questions.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">3. Use of Your Information</h2>
<p>
Having accurate information about you permits us to provide you with a smooth, efficient, and customized experience. Specifically, we may use information collected about you via the Site to:
</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Create and manage your account.</li>
<li>Deliver targeted educational content, customized CBT exams, and adaptive DPPs based on your performance history.</li>
<li>Process your purchases, subscriptions, and payments.</li>
<li>Email you regarding your account, subscription status, or test results.</li>
<li>Fulfill and manage purchases, orders, payments, and other transactions related to the Site.</li>
<li>Generate a personal profile about you to make future visits to the Site more personalized.</li>
<li>Increase the efficiency and operation of the Site.</li>
<li>Monitor and analyze usage and trends to improve your experience with the Site.</li>
<li>Notify you of updates to the Site or our educational offerings.</li>
<li>Request feedback and contact you about your use of the Site.</li>
<li>Resolve disputes and troubleshoot problems.</li>
<li>Respond to product and customer service requests.</li>
</ul>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">4. Disclosure of Your Information</h2>
<p>
We may share information we have collected about you in certain situations. Your information may be disclosed as follows:
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">4.1 By Law or to Protect Rights</h3>
<p>
If we believe the release of information about you is necessary to respond to legal process, to investigate or remedy potential violations of our policies, or to protect the rights, property, and safety of others, we may share your information as permitted or required by any applicable law, rule, or regulation.
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">4.2 Third-Party Service Providers</h3>
<p>
We may share your information with third parties that perform services for us or on our behalf, including payment processing, data analysis, email delivery, hosting services, customer service, and marketing assistance.
</p>
<h3 className="text-lg font-semibold mt-4 mb-2">4.3 Educational Institutions/Parents (If applicable)</h3>
<p>
If your account was provisioned through a school/coaching institute partnership, or if a parent/guardian account is linked to your student account, we may share your performance and usage data with those authorized parties.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">5. Data Security</h2>
<p>
We use administrative, technical, and physical security measures to help protect your personal information. While we have taken reasonable steps to secure the personal information you provide to us, please be aware that despite our efforts, no security measures are perfect or impenetrable, and no method of data transmission can be guaranteed against any interception or other type of misuse.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">6. Policy for Children</h2>
<p>
We do not knowingly solicit information from or market to children under the age of 13. If you become aware of any data we have collected from children under age 13 without parental consent, please contact us using the contact information provided below. We encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">7. Contact Us</h2>
<p>
If you have questions or comments about this Privacy Policy, please contact us at:
</p>
<div className="mt-4 p-4 bg-background rounded-xl border border-border text-sm">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>Email: contact@pragativeer.com</p>
<p>Phone: +91 9079337961</p>
<p>Address: OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
</div>
</section>
</div>
</div>
</div>
</div>
</PublicLayout>
);
}

View File

@@ -0,0 +1,96 @@
import Link from 'next/link';
export default function RefundPolicyPage() {
return (
<div className="min-h-screen bg-background pt-24 pb-16">
<div className="max-w-4xl mx-auto px-6">
<h1 className="text-4xl md:text-5xl font-black tracking-tight mb-12 text-foreground">
PragatiVeer Refund Policy
</h1>
<div className="prose prose-invert prose-lg max-w-none text-muted-foreground">
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 1.0<br/>
<strong>Document Ref:</strong> PV-LEG-RP-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Overview</h2>
<p>This Refund Policy governs all paid subscriptions on PragatiVeer, operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021).</p>
<p>By purchasing a subscription, you agree to the terms of this policy.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. Subscription Plans</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Plan</th>
<th className="p-3 border">Price (incl. GST)</th>
<th className="p-3 border">Duration</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Pro</td><td className="p-3 border">₹3,499</td><td className="p-3 border">Per annum</td></tr>
<tr><td className="p-3 border font-bold">AI Coach</td><td className="p-3 border">₹11,999</td><td className="p-3 border">Per annum</td></tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Refund Eligibility</h2>
<p>You may request a full refund within <strong>7 days</strong> of purchase provided that you have <strong>not accessed more than 5 DPS sheets</strong> during that period.</p>
<p>Refunds will <strong>not</strong> be issued if:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>More than 7 days have passed since purchase</li>
<li>You have accessed more than 5 DPS sheets</li>
<li>The request is made after the subscription has been active beyond the refund window</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">4. How to Request a Refund</h2>
<p>To request a refund, email <strong>contact@pragativeer.com</strong> with:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li><strong>Subject:</strong> “Refund Request”</li>
<li><strong>Your registered email address or phone number</strong></li>
<li><strong>Order/transaction ID</strong> (if available)</li>
</ul>
<p>We will process your request within <strong>7-10 business days</strong> and the refund will be credited to the original payment method.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Non-Refundable Items</h2>
<p>The following are non-refundable:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Subscription fees beyond the 7-day refund window</li>
<li>Discounted or promotional purchases (unless otherwise stated)</li>
<li>Any add-on services purchased separately</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">6. Cancellation</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Contact</h2>
<p>For any questions regarding this Refund Policy, contact us at <strong>contact@pragativeer.com</strong> or reach out to our Grievance Officer:</p>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Grievance Officer</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
</tbody>
</table>
</div>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
</div>
</div>
</div>
);
}

148
src/app/refund/page.tsx Normal file
View File

@@ -0,0 +1,148 @@
"use client";
import { PublicLayout } from "@/components/Landing/PublicLayout";
export default function RefundPolicyPage() {
return (
<PublicLayout>
<div className="bg-background min-h-screen pt-24 pb-16">
<div className="container max-w-4xl mx-auto px-4 md:px-8">
<div className="bg-secondary/20 p-8 md:p-12 rounded-3xl border border-border shadow-sm">
<div className="mb-10 border-b border-border pb-6">
<h1 className="text-3xl md:text-5xl font-black mb-4">PragatiVeer Refund Policy</h1>
<p className="text-muted-foreground font-medium">Gyannetra Pvt Ltd</p>
<div className="mt-4 space-y-1 text-sm text-muted-foreground/80">
<p><span className="font-semibold text-muted-foreground">Last Updated:</span> 27 July 2026</p>
<p><span className="font-semibold text-muted-foreground">Version:</span> 1.0</p>
<p><span className="font-semibold text-muted-foreground">Document Ref:</span> PV-LEG-RP-001</p>
<p><span className="font-semibold text-muted-foreground">Applicable To:</span> PragatiVeer website (pragativeer.com) and associated services.</p>
</div>
</div>
<div className="space-y-8 prose prose-invert max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-li:text-muted-foreground">
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">1. Overview</h2>
<p>
This Refund Policy governs all paid subscriptions on PragatiVeer, operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021).
</p>
<p className="mt-2">
By purchasing a subscription, you agree to the terms of this policy.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">2. Subscription Plans</h2>
<div className="overflow-x-auto my-4">
<table className="min-w-full border-collapse border border-border text-sm">
<thead>
<tr className="bg-secondary/50">
<th className="border border-border px-4 py-2 text-left">Plan</th>
<th className="border border-border px-4 py-2 text-left">Price (incl. GST)</th>
<th className="border border-border px-4 py-2 text-left">Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border border-border px-4 py-2 font-medium">Pro</td>
<td className="border border-border px-4 py-2">₹3,499</td>
<td className="border border-border px-4 py-2">Per annum</td>
</tr>
<tr>
<td className="border border-border px-4 py-2 font-medium">AI Coach</td>
<td className="border border-border px-4 py-2">₹11,999</td>
<td className="border border-border px-4 py-2">Per annum</td>
</tr>
</tbody>
</table>
</div>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">3. Refund Eligibility</h2>
<p>
You may request a full refund within <strong>7 days</strong> of purchase provided that you have <strong>not accessed more than 5 DPS sheets</strong> during that period.
</p>
<p className="mt-4">Refunds will <strong>not</strong> be issued if:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>More than 7 days have passed since purchase</li>
<li>You have accessed more than 5 DPS sheets</li>
<li>The request is made after the subscription has been active beyond the refund window</li>
</ul>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">4. How to Request a Refund</h2>
<p>To request a refund, email <strong>contact@pragativeer.com</strong> with:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li><strong>Subject:</strong> &ldquo;Refund Request&rdquo;</li>
<li><strong>Your registered email address or phone number</strong></li>
<li><strong>Order/transaction ID</strong> (if available)</li>
</ul>
<p className="mt-4">
We will process your request within <strong>7-10 business days</strong> and the refund will be credited to the original payment method.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">5. Non-Refundable Items</h2>
<p>The following are non-refundable:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Subscription fees beyond the 7-day refund window</li>
<li>Discounted or promotional purchases (unless otherwise stated)</li>
<li>Any add-on services purchased separately</li>
</ul>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">6. Cancellation</h2>
<p>
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.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">7. Contact</h2>
<p>
For any questions regarding this Refund Policy, contact us at <strong>contact@pragativeer.com</strong> or reach out to our Grievance Officer:
</p>
<div className="overflow-x-auto my-4">
<table className="min-w-full border-collapse border border-border text-sm">
<thead>
<tr className="bg-secondary/50">
<th className="border border-border px-4 py-2 text-left">Detail</th>
<th className="border border-border px-4 py-2 text-left">Information</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border border-border px-4 py-2 font-medium">Grievance Officer</td>
<td className="border border-border px-4 py-2">Himani Kachhawaha</td>
</tr>
<tr>
<td className="border border-border px-4 py-2 font-medium">Email</td>
<td className="border border-border px-4 py-2">grievance@pragativeer.com</td>
</tr>
<tr>
<td className="border border-border px-4 py-2 font-medium">Phone</td>
<td className="border border-border px-4 py-2">+91 9079337961</td>
</tr>
</tbody>
</table>
</div>
</section>
<section>
<div className="mt-8 p-4 bg-background rounded-xl border border-border text-sm">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
</section>
</div>
</div>
</div>
</div>
</PublicLayout>
);
}

View File

@@ -11,13 +11,13 @@ import { Logo } from "@/components/Logo"
export default function SignupPage() {
const [step, setStep] = useState(1)
const [showPassword, setShowPassword] = useState(false)
const [authMethod, setAuthMethod] = useState<"email" | "phone">("email")
const [showOTP, setShowOTP] = useState(false)
const [isSubmitted, setIsSubmitted] = useState(false)
const router = useRouter()
const handleNextStep = () => {
if (step === 1) setStep(2)
else router.push("/dashboard")
else setIsSubmitted(true)
}
return (
@@ -45,125 +45,133 @@ export default function SignupPage() {
</div>
</div>
<Card className="border-border bg-secondary/10 backdrop-blur-xl shadow-[0_0_50px_-20px_var(--color-primary)] rounded-[2rem] overflow-hidden">
<CardHeader className="text-center pb-2">
<CardTitle className="text-2xl font-bold tracking-tight">
{step === 1 ? "Create an account" : "Complete your profile"}
</CardTitle>
<CardDescription className="text-muted-foreground">
{step === 1 ? "Start your AI-powered preparation journey today." : "Help us personalize your study plan."}
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-4">
{isSubmitted ? (
<Card className="border-border bg-secondary/10 backdrop-blur-xl shadow-[0_0_50px_-20px_var(--color-primary)] rounded-[2rem] overflow-hidden text-center py-8">
<CardContent className="space-y-6 pt-4 flex flex-col items-center">
<CheckCircle2 size={64} className="text-primary mb-4" />
<h2 className="text-3xl font-black tracking-tight text-foreground">Thanks for registering!</h2>
<p className="text-muted-foreground text-sm font-medium px-4 leading-relaxed">
Our team will get back to you shortly. <br/><br/>
<span className="font-bold text-foreground">Note:</span> We are not live currently. Please use the demo credentials provided at the login screen to explore the platform.
</p>
<Link href="/login" className="w-full mt-4">
<Button className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]">
Go to Login
</Button>
</Link>
</CardContent>
</Card>
) : (
<Card className="border-border bg-secondary/10 backdrop-blur-xl shadow-[0_0_50px_-20px_var(--color-primary)] rounded-[2rem] overflow-hidden">
<CardHeader className="text-center pb-2">
<CardTitle className="text-2xl font-bold tracking-tight">
{step === 1 ? "Create an account" : "Complete your profile"}
</CardTitle>
<CardDescription className="text-muted-foreground">
{step === 1 ? "Start your AI-powered preparation journey today." : "Help us personalize your study plan."}
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-4">
{step === 1 ? (
<div className="space-y-4 animate-in fade-in zoom-in-95">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">First Name</label>
<input type="text" placeholder="John" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Last Name</label>
<input type="text" placeholder="Doe" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
</div>
<div className="flex bg-secondary p-1 rounded-xl">
<button type="button" onClick={() => setAuthMethod("email")} className={`flex-1 text-sm font-bold py-2 rounded-lg transition-all ${authMethod === "email" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`}>Email</button>
<button type="button" onClick={() => setAuthMethod("phone")} className={`flex-1 text-sm font-bold py-2 rounded-lg transition-all ${authMethod === "phone" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`}>Phone</button>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">{authMethod === "email" ? "Email Address" : "Phone Number"}</label>
<div className="flex gap-2">
<input type={authMethod === "email" ? "email" : "tel"} placeholder={authMethod === "email" ? "student@example.com" : "+91 9876543210"} className="flex-1 bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
{!showOTP && (
<Button variant="secondary" onClick={() => setShowOTP(true)} className="h-[46px] rounded-xl font-bold">Send OTP</Button>
)}
</div>
</div>
{showOTP && (
<div className="space-y-2 animate-in slide-in-from-top-2">
<label className="text-sm font-bold text-foreground flex justify-between">
<span>Verification Code</span>
<span className="text-primary text-xs">Resend in 30s</span>
</label>
<div className="flex gap-2">
{[1,2,3,4,5,6].map((i) => (
<input key={i} type="text" maxLength={1} className="w-12 h-12 text-center text-lg font-bold bg-background border border-border rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" />
))}
{step === 1 ? (
<div className="space-y-4 animate-in fade-in zoom-in-95">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">First Name</label>
<input type="text" placeholder="John" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Last Name</label>
<input type="text" placeholder="Doe" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
</div>
)}
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Password (Optional if using OTP)</label>
<div className="relative">
<input type={showPassword ? "text" : "password"} placeholder="••••••••" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all pr-10"/>
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors">
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Phone Number</label>
<div className="flex gap-2">
<div className="flex items-center justify-center bg-secondary/50 border border-border rounded-xl px-3 text-sm font-bold text-muted-foreground">
+91
</div>
<input
type="tel"
maxLength={10}
pattern="[0-9]*"
onInput={(e) => {
e.currentTarget.value = e.currentTarget.value.replace(/[^0-9]/g, '');
}}
placeholder="9876543210"
className="flex-1 bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
/>
{!showOTP && (
<Button variant="secondary" onClick={() => setShowOTP(true)} className="h-[46px] rounded-xl font-bold">Send OTP</Button>
)}
</div>
</div>
{showOTP && (
<div className="space-y-2 animate-in slide-in-from-top-2">
<label className="text-sm font-bold text-foreground flex justify-between">
<span>Verification Code</span>
<span className="text-primary text-xs">Resend in 30s</span>
</label>
<div className="flex gap-2">
{[1,2,3,4,5,6].map((i) => (
<input key={i} type="text" maxLength={1} className="w-12 h-12 text-center text-lg font-bold bg-background border border-border rounded-xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" />
))}
</div>
</div>
)}
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Password (Optional if using OTP)</label>
<div className="relative">
<input type={showPassword ? "text" : "password"} placeholder="••••••••" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all pr-10"/>
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors">
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
</div>
</div>
) : (
<div className="space-y-4 animate-in fade-in slide-in-from-right-8">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Target Exam</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="jee_main">JEE Main</option>
<option value="jee_adv">JEE Advanced (Main + Adv)</option>
<option value="neet">NEET UG</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Current Class</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="11">Class 11</option>
<option value="12">Class 12</option>
<option value="dropper">Dropper / 12th Pass</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Study Medium</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="en">English</option>
<option value="hi">Hindi (हिंदी)</option>
<option value="bi">Bilingual (English + Hindi)</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">School / Coaching Name <span className="text-muted-foreground font-normal">(Optional)</span></label>
<input type="text" placeholder="e.g. DPS R.K. Puram" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
</div>
)}
<Button onClick={handleNextStep} className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]">
{step === 1 ? "Continue" : "Complete Setup"}
</Button>
{step === 1 && (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-border"></span>
) : (
<div className="space-y-4 animate-in fade-in slide-in-from-right-8">
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Target Exam</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="jee_main">JEE Main</option>
<option value="jee_adv">JEE Advanced</option>
<option value="neet">NEET</option>
<option value="jee_neet">JEE + NEET</option>
</select>
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background/80 px-2 text-muted-foreground font-bold tracking-widest backdrop-blur-md">Or continue with</span>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Current Class</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="11">Class 11th</option>
<option value="12">Class 12th</option>
<option value="dropper">Dropper</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">Study Medium</label>
<select className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all appearance-none cursor-pointer">
<option value="en">English</option>
<option value="hi">Hindi (हिंदी)</option>
<option value="bi">Bilingual (English + Hindi)</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-bold text-foreground">School / Coaching Name <span className="text-muted-foreground font-normal">(Optional)</span></label>
<input type="text" placeholder="e.g. DPS R.K. Puram" className="w-full bg-background border border-border rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"/>
</div>
</div>
)}
<Button variant="outline" className="w-full rounded-xl h-12 font-bold bg-background border-border hover:bg-secondary/50 transition-all flex items-center justify-center gap-2">
<svg viewBox="0 0 24 24" className="h-5 w-5"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/><path d="M1 1h22v22H1z" fill="none"/></svg>
Google
</Button>
</>
)}
</CardContent>
</Card>
<Button onClick={handleNextStep} className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]">
{step === 1 ? "Continue" : "Complete Setup"}
</Button>
</CardContent>
</Card>
)}
{step === 1 ? (
<p className="text-center text-sm text-muted-foreground font-medium">

View File

@@ -0,0 +1,128 @@
import Link from 'next/link';
export default function TermsAndConditionsPage() {
return (
<div className="min-h-screen bg-background pt-24 pb-16">
<div className="max-w-4xl mx-auto px-6">
<h1 className="text-4xl md:text-5xl font-black tracking-tight mb-12 text-foreground">
PragatiVeer Terms & Conditions
</h1>
<div className="prose prose-invert prose-lg max-w-none text-muted-foreground">
<p className="text-muted-foreground mb-8"><strong>Last Updated:</strong> 27 July 2026<br/>
<strong>Version:</strong> 3.0<br/>
<strong>Document Ref:</strong> PV-LEG-TC-001<br/>
<strong>Applicable To:</strong> PragatiVeer website (pragativeer.com) and associated services.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">1. Introduction</h2>
<p>PragatiVeer (“we”, “our”, “us”) is an exam preparation platform operated by <strong>Gyannetra Pvt Ltd</strong> (CIN: U72900PN2015PTC156021), registered office at OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India.</p>
<p>By accessing or using PragatiVeer, you agree to these Terms. If you do not agree, do not use the Platform.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">2. Eligibility</h2>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You must be at least <strong>13 years of age</strong>.</li>
<li>If you are between <strong>13 and 18 years</strong>, by using the Platform you represent and warrant that you have obtained the necessary consent from your parent or lawful guardian. PragatiVeer is not liable for verifying such consent.</li>
<li>Parents or guardians are encouraged to supervise their child’s use of the Platform.</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">3. Account</h2>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You are responsible for your account credentials and all activity under your account.</li>
<li>Notify us immediately at <strong>contact@pragativeer.com</strong> of any unauthorised use.</li>
<li>We may suspend or terminate your account if you violate these Terms or engage in harmful, fraudulent, or illegal activity.</li>
</ul>
<h2 className="text-2xl font-bold mt-8 mb-4">4. Subscriptions & Payments</h2>
<h3 className="text-xl font-bold mt-6 mb-3">4.1 Plans</h3>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Plan</th>
<th className="p-3 border">Price (incl. GST)</th>
<th className="p-3 border">Duration</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Pro</td><td className="p-3 border">₹3,499</td><td className="p-3 border">Per annum</td></tr>
<tr><td className="p-3 border font-bold">AI Coach</td><td className="p-3 border">₹11,999</td><td className="p-3 border">Per annum</td></tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-bold mt-6 mb-3">4.2 Payment</h3>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>All payments are processed through our <strong>payment gateway partner</strong>. We do not store your full card or UPI details.</li>
<li>Fees are non-refundable except as stated in our Refund Policy (Section 5).</li>
<li>Prices may change with 30 days’ notice.</li>
</ul>
<h3 className="text-xl font-bold mt-6 mb-3">4.3 Auto-Renewal</h3>
<p>Subscriptions auto-renew at the end of each billing period unless cancelled at least <strong>24 hours</strong> before renewal. You may cancel auto-renewal from your account settings or by emailing <strong>contact@pragativeer.com</strong>.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">5. Refund Policy</h2>
<p>You may request a full refund within <strong>7 days</strong> of purchase if you have not accessed more than 5 DPS sheets. No refunds after 7 days. To request a refund, email <strong>contact@pragativeer.com</strong> with subject “Refund Request”.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">6. User Conduct</h2>
<p>You agree to:</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Use the Platform only for lawful purposes</li>
<li>Not harass, abuse, or harm other users</li>
<li>Not use bots, scrapers, or automated tools to access the Platform</li>
<li>Not attempt to hack, reverse engineer, or disrupt the Platform</li>
</ul>
<p>Violation may result in account suspension or termination.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">7. Intellectual Property</h2>
<p>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.</p>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">8. Privacy</h2>
<p>Our data practices are governed by our <strong>Privacy Policy</strong> and the <strong>Digital Personal Data Protection Act, 2023</strong>. We do not sell your data.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">9. No Guarantee of Results</h2>
<p>PragatiVeer is a practice tool. We do <strong>not</strong> guarantee any specific exam score, rank, or admission.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">10. Limitation of Liability</h2>
<p>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.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">11. Termination</h2>
<p>You may delete your account anytime via settings or by emailing <strong>contact@pragativeer.com</strong>. We may terminate your account for breach of these Terms. Fees paid are non-refundable upon termination.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">12. Changes to Terms</h2>
<p>We may update these Terms with <strong>7 days’ notice</strong> via email. Continued use after changes take effect constitutes acceptance.</p>
<h2 className="text-2xl font-bold mt-8 mb-4">13. Grievance Officer</h2>
<div className="overflow-x-auto mb-6">
<table className="w-full border-collapse">
<thead>
<tr className="bg-secondary/50 text-left">
<th className="p-3 border">Detail</th>
<th className="p-3 border">Information</th>
</tr>
</thead>
<tbody>
<tr><td className="p-3 border font-bold">Name</td><td className="p-3 border">Himani Kachhawaha</td></tr>
<tr><td className="p-3 border font-bold">Email</td><td className="p-3 border">grievance@pragativeer.com</td></tr>
<tr><td className="p-3 border font-bold">Phone</td><td className="p-3 border">+91 9079337961</td></tr>
<tr><td className="p-3 border font-bold">Address</td><td className="p-3 border">OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045</td></tr>
<tr><td className="p-3 border font-bold">Response Time</td><td className="p-3 border">Acknowledged within 24 hours; resolved within 30 days</td></tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mt-8 mb-4">14. Governing Law</h2>
<p>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.</p>
<div className="mt-8 p-6 bg-secondary/20 rounded-xl">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>CIN: U72900PN2015PTC156021</p>
<p>OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
<p>pragativeer.com</p>
</div>
</div>
</div>
</div>
);
}

141
src/app/terms/page.tsx Normal file
View File

@@ -0,0 +1,141 @@
"use client";
import { PublicLayout } from "@/components/Landing/PublicLayout";
export default function TermsAndConditionsPage() {
return (
<PublicLayout>
<div className="bg-background min-h-screen pt-24 pb-16">
<div className="container max-w-4xl mx-auto px-4 md:px-8">
<div className="bg-secondary/20 p-8 md:p-12 rounded-3xl border border-border shadow-sm">
<div className="mb-10 border-b border-border pb-6">
<h1 className="text-3xl md:text-5xl font-black mb-4">PragatiVeer Terms &amp; Conditions</h1>
<p className="text-muted-foreground font-medium">Gyannetra Pvt Ltd</p>
<div className="mt-4 space-y-1 text-sm text-muted-foreground/80">
<p><span className="font-semibold text-muted-foreground">Last Updated:</span> 27 July 2026</p>
<p><span className="font-semibold text-muted-foreground">Version:</span> 3.0</p>
<p><span className="font-semibold text-muted-foreground">Document Ref:</span> PV-LEG-TC-003</p>
<p><span className="font-semibold text-muted-foreground">Applicable To:</span> PragatiVeer website (pragativeer.com) and associated services.</p>
</div>
</div>
<div className="space-y-8 prose prose-invert max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-li:text-muted-foreground">
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">1. Agreement to Terms</h2>
<p>
These Terms and Conditions constitute a legally binding agreement made between you, whether personally or on behalf of an entity ("you") and Gyannetra Pvt Ltd ("we," "us," or "our"), concerning your access to and use of the PragatiVeer website as well as any other media form, media channel, mobile website, or mobile application related, linked, or otherwise connected thereto (collectively, the "Site").
</p>
<p className="mt-2">
You agree that by accessing the Site, you have read, understood, and agree to be bound by all of these Terms and Conditions. IF YOU DO NOT AGREE WITH ALL OF THESE TERMS AND CONDITIONS, THEN YOU ARE EXPRESSLY PROHIBITED FROM USING THE SITE AND YOU MUST DISCONTINUE USE IMMEDIATELY.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">2. Intellectual Property Rights</h2>
<p>
Unless otherwise indicated, the Site is our proprietary property and all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics on the Site (collectively, the "Content") and the trademarks, service marks, and logos contained therein (the "Marks") are owned or controlled by us or licensed to us, and are protected by copyright and trademark laws and various other intellectual property rights and unfair competition laws of India, international copyright laws, and international conventions.
</p>
<p className="mt-2">
Provided that you are eligible to use the Site, you are granted a limited license to access and use the Site and to download or print a copy of any portion of the Content to which you have properly gained access solely for your personal, non-commercial, educational use.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">3. User Representations</h2>
<p>
By using the Site, you represent and warrant that:
</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>All registration information you submit will be true, accurate, current, and complete.</li>
<li>You will maintain the accuracy of such information and promptly update such registration information as necessary.</li>
<li>You have the legal capacity and you agree to comply with these Terms and Conditions (If you are a minor, you have received parental permission to use the Site).</li>
<li>You will not access the Site through automated or non-human means, whether through a bot, script, or otherwise.</li>
<li>You will not use the Site for any illegal or unauthorized purpose.</li>
<li>Your use of the Site will not violate any applicable law or regulation.</li>
</ul>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">4. User Registration</h2>
<p>
You may be required to register with the Site to access certain educational features like CBT and DPPs. You agree to keep your password confidential and will be responsible for all use of your account and password. We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that such username is inappropriate, obscene, or otherwise objectionable.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">5. Subscriptions and Payments</h2>
<p>
We offer various subscription plans (e.g., Free, Pro, AI Coach) for access to premium educational content. By selecting a premium plan, you agree to pay the fees specified for that plan. All fees are inclusive of GST unless otherwise stated.
</p>
<p className="mt-2">
Payments are processed securely through our third-party payment gateways. We do not store your full credit card or banking details on our servers.
</p>
<p className="mt-2">
For details regarding refunds and cancellations, please refer to our separate <strong>Refund Policy</strong> and <strong>Cancellation Policy</strong> documents.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">6. Prohibited Activities</h2>
<p>
You may not access or use the Site for any purpose other than that for which we make the Site available. The Site may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us.
</p>
<p className="mt-2">
As a user of the Site, you agree not to:
</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Systematically retrieve data or other content from the Site to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us.</li>
<li>Circumvent, disable, or otherwise interfere with security-related features of the Site.</li>
<li>Engage in unauthorized framing of or linking to the Site.</li>
<li>Make improper use of our support services or submit false reports of abuse or misconduct.</li>
<li>Engage in any automated use of the system, such as using scripts to send comments or messages, or using any data mining, robots, or similar data gathering and extraction tools.</li>
<li>Interfere with, disrupt, or create an undue burden on the Site or the networks or services connected to the Site.</li>
<li>Attempt to bypass any measures of the Site designed to prevent or restrict access to the Site, or any portion of the Site.</li>
<li>Copy or adapt the Site's software, including but not limited to Flash, PHP, HTML, JavaScript, or other code.</li>
<li>Share your account credentials with multiple individuals to bypass subscription fees. Account sharing is strictly prohibited and may result in immediate termination of your account without refund.</li>
</ul>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">7. Privacy Policy</h2>
<p>
We care about data privacy and security. Please review our <strong>Privacy Policy</strong>. By using the Site, you agree to be bound by our Privacy Policy, which is incorporated into these Terms and Conditions. Please be advised the Site is hosted in India.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">8. Modifications and Interruptions</h2>
<p>
We reserve the right to change, modify, or remove the contents of the Site at any time or for any reason at our sole discretion without notice. However, we have no obligation to update any information on our Site. We also reserve the right to modify or discontinue all or part of the Site without notice at any time.
</p>
<p className="mt-2">
We will not be liable to you or any third party for any modification, price change, suspension, or discontinuance of the Site. We cannot guarantee the Site will be available at all times. We may experience hardware, software, or other problems or need to perform maintenance related to the Site, resulting in interruptions, delays, or errors.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">9. Governing Law</h2>
<p>
These Terms shall be governed by and defined following the laws of India. Gyannetra Pvt Ltd and yourself irrevocably consent that the courts of Pune, Maharashtra, India shall have exclusive jurisdiction to resolve any dispute which may arise in connection with these terms.
</p>
</section>
<section>
<h2 className="text-xl font-bold border-b border-border pb-2 mb-4">10. Contact Us</h2>
<p>
In order to resolve a complaint regarding the Site or to receive further information regarding use of the Site, please contact us at:
</p>
<div className="mt-4 p-4 bg-background rounded-xl border border-border text-sm">
<p className="font-bold">Gyannetra Pvt Ltd</p>
<p>Email: contact@pragativeer.com</p>
<p>Phone: +91 9079337961</p>
<p>Address: OFF NO 201, 2ND FLOOR, SAI EMPIRE, S NO 183/1/2, Baner Gaon, Haveli, Pune - 411045, Maharashtra, India</p>
</div>
</section>
</div>
</div>
</div>
</div>
</PublicLayout>
);
}

View File

@@ -3,7 +3,7 @@
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Check, X, ShieldCheck, Loader2, Minus } from "lucide-react"
import { Check, X, ShieldCheck, Loader2, Minus, CheckCircle2 } from "lucide-react"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -103,42 +103,46 @@ export default function UpgradePage() {
<p className="text-xl text-muted-foreground">Unlock the full power of PragatiVeer to master JEE & NEET.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 items-start">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 items-stretch">
{/* Free Tier */}
<Card className="bg-background/80 backdrop-blur-md border-white p-6 rounded-[2rem] flex flex-col h-full z-10 relative">
<CardHeader>
<CardTitle className="text-2xl">Free</CardTitle>
<CardDescription>Basic access for everyone.</CardDescription>
<div className="mt-4 font-black text-4xl">₹10<span className="text-xl text-muted-foreground font-semibold">/year</span></div>
</CardHeader>
<CardContent className="space-y-4 text-sm font-medium">
<div className="flex items-center gap-3"><Check className="text-white" size={18}/> Subject-wise diagnostics</div>
<div className="flex items-center gap-3"><Check className="text-white" size={18}/> Basic progress tracking</div>
<div className="flex items-center gap-3 text-muted-foreground opacity-50"><Minus size={18}/> Tests</div>
<div className="flex items-center gap-3 text-muted-foreground opacity-50"><Minus size={18}/> AI Spaced Repetition</div>
<div className="flex items-center gap-3 text-muted-foreground opacity-50"><Minus size={18}/> Parent Analytics</div>
</CardContent>
<CardFooter>
<Button variant="outline" className="w-full rounded-2xl h-12 border-white text-white font-bold text-lg hover:bg-secondary hover:text-white" disabled>Current Plan</Button>
<Card className="bg-background/80 backdrop-blur-md border border-border p-6 rounded-[2rem] flex flex-col justify-between h-full z-10 relative shadow-xl hover:border-border/80 transition-colors">
<div>
<CardHeader className="p-0 mb-6">
<CardTitle className="text-3xl font-bold mb-1 text-foreground">Free</CardTitle>
<CardDescription className="text-muted-foreground mb-6 text-base">Perfect to get started.</CardDescription>
<div className="text-6xl font-bold tracking-tighter mb-6 text-foreground">
₹0<span className="text-xl text-white/60 font-medium tracking-normal ml-1">/year</span>
</div>
</CardHeader>
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> 1 Daily Practice Sheet</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Basic Performance Analytics</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-foreground shrink-0" size={24}/> Community Access</div>
</CardContent>
</div>
<CardFooter className="p-0">
<Button variant="outline" className="w-full rounded-2xl h-12 border-border text-foreground font-bold text-lg hover:bg-secondary" disabled>Get Started Free</Button>
</CardFooter>
</Card>
{/* Pro Tier */}
<Card className="bg-background/80 backdrop-blur-md border-2 border-yellow-500 shadow-[0_0_25px_-5px_var(--color-yellow-500)] p-6 rounded-[2rem] flex flex-col h-full z-10 relative">
<CardHeader>
<CardTitle className="text-2xl text-yellow-500">Pro</CardTitle>
<CardDescription>For serious aspirants.</CardDescription>
<div className="mt-4 font-black text-4xl">₹1299<span className="text-xl text-muted-foreground font-semibold">/year</span></div>
</CardHeader>
<CardContent className="space-y-4 text-sm font-medium">
<div className="flex items-center gap-3"><Check className="text-yellow-500" size={18}/> Everything in Free</div>
<div className="flex items-center gap-3"><Check className="text-yellow-500" size={18}/> Unlimited Sectional Tests</div>
<div className="flex items-center gap-3"><Check className="text-yellow-500" size={18}/> Performance Analytics</div>
<div className="flex items-center gap-3"><Check className="text-yellow-500" size={18}/> Parent Dashboard</div>
<div className="flex items-center gap-3 text-muted-foreground opacity-50"><Minus size={18}/> AI Mistake Heatmap</div>
<div className="flex items-center gap-3 text-muted-foreground opacity-50"><Minus size={18}/> Spaced Repetition (SM-2)</div>
</CardContent>
<CardFooter>
<Card className="bg-background/80 backdrop-blur-md border border-border p-6 rounded-[2rem] flex flex-col justify-between h-full z-10 relative shadow-xl hover:border-border/80 transition-colors">
<div>
<CardHeader className="p-0 mb-6">
<CardTitle className="text-3xl font-bold mb-1 text-yellow-500">Pro</CardTitle>
<CardDescription className="text-muted-foreground mb-6 text-base">Perfect for self-paced practice.</CardDescription>
<div className="text-6xl font-bold tracking-tighter mb-6 text-yellow-500">
₹3,499<span className="text-xl text-yellow-500/60 font-medium tracking-normal ml-1">/year</span>
</div>
</CardHeader>
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Unlimited Daily Practice Sheets</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Weekly Test Series & Mock Tests</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Detailed Performance Analytics</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-yellow-500 shrink-0" size={24}/> Parent Dashboard Access</div>
</CardContent>
</div>
<CardFooter className="p-0">
<Button
className="w-full bg-yellow-500 text-black hover:bg-yellow-400 font-bold text-lg rounded-2xl h-12"
onClick={() => handleUpgrade("Pro")}
@@ -150,20 +154,23 @@ export default function UpgradePage() {
</Card>
{/* AI Coach Tier */}
<Card className="bg-secondary/30 backdrop-blur-xl border-2 border-[#00F0FF] shadow-[0_0_25px_-5px_#00F0FF] p-6 rounded-[2rem] flex flex-col transition-all h-full relative">
<CardHeader>
<CardTitle className="text-2xl flex items-center gap-2 text-[#00F0FF]">AI Coach <ShieldCheck size={24} className="text-[#00F0FF]"/></CardTitle>
<CardDescription>Personalized mastery loop.</CardDescription>
<div className="mt-4 font-black text-4xl">₹1599<span className="text-xl text-muted-foreground font-semibold">/year</span></div>
</CardHeader>
<CardContent className="space-y-4 text-sm font-medium">
<div className="flex items-center gap-3"><Check className="text-[#00F0FF]" size={18}/> Everything in Pro</div>
<div className="flex items-center gap-3"><Check className="text-[#00F0FF]" size={18}/> Daily AI Error Review Queue</div>
<div className="flex items-center gap-3"><Check className="text-[#00F0FF]" size={18}/> SM-2 Spaced Repetition</div>
<div className="flex items-center gap-3"><Check className="text-[#00F0FF]" size={18}/> Mistake Heatmap Tracking</div>
<div className="flex items-center gap-3"><Check className="text-[#00F0FF]" size={18}/> AI Chat Tutor Support</div>
</CardContent>
<CardFooter>
<Card className="bg-background/80 backdrop-blur-md border border-border p-6 rounded-[2rem] flex flex-col justify-between h-full z-10 relative shadow-xl hover:border-border/80 transition-colors">
<div>
<CardHeader className="p-0 mb-6">
<CardTitle className="text-3xl font-bold mb-2 text-[#00F0FF]">AI Coach</CardTitle>
<CardDescription className="text-muted-foreground mb-6 text-base">The complete AI-driven system.</CardDescription>
<div className="text-6xl font-bold tracking-tighter mb-6 text-[#00F0FF]">
₹11,999<span className="text-xl text-[#00F0FF]/60 font-medium tracking-normal ml-1">/year</span>
</div>
</CardHeader>
<CardContent className="p-0 space-y-3 mb-6 font-medium text-lg">
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Everything in Pro</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> AI Spaced Repetition (Mistakes)</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> 24/7 Streaming AI Tutor Chat</div>
<div className="flex items-center gap-4"><CheckCircle2 className="text-[#00F0FF] shrink-0" size={24}/> Predictive Rank & College Predictor</div>
</CardContent>
</div>
<CardFooter className="p-0">
<Button
className="w-full font-bold text-lg rounded-2xl h-12 bg-[#00F0FF] text-black hover:bg-[#00F0FF]/90 transition-all"
onClick={() => handleUpgrade("Al_Coach")}
@@ -175,6 +182,72 @@ export default function UpgradePage() {
</Card>
</div>
<p className="text-center mt-8 text-sm text-muted-foreground font-medium w-full">
* All above prices are inclusive of GST.
</p>
{/* ── Tier Comparison Matrix ── */}
<section className="mt-20 py-8">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight mb-3">Compare All Features</h2>
<p className="text-muted-foreground text-base md:text-lg max-w-2xl mx-auto">
Detailed breakdown of capabilities across Free, Pro, and AI Coach tiers.
</p>
</div>
<div className="overflow-x-auto rounded-2xl border border-border/50 bg-secondary/10 backdrop-blur-sm p-3 md:p-6 shadow-2xl">
<table className="w-full text-left border-collapse min-w-[700px]">
<thead>
<tr className="border-b border-border/50 bg-background/50">
<th className="p-4 md:p-6 font-bold text-lg w-2/5 border-l border-t border-border/50">Feature</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 border-x border-t border-white">Free</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-yellow-500 border-x border-t border-yellow-500">Pro</th>
<th className="p-4 md:p-6 font-bold text-lg text-center w-1/5 text-[#00F0FF] bg-[#00F0FF]/5 border-x border-t border-[#00F0FF]">AI Coach</th>
</tr>
</thead>
<tbody className="divide-y divide-border/30 text-sm md:text-base">
{[
{ name: "Daily Practice Sheets", free: "1/day", pro: "Unlimited", ai: "Unlimited" },
{ name: "Answer Keys", free: true, pro: true, ai: true },
{ name: "Basic Progress Dashboard", free: true, pro: true, ai: true },
{ name: "Streak Tracking", free: true, pro: true, ai: true },
{ name: "Leaderboard (City/School)", free: true, pro: true, ai: true },
{ name: "Topic-wise Micro-tests", free: "5/day", pro: "Unlimited", ai: "Unlimited" },
{ name: "Weekly Test Series", free: false, pro: true, ai: true },
{ name: "Full-Length Mock Tests", free: false, pro: "4/month", ai: "Unlimited" },
{ name: "Previous Year Papers", free: false, pro: true, ai: true },
{ name: "Detailed Performance Analytics", free: false, pro: true, ai: true },
{ name: "Chapter-wise Progress", free: false, pro: true, ai: true },
{ name: "Doubt Resolution (Community)", free: false, pro: true, ai: true },
{ name: "Parent Dashboard", free: false, pro: true, ai: true },
{ name: "Offline Mode (Download DPS)", free: false, pro: true, ai: true },
{ name: "AI Mistake Tracking", free: false, pro: false, ai: true },
{ name: "Spaced Repetition (SM-2)", free: false, pro: false, ai: true },
{ name: "Personalized Daily Study Plan", free: false, pro: false, ai: true },
{ name: "AI Tutor Chat", free: false, pro: false, ai: true },
{ name: "Exam Readiness Score", free: false, pro: false, ai: true },
{ name: "Exam Day Strategy", free: false, pro: false, ai: true },
{ name: "College Predictor", free: false, pro: false, ai: true },
{ name: "Study Time Optimization", free: false, pro: false, ai: true },
].map((feature, idx) => (
<tr key={idx} className="hover:bg-background/40 transition-colors">
<td className="p-4 md:p-6 font-medium border-y border-l border-border/30">{feature.name}</td>
<td className="p-4 md:p-6 text-center text-muted-foreground border border-white">
{typeof feature.free === 'boolean' ? (feature.free ? <CheckCircle2 className="mx-auto text-white" size={20} /> : <Minus className="mx-auto text-white/30" size={20} />) : <span className="font-semibold text-white">{feature.free}</span>}
</td>
<td className="p-4 md:p-6 text-center border border-yellow-500">
{typeof feature.pro === 'boolean' ? (feature.pro ? <CheckCircle2 className="mx-auto text-yellow-500" size={20} /> : <Minus className="mx-auto text-yellow-500/30" size={20} />) : <span className="font-semibold text-yellow-500">{feature.pro}</span>}
</td>
<td className="p-4 md:p-6 text-center bg-[#00F0FF]/5 border border-[#00F0FF]">
{typeof feature.ai === 'boolean' ? (feature.ai ? <CheckCircle2 className="mx-auto text-[#00F0FF]" size={20} /> : <Minus className="mx-auto text-[#00F0FF]/30" size={20} />) : <span className="font-semibold text-[#00F0FF]">{feature.ai}</span>}
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
{/* Mock Razorpay Modal */}
{showModal && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center z-50 p-4">

View File

@@ -1,14 +1,16 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { GraduationCap, MapPin, RefreshCw } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
export function CollegePredictorCard() {
const [data, setData] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState("Target")
const { userId, loading: userLoading } = useActiveUser();
useEffect(() => {
@@ -54,34 +56,65 @@ export function CollegePredictorCard() {
}
return (
<Card className="border-border shadow-sm h-full flex flex-col bg-gradient-to-br from-background to-secondary/30 relative overflow-hidden">
<div className="absolute -right-12 -top-12 opacity-5 pointer-events-none">
<GraduationCap size={150} />
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🎓 College Predictor
</h2>
<Link href="/dashboard/ai-coach/college-predictor" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center justify-between">
<div className="flex items-center gap-2">
<MapPin className="text-primary" size={24} />
Admissions Radar
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full">
<div className="flex gap-1.5 mb-3.5">
{["Reach", "Target", "Safety"].map(tab => (
<div
key={tab}
onClick={() => setActiveTab(tab)}
className={`flex-1 p-2 text-center text-[11px] font-semibold rounded-md cursor-pointer transition-colors border ${
activeTab === tab
? "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]"
: "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"
}`}
>
{tab}
</div>
))}
</div>
<span className={`px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest border ${getCategoryColor(category)}`}>
{category}
</span>
</CardTitle>
<CardDescription>AI-projected admission trajectory.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-center">
<div className="p-5 bg-background rounded-xl border border-border shadow-sm mt-4">
<p className="font-semibold text-lg leading-snug">
"{prediction}"
</p>
</div>
<p className="text-xs text-muted-foreground mt-4 font-medium flex items-center gap-1 opacity-70">
<RefreshCw size={10} /> Prediction updates dynamically after every test.
</p>
</CardContent>
</Card>
<div className="flex flex-col gap-2 flex-1">
<div className="flex items-center gap-3 p-2.5 rounded-md bg-white/5 border border-border hover:bg-white/10 transition-colors">
<div className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#00F0FF]/10 text-[#00F0FF]">
#1
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">NIT Trichy</div>
<div className="text-[11px] text-muted-foreground truncate">Computer Science</div>
</div>
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-green-500 bg-green-500/10">
85%
</div>
</div>
<div className="flex items-center gap-3 p-2.5 rounded-md bg-white/5 border border-border hover:bg-white/10 transition-colors">
<div className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#00F0FF]/10 text-[#00F0FF]">
#2
</div>
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">NIT Surathkal</div>
<div className="text-[11px] text-muted-foreground truncate">Information Tech</div>
</div>
<div className="text-[12px] font-bold px-2 py-0.5 rounded-md shrink-0 text-yellow-500 bg-yellow-500/10">
72%
</div>
</div>
</div>
<div className="mt-4 p-2.5 bg-yellow-500/10 border border-yellow-500/20 rounded-md text-xs text-yellow-500 font-medium text-center">
Just 15 more marks in Maths can upgrade you to IIT Roorkee!
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,87 @@
"use client"
import { useState, useEffect } from "react"
import { Flame, ArrowRight, Timer, BookOpen } from "lucide-react"
const FOCUS_TOPICS = [
{ subject: "Physics", topic: "Rotational Dynamics", color: "from-blue-600/30 to-[#00F0FF]/20", accent: "#00F0FF", icon: "⚛️" },
{ subject: "Chemistry", topic: "Organic Reactions — Mechanism", color: "from-amber-600/30 to-yellow-500/20", accent: "#F59E0B", icon: "🧪" },
{ subject: "Biology", topic: "Human Nervous System", color: "from-green-600/30 to-emerald-400/20", accent: "#10B981", icon: "🧬" },
{ subject: "Maths", topic: "Integral Calculus", color: "from-purple-600/30 to-violet-400/20", accent: "#A78BFA", icon: "∫" },
]
export function DailyChallengeBanner() {
const [todayFocus] = useState(() => FOCUS_TOPICS[new Date().getDay() % FOCUS_TOPICS.length])
const [timeLeft, setTimeLeft] = useState("")
useEffect(() => {
const tick = () => {
const now = new Date()
const end = new Date()
end.setHours(23, 59, 59, 0)
const diff = Math.max(0, end.getTime() - now.getTime())
const h = Math.floor(diff / 3600000)
const m = Math.floor((diff % 3600000) / 60000)
const s = Math.floor((diff % 60000) / 1000)
setTimeLeft(`${h}h ${m}m ${s}s`)
}
tick()
const id = setInterval(tick, 1000)
return () => clearInterval(id)
}, [])
return (
<div
className={`relative overflow-hidden rounded-2xl border border-white/10 bg-gradient-to-r ${todayFocus.color} p-5 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 shadow-[0_4px_30px_rgba(0,0,0,0.3)]`}
>
<div
className="absolute -right-10 -top-10 w-48 h-48 rounded-full blur-3xl opacity-20 pointer-events-none"
style={{ background: todayFocus.accent }}
/>
<div className="flex items-center gap-4 relative z-10">
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl font-bold border border-white/10 shrink-0"
style={{ background: `${todayFocus.accent}20` }}
>
{todayFocus.icon}
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-[10px] font-black uppercase tracking-widest text-white/50">
AI-Recommended Focus · Today
</span>
<span className="inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded-full bg-white/10 text-white/70">
<Flame size={9} />
DAILY CHALLENGE
</span>
</div>
<h3 className="text-lg font-extrabold text-white tracking-tight leading-tight">
{todayFocus.subject}:{" "}
<span style={{ color: todayFocus.accent }}>{todayFocus.topic}</span>
</h3>
<p className="text-xs text-white/50 mt-0.5 flex items-center gap-1.5">
<BookOpen size={11} />
Mastering this will boost your rank by an estimated{" "}
<span className="font-bold text-white/70">+120 positions</span>
</p>
</div>
</div>
<div className="flex items-center gap-3 relative z-10 shrink-0">
<div className="text-right hidden sm:block">
<div className="text-[10px] text-white/40 font-semibold uppercase flex items-center gap-1 justify-end">
<Timer size={10} /> Expires In
</div>
<div className="text-sm font-black text-white/80 font-mono tracking-wider">{timeLeft}</div>
</div>
<button
className="flex items-center gap-2 px-4 py-2 rounded-xl font-bold text-sm text-background transition-all hover:scale-105 active:scale-95"
style={{ background: todayFocus.accent }}
>
Start Session <ArrowRight size={15} />
</button>
</div>
</div>
)
}

View File

@@ -0,0 +1,48 @@
import { Card, CardContent } from "@/components/ui/card"
import { Printer } from "lucide-react"
import Link from "next/link"
export function ExamStrategyCard() {
return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🧠 Exam Day Strategy
</h2>
<Link href="/dashboard/ai-coach/strategy" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full justify-between">
<div className="grid grid-cols-2 gap-2">
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">🧪</div>
<div className="text-[11px] font-bold text-primary">Chemistry First</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Finish in 40 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">🧲</div>
<div className="text-[11px] font-bold text-primary">Physics Next</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Target 55 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">📐</div>
<div className="text-[11px] font-bold text-primary">Maths Last</div>
<div className="text-[10px] text-muted-foreground mt-0.5">Reserve 75 mins</div>
</div>
<div className="p-3 rounded-md bg-white/5 border border-border text-center">
<div className="text-xl mb-1">⏱️</div>
<div className="text-[11px] font-bold text-primary">Buffer Time</div>
<div className="text-[10px] text-muted-foreground mt-0.5">10 mins for review</div>
</div>
</div>
<div className="mt-3 flex items-center justify-center gap-1.5 p-2.5 rounded-md bg-[#00F0FF]/10 border border-dashed border-[#00F0FF]/20 text-xs text-[#00F0FF] font-bold cursor-pointer hover:bg-[#00F0FF]/20 transition-colors">
<Printer size={16} className="stroke-[#00F0FF]" />
Print Exam Day Blueprint
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,33 @@
import Link from "next/link"
const features = [
{ name: "Mistake Tracking", href: "/dashboard/ai-coach/mistakes" },
{ name: "Spaced Repetition", href: "/dashboard/ai-coach/mistakes" },
{ name: "Study Plan", href: "/dashboard/ai-coach/study-plan" },
{ name: "AI Tutor", href: "/dashboard/ai-coach/tutor" },
{ name: "Readiness Score", href: "/dashboard/ai-coach/readiness" },
{ name: "Exam Strategy", href: "/dashboard/ai-coach/strategy" },
{ name: "College Predictor", href: "/dashboard/ai-coach/college-predictor" },
{ name: "Time Optimization", href: "/dashboard/ai-coach/strategy" },
]
export function FeatureIndex() {
return (
<div className="py-2 mb-4">
<div className="flex flex-wrap gap-2.5">
{features.map((f) => (
<Link
key={f.name}
href={f.href}
className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-extrabold bg-[#00F0FF]/10 border border-[#00F0FF]/30 text-[#00F0FF] hover:bg-[#00F0FF] hover:text-black transition-all shadow-[0_0_12px_rgba(0,240,255,0.1)] group"
>
<span className="size-2 rounded-full bg-[#00F0FF] group-hover:bg-black transition-colors" />
<span>{f.name}</span>
<span className="text-[10px] opacity-75">→</span>
</Link>
))}
</div>
</div>
)
}

View File

@@ -2,9 +2,8 @@
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { CheckCircle2, XCircle, BrainCircuit } from "lucide-react"
import { Flame } from "lucide-react"
import Link from "next/link"
import ReactMarkdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
@@ -69,101 +68,132 @@ export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeRevie
const { mistake, question } = data;
return (
<Card className="border-border shadow-sm overflow-hidden flex flex-col">
<div className="p-4 border-b border-border bg-secondary/20 flex items-center justify-between">
<div className="flex items-center gap-3">
<Badge variant="outline" className="text-muted-foreground border-primary/20 bg-primary/5 text-primary">
Attempt {mistake.review_count + 1}
</Badge>
<span className="text-xs font-bold text-muted-foreground uppercase tracking-wider">{mistake.topic} • {mistake.subtopic}</span>
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center pb-1">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
♻️ Spaced Repetition Queue
</h2>
<Link href="/dashboard/ai-coach/mistakes" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
{/* Spaced Repetition Timeline */}
<div className="flex justify-between items-center relative py-4 px-2">
<div className="absolute top-1/2 left-0 right-0 h-0.5 bg-secondary -translate-y-1/2 z-0" />
<div className="absolute top-1/2 left-0 w-1/2 h-0.5 bg-[#00F0FF] -translate-y-1/2 z-0" />
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 1</span>
<span className="text-[9px] text-muted-foreground font-semibold">10 Jul</span>
</div>
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
<BrainCircuit size={14} className={mistake.consecutive_correct > 0 ? "text-amber-500" : ""} />
Consecutive Correct: <span className="font-bold text-foreground">{mistake.consecutive_correct}/3</span> before mastery
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-[10px] font-bold text-[#00F0FF]">Attempt 2</span>
<span className="text-[9px] text-muted-foreground font-semibold">14 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 scale-110">
<div className="w-4 h-4 rounded-full border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" />
<span className="text-[11px] font-bold text-[#00F0FF]">Today</span>
<span className="text-[9px] text-muted-foreground font-semibold">19 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-50">
<div className="w-3.5 h-3.5 rounded-full bg-secondary border-2 border-background" />
<span className="text-[10px] font-bold text-muted-foreground">Mastery</span>
<span className="text-[9px] text-muted-foreground font-semibold">26 Jul</span>
</div>
</div>
<CardContent className="p-6 flex-1 flex flex-col">
<div className="prose prose-neutral dark:prose-invert max-w-none text-base md:text-lg mb-8 font-medium">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.question_text}
</ReactMarkdown>
</div>
<div className="space-y-3 mt-auto">
{Object.entries(question.options).map(([letter, text]) => {
const isSelected = selected === letter
let btnClass = "border-border bg-background hover:border-primary/50 hover:bg-secondary/20"
let iconClass = "border-border text-muted-foreground group-hover:border-primary/50 group-hover:text-primary"
if (result) {
// Post-check styling
if (letter === correctAnswer) {
btnClass = "border-green-500 bg-green-500/10 shadow-[0_0_15px_-5px_#22c55e]"
iconClass = "border-green-500 bg-green-500 text-white"
} else if (isSelected && letter !== correctAnswer) {
btnClass = "border-red-500 bg-red-500/10"
iconClass = "border-red-500 bg-red-500 text-white"
}
} else if (isSelected) {
btnClass = "border-primary bg-primary/5 shadow-[0_0_15px_-5px_var(--color-primary)]"
iconClass = "border-primary bg-primary text-primary-foreground"
}
return (
<button
key={letter}
onClick={() => !result && setSelected(letter)}
disabled={!!result}
className={`w-full text-left p-4 rounded-xl border transition-all duration-200 flex items-center gap-4 group ${btnClass}`}
>
<div className={`flex-shrink-0 size-8 rounded-lg border flex items-center justify-center font-medium transition-colors ${iconClass}`}>
{letter}
</div>
<span className="text-base font-medium flex-1">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
</span>
{result && letter === correctAnswer && <CheckCircle2 className="text-green-500 ml-2" size={20} />}
{result && isSelected && letter !== correctAnswer && <XCircle className="text-red-500 ml-2" size={20} />}
</button>
)
})}
</div>
<div className="mt-8 flex justify-end">
{!result ? (
<Button
size="lg"
onClick={handleCheck}
disabled={!selected || isSubmitting}
className="rounded-xl px-8 font-bold"
>
Check Answer
</Button>
) : (
<Button
size="lg"
onClick={handleNext}
className="rounded-xl px-8 font-bold bg-secondary text-foreground hover:bg-secondary/80 border border-border"
>
Continue to Next
</Button>
)}
</div>
{/* Solution Block */}
{result && (
<div className="mt-6 p-4 rounded-xl border border-primary/20 bg-primary/5 animate-in slide-in-from-top-2 fade-in">
<h4 className="text-sm font-bold text-primary mb-2 flex items-center gap-2"><BrainCircuit size={16}/> AI Coach Explanation</h4>
<div className="prose prose-sm dark:prose-invert max-w-none text-muted-foreground">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.solution_text}
</ReactMarkdown>
</div>
{/* Mistake Card */}
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] relative overflow-hidden flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-5 flex-1 flex flex-col">
<div className="flex items-center gap-2 mb-4">
<span className="text-[10px] font-bold px-2.5 py-1 rounded-md bg-white/5 border border-border text-muted-foreground uppercase tracking-widest">
{mistake.topic}
</span>
<span className="text-[11px] font-bold text-red-500 bg-red-500/10 px-2 py-0.5 rounded-md">
{mistake.error_type}
</span>
</div>
)}
</CardContent>
</Card>
<div className="prose prose-sm dark:prose-invert max-w-none text-base md:text-lg mb-6 font-medium text-foreground leading-relaxed">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.question_text}
</ReactMarkdown>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-auto">
{Object.entries(question.options).map(([letter, text]) => {
const isSelected = selected === letter
let btnClass = "border-border bg-white/5 hover:bg-white/10 hover:border-border text-muted-foreground"
if (result) {
if (letter === correctAnswer) {
btnClass = "border-green-500 bg-green-500/10 text-green-500 shadow-[0_0_15px_rgba(34,197,94,0.15)]"
} else if (isSelected && letter !== correctAnswer) {
btnClass = "border-red-500 bg-red-500/10 text-red-500"
}
} else if (isSelected) {
btnClass = "border-[#00F0FF] bg-[#00F0FF]/10 text-[#00F0FF] shadow-[0_0_15px_rgba(0,240,255,0.15)]"
}
return (
<button
key={letter}
onClick={() => !result && setSelected(letter)}
disabled={!!result || isSubmitting}
className={`text-left p-3.5 rounded-lg border-[1.5px] transition-all font-medium flex gap-3 ${btnClass}`}
>
<span className="font-bold opacity-70">{letter})</span>
<span className="flex-1">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
</span>
</button>
)
})}
</div>
{/* Action Button */}
{!result ? (
<div className="mt-5 flex justify-end">
<button
onClick={handleCheck}
disabled={!selected || isSubmitting}
className="px-6 py-2.5 rounded-md font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity disabled:opacity-50"
>
{isSubmitting ? "Checking..." : "Submit Answer"}
</button>
</div>
) : (
<>
{/* Solution Block */}
<div className="mt-5 p-4 rounded-xl border border-[#00F0FF]/20 bg-[#00F0FF]/5">
<h4 className="text-[13px] font-bold text-[#00F0FF] mb-2 flex items-center gap-2">
<Flame size={16} className="fill-[#00F0FF]/20" />
AI Coach Explanation
</h4>
<div className="prose prose-sm dark:prose-invert max-w-none text-[13px] text-muted-foreground leading-relaxed [&>p]:m-0">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.solution_text}
</ReactMarkdown>
</div>
</div>
<div className="mt-5 flex justify-end">
<button
onClick={handleNext}
className="px-6 py-2.5 rounded-md font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity"
>
Got It, Next Question →
</button>
</div>
</>
)}
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,8 +1,9 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Activity, RefreshCw } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { TrendingUp, Flame, RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -59,65 +60,108 @@ export function ReadinessGauge() {
}
return (
<Card className="border-border shadow-sm h-full flex flex-col">
<CardHeader className="pb-2">
<CardTitle className="text-xl flex items-center gap-2">
<Activity className="text-primary" size={24} />
Exam Readiness
</CardTitle>
<CardDescription>Real-time AI composite score.</CardDescription>
</CardHeader>
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🎯 Exam Readiness
</h2>
<Link href="/dashboard/ai-coach/readiness" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<CardContent className="flex flex-col items-center flex-1 justify-center p-6 space-y-6">
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] relative overflow-hidden flex-1 flex flex-col justify-between p-4 group hover:border-[#00F0FF]/30 transition-colors">
{/* Background glow decoration */}
<div className="absolute -top-1/2 -right-1/4 w-48 h-48 bg-[radial-gradient(circle,rgba(94,196,184,0.06)_0%,transparent_70%)] rounded-full pointer-events-none" />
{/* SVG Circular Gauge */}
<div className="relative w-32 h-32 flex items-center justify-center">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
{/* Background Ring */}
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="8" fill="transparent" className="text-secondary" />
{/* Progress Ring */}
<circle
cx="50" cy="50" r="45"
stroke="currentColor"
strokeWidth="8"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className={`transition-all duration-1000 ease-out ${getColor(score)}`}
/>
</svg>
<div className="absolute flex flex-col items-center justify-center">
<span className="text-3xl font-black">{score}</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-wider font-bold">/ 100</span>
<div className="flex justify-between items-start mb-4 relative z-10">
<span className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">Updated daily • Today</span>
<span className="flex items-center gap-1 text-xs font-bold text-green-500 bg-green-500/10 px-2.5 py-0.5 rounded-full">
<TrendingUp size={14} className="stroke-[2.5px]" />
+5 this week
</span>
</div>
<div className="flex flex-col md:flex-row items-center gap-6 mb-4 relative z-10">
{/* Circular Score */}
<div className="relative w-[110px] h-[110px] shrink-0">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
<defs>
<linearGradient id="scoreGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#00F0FF" />
<stop offset="100%" stopColor="#3B82F6" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" stroke="currentColor" strokeWidth="6" fill="transparent" className="text-[#00F0FF]/10" />
<circle
cx="50" cy="50" r="45"
stroke="url(#scoreGradient)"
strokeWidth="6"
fill="transparent"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className="transition-all duration-1500 ease-out"
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center mt-1">
<span className="text-[32px] font-extrabold leading-none">{score}</span>
<span className="text-[9px] text-muted-foreground font-semibold mt-0.5">SCORE</span>
</div>
</div>
{/* Quick metric grid beside ring */}
<div className="flex-1 grid grid-cols-2 gap-2 w-full">
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">88% <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Accuracy</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">45s <span className="text-[10px] text-red-500">↓</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Speed/Q</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">High <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Consistency</div>
</div>
<div className="bg-white/5 rounded-md p-2">
<div className="text-sm font-bold flex items-baseline gap-1">64% <span className="text-[10px] text-green-500">↑</span></div>
<div className="text-[10px] font-medium text-muted-foreground">Syllabus</div>
</div>
</div>
</div>
{/* Breakdown Bars */}
<div className="w-full space-y-3">
{/* Subject Bars */}
<div className="space-y-1.5 mb-3 relative z-10">
{[
{ label: "Accuracy", value: breakdown.accuracy, max: 40 },
{ label: "Speed", value: breakdown.speed, max: 20 },
{ label: "Coverage", value: breakdown.coverage, max: 20 },
{ label: "Consistency", value: breakdown.consistency, max: 10 },
{ label: "Recovery", value: breakdown.recovery, max: 10 },
].map((item, idx) => (
<div key={idx} className="space-y-1">
<div className="flex justify-between text-xs font-semibold">
<span className="text-muted-foreground">{item.label}</span>
<span>{item.value} <span className="opacity-50">/ {item.max}</span></span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
{ name: "Physics", val: 68, color: "#00F0FF" },
{ name: "Chemistry", val: 74, color: "#F59E0B" },
{ name: "Biology", val: 82, color: "#10B981" },
].map(sub => (
<div key={sub.name} className="flex items-center gap-2">
<span className="text-[9px] font-semibold text-muted-foreground w-14 shrink-0">{sub.name}</span>
<div className="flex-1 h-1.5 rounded-full bg-white/10 overflow-hidden">
<div
className="h-full bg-primary transition-all duration-1000 ease-out"
style={{ width: `${(item.value / item.max) * 100}%` }}
className="h-full rounded-full transition-all duration-1000"
style={{ width: `${sub.val}%`, background: sub.color }}
/>
</div>
<span className="text-[9px] font-bold w-7 text-right" style={{ color: sub.color }}>{sub.val}%</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* National Avg */}
<div className="flex items-center justify-between px-2 py-1.5 bg-white/5 rounded-md mb-3 relative z-10">
<span className="text-[10px] text-muted-foreground font-semibold">National Avg</span>
<span className="text-[10px] font-bold text-muted-foreground">58 / 100</span>
<span className="text-[10px] font-bold text-green-500">You: +{score - 58} above avg</span>
</div>
<div className="mt-auto px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-md text-xs text-[#00F0FF] font-semibold flex items-center gap-2 relative z-10">
<Flame size={16} className="fill-[#00F0FF]/20 shrink-0" />
Your speed has dropped slightly. Focus on time-bound practice today.
</div>
</Card>
</div>
)
}

View File

@@ -1,9 +1,9 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { CalendarClock, CheckCircle2, Circle, Clock, Loader2, PlayCircle, RefreshCw, XCircle } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { Check, RefreshCw } from "lucide-react"
import Link from "next/link"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -30,9 +30,11 @@ export function StudyPlanCard() {
}
// Mock fallback if celery hasn't seeded yet
setTasks([
{ type: "Revision", description: "Revise Thermodynamics", duration_minutes: 15, status: "Pending" },
{ type: "Mistake_Review", description: "Daily Mistake Review Session", duration_minutes: 10, status: "Pending" },
{ type: "DPS", description: "Attempt Thermodynamics Practice Sheet", duration_minutes: 20, status: "Pending" }
{ type: "DPS", description: "DPS — Electrostatics + Kinematics", duration_minutes: 20, status: "Done" },
{ type: "Mistake_Review", description: "Mistake Review — Thermodynamics", duration_minutes: 10, status: "Done" },
{ type: "Micro_test", description: "Micro-test — GOC (General Organic Chemistry)", duration_minutes: 5, status: "Pending" },
{ type: "Test", description: "Weekly Test — Full Syllabus", duration_minutes: 180, status: "Pending" },
{ type: "Revision", description: "Revise — Circles + Straight Lines", duration_minutes: 15, status: "Pending" }
])
} finally {
setLoading(false)
@@ -49,91 +51,112 @@ export function StudyPlanCard() {
})
}
const completedTasks = tasks.filter(t => t.status === "Done").length
const totalTasks = tasks.length
const progressPercent = totalTasks === 0 ? 0 : Math.round((completedTasks / totalTasks) * 100)
const getTagStyle = (type: string) => {
switch(type) {
case "Revision": return "bg-yellow-500/10 text-yellow-500"
case "Mistake_Review": return "bg-red-500/10 text-red-500"
case "DPS": return "bg-[#00F0FF]/10 text-[#00F0FF]"
case "Micro_test": return "bg-green-500/10 text-green-500"
case "Test": return "bg-purple-500/10 text-purple-500"
default: return "bg-secondary text-muted-foreground"
}
}
const getDisplayType = (type: string) => {
switch(type) {
case "Revision": return "Revision"
case "Mistake_Review": return "Review"
case "DPS": return "Practice"
case "Micro_test": return "Micro-test"
case "Test": return "Test"
default: return "Task"
}
}
return (
<Card className="border-border shadow-sm h-full flex flex-col">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<CardTitle className="text-xl flex items-center gap-2">
<CalendarClock className="text-primary" size={24} />
Today's Plan
</CardTitle>
<Badge variant="outline" className="bg-primary/5 text-primary border-primary/20">
{tasks.reduce((acc, curr) => acc + curr.duration_minutes, 0)} min total
</Badge>
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
📅 Today's Study Plan
</h2>
<Link href="/dashboard/ai-coach/study-plan" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden group hover:border-[#00F0FF]/30 transition-colors">
<div className="px-4 py-3.5 flex justify-between items-center border-b border-border">
<div className="text-[13px] font-bold text-muted-foreground">
{new Date().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'short' })}
</div>
<div className="text-[12px] text-[#00F0FF] font-bold">{progressPercent}% Completed</div>
</div>
<CardDescription>AI-generated curriculum targeting your weak spots.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col">
{loading ? (
<div className="flex-1 flex items-center justify-center p-8">
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading schedule...
</div>
) : (
<div className="space-y-3">
{tasks.map((task, idx) => {
const isDone = task.status === "Done"
const isSkipped = task.status === "Skipped"
const isPending = task.status === "Pending"
return (
<div
key={idx}
className={`flex items-center p-4 rounded-xl border transition-all duration-300 ${
isDone ? "bg-green-500/5 border-green-500/20" :
isSkipped ? "bg-secondary/50 border-transparent opacity-60" :
"bg-background border-border hover:border-primary/50"
}`}
>
<div className="flex flex-col flex-1 mr-4">
<div className="flex items-center gap-2 mb-1">
<Badge variant="secondary" className="text-[10px] uppercase tracking-wider">{task.type}</Badge>
<span className="text-xs font-semibold text-muted-foreground flex items-center gap-1">
<Clock size={12}/> {task.duration_minutes} min
</span>
</div>
<p className={`font-semibold text-sm transition-all duration-300 ${
isDone || isSkipped ? "line-through text-muted-foreground" : "text-foreground"
{/* Progress bar */}
<div className="h-[3px] bg-[#00F0FF]/10 w-full">
<div
className="h-full bg-gradient-to-r from-[#00F0FF] to-teal-400 rounded-r-[2px] transition-all duration-500"
style={{ width: `${progressPercent}%` }}
/>
</div>
<CardContent className="p-0 flex-1 flex flex-col">
{loading ? (
<div className="flex-1 flex items-center justify-center p-8 min-h-[200px]">
<RefreshCw className="animate-spin text-muted-foreground mr-3" /> Loading schedule...
</div>
) : (
<div className="py-1">
{tasks.map((task, idx) => {
const isDone = task.status === "Done"
const isCurrent = idx === completedTasks && !isDone // First pending task is current
return (
<div
key={idx}
onClick={() => toggleTask(idx, isDone ? "Pending" : "Done")}
className={`flex items-center gap-3 px-4 py-2.5 transition-colors cursor-pointer border-l-[3px] ${
isDone ? "opacity-60 border-green-500 hover:bg-white/5" :
isCurrent ? "border-[#00F0FF] bg-[#00F0FF]/5 hover:bg-[#00F0FF]/10" :
"border-transparent hover:bg-white/5"
}`}
>
{/* Custom Checkbox */}
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center shrink-0 transition-all ${
isDone ? "bg-green-500 border-green-500" :
isCurrent ? "border-[#00F0FF] shadow-[0_0_0_0_rgba(0,240,255,0.3)] animate-[pulse-border_2s_infinite]" :
"border-muted-foreground"
}`}>
{task.description}
</p>
</div>
{isDone && <Check size={12} className="stroke-white stroke-[3px]" />}
</div>
{/* Controls */}
<div className="flex items-center gap-2">
{isPending && (
<>
<button
onClick={() => toggleTask(idx, "Skipped")}
className="p-2 rounded-full hover:bg-secondary text-muted-foreground hover:text-foreground transition-colors"
title="Skip Task"
>
<XCircle size={20} />
</button>
<button
onClick={() => toggleTask(idx, "Done")}
className="p-2 rounded-full hover:bg-green-500/10 text-muted-foreground hover:text-green-500 transition-colors"
title="Complete Task"
>
<Circle size={20} />
</button>
</>
)}
{(isDone || isSkipped) && (
<button
onClick={() => toggleTask(idx, "Pending")}
className={`flex items-center gap-1 text-xs font-bold px-3 py-1.5 rounded-full ${isDone ? "bg-green-500 text-white" : "bg-secondary text-muted-foreground"}`}
>
{isDone ? <CheckCircle2 size={14}/> : <PlayCircle size={14}/>}
{isDone ? "Completed" : "Resume"}
</button>
)}
<div className="flex-1 min-w-0">
<div className="text-[13px] font-bold text-primary truncate">{task.description}</div>
<div className="text-[11px] text-muted-foreground mt-0.5">{task.type.replace('_', ' ')} • Active Session</div>
</div>
<div className={`text-[10px] font-bold px-2 py-0.5 rounded-md shrink-0 ${getTagStyle(task.type)}`}>
{getDisplayType(task.type)}
</div>
<div className="text-[12px] font-bold text-muted-foreground bg-white/5 px-2 py-0.5 rounded-md shrink-0">
{task.duration_minutes}m
</div>
</div>
</div>
)
})}
</div>
)}
</CardContent>
</Card>
)
})}
</div>
)}
</CardContent>
</Card>
<style dangerouslySetInnerHTML={{__html: `
@keyframes pulse-border {
0%, 100% { box-shadow: 0 0 0 0 rgba(0, 240, 255, 0.3); }
50% { box-shadow: 0 0 0 6px rgba(0, 240, 255, 0); }
}
`}} />
</div>
)
}

View File

@@ -0,0 +1,74 @@
"use client"
import { Card, CardContent } from "@/components/ui/card"
import { Flame, Star } from "lucide-react"
import { useState } from "react"
import Link from "next/link"
const SLOTS = [
{ label: "6 AM", h: 30, peak: false },
{ label: "9 AM", h: 50, peak: false },
{ label: "12 PM", h: 65, peak: false },
{ label: "3 PM", h: 100, peak: true },
{ label: "6 PM", h: 80, peak: false },
{ label: "9 PM", h: 60, peak: false },
{ label: "12 AM", h: 38, peak: false },
]
export function StudyTimeOptimizationCard() {
const [hovered, setHovered] = useState<number | null>(null)
return (
<div>
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
📈 Study Time Optimization
</h2>
<Link href="/dashboard/ai-coach/strategy" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">Expand View ↗</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4">
{/* Peak window badge */}
<div className="flex items-center gap-2 mb-4">
<Star size={13} className="fill-yellow-500 text-yellow-500" />
<span className="text-xs font-bold text-yellow-500">Peak Productivity:</span>
<span className="text-xs font-semibold text-foreground bg-yellow-500/10 border border-yellow-500/20 px-2 py-0.5 rounded-full">
2 PM – 5 PM
</span>
</div>
{/* Bar chart */}
<div className="flex items-end gap-1.5 h-20 px-1 mb-6">
{SLOTS.map((slot, i) => (
<div
key={slot.label}
className="flex-1 flex flex-col items-center gap-0.5 cursor-pointer"
onMouseEnter={() => setHovered(i)}
onMouseLeave={() => setHovered(null)}
>
<div
className={`w-full rounded-t-md transition-all duration-300 ${
slot.peak
? "bg-gradient-to-t from-yellow-600/60 to-yellow-400 shadow-[0_0_10px_rgba(234,179,8,0.4)]"
: hovered === i
? "bg-gradient-to-t from-[#00F0FF]/50 to-[#00F0FF]"
: "bg-gradient-to-t from-[#00F0FF]/20 to-[#00F0FF]/60"
}`}
style={{ height: `${slot.h}%` }}
/>
<span className="text-[8px] text-muted-foreground font-medium whitespace-nowrap mt-1">
{slot.label}
</span>
</div>
))}
</div>
<div className="mt-1 px-3 py-2.5 bg-yellow-500/10 border border-yellow-500/20 rounded-md text-xs text-yellow-500 font-medium flex items-center gap-2">
<Flame size={16} className="fill-yellow-500 shrink-0" />
You perform best 2 PM – 5 PM. Schedule your hardest mock tests in this window.
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,10 +1,9 @@
"use client"
import { useState, useRef, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Send, Bot, User, Loader2 } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { Send, Loader2 } from "lucide-react"
import Link from "next/link"
import ReactMarkdown from "react-markdown"
import remarkMath from "remark-math"
import rehypeKatex from "rehype-katex"
@@ -93,53 +92,81 @@ export function TutorChat() {
}
return (
<Card className="border-border shadow-sm flex flex-col h-[600px]">
<CardHeader className="border-b bg-secondary/10 pb-4">
<CardTitle className="text-xl flex items-center gap-2">
<Bot className="text-primary" size={24} />
AI Tutor Chat
</CardTitle>
</CardHeader>
<div className="flex flex-col h-full min-h-[500px]">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
🤖 Ask AI Tutor
</h2>
</div>
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
<div className={`max-w-[80%] rounded-2xl p-4 flex gap-3 ${
msg.role === "user" ? "bg-primary text-primary-foreground rounded-tr-sm" : "bg-secondary rounded-tl-sm"
}`}>
{msg.role === "assistant" && <Bot size={20} className="mt-1 shrink-0" />}
<div className="prose prose-sm dark:prose-invert max-w-none overflow-hidden">
{msg.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{msg.content || "..."}
</ReactMarkdown>
) : (
msg.content
)}
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 overflow-hidden flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
<div className="px-4 py-3 bg-[#00F0FF]/5 border-b border-border flex items-center gap-2.5">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#00F0FF] to-blue-500 flex items-center justify-center text-sm font-bold text-background shrink-0">
AI
</div>
<div>
<div className="text-[13px] font-bold text-primary">PragatiVeer Tutor</div>
<div className="text-[11px] text-green-500 flex items-center gap-1 font-medium">
<span className="w-1.5 h-1.5 rounded-full bg-green-500"></span> Online
</div>
</div>
))}
<div ref={messagesEndRef} />
</CardContent>
<div className="ml-auto text-[11px] text-muted-foreground">
<span className="text-yellow-500 font-bold">14</span> msgs left
</div>
</div>
<div className="p-4 border-t bg-background">
<form onSubmit={handleSend} className="flex gap-2">
<Input
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
<div className="max-w-[85%] flex flex-col gap-1">
<div className={`p-3 text-[13px] leading-relaxed ${
msg.role === "user"
? "bg-[#00F0FF] text-background rounded-[12px] rounded-br-[4px] font-medium"
: "bg-[#00F0FF]/10 border border-[#00F0FF]/20 text-primary rounded-[12px] rounded-bl-[4px]"
}`}>
<div className="prose prose-sm dark:prose-invert max-w-none [&>p]:m-0">
{msg.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{msg.content || "..."}
</ReactMarkdown>
) : (
msg.content
)}
</div>
</div>
<div className={`text-[10px] text-muted-foreground ${msg.role === "user" ? "text-right" : "text-left"}`}>
Now
</div>
</div>
</div>
))}
<div ref={messagesEndRef} />
</CardContent>
<div className="p-2.5 border-t border-border bg-black/20 flex items-center gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask me to explain a concept..."
className="flex-1 bg-secondary/50 border-transparent focus-visible:ring-primary"
onKeyDown={(e) => {
if (e.key === 'Enter') handleSend();
}}
placeholder="Message AI Tutor..."
className="flex-1 bg-white/5 border border-border rounded-full px-4 py-2 text-[13px] text-primary focus:outline-none focus:border-[#00F0FF] focus:bg-[#00F0FF]/5 transition-colors"
disabled={streaming}
/>
<Button type="submit" size="icon" disabled={!input.trim() || streaming}>
{streaming ? <Loader2 className="animate-spin" size={18} /> : <Send size={18} />}
</Button>
</form>
</div>
</Card>
<button
onClick={handleSend}
disabled={!input.trim() || streaming}
className="w-9 h-9 rounded-full bg-[#00F0FF] flex items-center justify-center shrink-0 hover:bg-[#00F0FF]/90 transition-transform hover:scale-105 disabled:opacity-50 disabled:hover:scale-100"
>
{streaming ? <Loader2 className="animate-spin text-background" size={16} /> : <Send size={16} className="text-background" />}
</button>
</div>
</Card>
</div>
)
}

View File

@@ -0,0 +1,77 @@
"use client"
import { TrendingUp, TrendingDown, Brain, Zap } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import Link from "next/link"
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const BAR_HEIGHTS = [55, 70, 45, 90, 80, 60, 75] // % heights
const ACCURACY = [82, 78, 91, 88, 85, 79, 93]
export function WeeklyProgressReport() {
const avgAccuracy = Math.round(ACCURACY.reduce((a, b) => a + b, 0) / ACCURACY.length)
const trend = ACCURACY[ACCURACY.length - 1] - ACCURACY[0]
const maxBar = Math.max(...BAR_HEIGHTS)
return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center pb-3">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
📊 This Week at a Glance
</h2>
<Link href="/dashboard/ai-coach/study-plan" className="text-xs text-[#00F0FF] font-semibold hover:underline transition-colors">
Expand View ↗
</Link>
</div>
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-4 flex flex-col h-full gap-4">
{/* Summary chips */}
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 border border-green-500/20">
<TrendingUp size={12} />
{avgAccuracy}% avg accuracy
</div>
<div className={`flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border ${trend >= 0 ? "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20" : "bg-red-500/10 text-red-500 border-red-500/20"}`}>
{trend >= 0 ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
{trend >= 0 ? "+" : ""}{trend}% vs Mon
</div>
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-yellow-500/10 text-yellow-500 border border-yellow-500/20">
<Zap size={12} />
7/7 days active
</div>
</div>
{/* Bar Chart */}
<div className="flex items-end gap-2 h-24 flex-1">
{DAYS.map((day, i) => (
<div key={day} className="flex flex-col items-center gap-1 flex-1">
<div className="relative w-full flex items-end justify-center" style={{ height: "80px" }}>
<div
className={`w-full rounded-t-md transition-all duration-700 ${
i === 6
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
: "bg-gradient-to-t from-white/10 to-white/20"
}`}
style={{ height: `${(BAR_HEIGHTS[i] / maxBar) * 80}px` }}
title={`${ACCURACY[i]}% accuracy`}
/>
</div>
<span className="text-[9px] text-muted-foreground font-semibold">{day}</span>
</div>
))}
</div>
{/* AI insight */}
<div className="px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-md text-xs text-[#00F0FF] font-medium flex items-start gap-3">
<Brain size={16} className="shrink-0 mt-0.5" />
<div className="flex flex-col gap-1.5">
<span><strong className="text-white">You keep making sign errors in Thermodynamics.</strong> 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.</span>
<span className="text-[#00F0FF]/80">💡 <strong className="text-white">Tip:</strong> You perform best between 7-9 PM. Schedule difficult topics then.</span>
</div>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { CheckCircle2, XCircle, AlertCircle, Clock, Award, ArrowRight, RotateCcw } from "lucide-react";
import { CheckCircle2, XCircle, AlertCircle, Clock, Award, ArrowRight, RotateCcw, Share2, Mail } from "lucide-react";
interface ExamAttemptPayload {
score: number;
@@ -153,6 +153,26 @@ export function ExamResult({ payload, attemptId, onRetake }: ExamResultProps) {
</Button>
</div>
<div className="flex flex-col items-center pt-8 border-t border-border mt-8 space-y-4">
<h3 className="text-xl font-bold tracking-tight">Challenge a Friend</h3>
<p className="text-muted-foreground text-center max-w-md">Share this test with your friends and see if they can beat your score of {finalScore}!</p>
<div className="flex gap-4">
<Button variant="outline" className="gap-2 font-bold rounded-xl" onClick={() => {
const subject = encodeURIComponent("Can you beat my PragatiVeer Mock Test Score?");
const body = encodeURIComponent(`I just scored ${finalScore}/${maxPossibleScore} with ${Math.round(payload.accuracy)}% accuracy in a PragatiVeer Mock Test!\n\nTake the challenge here: https://pragativeer.com/challenge`);
window.open(`mailto:?subject=${subject}&body=${body}`);
}}>
<Mail className="size-4" /> Share via Email
</Button>
<Button className="gap-2 font-bold rounded-xl bg-green-500 hover:bg-green-600 text-white shadow-lg" onClick={() => {
const text = encodeURIComponent(`I just scored ${finalScore}/${maxPossibleScore} in a PragatiVeer Mock Test! Can you beat me? \nTake the challenge here: https://pragativeer.com/challenge`);
window.open(`https://wa.me/?text=${text}`, '_blank');
}}>
<Share2 className="size-4" /> Share on WhatsApp
</Button>
</div>
</div>
</div>
</div>
);

View File

@@ -72,7 +72,6 @@ export function StreakOnboardingModal() {
src={`/streak-animation.html?streak=${streak}&theme=${activeTheme === 'light' ? 'light' : 'dark'}`}
className="w-full h-full border-none"
title="Streak Celebration"
allowtransparency="true"
/>
</div>

View File

@@ -0,0 +1,101 @@
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
export function PublicLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-background text-foreground overflow-x-hidden selection:bg-primary/20 relative">
{/* Navigation */}
<nav className="fixed top-0 w-full z-50 bg-background/50 backdrop-blur-xl border-b border-border/50">
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
<Link href="/" className="relative h-14 w-60 hover:opacity-90 transition-opacity block">
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_light_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left dark:hidden" priority
/>
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_dark_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left hidden dark:block" priority
/>
</Link>
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-muted-foreground">
<Link href="/#features" className="hover:text-foreground transition-colors">Features</Link>
<Link href="/#pricing" className="hover:text-foreground transition-colors">Pricing</Link>
<Link href="/about" className="hover:text-foreground transition-colors">About</Link>
</div>
<div className="flex items-center gap-4">
<Link href="/login" className="text-sm font-bold hover:text-primary transition-colors hidden sm:block">Log in</Link>
<Link href="/signup">
<Button className="rounded-full font-bold px-6 shadow-[0_0_15px_-5px_var(--color-primary)] hover:shadow-[0_0_25px_-5px_var(--color-primary)] transition-all">Get Started</Button>
</Link>
</div>
</div>
</nav>
{/* Main Content */}
<main className="relative z-10 min-h-screen flex flex-col">
{children}
</main>
{/* Global Footer (Static) */}
<footer className="relative z-10 bg-background/90 backdrop-blur-md border-t border-border pt-16 pb-8">
<div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 mb-16">
<div className="md:col-span-1 space-y-4">
<div className="relative h-12 w-64 mb-6">
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_light_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left dark:hidden"
/>
<Image
src="/PragatiVeer_Logo/SVG_Logos/Rectangular_dark_background.svg"
alt="PragatiVeer" fill className="object-contain object-left scale-135 origin-left hidden dark:block"
/>
</div>
<p className="text-sm text-muted-foreground font-medium leading-relaxed">
Re-engineering NEET & JEE preparation with adaptive AI models. Learn faster, rank higher.
</p>
</div>
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Product</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="/#features" className="hover:text-primary transition-colors">Features</Link></li>
<li><Link href="/#pricing" className="hover:text-primary transition-colors">Pricing</Link></li>
<li><Link href="/dashboard" className="hover:text-primary transition-colors">Student Dashboard</Link></li>
<li><Link href="/admin" className="hover:text-[#00F0FF] transition-colors">Admin Panel</Link></li>
</ul>
</div>
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Company</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="/about" className="hover:text-primary transition-colors">About Us</Link></li>
<li><Link href="/contact" className="hover:text-primary transition-colors">Contact</Link></li>
<li><Link href="#" className="hover:text-primary transition-colors">Careers</Link></li>
</ul>
</div>
<div>
<h4 className="font-bold mb-6 tracking-tight text-foreground">Legal</h4>
<ul className="space-y-3 text-sm font-medium text-muted-foreground">
<li><Link href="/terms" className="hover:text-primary transition-colors">Terms & Conditions</Link></li>
<li><Link href="/privacy" className="hover:text-primary transition-colors">Privacy Policy</Link></li>
<li><Link href="/refund" className="hover:text-primary transition-colors">Refund Policy</Link></li>
<li><Link href="/cancellation" className="hover:text-primary transition-colors">Cancellation Policy</Link></li>
<li><Link href="/disclaimer" className="hover:text-primary transition-colors">Disclaimer</Link></li>
</ul>
</div>
</div>
<div className="border-t border-border pt-8 flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-sm text-muted-foreground font-medium">© {new Date().getFullYear()} PragatiVeer. All rights reserved.</p>
<div className="flex items-center gap-2 text-sm text-muted-foreground font-medium">
Made with <div className="size-2 rounded-full bg-primary animate-pulse"></div> by AI Agents
</div>
</div>
</div>
</footer>
</div>
)
}

View File

@@ -0,0 +1,32 @@
"use client";
import { Wrench } from "lucide-react";
import { motion } from "framer-motion";
export default function MaintenanceScreen() {
return (
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md w-full bg-secondary/20 border border-border rounded-2xl p-8 text-center space-y-6"
>
<div className="mx-auto w-16 h-16 bg-yellow-500/10 text-yellow-500 rounded-full flex items-center justify-center mb-6">
<Wrench size={32} />
</div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">We're Upgrading Your Learning Experience</h1>
<p className="text-muted-foreground">
Our platform is currently undergoing maintenance to bring you faster performance and exciting new features.
</p>
<div className="pt-4 border-t border-border/50">
<p className="text-sm font-medium text-foreground">
We'll be back shortly. Thank you for your patience!
</p>
</div>
</motion.div>
</div>
);
}

118
src/components/ui/table.tsx Normal file
View File

@@ -0,0 +1,118 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn("[&_tr]:border-b", className)}
{...props}
/>
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("bg-primary font-medium text-primary-foreground", className)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -24,9 +24,10 @@ export interface UserProfile {
export const DEFAULT_USER_ID = "b0e17378-864d-439e-8806-708dc48a891b"; // AI Coach
export const DEMO_USERS = {
FREE: { id: "11111111-1111-1111-1111-111111111111", label: "Amit Kumar (Free Tier)" },
PRO: { id: "22222222-2222-2222-2222-222222222222", label: "Priya Singh (Pro Tier)" },
AI_COACH: { id: DEFAULT_USER_ID, label: "Rahul Sharma (AI Coach)" }
FREE: { id: "11111111-1111-1111-1111-111111111111", label: "Amit Kumar (Free Tier)" },
PRO: { id: "22222222-2222-2222-2222-222222222222", label: "Priya Singh (Pro Tier)" },
NEHA: { id: "33333333-3333-3333-3333-333333333333", label: "Neha Verma (AI Coach)" },
AI_COACH: { id: DEFAULT_USER_ID, label: "Rahul Sharma (AI Coach)" },
};
export function useActiveUser() {
@@ -38,43 +39,61 @@ export function useActiveUser() {
});
const [user, setUser] = useState<UserProfile | null>(null);
const [globalSettings, setGlobalSettings] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const failCountRef = { current: 0 };
const fetchUserProfile = useCallback(async (id: string, isPoll: boolean = false) => {
if (!isPoll) setLoading(true);
setError(null);
try {
const res = await fetch(`/api/users/${id}`);
const settingsRes = await fetch(`/api/admin/settings`, { cache: "no-store" });
if (!res.ok) {
console.error("Failed to fetch user profile, status:", res.status);
setError("Failed to fetch user profile");
setUser(null);
if (!isPoll) {
setError("Failed to fetch user profile");
setUser(null);
}
return;
}
const data = await res.json();
if (settingsRes.ok) {
const settingsData = await settingsRes.json();
setGlobalSettings(settingsData);
}
// Single Active Session Check
if (typeof window !== 'undefined') {
const localToken = localStorage.getItem("active_session_token");
if (data.active_session_token && localToken && data.active_session_token !== localToken) {
localStorage.removeItem("activeUserId");
localStorage.removeItem("active_session_token");
alert("Your account was logged in from another device. You have been logged out.");
window.location.href = "/";
return;
}
failCountRef.current = 0;
// Single Active Session Check
if (typeof window !== "undefined") {
const localToken = localStorage.getItem("active_session_token");
if (data.active_session_token && localToken && data.active_session_token !== localToken) {
localStorage.removeItem("activeUserId");
localStorage.removeItem("active_session_token");
alert("Your account was logged in from another device. You have been logged out.");
window.location.href = "/";
return;
}
}
setUser(data);
setUser(data);
} catch (err: any) {
console.error("Error fetching active user profile:", err);
setError(err.message || "Failed to load user");
// Network error (backend down / restarting) — only surface on initial load
failCountRef.current = (failCountRef.current || 0) + 1;
if (!isPoll) {
console.warn("Backend unreachable, will retry:", err.message);
setError("Backend unreachable — retrying...");
}
// Silently swallow polling errors so the UI doesn't crash
} finally {
if (!isPoll) setLoading(false);
}
}, []);
// Initial fetch
useEffect(() => {
if (typeof window !== "undefined") {
const storedId = localStorage.getItem("activeUserId") || DEFAULT_USER_ID;
@@ -83,22 +102,25 @@ export function useActiveUser() {
}
}, [fetchUserProfile]);
// Session validity polling — only runs when we already have a user
useEffect(() => {
if (typeof window === "undefined") return;
const handleUpdate = () => fetchUserProfile(userId);
window.addEventListener('pragativeer-user-updated', handleUpdate);
// Poll every 10 seconds for session validity
const handleUpdate = () => fetchUserProfile(userId);
window.addEventListener("pragativeer-user-updated", handleUpdate);
// Poll every 30s (was 10s — less aggressive, kinder on backend restarts)
const interval = setInterval(() => {
fetchUserProfile(userId, true);
}, 10000);
}, 30000);
return () => {
window.removeEventListener('pragativeer-user-updated', handleUpdate);
window.removeEventListener("pragativeer-user-updated", handleUpdate);
clearInterval(interval);
};
}, [userId, fetchUserProfile]);
const switchUser = useCallback(async (newId: string) => {
if (typeof window !== "undefined") {
const sessionToken = crypto.randomUUID();
@@ -122,7 +144,25 @@ export function useActiveUser() {
const hasAccessTo = useCallback((feature: string): boolean => {
if (!user) return false;
const tier = user.tier;
let tier = user.tier;
// Trial logic interception
if (globalSettings && (user.tier === 'Free' || user.id === DEMO_USERS.NEHA.id)) {
const trialDays = globalSettings.trial_duration_days !== undefined ? globalSettings.trial_duration_days : 15;
const trialEnabled = globalSettings.enable_free_trial !== undefined ? globalSettings.enable_free_trial : true;
let isTrialActive = false;
if (trialEnabled && trialDays > 0) {
// For demo purposes, we ignore created_at and just assume trial is active if days > 0
isTrialActive = true;
}
if (isTrialActive) {
tier = (globalSettings.trial_tier as "Pro" | "Al_Coach") || "Al_Coach";
} else {
tier = "Free";
}
}
// Core restrictions mapping based on PRD feature matrix
switch (feature) {
@@ -157,6 +197,7 @@ export function useActiveUser() {
return {
userId,
user,
globalSettings,
loading,
error,
switchUser,

1
userlist.txt Normal file
View File

@@ -0,0 +1 @@
"pragativeer_admin" "local_secure_password_2026"