BlueGroup | AfriStream Portal
AfriStream Customer Portal - WordPress plugin on the bluegroup_core_foundation guardrails
by BlueWorx · github.com/blueworx-io/bluegroup_project_afristream · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/blueworx-io/bluegroup_project_afristream/archive/refs/heads/main.zipReadme
bluegroup_project_afristream
BlueGroup | AfriStream Portal — a WordPress plugin giving AfriStream subscribers their app profile credentials, a "What to Watch" catalog, tips & tricks, and troubleshooting guides. Built from the Claude Design handoff (AfriStream Portal v2) on the shared bluegroup_core_foundation guardrails.
Using the plugin
Install bluegroup-project-afristream.zip (the deployment artifact, built one level above the repo), then place the shortcode on any page:
[afristream_portal default_tab="profile" show_sport="true"]
default_tab is one of profile | watch | apps | editor | tips | help.
The landing page
The plugin also ships a public marketing landing page. On any page's editor,
set the Template dropdown to AfriStream Landing — the template renders
the whole document itself (its own header and footer), so no theme wrapper
or Elementor is needed. [afristream_landing] renders the same sections as
a shortcode instead, inside an existing page's theme wrapper, for anyone who
wants that.
Under Settings → AfriStream Portal, set the Portal page to the page
carrying [afristream_portal] — the landing page's header and footer
"Dashboard" links point at it. Left on "Detect automatically" it finds the
first published page containing that shortcode.
Set Get Started URL on the same settings screen to whatever should take an order — a checkout, an order form, a WhatsApp or mailto link. Every "Get Started" button on the landing page follows this URL. Left empty they scroll to the pricing section instead, so the page never has a dead button.
Every "Get Started" button opens the onboarding flow first — four short steps covering whether we should source a device and which services the customer already pays for, ending at one of the two configured checkouts (the plain Get Started URL, or the setup fee checkout when they asked us to source a device). Those URLs are still where it ends up; the flow only decides which one. Leave the setup fee at 0 and the device question is never asked.
Policy pages
The plugin ships four legal documents — Terms of Service, Privacy Policy, Refund
Policy and Cancellation Policy — and publishes a page for each of them itself
at /terms, /privacy, /refund-policy and /cancellation-policy. There is
nothing to set up: activating or updating the plugin creates them, and the
footer's Legal column starts linking them.
It runs once and remembers that it has, so a page you delete stays deleted. A published page you have already made at one of those addresses is adopted rather than duplicated — it keeps its title and gets the template. A draft or a trashed page at that address is left alone and a readable one is published alongside it.
The footer's Legal column links whichever of the four are published, on every page including the portal, and each document links the other three. A policy with no page is left out rather than linked at a dead URL.
Each created page also carries [afristream_policy doc="…"] in its content, so
the document still renders if the template is ever switched off.
The wording lives in includes/policies.php, not in
WordPress, so it cannot be edited into contradicting the plugin. The price in
the Terms is the same setting the pricing card and the checkout read, and the
delivery timeline is the same list the pricing section prints.
[afristream_policy doc="refunds"] renders one inside an existing page instead.
Currency switcher
The header carries a currency switcher offering Pounds, Namibian Dollars, Rand, US Dollars and Đồng — the same five SureCart is configured for. It opens on whichever suits the visitor's timezone (US Dollars anywhere we do not recognise), and remembers a manual choice for their next visit.
It is display only. Everything is priced in Rand in the markup and converted in the browser, so the page is correct before the script runs and stays correct if it never does. SureCart still bills in Rand and shows its own currency control on the checkout, and every converted price carries a line saying so.
Rates come from open.er-api.com — free, no key — refreshed once a day on cron and cached for two days, so a day's outage leaves yesterday's rates in place. If there is nothing cached at all, the plugin's own fallback rates in includes/currency.php price the page. Nothing is ever fetched during a page render.
Live "What to Watch" data
Sport (no key needed): major global events — Soccer (FIFA World Cup, Premier League, Champions League), Cricket (ICC World Cup / T20 / Champions Trophy), Rugby, Golf, F1, UFC, NFL, NBA, tennis — come from ESPN's public scoreboard API: live events first, then the week's soonest kick-offs with broadcaster, in the viewer's local time. Extend the league list in bluegroup-project-afristream.php and scripts/preview-server.mjs (keep the two in sync). Cached 2 hours. Note this is an unofficial API — if it ever breaks, the portal just shows its curated sport list (which always includes Cricket, Golf, Rugby and Soccer).
Movies & series (TMDB key): trending movies/series and new releases come from TMDB (free API key, non-commercial use, attribution shown). Without a key the portal falls back to its built-in curated lists — nothing breaks.
- WordPress: paste the key under Settings → AfriStream Portal in wp-admin (no
wp-config.phpaccess needed). IfAFRISTREAM_TMDB_API_KEYis defined inwp-config.phpit takes precedence. Never commit the key. - Local preview: set the
TMDB_API_KEYenvironment variable beforenpm run preview. - Get a key: themoviedb.org → sign up → Settings → API → request a key (choose "Developer"/non-commercial).
- Cached 12 hours (WP transient / in-memory locally).
Live TV is a curated list — edit it in assets/portal.js. Collections are live queries over the fetched TMDB catalog (defined by a match() predicate per collection in assets/portal.js), so they populate themselves and show real counts — no manual title lists.
Editor Picks (IMDb watchlist): the picks come from an IMDb watchlist. IMDb's watchlist is behind a WAF and can't be read by the WordPress server, so the IDs are pulled at build/deploy time by a real browser:
IMDB_WATCHLIST_URL="https://www.imdb.com/user/…/watchlist/" npm run sync-watchlist
This writes the ordered tt IDs into data/editor-picks-ids.txt, then resolves every title through TMDB and writes the finished list to data/editor-picks.json. Both are bundled in the plugin zip, and it is the JSON the plugin actually serves — a file read, rather than one TMDB round-trip per title from WordPress. Needs TMDB_API_KEY in the environment.
IMDb renders at most 250 rows per page, so the sync walks ?page=N until the list total is covered. It also only ever adds: the ID file accumulates, so a failed or partial scrape is a no-op instead of data loss. To drop a title, delete its line from the ID file by hand and re-run bake-picks -- --refresh.
npm run bake-picks # resolves only titles not already in editor-picks.json
npm run bake-picks -- --refresh # ignores the previous bake, re-resolves everything
Baked picks record the IMDb ID they came from, so a re-bake re-uses what it resolved last time — adding a few titles to a 250-title list is a couple of seconds, not 250 round-trips.
The wp-admin Editor Picks (IMDb IDs) box overrides both files when set; because hand-entered IDs have no baked copy, that path resolves through TMDB live and fills in over the first few page loads. Run the sync whenever the watchlist changes, before npm run build.
Free Apps: a bundled directory of free and free-tier streaming apps (data/apps.json), filterable by device, content type and region, with per-device install steps. No API key or configuration needed — the file ships with the plugin.
Sport (no key needed): sport listings merge three permanently free sources, so there is nothing to configure:
| Source | Supplies | When |
|---|---|---|
| ESPN public scoreboard | Fixtures worldwide, US networks | Live, cached 2h |
TheSportsDB free tier (public key 123) |
Broadcasters outside the US | Live, cached 2h |
| iptv-org/epg | What is actually on SuperSport and Sky Sports | Baked at build time |
The first two are plain HTTP calls the WordPress server makes itself. The third is a grabber that clones ~150MB and takes minutes, so it runs at build/deploy time like the watchlist sync:
npm run sync-listings # ~3 days of listings
EPG_DAYS=5 npm run sync-listings
This writes data/sports-listings.json (bundled in the plugin zip); the grabber itself is cached in .cache/ and never committed. DStv publishes a separate channel list per African market and Sky a separate feed for the UK and Ireland, but the same channel carries the same programming in each, so the sync keeps one entry per channel rather than grabbing — and then showing — the same listing several times over. The plugin drops any programme that has already finished and ignores the file entirely once it is more than 10 days old, so a stale build degrades to the two live feeds rather than showing yesterday's guide. Re-run it before npm run build.
Local preview (no WordPress needed)
npm install
npm run preview # → http://localhost:4173
The preview page loads the exact assets the plugin enqueues, and /api/watch mirrors the plugin's REST endpoint.
Tests
npm test
Playwright smoke tests run against the local preview harness until a real staging URL exists — see the note in playwright.config.js. Once staging exists, set preview_url in .github/workflows/ci.yml and tests run against it instead.
Build / deploy
npm run build # stages dist/bluegroup-project-afristream/
Then zip the staged folder as bluegroup-project-afristream.zip one level above the repo — the zip is the deployment artifact; only the current version's zip may exist. Build it with bsdtar, which writes forward-slash entry names on every platform:
/c/Windows/System32/tar.exe -a -c -f ../bluegroup-project-afristream.zip -C dist bluegroup-project-afristream # Windows
tar -a -c -f ../bluegroup-project-afristream.zip -C dist bluegroup-project-afristream # macOS / Linux
scripts/zip-plugin.ps1 is the PowerShell equivalent. Never use Compress-Archive (or .NET Framework's ZipFile.CreateFromDirectory): both write backslash path separators, which WordPress's extractor mishandles — the plugin then installs as a stray file instead of a folder and activation fails with "Plugin file does not exist." Always list the finished zip (unzip -l) and confirm every entry reads bluegroup-project-afristream/... with forward slashes.
Process
Every change: branch → pull request → CI guardrails (lint, build, version bump, changelog, approved deps, plugin checks, Playwright) → merge. main is protected; the guardrails / guardrails check must pass.