StatsHub Docs
Reports

Legacy web access gates

A source audit of which legacy StatsHub pages required payment, login, or accepted a limited guest experience.

Source audit of apps/statshub on 2026-08-28. This describes the legacy Pages Router application, not the replacement web or Expo applications.

Bottom line

Legacy did not have one route-level access policy. Its middleware refreshed the Supabase session and handled locale and basketball rewrites, then returned the response. It did not classify routes as public, signed-in, or paid (apps/statshub/src/middleware.ts:120-151). Pages and API handlers enforced access independently.

The useful product contract is:

Access levelLegacy behavior
GuestMost pages and tools were public. PropHunter exposed only four blurred results.
Free accountUnlocked full PropHunter results, favourites, watchlists, saved filters, saved bookmaker preferences, and account pages.
Any active paid planUnlocked Value Bets, Bet Builder, Team Models, Super Sub Heroes, and alert activation.
Top paid planAdded Value Bets raw model outputs and richer model controls.
SuperadminBypassed the normal subscription gate and unlocked a small number of admin-only calculations.

Four page routes contain an explicit premium gate. Three full pages require a login without requiring a paid plan. One core tool, PropHunter, implements a real guest preview cap. Several other public pages gate only save, favourite, watchlist, or alert actions.

What counted as paid

The client considered a user subscribed when the latest subscription was active or trialing and was not paused, or when the email belonged to the configured superadmin list (apps/statshub/src/contexts/user-context.tsx:120-137). The shared server helper accepted active or trialing and also bypassed the check for a superadmin (apps/statshub/src/lib/require-subscription.ts:43-62).

The pricing page exposed two paid tiers:

Display nameStored tierIntended access
ProstarterReady-to-use scanner and standard Value Bets controls.
Pro+ All AccessproAll models, time windows, thresholds, and early-access features.

Evidence: apps/statshub/src/pages/pricing.tsx:66-97.

Fully paid-gated pages

RouteGuestSigned in without paymentActive or trialingEnforcement and confidence
/value-bets-v2Redirected to /login.Rendered the paid-plan gate.Opened the scanner and paid subviews.High. Page gate at value-bets-v2.tsx:294-301,1970-1983; data API returns 401/403 at api/value-bets-v2.ts:95-131.
/bet-builderRendered the paid-plan gate.Rendered the same gate.Opened the builder.High for the UI. bet-builder.tsx:1005-1014,1597-1608. The route gate was client-side rather than middleware-based.
/super-sub-heroesRedirected to /login.Rendered the paid-plan gate.Opened the leaderboards.High. Page at super-sub-heroes.tsx:114-140,190-208; API returns 401/403 at api/props/super-sub-heroes.ts:133-145.
/value-bets-v3Redirected to /login.Rendered the paid-plan gate.Opened the experimental v3 page.High for behavior, low for product relevance. value-bets-v3.tsx:98-123,820-833; no production navigation link was found.

/value-bets-v2?view=team-shots was a paid subview rather than a separate page. Its team-model API repeated the subscription check and returned 401 or 403 (apps/statshub/src/pages/api/team-shots-glm-predictions.ts:79-112).

Top-tier limitations inside Value Bets

The page-level test did not distinguish the two paid plans: either paid plan passed hasAccess. The page separately treated stored tier pro as the top plan (apps/statshub/src/pages/value-bets-v2.tsx:294-295).

Pro, stored as starter, received:

  • the ready-to-use scanner;
  • Team Models;
  • watchlist and tracker access;
  • a compact scanner-style watchlist.

Pro+ All Access, stored as pro, additionally received:

  • Raw Model Outputs, described as all models, time windows, and bookmaker comparison;
  • the richer Value Bet card treatment in the watchlist;
  • model and threshold controls advertised on the pricing page.

Selecting Raw Model Outputs as a lower-tier subscriber sent the user to /pricing, and a deep link was forced back to the scanner (apps/statshub/src/pages/value-bets-v2.tsx:407-413,2127-2149). V3 repeated the same raw-output restriction (apps/statshub/src/pages/value-bets-v3.tsx:164-172).

The under direction in the Value Bets API was not merely top-tier. It was superadmin-only (apps/statshub/src/pages/api/value-bets-v2.ts:135-139).

/fouls-alerts required a login to render, but an account alone was not enough to activate alerts. The API reported eligibility only for active or trialing subscriptions and returned the feature disabled otherwise (apps/statshub/src/pages/api/live-foul-card-alerts.ts:28-56,83-86). Value Bet lineup alerts also required an active or trialing subscription and a linked Telegram account (apps/statshub/src/pages/api/value-bet-lineup-alerts.ts:17-49).

What required login but not payment

Route or capabilityGuest behaviorFree-account behaviorEvidence
/accountRedirected to / and rendered only a loading state during the redirect.Account, subscription, Telegram, and alert settings rendered.apps/statshub/src/pages/account.tsx:58-69,88-94
/favouritesRendered an inline sign-in prompt.Team and player favourites, notes, and add/remove controls rendered.apps/statshub/src/pages/favourites.tsx:162-189,293-349
/fouls-alertsRedirected to /.Page rendered; paid eligibility still controlled whether alerts could be active.apps/statshub/src/pages/fouls-alerts.tsx:11-30
Team and player favouritesPublic detail data remained visible; saving opened login or did nothing until a session existed.Favourite mutations and notes were available.apps/statshub/src/pages/player/[playerSlug]/[playerId].tsx:437-490; apps/statshub/src/pages/team/[teamSlug]/[teamId].tsx:527-528,695-728
Prop watchlistsPublic screener results remained visible; starring a result opened the auth drawer.Personal prop watchlist was available.apps/statshub/src/pages/prop-screener.tsx:251-280,469-486; apps/statshub/src/pages/100-club.tsx:134-162,269-282
Saved filters and bookmaker preferencesApplying filters was public; saving or loading a saved configuration opened the auth drawer.Saved filters and preferences were persisted through /api/props/saved-filters.apps/statshub/src/components/saved-filters-button.tsx:68-103,230-258; apps/statshub/src/components/bookmaker-filter-dialog.tsx:68-116

The tipster preview authoring APIs also required a Supabase session and scoped records to the author (apps/statshub/src/pages/api/tipster/preview/index.ts:8-30; apps/statshub/src/pages/api/tipster/preview/[id].ts:8-32). Those pages were not part of the primary product navigation and look like an ancillary authoring workflow, so they should not define customer-facing parity.

What guests could see only in limited form

PropHunter had a real four-result preview

/prophunter was public enough to run the search, but the result component did three things for a guest:

  • overlaid a sign-up prompt when results existed;
  • blurred the result grid;
  • rendered only the first four results.

Evidence: apps/statshub/src/components/player-results-list.tsx:155-160,613-638. The page passed the current user and auth-drawer callback into that result component (apps/statshub/src/pages/prophunter.tsx:2816-2850). A free account removed the cap. Saved PropHunter presets also required login (apps/statshub/src/pages/prophunter.tsx:1197-1241). No paid subscription check was present.

Prop Screener limited personal features, not the result set

/prop-screener did not cap or blur guest results. Guests could run the screener and use its ordinary filters. Login was required for the watchlist, saved filters, and saved bookmaker preferences.

The page displayed a create free account to unlock future fixtures banner when future-dated props were present (apps/statshub/src/pages/prop-screener.tsx:759-790). That banner was an upsell, not an enforced gate: the same props array, including future events, was passed to the table for guests (apps/statshub/src/pages/prop-screener.tsx:793-859), and the screener API had no user or subscription check (apps/statshub/src/pages/api/props/enriched-screener.ts:300-308). Treat the intended future-fixture restriction as unclear rather than implemented.

Other public pages gated only personal actions

/100-club remained public and gated only watchlist mutations. Public team, player, fixture, league, referee, trend, and screener pages similarly did not become paid merely because they showed a favourite or save control.

Pages that looked paid but were public

The toolkit sidebar is not reliable access-control evidence. It placed Outliers and Lineup Changes under the Pro heading and gave both a pro badge (apps/statshub/src/components/toolkit-sidebar.tsx:26-49,160-185), but the pages and their primary APIs had no auth or subscription checks:

RouteActual source behaviorConfidence
/outliersPublic page fetched /api/props/outliers; the handler accepted any GET request.High. outliers.tsx:1299-1327; api/props/outliers.ts:62-70.
/lineupsPublic page fetched /api/lineups/players; the handler accepted any GET request.High. lineups/index.tsx:108-123,203-213; api/lineups/players.ts:83-92.

The old /value-bets route also contained no user or subscription check, but the active navigation linked /value-bets-v2, not /value-bets. Treat the old route as archive-only rather than evidence that Value Bets was intended to be free. /value-bets-v3 has the opposite problem: its gate is clear, but no production navigation link was found.

Enforcement gaps in legacy

Paused subscriptions disagreed between client and server

The client denied a paused subscription even if its status remained active or trialing (apps/statshub/src/contexts/user-context.tsx:134-137). The shared server helper checked only the status and did not inspect pauseCollectionResumesAt (apps/statshub/src/lib/require-subscription.ts:55-62). A paused user could therefore be blocked by the page while still passing APIs that used requireSubscription.

Some paid pages relied on client gates

Value Bets, Super Sub Heroes, and Team Models repeated the paid check in their data APIs. Bet Builder's route gate lived in React. Its underlying data came from several APIs with different access policies, including the paid-protected Value Bets API. Legacy did not provide a single server-side guarantee that all Bet Builder inputs were paid-only.

Personal APIs did not all repeat the page's paid rule

The Value Bets page was paid, but its watchlist and tracker APIs checked login, not subscription (apps/statshub/src/pages/api/watchlist/value-bet.ts:48-59; apps/statshub/src/pages/api/tracker/settings.ts:13-30). The main Value Bets data API did enforce payment, so this did not expose the paid feed through the normal UI, but it shows why page labels cannot stand in for endpoint policy.

Parity decisions to carry forward

Legacy behaviorPorting interpretationConfidence
Value Bets v2, Bet Builder, Team Models, Super Sub HeroesPaid capability. Require login and an active entitlement; protect the data API as well as the screen.High
Raw Model Outputs and advanced model controlsTop-tier capability in the two-tier Stripe model. Decide explicitly whether this distinction still exists under the current entitlement model.High for legacy, unclear for current product intent
Account and FavouritesLogin-only, not paid-only.High
PropHunter full resultsFree-account capability; guests get a four-row blurred preview.High
Prop Screener resultsPublic. Login gates saved state and watchlists only.High
Prop Screener future fixturesAdvertised as account-only but not enforced. Do not copy this contradiction.High
Outliers and Lineup ChangesAccess intent is ambiguous: navigation said Pro while code and APIs were public. Product must choose.High
Paused subscriptionMust be resolved once in the shared entitlement model and applied identically on client and server.High

Scope and caveats

The audit searched every non-API page under apps/statshub/src/pages for PremiumGate, isSubscribed, user-dependent rendering, redirects, auth-drawer calls, and result slicing. It separately traced the API handlers used by each identified surface. Marketing, demos, test pages, superadmin pages, database tools, and dead duplicate routes were excluded from the customer matrix.

This is a source audit of the current main working tree. The repository was already heavily modified when the audit began. None of the access-control files cited above were among the modified legacy files, but the report still records source behavior, not a historical production deployment or runtime feature-flag snapshot.

On this page