Files
pragativeer-v2/src/app/upgrade/page.tsx

230 lines
11 KiB
TypeScript

"use client"
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 { useActiveUser } from "@/hooks/useActiveUser"
const WEBHOOK_SECRET = "local_webhook_secret"
export default function UpgradePage() {
const { userId, loading: userLoading, refreshUser } = useActiveUser()
const [loadingTier, setLoadingTier] = useState<string | null>(null)
const [showModal, setShowModal] = useState(false)
const [orderData, setOrderData] = useState<any>(null)
const [processing, setProcessing] = useState(false)
const [success, setSuccess] = useState(false)
const handleUpgrade = async (tier: string) => {
if (userLoading || !userId) return
setLoadingTier(tier)
try {
const res = await fetch(`/api/billing/create-order?user_id=${userId}&plan_tier=${tier}`, {
method: "POST"
})
if (!res.ok) throw new Error("Order creation failed")
const order = await res.json()
setOrderData({ ...order, tier })
setShowModal(true)
} catch (err) {
console.error(err)
alert("Failed to create order")
} finally {
setLoadingTier(null)
}
}
const generateMockSignature = async (payloadStr: string) => {
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(WEBHOOK_SECRET),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(payloadStr));
return Array.from(new Uint8Array(signature)).map(b => b.toString(16).padStart(2, '0')).join('');
}
const simulatePayment = async () => {
if (!userId) return
setProcessing(true)
try {
const payloadObj = {
event: "payment.captured",
payload: {
payment: {
entity: {
id: `pay_mock_${Date.now()}`,
amount: orderData.amount,
notes: {
user_id: userId,
tier_requested: orderData.tier
}
}
}
}
}
const payloadStr = JSON.stringify(payloadObj)
const signature = await generateMockSignature(payloadStr)
const res = await fetch(`/api/billing/webhook/razorpay`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Razorpay-Signature": signature
},
body: payloadStr
})
if (!res.ok) throw new Error("Webhook failed")
setSuccess(true)
await refreshUser()
setTimeout(() => {
window.location.href = "/dashboard"
}, 2000)
} catch (err) {
console.error(err)
alert("Payment simulation failed")
setProcessing(false)
}
}
return (
<div className="p-8 max-w-7xl mx-auto animate-in fade-in duration-500 min-h-screen">
<div className="text-center max-w-2xl mx-auto mb-16">
<h1 className="text-4xl font-extrabold tracking-tight mb-4">Upgrade Your Preparation</h1>
<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">
{/* 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-normal">/month</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>
</CardFooter>
</Card>
{/* Pro Tier */}
<Card className="bg-background/80 backdrop-blur-md border-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-normal">/month</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>
<Button
className="w-full bg-yellow-500 text-black hover:bg-yellow-400 font-bold text-lg rounded-2xl h-12"
onClick={() => handleUpgrade("Pro")}
disabled={!!loadingTier}
>
{loadingTier === "Pro" ? <Loader2 className="animate-spin" /> : "Get Pro"}
</Button>
</CardFooter>
</Card>
{/* AI Coach Tier */}
<Card className="bg-secondary/30 backdrop-blur-xl border-[#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-normal">/month</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>
<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")}
disabled={!!loadingTier}
>
{loadingTier === "Al_Coach" ? <Loader2 className="animate-spin" /> : "Get AI Coach"}
</Button>
</CardFooter>
</Card>
</div>
{/* Mock Razorpay Modal */}
{showModal && (
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<Card className="max-w-md w-full shadow-2xl border-primary">
<CardHeader className="text-center pb-2">
<div className="mx-auto w-16 h-12 bg-blue-600 rounded-lg flex items-center justify-center mb-4">
<span className="text-white font-black text-2xl">RZP</span>
</div>
<CardTitle>Mock Checkout Window</CardTitle>
<CardDescription>Razorpay Test Sandbox Simulation</CardDescription>
</CardHeader>
<CardContent className="space-y-4 mt-4">
<div className="bg-secondary p-4 rounded-xl space-y-2 text-sm font-mono">
<div className="flex justify-between">
<span className="text-muted-foreground">Order ID:</span>
<span className="font-bold">{orderData?.id}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Amount:</span>
<span className="font-bold">₹{(orderData?.amount / 100).toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Tier:</span>
<span className="font-bold">{orderData?.tier}</span>
</div>
</div>
{success ? (
<div className="p-4 bg-green-500/10 text-green-500 rounded-xl text-center font-bold animate-in zoom-in">
Payment Captured! Redirecting...
</div>
) : (
<p className="text-sm text-muted-foreground text-center">
Clicking confirm will securely sign and POST a payment.captured webhook to the backend.
</p>
)}
</CardContent>
<CardFooter className="flex gap-3">
<Button variant="outline" className="w-full" onClick={() => setShowModal(false)} disabled={processing || success}>
Cancel
</Button>
<Button className="w-full bg-blue-600 hover:bg-blue-700 text-white" onClick={simulatePayment} disabled={processing || success}>
{processing ? <Loader2 className="animate-spin" /> : "Confirm Payment"}
</Button>
</CardFooter>
</Card>
</div>
)}
</div>
)
}