feat: Add Bulk Registration, Change Password, and update Auth UI

This commit is contained in:
2026-09-24 20:53:21 +05:30
parent 3ed7028bda
commit 85974b97f5
7 changed files with 204 additions and 14 deletions

View File

@@ -12,7 +12,7 @@ export default function AboutPage() {
<p className="text-xl text-muted-foreground max-w-4xl leading-relaxed mb-8 mx-auto text-left md:text-center">
PragatiVeer is India's first AI-powered daily practice platform for JEE and NEET aspirants — a product of Gyannetra Pvt Ltd, the first company to bring the power of artificial intelligence into Indian exam preparation. We combine daily practice sheets, exam-realistic test series, and an AI coach that personalises every student's learning journey, making high-quality preparation accessible and affordable for every Indian student.
</p>
<Link href="/signup">
<Link href="/login">
<Button size="lg" className="rounded-full font-bold px-8 shadow-xl">Join the Revolution</Button>
</Link>
</main>

View File

@@ -1,11 +1,12 @@
"use client"
"use client"
import { fetchWithAuth } from "@/lib/apiClient";
import { useEffect, useState } from "react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Search, Download, Flame, RefreshCw } from "lucide-react"
import { Search, Download, Flame, RefreshCw, Upload } from "lucide-react"
import { useRouter } from "next/navigation"
import { useRef } from "react"
const TIER_STYLE: Record<string, string> = {
"Al_Coach": "bg-[var(--cyan-ai-10)] text-[var(--cyan-ai)] border-[var(--cyan-ai-20)] font-bold",
@@ -25,6 +26,61 @@ export default function StudentsPage() {
const [loading, setLoading] = useState(true)
const [search, setSearch] = useState("")
const fileInputRef = useRef<HTMLInputElement>(null)
const handleBulkUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
setLoading(true)
try {
const formData = new FormData()
formData.append("file", file)
const rawRes = await fetch(process.env.NEXT_PUBLIC_API_URL ? `${process.env.NEXT_PUBLIC_API_URL}/api/admin/students/bulk` : "http://localhost:8000/api/admin/students/bulk", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${localStorage.getItem("active_session_token")}`
}
})
if (!rawRes.ok) throw new Error("Upload failed")
const results = await rawRes.json()
alert(`Successfully registered ${results.length} students!`)
// Download CSV with passwords
let csvContent = "data:text/csv;charset=utf-8,name,email,password\\n"
results.forEach((row: any) => {
csvContent += `${row.name},${row.email},${row.password}\\n`
})
const encodedUri = encodeURI(csvContent)
const link = document.createElement("a")
link.setAttribute("href", encodedUri)
link.setAttribute("download", "bulk_registered_passwords.csv")
document.body.appendChild(link)
link.click()
// Refresh students
const fresh = await fetchWithAuth("/api/admin/students").then(r => r.json())
setStudents(fresh)
} catch (err: any) {
alert("Error: " + err.message)
} finally {
setLoading(false)
if (fileInputRef.current) fileInputRef.current.value = ""
}
}
const downloadTemplate = () => {
const template = "data:text/csv;charset=utf-8,name,email,phone,target_exam,tier\\nJohn Doe,john@example.com,9876543210,JEE_Main,Free\\nJane Smith,jane@example.com,9876543211,NEET,Pro"
const encodedUri = encodeURI(template)
const link = document.createElement("a")
link.setAttribute("href", encodedUri)
link.setAttribute("download", "bulk_register_template.csv")
document.body.appendChild(link)
link.click()
}
useEffect(() => {
fetchWithAuth("/api/admin/students")
.then(r => r.json())
@@ -53,9 +109,18 @@ export default function StudentsPage() {
{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>
<div className="flex gap-2">
<input type="file" accept=".csv" className="hidden" ref={fileInputRef} onChange={handleBulkUpload} />
<Button variant="outline" className="border-border hover:bg-secondary text-[var(--cyan-ai)]" onClick={() => fileInputRef.current?.click()}>
<Upload size={16} className="mr-2" /> Bulk Register CSV
</Button>
<Button variant="outline" className="border-border hover:bg-secondary" onClick={downloadTemplate}>
<Download size={16} className="mr-2" /> Download Template
</Button>
<Button variant="outline" className="border-border hover:bg-secondary">
<Download size={16} className="mr-2" /> Export CSV
</Button>
</div>
</div>
<div className="bg-secondary/10 border border-border rounded-xl overflow-hidden backdrop-blur-sm">

View File

@@ -69,6 +69,44 @@ export default function SettingsPage() {
}
};
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [passwordSaving, setPasswordSaving] = useState(false);
const handleChangePassword = async () => {
if (newPassword !== confirmPassword) {
alert("New passwords do not match.");
return;
}
setPasswordSaving(true);
try {
const res = await fetchWithAuth("/api/auth/change-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
current_password: currentPassword,
new_password: newPassword,
}),
});
const data = await res.json();
if (res.ok) {
alert(data.message || "Password updated successfully!");
setCurrentPassword("");
setNewPassword("");
setConfirmPassword("");
} else {
alert("Failed to change password: " + (data.detail || "Unknown error"));
}
} catch (err) {
console.error(err);
alert("Network error. Could not change password.");
} finally {
setPasswordSaving(false);
}
};
const handleSave = async () => {
if (!userId) return;
setSaving(true);

View File

@@ -138,7 +138,7 @@ export default function LoginPage() {
</Card>
<p className="text-center text-sm text-muted-foreground font-medium">
Don't have an account? <Link href="/signup" className="font-bold text-foreground hover:text-primary transition-colors">Sign up</Link>
For registration contact us: support@pragativeer.com
</p>
</div>
</div>

View File

@@ -83,7 +83,7 @@ export default function MarketingPage() {
<div className="flex items-center gap-4">
<ThemeToggle />
<Link href="/login" className="text-sm font-bold hover:text-primary transition-colors hidden sm:block">Log in</Link>
<Link href="/signup">
<Link href="/login">
<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>
@@ -128,7 +128,7 @@ export default function MarketingPage() {
transition={{ duration: 0.8, delay: 0.4 }}
className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-8"
>
<Link href="/signup">
<Link href="/login">
<Button size="lg" className="group rounded-full h-14 px-8 text-base font-bold shadow-[0_0_40px_-10px_var(--color-primary)] hover:shadow-[0_0_60px_-10px_var(--color-primary)] transition-all w-full sm:w-auto relative overflow-hidden">
<span className="relative z-10 flex items-center">Start Free Trial <ArrowRight className="ml-2 group-hover:translate-x-1 transition-transform" size={18} /></span>
<div className="absolute inset-0 -translate-x-full animate-shine bg-gradient-to-r from-transparent via-white/20 to-transparent z-0"></div>
@@ -437,8 +437,8 @@ export default function MarketingPage() {
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-white" /> Basic Performance Analytics</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-white" /> Community Access</li>
</ul>
<Link href="/signup">
<Button variant="outline" size="lg" className="w-full rounded-2xl h-12 border-white text-white font-bold text-lg hover:bg-secondary hover:text-white">Get Started Free</Button>
<Link href="/login">
<Button variant="outline" size="lg" className="w-full rounded-2xl h-12 border-white text-white font-bold text-lg hover:bg-secondary hover:text-white">Login</Button>
</Link>
</Card>
</motion.div>
@@ -461,7 +461,7 @@ export default function MarketingPage() {
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-yellow-500" /> Detailed Performance Analytics</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-yellow-500" /> Parent Dashboard Access</li>
</ul>
<Link href="/signup">
<Link href="/login">
<Button size="lg" className="w-full rounded-2xl h-12 bg-yellow-500 text-black font-bold text-lg hover:bg-yellow-400">Get Pro</Button>
</Link>
</Card>
@@ -485,7 +485,7 @@ export default function MarketingPage() {
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-[var(--cyan-ai)]" /> 24/7 Streaming AI Tutor Chat</li>
<li className="flex items-center gap-4"><CheckCircle2 size={24} className="text-[var(--cyan-ai)]" /> Predictive Rank & College Predictor</li>
</ul>
<Link href="/signup">
<Link href="/login">
<Button size="lg" className="w-full rounded-2xl h-12 font-bold text-lg bg-[var(--cyan-ai)] text-black hover:bg-[var(--cyan-ai-80)] transition-all">Get AI Coach</Button>
</Link>
</Card>