Session 41: P0 audit fixes — MLB stat_types, broken routes, tier mismatch, self-hosted fonts (1940 tests)

- Backend: whitelist MLB stat_types in analyze.js + scan.js gates (mirrors
  python validation.py); fixes MLB scans 400ing.
- Routes: /settings -> /profile, /report -> /blog redirect pages.
- Profile: read tier from useAuth().tier (nav's source) to kill the
  Free-vs-DESK mismatch.
- Fonts: self-host Inter/JetBrains Mono/IBM Plex Mono via next/font, drop the
  503ing fonts.googleapis.com <link>; rewire literal font-family refs to vars.
- Kept /settings/security (real MFA page) intact — NOT clobbered to a redirect.
- +33 tests (1907 -> 1940), 149 suites; web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-17 21:55:40 -04:00
parent e453c24d2c
commit 32069863dc
17 changed files with 318 additions and 41 deletions
+14 -8
View File
@@ -18,7 +18,7 @@ interface FullProfile {
export default function ProfilePage() {
const router = useRouter();
const { user, signOut, loading: authLoading } = useAuth();
const { user, tier: authTier, signOut, loading: authLoading } = useAuth();
const [profile, setProfile] = useState<FullProfile | null>(null);
const [working, setWorking] = useState(false);
const [error, setError] = useState('');
@@ -69,6 +69,12 @@ export default function ProfilePage() {
);
}
// Session 41 — tier is read from useAuth (the same live-session source the
// nav uses) so the two can't disagree. The /api/user/profile fetch can lag
// or return a stale/null tier; the auth context is authoritative. Profile
// still supplies the other fields (scan_count, subscription_*, founder).
const tier = authTier || profile.tier || 'free';
return (
<section style={{ maxWidth: 600, margin: '0 auto', padding: '24px 16px 120px' }}>
<header style={{ marginBottom: 24 }}>
@@ -88,8 +94,8 @@ export default function ProfilePage() {
{/* Session 27 — always render a tier label. When the profile
API returns null/undefined tier (free users sometimes do),
fall back to 'free' so the field is never blank. */}
<h2 style={{ fontSize: 28, fontWeight: 800, marginTop: 4, textTransform: 'capitalize', color: tierColor(profile.tier || 'free') }}>
{profile.tier || 'free'}
<h2 style={{ fontSize: 28, fontWeight: 800, marginTop: 4, textTransform: 'capitalize', color: tierColor(tier) }}>
{tier}
{profile.founder_pricing && (
<span
className="mono"
@@ -108,27 +114,27 @@ export default function ProfilePage() {
)}
</h2>
</div>
{profile.tier === 'free' && (
{tier === 'free' && (
<a href="/api/checkout?tier=analyst" className="btn-primary" style={{ padding: '10px 18px', fontSize: 13 }}>
Upgrade
</a>
)}
</div>
{profile.tier !== 'free' && (
{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' && (
{tier === 'free' && (
<Stat label="Reads this month" value={`${profile.scan_count} of 5`} />
)}
</section>
{/* Founder pricing promo for free users */}
{profile.tier === 'free' && (
{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
@@ -146,7 +152,7 @@ export default function ProfilePage() {
)}
{/* Subscription actions */}
{profile.tier !== 'free' && !profile.cancel_at_period_end && (
{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 }}>