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>
This commit is contained in:
203
frontend/src/components/shared/PricingModal.tsx
Normal file
203
frontend/src/components/shared/PricingModal.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
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"
|
||||
>
|
||||
×
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user