Import source: cshare-app/src/pages/PricingPage
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { CheckIcon, ZapIcon, ShieldIcon, GlobeIcon } from '../components/Icons';
|
||||
|
||||
const PLANS = [
|
||||
{
|
||||
name: 'Free',
|
||||
price: null,
|
||||
desc: 'For casual sharing. No card required.',
|
||||
cta: 'Get started free',
|
||||
ctaPath: '/upload',
|
||||
primary: false,
|
||||
features: [
|
||||
'5 uploads per month',
|
||||
'100 MB max per file',
|
||||
'7-day link expiry',
|
||||
'Basic view tracking',
|
||||
'Copy share link',
|
||||
],
|
||||
missing: ['Custom expiry', 'Password protection', 'Analytics dashboard', 'Priority support'],
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
price: 9,
|
||||
desc: 'For individuals who share files seriously.',
|
||||
cta: 'Start Pro trial',
|
||||
ctaPath: '/upload',
|
||||
primary: true,
|
||||
features: [
|
||||
'Unlimited uploads',
|
||||
'500 MB max per file',
|
||||
'Custom expiry (1h → 90 days)',
|
||||
'Full analytics dashboard',
|
||||
'Password-protected links',
|
||||
'Download tracking',
|
||||
'Priority support',
|
||||
],
|
||||
missing: ['Team dashboard', 'SSO / SAML'],
|
||||
},
|
||||
{
|
||||
name: 'Team',
|
||||
price: 24,
|
||||
desc: 'For small teams that need shared oversight.',
|
||||
cta: 'Talk to us',
|
||||
ctaPath: '/contact',
|
||||
primary: false,
|
||||
features: [
|
||||
'Everything in Pro',
|
||||
'Up to 10 seats',
|
||||
'Shared team dashboard',
|
||||
'Centralized billing',
|
||||
'Role-based permissions',
|
||||
'API access',
|
||||
'SLA + dedicated support',
|
||||
],
|
||||
missing: [],
|
||||
},
|
||||
];
|
||||
|
||||
export default function PricingPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="page-enter" style={{ minHeight: '100vh' }}>
|
||||
{/* Header */}
|
||||
<section style={{ padding: 'clamp(64px, 10vh, 100px) 24px 48px', textAlign: 'center', position: 'relative', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 70% 60% at 50% 0%, rgba(74,144,255,0.06) 0%, transparent 70%)', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'relative', zIndex: 1, maxWidth: '640px', margin: '0 auto' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }}>
|
||||
<span style={{ color: 'var(--accent)', fontFamily: 'var(--font-heading)', fontWeight: 700 }}>//</span>
|
||||
<span style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-heading)', fontWeight: 600, fontSize: '0.75rem', letterSpacing: '0.12em', textTransform: 'uppercase' }}>Pricing</span>
|
||||
</div>
|
||||
<h1 className="heading-lg" style={{ marginBottom: '16px' }}>Simple, honest pricing.</h1>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '1.0625rem', lineHeight: 1.7 }}>
|
||||
No per-file fees, no storage surprises. Pay for what you need, cancel any time.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Plan cards */}
|
||||
<section style={{ padding: '0 24px 80px', maxWidth: '1080px', margin: '0 auto' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '20px', alignItems: 'start' }}>
|
||||
{PLANS.map((plan) => (
|
||||
<div
|
||||
key={plan.name}
|
||||
className="card"
|
||||
style={{
|
||||
padding: '32px',
|
||||
position: 'relative',
|
||||
border: plan.primary ? '1px solid var(--accent-border)' : '1px solid var(--border-subtle)',
|
||||
boxShadow: plan.primary ? '0 0 40px rgba(74,144,255,0.08)' : 'none',
|
||||
}}
|
||||
>
|
||||
{plan.primary && (
|
||||
<>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, transparent, var(--accent), transparent)' }} />
|
||||
<div style={{
|
||||
position: 'absolute', top: '16px', right: '16px',
|
||||
padding: '3px 10px', background: 'var(--accent)', borderRadius: '999px',
|
||||
fontSize: '0.6875rem', fontFamily: 'var(--font-body)', fontWeight: 600,
|
||||
color: '#fff', letterSpacing: '0.04em',
|
||||
}}>
|
||||
Most popular
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<div style={{ fontFamily: 'var(--font-heading)', fontWeight: 700, fontSize: '1rem', color: 'var(--text-secondary)', letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: '12px' }}>
|
||||
{plan.name}
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4px', marginBottom: '8px' }}>
|
||||
{plan.price ? (
|
||||
<>
|
||||
<span style={{ fontFamily: 'var(--font-heading)', fontWeight: 800, fontSize: '2.75rem', letterSpacing: '-0.04em', lineHeight: 1 }}>
|
||||
${plan.price}
|
||||
</span>
|
||||
<span style={{ color: 'var(--text-muted)', fontSize: '0.875rem' }}>/mo</span>
|
||||
</>
|
||||
) : (
|
||||
<span style={{ fontFamily: 'var(--font-heading)', fontWeight: 800, fontSize: '2.75rem', letterSpacing: '-0.04em', lineHeight: 1 }}>
|
||||
Free
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.875rem', lineHeight: 1.6 }}>{plan.desc}</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`btn ${plan.primary ? 'btn-primary' : 'btn-secondary'}`}
|
||||
style={{ width: '100%', marginBottom: '28px' }}
|
||||
onClick={() => navigate(plan.ctaPath)}
|
||||
>
|
||||
{plan.cta}
|
||||
</button>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||
{plan.features.map((f) => (
|
||||
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.875rem', color: 'var(--text-secondary)' }}>
|
||||
<CheckIcon size={14} style={{ color: 'var(--success)', flexShrink: 0 }} />
|
||||
{f}
|
||||
</div>
|
||||
))}
|
||||
{plan.missing.map((f) => (
|
||||
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '0.875rem', color: 'var(--text-muted)', opacity: 0.5 }}>
|
||||
<div style={{ width: '14px', height: '14px', borderRadius: '50%', border: '1.5px solid var(--border)', flexShrink: 0 }} />
|
||||
{f}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* FAQ strip */}
|
||||
<div style={{ marginTop: '64px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: '20px' }}>
|
||||
{[
|
||||
{ icon: <ZapIcon size={18} />, q: 'Can I upgrade or downgrade?', a: 'Yes, any time. Changes take effect immediately and are prorated to the day.' },
|
||||
{ icon: <ShieldIcon size={18} />, q: 'What happens when a free link expires?', a: 'The file and link are permanently deleted from our servers. No recovery.' },
|
||||
{ icon: <GlobeIcon size={18} />, q: 'Do recipients need an account?', a: 'Never. Anyone with the link can download — no sign-up wall on their end.' },
|
||||
].map(({ icon, q, a }) => (
|
||||
<div key={q} className="card" style={{ padding: '24px' }}>
|
||||
<div style={{ color: 'var(--accent)', marginBottom: '10px' }}>{icon}</div>
|
||||
<div style={{ fontFamily: 'var(--font-heading)', fontWeight: 600, fontSize: '0.9375rem', marginBottom: '8px' }}>{q}</div>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '0.875rem', lineHeight: 1.65 }}>{a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user