尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A rebuild of the BolaChat marketing site (https://bolachat.africa) - the WhatsApp-native AI sports betting product from Bola Group / Bolabet, live in Zambia with Zimbabwe and Malawi planned. This repo replaces the original one-off page with a maintainable, multi-market Next.js app.
This is not a redesign. It reproduces the live product's structure and copy, on a clean architecture that can add new countries without touching presentation code.
Visual design follows the Bolabet Corporate Identity Manual v1.0 (the parent brand's CI, applied here to the BolaChat sub-brand):
#231F20 background, Action Yellow
#F5E500 as the single accent, Chalk White #FFFFFF text. This is the
CI's "Home Kit" (3.3) - the black/yellow combination specified for core
sports betting product UI.--whatsapp) and Telegram CTA buttons use Telegram's blue
(--telegram), both in globals.css, so they still read as "this opens
WhatsApp / Telegram" at a glance. Every other accent on the site - links,
badges, focus states, the wordmark split - is Action Yellow. See
Button.tsx: the whatsapp and telegram variants are reserved for
wa.me / t.me links only..font-heading utility class; Inter for
body/UI text, with tabular lining numerals turned on site-wide
(font-feature-settings: "tnum") so odds and amounts align in columns -
apply the tabular-nums class directly to any new numeric display.
Both fonts are self-hosted via @fontsource-variable/* (no runtime call
to Google's font CDN).-9.45deg, stored as --bola-angle.
Applied to graphic elements only (the BolaSlash component, the
Responsible Gaming badge's plates, the half-tone texture) - never to
running text, which gets its slant from the italic headline typeface
instead..bola-halftone CSS utility (see SportArt.tsx for usage).ResponsibleGamingBadge.tsx, used in the Footer.BolaWordmark.tsx reproduces the
BOLA/BET colour-split pattern for BOLA/CHAT.New brand-system components live in src/components/ui/:
BolaWordmark.tsx, BolaSlash.tsx, ResponsibleGamingBadge.tsx.
eslint-config-next)@fontsource-variable/*) for
brand typography - see "Brand system" aboveNo Google Fonts CDN dependency: fonts are bundled as static files through
npm (@fontsource-variable/archivo, @fontsource-variable/inter) rather
than loaded via next/font/google, so the build never depends on reaching
fonts.googleapis.com at request time - one less thing that can fail in a
locked-down CI/build environment, and one fewer third-party network request
for visitors.
src/
app/
layout.tsx Root HTML shell, global metadata defaults
page.tsx "/" - choose-your-country fallback (see below)
robots.ts /robots.txt
sitemap.ts /sitemap.xml, generated from the market registry
zm/page.tsx Zambia route - metadata + <MarketPage market={zm} />
mw/page.tsx Malawi route
zw/page.tsx Zimbabwe route
components/
MarketPage.tsx The one page template every market route renders
layout/ Header, Footer
sections/ Hero, PowerPlays, HowItWorks, WhyMessaging, SportsGrid, FinalCta
content/
powerplays/ <code>.json - weekly PowerPlays, edited via /admin
ui/ Button, ChannelButtons, Container, Badge, SectionHeading, SportArt, ChatMockup, WhatsAppIcon, TelegramIcon
config/
markets/ zm.ts / mw.ts / zw.ts / index.ts - see "Market configuration" below
lib/
whatsapp.ts wa.me URL builder (single source of truth for WhatsApp links)
telegram.ts t.me URL builder (single source of truth for Telegram links)
channels.ts channelNames(): "WhatsApp" vs "WhatsApp and Telegram" copy per market
utils.ts cx() class-name helper
types/
market.ts MarketConfig contract every market file must satisfy
styles/
globals.css Design tokens (CSS variables) + Tailwind import
public/
images/sports/ Drop real photography here (see below)
icons/
Nothing in components/ imports a market-specific value directly - every
component that needs one receives a market: MarketConfig prop and reads
from it. That's what makes /zm, /mw, and /zw three routes over one
codebase instead of three separate sites.
Each market is one file in src/config/markets/ implementing the
MarketConfig type (src/types/market.ts): currency, WhatsApp number, optional Telegram bot username (omit telegram for markets without Telegram), optional comingSoon flag,
registration/login/deposit URLs, legal links, support details, SEO
metadata, and analytics IDs (left empty until you're ready to enable them).
Known placeholders you must replace before a market goes live - each is
marked // TODO in its config file:
| Market | What's a placeholder |
|---|---|
Zambia (zm.ts) | Helpline contact (WhatsApp number set; no Telegram in this market) |
Malawi (mw.ts) | Currently comingSoon (renders the coming-soon page). To launch: remove comingSoon, add the real WhatsApp number (and a telegram block if applicable), then confirm domain, all legal URLs, helpline |
Zimbabwe (zw.ts) | WhatsApp number and Telegram bot set. Still to confirm: domain (bolabet.co.zw), currency (USD vs ZiG), all legal URLs, helpline |
Zambia's domain (bolabet.co.zm) and currency (Kwacha, symbol K) are
carried over from the live site's own copy, so those aren't placeholders.
src/config/markets/zm.ts to src/config/markets/<code>.ts (use
the two-letter country code, lowercase).src/config/markets/index.ts:
import { <code> } from "./<code>";
export const markets: Record<MarketCode, MarketConfig> = {
zm, mw, zw, <code>,
};
"<code>" to the MarketCode union in src/types/market.ts.src/app/<code>/page.tsx - copy src/app/zm/page.tsx and swap
the import.That's it. sitemap.ts and the root "choose your country" page both read
from the registry automatically, so they pick up the new market without
any further changes.
The site lives on bolachat.africa (set in src/config/site.ts). Each
market is a path on that one domain:
| Address | Shows |
|---|---|
bolachat.africa | "Choose your country" page |
bolachat.africa/zm | Zambia |
bolachat.africa/zw | Zimbabwe |
bolachat.africa/mw | Malawi (coming soon page) |
Canonical URLs and the sitemap use these addresses. Connecting the domain
(Vercel): add bolachat.africa and www.bolachat.africa under Project ->
Settings -> Domains, then create the DNS records Vercel shows at the DNS
provider. No per-market domain setup is needed.
Just edit the relevant field in that market's file under
src/config/markets/. There's no build step or code generation - the
config file is the config.
Photography lives in public/images/sports/ and is rendered with
next/image in Hero (epl.jpg) and SportsGrid
(soccer.jpg, cricket.jpg, rugby.jpg). To swap an image, replace the
file (keep the name) or change the src in the component. Each image sits
in a parent with a fixed aspect ratio (aspect-[4/3]) and uses object-cover, so layout doesn't
shift. <SportArt /> is kept as a fallback placeholder but is no longer
used on the page.
/admin)The weekly PowerPlays section on each market page is edited through a
password-protected admin panel at bolachat.africa/admin - no code changes
needed.
copy.powerPlayExample in the market config), labelled
"Example", exactly as before.src/content/powerplays/<code>.json and uploaded images in
public/images/powerplays/. "Save & publish" commits both to GitHub in a
single commit, which triggers the normal deploy - the live site updates
once that deploy finishes (a couple of minutes). Market pages stay fully
static; only the /api/admin/* routes run server-side. If two people
edit at once, the second save is refused with a "reload" message rather
than overwriting the first.| Variable | Value |
|---|---|
ADMIN_PASSWORD | The admin sign-in password. Use a long one. Unset = admin disabled. |
ADMIN_SESSION_SECRET | Optional random string for signing the session cookie (defaults to ADMIN_PASSWORD). |
ADMIN_GITHUB_TOKEN | A GitHub fine-grained personal access token limited to this repo with Contents: Read and write. |
ADMIN_GITHUB_REPO | Optional, defaults to Bolabet/BolaChat. |
ADMIN_GITHUB_BRANCH | Optional, the branch the site deploys from - defaults to main. |
See .env.example. Locally, npm run dev without ADMIN_GITHUB_TOKEN
saves straight to the files in your working copy instead of GitHub.
npm install
npm run dev
Open http://localhost:3000, then visit /zm, /mw, or /zw directly
(there's no automatic redirect yet - see "Geo-routing" below).
npm run build
npm run start
npm run build must complete with zero errors before anything is pushed.
It prerenders every public route as static HTML (/, /zm, /mw,
/zw, /robots.txt, /sitemap.xml). The only server-side code is the
/api/admin/* routes behind the PowerPlays admin (see above).
If you're editing through the GitHub web UI rather than cloning locally:
This app deliberately avoids anything Vercel-only:
@vercel/* packages.fetch and node:crypto.robots.ts, sitemap.ts) uses the standard Next.js Metadata
API, which works the same regardless of host.Geo-routing is intentionally not implemented here. Per the brief, this
codebase only prepares the ground for it: /zm, /mw, and /zw exist as
fully independent static routes, so a Cloudflare Worker sitting in front of
this deployment can inspect the visitor's country and redirect/rewrite to
the right path - the same pattern already used on the ChatBet geo-redirect
project - without any changes to this frontend. When that Worker is ready,
/ (the current "choose your country" fallback) is the safe default for
traffic the Worker can't classify.
<title>, meta description, canonical
URL, and Open Graph/Twitter metadata (see each src/app/<code>/page.tsx).robots.ts and sitemap.ts are generated, not static files, so the
sitemap always matches the registered markets.<a>/<button> elements (no
clickable <div>s), focus states are visible, and images/icons carry
alt/aria-label text.AnalyticsConfig on each market (in src/types/market.ts) has empty slots
for a GA4 ID, GTM container ID, Meta Pixel ID, and TikTok Pixel ID. Nothing
is loaded until you fill these in and wire up the corresponding script -
intentionally, so no third-party script ships before it's actually needed.