From a6db3e7c17ea41aa615faa727b98e922480baa3a Mon Sep 17 00:00:00 2001 From: ectasy663 Date: Thu, 24 Sep 2026 12:12:37 +0530 Subject: [PATCH] feat: add per-user trial control in admin panel (backend + UI) --- backend/main.py | 10 +++- src/app/admin/students/[studentId]/page.tsx | 64 ++++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/backend/main.py b/backend/main.py index 1f53a28..86f625a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1470,6 +1470,7 @@ class AdminUserPatch(BaseModel): school: Optional[str] = None city: Optional[str] = None state: Optional[str] = None + trial_days: Optional[int] = None # number of trial days to grant from now (0 = remove trial) @app.get("/api/admin/students", tags=["Admin"]) def list_all_students(session: Session = Depends(get_session), current_user: models.User = Depends(get_current_user)): @@ -1608,7 +1609,7 @@ def get_student_detail(user_id: UUID, session: Session = Depends(get_session), c @app.patch("/api/admin/students/{user_id}", tags=["Admin"]) def patch_student(user_id: UUID, payload: AdminUserPatch, session: Session = Depends(get_session), current_user: models.User = Depends(get_current_user)): - """Admin update: tier, name, streak, exam, location.""" + """Admin update: tier, name, streak, exam, location, trial.""" user = session.get(models.User, user_id) if not user: raise HTTPException(status_code=404, detail="Student not found") @@ -1619,10 +1620,15 @@ def patch_student(user_id: UUID, payload: AdminUserPatch, session: Session = Dep 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 + if payload.trial_days is not None: + if payload.trial_days <= 0: + user.trial_end = None # remove trial + else: + user.trial_end = datetime.utcnow() + timedelta(days=payload.trial_days) session.add(user) session.commit() session.refresh(user) - return {"status": "ok", "user_id": str(user.id), "tier": user.tier.value} + return {"status": "ok", "user_id": str(user.id), "tier": user.tier.value, "trial_end": user.trial_end.isoformat() if user.trial_end else None} # --- BILLING & PAYMENTS --- diff --git a/src/app/admin/students/[studentId]/page.tsx b/src/app/admin/students/[studentId]/page.tsx index b4928dd..7cec9b5 100644 --- a/src/app/admin/students/[studentId]/page.tsx +++ b/src/app/admin/students/[studentId]/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client" import { fetchWithAuth } from "@/lib/apiClient"; import { useEffect, useState } from "react" @@ -45,6 +45,8 @@ export default function StudentKundliPage() { const [editTier, setEditTier] = useState("") const [editStreak, setEditStreak] = useState(0) const [editExam, setEditExam] = useState("") + const [trialDays, setTrialDays] = useState(15) + const [grantingTrial, setGrantingTrial] = useState(false) useEffect(() => { fetchWithAuth(`/api/admin/students/${studentId}`) @@ -370,6 +372,66 @@ export default function StudentKundliPage() { )} + {/* Trial Control Card */} + + + + Trial Access Control + + + +
+
+ +

+ {data.trial_end ? ( + new Date() ? "text-green-500" : "text-red-500"}> + {formatDate(data.trial_end)} ({new Date(data.trial_end) > new Date() ? "Active" : "Expired"}) + + ) : ( + No trial set + )} +

+
+
+ +
+ setTrialDays(Number(e.target.value))} + className="w-24 bg-background border border-border rounded-lg px-3 py-2 text-sm font-bold focus:outline-none focus:ring-2 focus:ring-amber-500" + /> + +
+

Set 0 to remove trial. Trial starts from now.

+
+
+
+
) }