Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { trackUpgradeClicked } from '@/lib/analytics';
|
||||
|
||||
type Cadence = 'monthly' | 'annual';
|
||||
|
||||
const PRICES: Record<Cadence, { display: string; sub: string; checkoutParam: string }> = {
|
||||
monthly: { display: '$44.99', sub: '/mo', checkoutParam: '' },
|
||||
annual: { display: '$449.99', sub: '/yr · save 17%', checkoutParam: '&cadence=annual' },
|
||||
};
|
||||
|
||||
const EARLY_FEATURES = [
|
||||
'Unlimited reads',
|
||||
'Full factor analysis (40+ signals)',
|
||||
'Kill conditions surfaced inline',
|
||||
'Cascade alerts when lineups shift',
|
||||
'Parlay leg history with grades',
|
||||
'Sportsbook deep links',
|
||||
];
|
||||
|
||||
const DESK_EXTRAS = [
|
||||
'Real-time multi-book odds screen',
|
||||
'Live probability tracker during games',
|
||||
'Middles detection',
|
||||
'Correlation explorer',
|
||||
'Fair odds comparison (VYNDR vs book)',
|
||||
'Kelly sizing recommendations',
|
||||
'Cascade alerts with full detail',
|
||||
'Priority support',
|
||||
];
|
||||
|
||||
export default function DeskUpgradePage() {
|
||||
const router = useRouter();
|
||||
const { tier } = useAuth();
|
||||
const [cadence, setCadence] = useState<Cadence>('monthly');
|
||||
const price = PRICES[cadence];
|
||||
|
||||
const upgrade = () => {
|
||||
trackUpgradeClicked({ current_tier: tier, target_tier: 'desk', trigger_location: 'desk_upgrade_page' });
|
||||
router.push(`/api/checkout?tier=desk${price.checkoutParam}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<section style={{ maxWidth: 980, margin: '0 auto', padding: '40px 16px 120px' }}>
|
||||
<header style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||
<p className="lbl" style={{ color: 'var(--grade-a)', marginBottom: 12 }}>UPGRADE</p>
|
||||
<h1 style={{ fontSize: 'clamp(28px, 4vw, 40px)', fontWeight: 700, letterSpacing: '-0.02em' }}>
|
||||
The full terminal.
|
||||
</h1>
|
||||
<p style={{ marginTop: 8, color: 'var(--text-1)', fontSize: 16, maxWidth: 620, marginInline: 'auto' }}>
|
||||
Everything in Early Intelligence, plus real-time odds, live probability, and the tools the sharps use.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div
|
||||
className="surface diagonal-cut tex-scan"
|
||||
style={{
|
||||
padding: 24,
|
||||
marginBottom: 24,
|
||||
textAlign: 'center',
|
||||
display: 'grid',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<p className="lbl" style={{ color: 'var(--text-1)' }}>BLOOMBERG ODDS SCREEN · PREVIEW</p>
|
||||
<div
|
||||
aria-hidden
|
||||
style={{
|
||||
margin: '8px auto 0',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||
gap: 4,
|
||||
maxWidth: 720,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 24 }, (_, i) => {
|
||||
const sample = ['—', '2.5', '+108', '-120', 'A-', 'A+'][i % 6];
|
||||
const isGrade = sample === 'A-' || sample === 'A+';
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="num"
|
||||
style={{
|
||||
padding: '8px 4px',
|
||||
fontSize: 12,
|
||||
background: i % 6 === 5 ? 'rgba(0, 212, 160, 0.08)' : 'var(--bg-1)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 4,
|
||||
color: isGrade ? 'var(--grade-a)' : 'var(--text-1)',
|
||||
textShadow: isGrade ? '0 0 8px rgba(0, 212, 160, 0.55)' : 'none',
|
||||
}}
|
||||
>
|
||||
{sample}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gap: 16,
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<TierCard
|
||||
label="Early Intelligence"
|
||||
price="$14.99"
|
||||
period="/mo"
|
||||
subtitle="Your current plan"
|
||||
features={EARLY_FEATURES}
|
||||
subdued
|
||||
/>
|
||||
<TierCard
|
||||
label="Desk"
|
||||
price={price.display}
|
||||
period={price.sub}
|
||||
subtitle="Everything above, plus:"
|
||||
features={DESK_EXTRAS}
|
||||
highlight
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="surface"
|
||||
style={{
|
||||
padding: 24,
|
||||
textAlign: 'center',
|
||||
display: 'grid',
|
||||
gap: 12,
|
||||
justifyItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div role="tablist" aria-label="Billing cadence" style={{ display: 'inline-flex', gap: 4, background: 'var(--bg-2)', borderRadius: 999, padding: 4 }}>
|
||||
{(['monthly', 'annual'] as Cadence[]).map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
role="tab"
|
||||
aria-selected={cadence === c}
|
||||
onClick={() => setCadence(c)}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
borderRadius: 999,
|
||||
border: 'none',
|
||||
background: cadence === c ? 'var(--bg-3)' : 'transparent',
|
||||
color: cadence === c ? 'var(--text-0)' : 'var(--text-1)',
|
||||
fontFamily: 'IBM Plex Mono, monospace',
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.08em',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{c.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p
|
||||
className="num"
|
||||
style={{
|
||||
fontSize: 40,
|
||||
color: 'var(--grade-a)',
|
||||
textShadow: '0 0 18px rgba(0, 212, 160, 0.7)',
|
||||
lineHeight: 1,
|
||||
marginTop: 4,
|
||||
}}
|
||||
>
|
||||
{price.display}
|
||||
<span style={{ fontSize: 14, color: 'var(--text-1)' }}>{price.sub}</span>
|
||||
</p>
|
||||
|
||||
{tier !== 'desk' && (
|
||||
<p className="lbl" style={{ color: 'var(--text-1)' }}>
|
||||
You're currently on {tier === 'analyst' ? 'Early Intelligence' : 'Free'}.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn-primary"
|
||||
style={{ padding: '14px 32px', fontSize: 15, marginTop: 4 }}
|
||||
onClick={upgrade}
|
||||
>
|
||||
Upgrade to Desk →
|
||||
</button>
|
||||
|
||||
<p style={{ color: 'var(--text-2)', fontSize: 12 }}>
|
||||
Questions? <a href="mailto:support@vyndr.app" style={{ color: 'var(--text-1)' }}>support@vyndr.app</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function TierCard({
|
||||
label,
|
||||
price,
|
||||
period,
|
||||
subtitle,
|
||||
features,
|
||||
subdued,
|
||||
highlight,
|
||||
}: {
|
||||
label: string;
|
||||
price: string;
|
||||
period: string;
|
||||
subtitle: string;
|
||||
features: string[];
|
||||
subdued?: boolean;
|
||||
highlight?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={highlight ? 'surface diagonal-cut tex-scan' : 'surface'}
|
||||
style={{
|
||||
padding: 24,
|
||||
opacity: subdued ? 0.85 : 1,
|
||||
borderColor: highlight ? 'var(--grade-a)' : undefined,
|
||||
boxShadow: highlight ? '0 0 0 1px rgba(0,212,160,0.25), 0 12px 40px rgba(0,212,160,0.15)' : undefined,
|
||||
}}
|
||||
>
|
||||
<p className="lbl" style={{ color: highlight ? 'var(--grade-a)' : 'var(--text-1)' }}>{label.toUpperCase()}</p>
|
||||
<p
|
||||
className="num"
|
||||
style={{
|
||||
fontSize: 28,
|
||||
marginTop: 6,
|
||||
color: highlight ? 'var(--grade-a)' : 'var(--text-0)',
|
||||
textShadow: highlight ? '0 0 14px rgba(0, 212, 160, 0.55)' : 'none',
|
||||
}}
|
||||
>
|
||||
{price}<span style={{ fontSize: 13, color: 'var(--text-1)' }}>{period}</span>
|
||||
</p>
|
||||
<p style={{ fontSize: 13, color: 'var(--text-1)', marginTop: 4 }}>{subtitle}</p>
|
||||
<ul style={{ listStyle: 'none', padding: 0, margin: '14px 0 0', display: 'grid', gap: 8 }}>
|
||||
{features.map((f) => (
|
||||
<li key={f} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 14, color: 'var(--text-0)' }}>
|
||||
<span aria-hidden style={{ color: 'var(--grade-a)', fontFamily: 'IBM Plex Mono, monospace' }}>›</span>
|
||||
<span>{f}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user