S3 (a1): affiliate + partner plumbing

Zero out-of-pocket; everything config-flip-ready but DISABLED/organic.

- BOOK IT deep links: web/src/lib/bookLinks.js + affiliateConfig.js
  (all books enabled:false, Impact/Partnerize param shapes documented,
  empty params skipped). Wired into StatStrip BookItTeaser (real anchor
  now) + scan hand-off links. Every book anchor renders
  rel="sponsored noopener noreferrer" (BOOK_LINK_REL).
- Best-price marker: slateAdapter.detectBestBook (only when >=2 books
  post the SAME line and prices differ — absent beats wrong) + subtle
  signal-green dot in StatStrip. Slate.groupByGame threads the grouped
  per-book rows (books[]) onto PropRowProp instead of discarding them.
- Partner refs: ?ref=CODE -> vyndr_ref cookie (90d, first-touch,
  PartnerRefCapture in layout) -> signup metadata partner_ref ->
  internal GET /api/partners/report/:code (requireInternalAuth; honest
  zeros + note until the TODO migration in docs/PARTNERS.md adds
  user_profiles.partner_ref — NOT run). Stripe promo-code convention:
  partner code == promotion code, verbatim.
- Tests: +41 (2398 -> 2439, 209 suites); bookItTeaser + vyndrCoreScreens
  invariants updated to the new (stronger) rel contract. Web build exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-11 14:28:18 -04:00
parent e4d2e79f95
commit 0996320bd1
23 changed files with 1151 additions and 36 deletions
+9 -1
View File
@@ -3,6 +3,7 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import type { Session, User } from '@supabase/supabase-js';
import { getBrowserSupabase } from '@/lib/supabase';
import { readRefCookie } from '@/lib/partnerRef';
export type Tier = 'free' | 'analyst' | 'desk';
@@ -135,10 +136,17 @@ export default function AuthProvider({ children }: { children: React.ReactNode }
async (email, password, ageVerified) => {
if (!ageVerified) return { error: 'You must confirm you are 21 or older.' };
if (!supabase) return { error: 'Auth is not configured. Set Supabase env vars.' };
// A1 S3 — partner attribution: forward the first-touch vyndr_ref
// cookie (set by PartnerRefCapture on a ?ref=CODE visit) into the
// signup metadata. The internal /api/partners report reads it.
const partnerRef = readRefCookie(document.cookie);
const { error } = await supabase.auth.signUp({
email,
password,
options: { emailRedirectTo: `${window.location.origin}/auth/callback` },
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
...(partnerRef ? { data: { partner_ref: partnerRef } } : {}),
},
});
if (error) return { error: error.message };
return {};