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:
@@ -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 }}>
|
||||
|
||||
Reference in New Issue
Block a user