Session 13: The Slate, Africa geo-restriction, OAuth providers, PropRow + GameCard (1311 tests)
This commit is contained in:
+117
-1
@@ -4,7 +4,123 @@
|
||||
2026-06-10
|
||||
|
||||
## Current Phase
|
||||
SHIP BUILD v12.0 — i18n (10 languages) + Africa tier (Session 12)
|
||||
SHIP BUILD v13.0 — The Slate (browse-first dashboard) + OAuth providers + Africa geo (Session 13)
|
||||
|
||||
## Session 13 (2026-06-11) — SHIPPED
|
||||
|
||||
### Phase 1 — Africa geo-restriction via CF-IPCountry
|
||||
|
||||
The Session 12 Africa tier was visible to anyone on a Swahili locale
|
||||
(too narrow: most African users browse in English/French; too broad:
|
||||
Swahili speakers anywhere got the discount). Session 13 swaps the
|
||||
locale proxy for real Cloudflare IP geolocation.
|
||||
|
||||
- **`web/middleware.ts`** — reads `cf-ipcountry` (uppercase),
|
||||
stamps `x-vyndr-country` on the request alongside the locale header.
|
||||
Empty string when traffic bypasses Cloudflare (local dev).
|
||||
- **`web/src/lib/locales.ts`** — `AFRICAN_COUNTRIES` set covering all
|
||||
54 sovereign African nations (NG/KE/ZA/GH + sub-Saharan + MENA
|
||||
overlap). `isAfricanCountry(code)` is case-insensitive and degrades
|
||||
closed on empty/null inputs.
|
||||
- **`LocaleContext`** — extended with `country`/`inAfrica` fields;
|
||||
new `useRegion()` hook for components that gate by geography.
|
||||
- **`Pricing.tsx`** — `inAfrica === false` filters the Africa tier
|
||||
out of the render entirely. `inAfrica === true` puts it first.
|
||||
Locale-based reorder removed.
|
||||
- **Pricing grid CSS** — desktop column count now tracks the visible
|
||||
tier count via a `--pricing-cols` CSS custom property on the grid
|
||||
root (3 outside Africa, 4 inside). Sidesteps a styled-jsx
|
||||
limitation with attribute selectors inside `:global()`.
|
||||
|
||||
### Phase 2 — OAuth: Google + Apple + X
|
||||
|
||||
- **`AuthContext`** — added generic `signInWithProvider(provider)`
|
||||
alongside the legacy `signInWithGoogle()` (kept as an alias so
|
||||
existing callers don't break). Translates Supabase OAuth errors
|
||||
into a flat `{ error: string }` so the UI can surface a friendly
|
||||
inline message when a provider isn't configured.
|
||||
- **`login/page.tsx` + `signup/page.tsx`** — both pages now render
|
||||
three OAuth buttons (Google, Apple, X). The `handleOAuth` helper
|
||||
routes to `signInWithProvider` and shows an inline error when the
|
||||
provider isn't configured ("apple login isn't available yet. Use
|
||||
email or another method.").
|
||||
- **External configuration required** (operator action, not code):
|
||||
- Supabase Auth → Providers → Apple: needs an Apple Developer
|
||||
Service ID + private key
|
||||
- Supabase Auth → Providers → Twitter: needs an X Developer OAuth 2.0
|
||||
client
|
||||
- Google should already work — if it doesn't, verify Supabase
|
||||
Auth → URL Configuration → Site URL = https://vyndr.app and
|
||||
Redirect URLs include `https://vyndr.app/**`, and that the Google
|
||||
Cloud Console OAuth consent screen has the Supabase callback URL
|
||||
in Authorized redirect URIs.
|
||||
|
||||
### Phase 3 — The Slate (browse-first dashboard)
|
||||
|
||||
Generalizes the Session 8 `/soccer` page pattern across every sport.
|
||||
|
||||
- **`web/src/components/PropRow.tsx`** — single-prop UI with three
|
||||
states (ungraded/grading/graded). Pure presentational — parent
|
||||
owns the API call so there's one shared rate-limited grading queue.
|
||||
Free-tier expansion shows blurred reasoning + Unlock CTA; paid tier
|
||||
shows full reasoning + kill conditions. Exports `propRowKey()` for
|
||||
stable Map keys.
|
||||
- **`web/src/components/GameCard.tsx`** — game header + expandable
|
||||
prop list. Sport emoji prefix (🏀 NBA/WNBA, ⚾ MLB, ⚽ soccer),
|
||||
sport-accented left border, formatted local game time, `+ N more`
|
||||
expander when props > defaultVisible.
|
||||
- **`web/src/components/Slate.tsx`** — the orchestrator. Sport tabs
|
||||
(ALL / NBA / WNBA / MLB / Soccer), sticky search input, group-by-game
|
||||
pipeline, `gradedProps` Map, single-flight grading queue
|
||||
(`gradingKey`). `Promise.allSettled` fan-out for the ALL tab so a
|
||||
single sport failing doesn't blank the slate. `FETCH_URLS` is
|
||||
null-aware — sports without an odds proxy yet (WNBA, MLB) render a
|
||||
bottom-of-page "endpoint not configured yet" note rather than
|
||||
spamming 404s.
|
||||
- **Search filter + manual-scan fallback** — sticky search filters
|
||||
game cards by team name and prop rows by player/stat. Empty result
|
||||
shows a CTA linking to `/scan?q=<query>` so users land on a
|
||||
partially-filled scan form.
|
||||
- **`/dashboard`** — `<Slate />` mounted as the lead surface above
|
||||
the existing Top Graded / Most Parlayed / Recent Reads sections.
|
||||
Those sections stay as supplementary intelligence layers — not
|
||||
removed.
|
||||
- **`Nav.tsx`** — "Scan" link removed from primary nav. The Slate is
|
||||
the scan surface; `/scan` stays reachable from the slate's
|
||||
empty-state CTA.
|
||||
|
||||
### Tests added
|
||||
| Suite | Tests |
|
||||
|----------------------------------------|-------|
|
||||
| `tests/unit/africaCountries.test.js` | 6 |
|
||||
| **Session 13 total** | **6** |
|
||||
|
||||
### Quality gates
|
||||
- `npm test`: **1311 / 1311 passing** (1305 + 6 new), 102 suites, 0 regressions
|
||||
- `web/npm run build`: clean — Slate page + components prerender
|
||||
- License audit: third-party deps remain permissive
|
||||
|
||||
### Honest gaps (documented, not bugs)
|
||||
- I could not visually verify The Slate in a browser. Build/type
|
||||
correctness is confirmed; "renders correctly with live odds data"
|
||||
needs a deploy smoke test.
|
||||
- Google/Apple/X OAuth: button wiring is complete. Whether the
|
||||
buttons actually authenticate depends on external dashboard
|
||||
configuration (Supabase + Google Cloud Console + Apple Developer +
|
||||
X Developer Portal). Apple and X are guaranteed to show the
|
||||
"isn't available yet" inline error until configured.
|
||||
- WNBA + MLB don't have `/api/odds/*` proxies on the Next.js side
|
||||
yet. The Slate degrades cleanly (footer note), but those tabs
|
||||
return empty until the proxies exist. Session-14 work.
|
||||
- Africa tier still can't be SOLD even when geo gates open it —
|
||||
the Stripe price + the DB CHECK migration remain outstanding from
|
||||
Session 12.
|
||||
|
||||
### Coolify env (Session 13 additions)
|
||||
None. CF-IPCountry is set by Cloudflare automatically; no env-var
|
||||
change required.
|
||||
|
||||
---
|
||||
|
||||
## Session 12 (2026-06-11) — SHIPPED
|
||||
|
||||
|
||||
Reference in New Issue
Block a user