feat: add per-user trial control in admin panel (backend + UI)

This commit is contained in:
2026-09-24 12:12:37 +05:30
parent 85665a5eab
commit a6db3e7c17
2 changed files with 71 additions and 3 deletions

View File

@@ -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() {
</CardContent>
</Card>
)}
{/* Trial Control Card */}
<Card className="border-amber-500/30 bg-amber-500/5 shadow-sm">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base text-amber-500">
<Flame size={16} className="text-amber-500" /> Trial Access Control
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap items-end gap-4">
<div className="space-y-1.5">
<label className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Current Trial End</label>
<p className="text-sm font-semibold">
{data.trial_end ? (
<span className={new Date(data.trial_end) > new Date() ? "text-green-500" : "text-red-500"}>
{formatDate(data.trial_end)} ({new Date(data.trial_end) > new Date() ? "Active" : "Expired"})
</span>
) : (
<span className="text-muted-foreground">No trial set</span>
)}
</p>
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-muted-foreground uppercase tracking-wider">Grant Trial (days)</label>
<div className="flex gap-2">
<input
type="number"
min={0}
max={365}
value={trialDays}
onChange={e => 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"
/>
<Button
onClick={async () => {
setGrantingTrial(true)
try {
const res = await fetchWithAuth(`/api/admin/students/${studentId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ trial_days: trialDays }),
})
if (!res.ok) throw new Error("Failed")
const fresh = await fetchWithAuth(`/api/admin/students/${studentId}`).then(r => r.json())
setData(fresh)
alert(`Trial ${trialDays > 0 ? `granted for ${trialDays} days` : 'removed'} successfully!`)
} catch { alert("Failed to update trial") }
finally { setGrantingTrial(false) }
}}
disabled={grantingTrial}
className="bg-amber-500 text-black hover:bg-amber-400 font-bold gap-2"
>
{grantingTrial ? <RefreshCw size={14} className="animate-spin" /> : <CheckCircle2 size={14} />}
{trialDays > 0 ? `Grant ${trialDays}d Trial` : "Remove Trial"}
</Button>
</div>
<p className="text-xs text-muted-foreground">Set 0 to remove trial. Trial starts from now.</p>
</div>
</div>
</CardContent>
</Card>
</div>
)
}