Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+196
View File
@@ -0,0 +1,196 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/contexts/AuthContext';
interface FullProfile {
id: string;
email: string;
tier: 'free' | 'analyst' | 'desk';
scan_count: number;
scan_reset_date: string;
subscription_status: string;
subscription_end: string | null;
founder_pricing: boolean;
cancel_at_period_end: boolean;
}
export default function ProfilePage() {
const router = useRouter();
const { user, signOut, loading: authLoading } = useAuth();
const [profile, setProfile] = useState<FullProfile | null>(null);
const [working, setWorking] = useState(false);
const [error, setError] = useState('');
useEffect(() => {
if (!authLoading && !user) router.replace('/login?next=/profile');
}, [authLoading, user, router]);
useEffect(() => {
if (!user) return;
const token = typeof window !== 'undefined' ? localStorage.getItem('sb-token') : null;
fetch('/api/user/profile', {
headers: token ? { Authorization: `Bearer ${token}` } : {},
})
.then((r) => r.json())
.then(setProfile)
.catch(() => setProfile(null));
}, [user]);
const handleCancel = async () => {
if (!confirm('Cancel your subscription at the end of the current period?')) return;
setWorking(true);
setError('');
const token = typeof window !== 'undefined' ? localStorage.getItem('sb-token') : null;
const res = await fetch('/api/user/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ cancel_at_period_end: true }),
});
setWorking(false);
if (res.ok) {
const updated = await res.json();
setProfile((p) => (p ? { ...p, ...updated } : p));
} else {
const body = await res.json().catch(() => ({}));
setError(body.error || 'Could not update — try again in a moment.');
}
};
if (authLoading || !user || !profile) {
return (
<section style={{ minHeight: '60vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<p className="mono" style={{ color: 'var(--text-tertiary)' }}>Loading profile</p>
</section>
);
}
return (
<section style={{ maxWidth: 600, margin: '0 auto', padding: '24px 16px 120px' }}>
<header style={{ marginBottom: 24 }}>
<h1 style={{ fontSize: 26, fontWeight: 700, letterSpacing: '-0.02em', marginBottom: 4 }}>
Profile
</h1>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-tertiary)', letterSpacing: '0.05em' }}>
{profile.email}
</p>
</header>
{/* Tier card */}
<section className="surface diagonal-cut animate-fade-up" style={{ padding: 24, marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 }}>
<div>
<p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.08em' }}>YOUR TIER</p>
<h2 style={{ fontSize: 28, fontWeight: 800, marginTop: 4, textTransform: 'capitalize', color: tierColor(profile.tier) }}>
{profile.tier}
{profile.founder_pricing && (
<span
className="mono"
style={{
marginLeft: 12,
fontSize: 11,
padding: '2px 8px',
borderRadius: 999,
background: 'var(--accent)',
color: 'var(--text-primary)',
verticalAlign: 'middle',
}}
>
FOUNDER
</span>
)}
</h2>
</div>
{profile.tier === 'free' && (
<a href="/api/checkout?tier=analyst" className="btn-primary" style={{ padding: '10px 18px', fontSize: 13 }}>
Upgrade
</a>
)}
</div>
{profile.tier !== 'free' && (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
<Stat label="Status" value={profile.subscription_status} tone={profile.subscription_status === 'active' ? 'good' : 'warn'} />
<Stat label="Renews" value={profile.subscription_end ? new Date(profile.subscription_end).toLocaleDateString() : '—'} />
</div>
)}
{profile.tier === 'free' && (
<Stat label="Reads this month" value={`${profile.scan_count} of 5`} />
)}
</section>
{/* Founder pricing promo for free users */}
{profile.tier === 'free' && (
<section className="surface diagonal-cut-strong" style={{ padding: 20, marginBottom: 16, borderColor: 'var(--grade-a)' }}>
<p className="mono" style={{ fontSize: 11, color: 'var(--grade-a)', letterSpacing: '0.08em', marginBottom: 8 }}>
FOUNDER ACCESS
</p>
<h3 style={{ fontSize: 18, fontWeight: 700, marginBottom: 8 }}>
Lock $14.99/mo for life before it&apos;s gone.
</h3>
<p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}>
First 100 users keep founder pricing even after the regular price jumps to $24.99.
</p>
<a href="/api/checkout?tier=analyst" className="btn-primary">
Lock founder price
</a>
</section>
)}
{/* Subscription actions */}
{profile.tier !== 'free' && !profile.cancel_at_period_end && (
<section className="surface" style={{ padding: 20, marginBottom: 16 }}>
<h3 style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>Cancel subscription</h3>
<p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12 }}>
Your access continues through the end of the current billing period. No refunds for partial months.
</p>
{error && <p style={{ color: 'var(--grade-d)', fontSize: 13, marginBottom: 12 }}>{error}</p>}
<button onClick={handleCancel} disabled={working} className="btn-ghost" style={{ color: 'var(--grade-d)' }}>
{working ? 'Working…' : 'Cancel at period end'}
</button>
</section>
)}
{profile.cancel_at_period_end && (
<section className="surface" style={{ padding: 20, marginBottom: 16, borderColor: 'var(--grade-c)' }}>
<p style={{ fontSize: 13, color: 'var(--grade-c)' }}>
Cancellation scheduled. Access ends{' '}
{profile.subscription_end ? new Date(profile.subscription_end).toLocaleDateString() : 'at period end'}.
</p>
</section>
)}
{/* Sign out */}
<section style={{ marginTop: 32, textAlign: 'center' }}>
<button onClick={() => signOut().then(() => router.replace('/'))} className="btn-ghost" style={{ padding: '12px 24px' }}>
Sign out
</button>
</section>
</section>
);
}
function Stat({ label, value, tone }: { label: string; value: string; tone?: 'good' | 'warn' }) {
const color = tone === 'good' ? 'var(--grade-a)' : tone === 'warn' ? 'var(--grade-c)' : 'var(--text-primary)';
return (
<div style={{ padding: '12px 14px', background: 'var(--bg-elevated)', borderRadius: 10 }}>
<div className="mono" style={{ fontSize: 10, color: 'var(--text-tertiary)', letterSpacing: '0.08em' }}>
{label.toUpperCase()}
</div>
<div className="mono" style={{ fontSize: 14, fontWeight: 700, color, marginTop: 4, textTransform: 'capitalize' }}>
{value}
</div>
</div>
);
}
function tierColor(tier: string): string {
if (tier === 'desk') return 'var(--grade-a)';
if (tier === 'analyst') return 'var(--grade-b)';
return 'var(--text-primary)';
}