// library-page.jsx — where a buyer redeems an access code and downloads what their tier includes. // Gating is client-side (access.js); see the limits noted there. const QUIZ_KEY = 'vc_quiz_v1'; function readQuiz() { try { return JSON.parse(localStorage.getItem(QUIZ_KEY) || '{}') || {}; } catch (e) { return {}; } } function V4Library() { const v4 = window.v4; const accent = v4.yellow; const access = window.useVCAccess(); const [busy, setBusy] = React.useState(''); const [err, setErr] = React.useState(''); const has = !!access.product; const quiz = readQuiz(); const chapters = (window.VC_QUIZ && window.VC_QUIZ.chapters) || []; const passed = chapters.filter(c => quiz[c.n] && quiz[c.n].passed).length; React.useEffect(() => { if (location.hash === '#code') { const el = document.getElementById('code'); if (el) setTimeout(() => el.scrollIntoView({ block: 'start' }), 60); } }, []); // Back from Stripe Checkout: library.html?session_id=cs_… → ask the server for the code, then unlock this browser. const sessionId = new URLSearchParams(location.search).get('session_id'); const [claim, setClaim] = React.useState(sessionId ? { status: 'loading' } : null); const [copied, setCopied] = React.useState(false); React.useEffect(() => { if (!sessionId) return; let alive = true; (async () => { try { const r = await fetch('/api/claim?session_id=' + encodeURIComponent(sessionId), { cache: 'no-store' }); const j = await r.json().catch(() => ({ ok: false, reason: 'The server answered in an unexpected way.' })); if (!j.ok) { if (alive) setClaim({ status: 'error', reason: j.reason }); return; } const v = await window.VCAccess.redeem(j.code); // Always hand over the code the server issued, even if this browser could not use it. if (alive) setClaim({ status: 'ok', code: j.code, name: j.name, receipt: j.receipt, unlocked: v.ok, why: v.ok ? null : v.reason }); } catch (e) { if (alive) setClaim({ status: 'error', reason: 'We could not reach the server. Reload this page to try again.' }); } })(); return () => { alive = false; }; }, []); const copy = (text) => { const done = () => { setCopied(true); setTimeout(() => setCopied(false), 2500); }; const fallback = () => { const t = document.createElement('textarea'); t.value = text; document.body.appendChild(t); t.select(); try { document.execCommand('copy'); done(); } catch (e) {} t.remove(); }; if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, fallback); else fallback(); }; const contact = window.VC_CONTACT_EMAIL; const get = async (id) => { setErr(''); setBusy(id); try { await window.VCWorkbooks.download(id); } catch (e) { setErr(e.message || 'Download failed.'); } setBusy(''); }; const kicker = (t, c) =>
{claim.code}
{has ? <>You have {access.name}{access.until ? <> until {access.until}> : <>, with no expiry>}. Everything it includes is below. Downloads are built in your browser.> : <>Paste the access code from your purchase email. It is checked in this browser and kept here; there is no account. Use the same code on each browser you read on.>}
{!has ? No code yet? See pricing → : null}
: null}