feat: add per-user trial control in admin panel (backend + UI)
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user