Files
PastpaperMaster/frontend/src/components/shared/PricingModal.tsx
Zhao 10fa2b74ef feat: guest access, pricing modal, UI polish, LaTeX prompt fix
- Remove login gate, allow guest browsing with Sign in link
- Add favicon (book logo)
- Add pricing modal (Free/Standard/Exam) with hover animations
- Dynamic course list from DB instead of hardcoded
- Enforce LaTeX in AI trio generation prompt
- UI improvements: homepage animations, analytics donut chart, error book cards
- Fix error book locked state for guests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:40:22 +07:00

204 lines
10 KiB
TypeScript

import { useEffect, useState } from "react";
export default function PricingModal({ onClose }: { onClose: () => void }) {
const [visible, setVisible] = useState(false);
// Animate in
useEffect(() => {
requestAnimationFrame(() => setVisible(true));
}, []);
const handleClose = () => {
setVisible(false);
setTimeout(onClose, 200);
};
return (
<div
className={`fixed inset-0 z-50 flex items-center justify-center transition-all duration-200 ${
visible ? "bg-black/50 backdrop-blur-sm" : "bg-black/0"
}`}
onClick={handleClose}
>
<div
className={`bg-gradient-to-b from-slate-50 to-white rounded-2xl shadow-2xl max-w-[820px] w-full mx-4 relative overflow-hidden transition-all duration-300 ${
visible ? "opacity-100 scale-100 translate-y-0" : "opacity-0 scale-95 translate-y-4"
}`}
onClick={(e) => e.stopPropagation()}
>
{/* Decorative top bar */}
<div className="h-1.5 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500" />
{/* Close */}
<button
onClick={handleClose}
className="absolute top-5 right-5 w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 text-gray-400 hover:bg-gray-200 hover:text-gray-600 hover:rotate-90 transition-all duration-200 text-lg"
>
&times;
</button>
<div className="px-8 pt-7 pb-8">
{/* Header */}
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 tracking-tight">
Choose Your Plan
</h2>
<p className="text-sm text-gray-400 mt-1.5">
Unlock smarter exam prep. All prices in HKD per month.
</p>
</div>
{/* Plans grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 items-stretch">
{/* ── Free ── */}
<div className="group relative rounded-2xl border border-gray-200 bg-white p-6 flex flex-col shadow-sm hover:shadow-md hover:border-gray-300 hover:-translate-y-1 transition-all duration-300 cursor-default">
{/* Hover glow */}
<div className="absolute inset-0 rounded-2xl bg-gradient-to-b from-gray-50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none" />
<div className="relative mb-5">
<h3 className="text-lg font-bold text-gray-800 group-hover:text-gray-900 transition-colors">Free</h3>
<p className="text-xs text-gray-400 mt-0.5">Get started</p>
</div>
<div className="relative mb-5">
<span className="text-4xl font-extrabold text-gray-900">$0</span>
<span className="text-sm text-gray-400 ml-1">/ month</span>
</div>
<ul className="relative space-y-2.5 flex-1 mb-6">
{[
"Browse public papers",
"View AI solutions",
"Basic error book",
].map((f, i) => (
<li
key={f}
className="flex items-start gap-2 text-sm text-gray-600 group-hover:text-gray-700 transition-colors"
style={{ transitionDelay: `${i * 30}ms` }}
>
<svg className="w-4 h-4 text-gray-300 group-hover:text-gray-400 shrink-0 mt-0.5 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
{f}
</li>
))}
</ul>
<button
disabled
className="relative w-full py-2.5 rounded-xl text-sm font-semibold bg-gray-100 text-gray-400 cursor-default"
>
Free Forever
</button>
</div>
{/* ── Standard (current, with early bird) ── */}
<div className="group relative rounded-2xl border-2 border-indigo-500 bg-white p-6 flex flex-col shadow-lg shadow-indigo-100 scale-[1.03] hover:shadow-xl hover:shadow-indigo-200 hover:-translate-y-1 transition-all duration-300">
{/* Animated shimmer */}
<div className="absolute inset-0 rounded-2xl overflow-hidden pointer-events-none">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-indigo-50/80 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 ease-in-out" />
</div>
{/* Badge */}
<span className="absolute -top-3.5 left-1/2 -translate-x-1/2 text-[11px] font-bold px-4 py-1 rounded-full bg-indigo-600 text-white shadow-md whitespace-nowrap group-hover:shadow-lg group-hover:bg-indigo-700 transition-all duration-200">
Your Plan
</span>
<div className="relative mb-5 mt-1">
<h3 className="text-lg font-bold text-gray-800">Standard</h3>
<p className="text-xs text-gray-400 mt-0.5">Most popular</p>
</div>
<div className="relative mb-1">
<span className="text-sm text-gray-400 line-through">$19.9</span>
</div>
<div className="relative mb-1 flex items-baseline gap-2">
<span className="text-4xl font-extrabold text-indigo-600 group-hover:text-indigo-700 transition-colors">$9.9</span>
<span className="text-sm text-gray-400">/ month</span>
</div>
<div className="relative mb-5">
<span className="inline-block text-[10px] font-bold px-2.5 py-0.5 rounded-full bg-gradient-to-r from-amber-400 to-orange-400 text-white tracking-wide group-hover:from-amber-500 group-hover:to-orange-500 group-hover:shadow-sm transition-all duration-200 animate-pulse">
EARLY BIRD PRICE
</span>
</div>
<ul className="relative space-y-2.5 flex-1 mb-6">
{[
"Unlimited paper uploads",
"AI question extraction",
"Step-by-step solutions",
"Photo auto-grading",
"Similar question retrieval",
"Course analytics",
"Error book + review",
].map((f, i) => (
<li
key={f}
className="flex items-start gap-2 text-sm text-gray-700 group-hover:translate-x-0.5 transition-transform duration-200"
style={{ transitionDelay: `${i * 30}ms` }}
>
<svg className="w-4 h-4 text-indigo-500 group-hover:text-indigo-600 shrink-0 mt-0.5 transition-colors group-hover:scale-110 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
{f}
</li>
))}
</ul>
<button
disabled
className="relative w-full py-2.5 rounded-xl text-sm font-bold bg-indigo-600 text-white cursor-default opacity-90"
>
Current Plan
</button>
</div>
{/* ── Exam ── */}
<div className="group relative rounded-2xl border border-gray-200 bg-white p-6 flex flex-col shadow-sm hover:shadow-lg hover:border-amber-300 hover:-translate-y-1 transition-all duration-300 cursor-pointer">
{/* Hover glow */}
<div className="absolute inset-0 rounded-2xl bg-gradient-to-b from-amber-50/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none" />
<span className="absolute -top-3.5 left-1/2 -translate-x-1/2 text-[11px] font-bold px-4 py-1 rounded-full bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-md whitespace-nowrap group-hover:shadow-lg group-hover:scale-105 transition-all duration-200">
Best Value
</span>
<div className="relative mb-5 mt-1">
<h3 className="text-lg font-bold text-gray-800 group-hover:text-gray-900 transition-colors">Exam</h3>
<p className="text-xs text-gray-400 mt-0.5">Finals season</p>
</div>
<div className="relative mb-5">
<span className="text-4xl font-extrabold text-gray-900 group-hover:text-amber-600 transition-colors duration-300">$29.9</span>
<span className="text-sm text-gray-400 ml-1">/ month</span>
</div>
<ul className="relative space-y-2.5 flex-1 mb-6">
{[
"Everything in Standard",
"AI Tutor (RAG-powered)",
"Priority processing",
"Unlimited variant generation",
"Cross-course analytics",
"Export & print",
].map((f, i) => (
<li
key={f}
className="flex items-start gap-2 text-sm text-gray-700 group-hover:translate-x-0.5 transition-transform duration-200"
style={{ transitionDelay: `${i * 30}ms` }}
>
<svg className="w-4 h-4 text-amber-500 group-hover:text-amber-600 shrink-0 mt-0.5 transition-all group-hover:scale-110" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
{f}
</li>
))}
</ul>
<button className="relative w-full py-2.5 rounded-xl text-sm font-bold bg-gray-900 text-white hover:bg-amber-600 hover:shadow-lg hover:shadow-amber-200 active:scale-[0.98] transition-all duration-200">
Upgrade
</button>
</div>
</div>
<p className="text-center text-xs text-gray-400 mt-7">
All registered users enjoy Early Bird pricing during beta.
Payment integration coming soon.
</p>
</div>
</div>
</div>
);
}