Skip to content

Reporting Hub Build

Owner: Bryan · Target time once familiar: about 10 to 15 minutes to a live gated site. Stack: MkDocs Material, private GitHub repo, Cloudflare Pages, Cloudflare Access.

This is Phase 2 of Client Onboarding: the end-to-end build of a new client's gated reporting hub. Exact Cloudflare and GitHub values live in Cloudflare (Pages + Access).

0. Gather these first

Thing Example Used for
Client short name (slug, lowercase, no spaces) acmestore repo + subdomain
Client display name Acme Store site title, footer
Client website URL https://acmestore.com footer credit link
Client email domain (for allow-list) @acmestore.com Cloudflare Access
Specific client emails (if no shared domain) jane@gmail.com Cloudflare Access
Logo file white/light wordmark PNG or SVG header branding
Brand colors hex codes (primary/accent/bg/ink) theme palette
Brand fonts e.g. Fraunces / Inter typography

Missing brand assets can usually be pulled from the client's site: logo at …/cdn/shop/files/*Logo.png on Shopify, colors and fonts from the site CSS.

Naming: repo = client-<slug>, Pages project = client-<slug> (deploys to client-<slug>.pages.dev), subdomain = <slug>.dalayon.work.

Generate the scaffold:

.\new-client.ps1 -Slug acmestore -Name "Acme Store" -ClientDomain "@acmestore.com" -Website "https://acmestore.com"

This pre-fills the name, subdomain, email domain, and footer ("Made by Bryan Tirado for <Client>").

Phase A: Create the site locally

The generator produces the full interactive stack. If you build by hand instead, copy an existing client folder (client-sample is the cleanest reference) and delete .git/, .venv/, site/, and the contents of personal-notes/ from the copy, then find-and-replace the client specifics in mkdocs.yml, docs/index.md, and README.md.

Preview locally:

cd client-<slug>
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe -m mkdocs serve

Open http://127.0.0.1:8000, confirm it looks right, then Ctrl+C.

Phase B: Push to GitHub as private

In GitHub Desktop: File → Add Local Repository → select client-<slug> → create a repository. Then Publish repository:

  • Owner: Dalayon-Reports
  • Keep this code private (critical).

Sanity check the Changes list: it must not include .venv/, site/, or personal-notes/. If it does, stop; .gitignore is not applying.

Phase C: Deploy on Cloudflare Pages

  1. Cloudflare dashboard → Compute → Workers & Pages → Create application.
  2. It defaults to the Worker flow. Click "Looking to deploy Pages? Get started" to switch to Pages.
  3. Connect to Git → GitHub → Dalayon-Reports → select client-<slug>Begin setup.
  4. Build settings:
Field Value
Project name client-<slug>
Production branch main
Framework preset None
Build command pip install -r requirements.txt && mkdocs build
Build output directory site
Env variable PYTHON_VERSION = 3.12
  1. Save and Deploy, wait for the green build, site live at client-<slug>.pages.dev.

Phase D: Custom domain

In the Pages project → Custom domainsSet up a domain → enter <slug>.dalayon.workActivate. DNS auto-creates (dalayon.work is on Cloudflare); SSL goes active in a few minutes.

Phase E: Gate it with Cloudflare Access

Full detail with screenshots-worth of values is in Cloudflare (Pages + Access). The short version:

  1. Zero Trust → Access → Applications → Add an application → Self-hosted.
  2. Destinations: add the custom hostname <slug>.dalayon.work and client-<slug>.pages.dev. Gating both closes the .pages.dev back door. Do not skip it.
  3. Policy: Allow, Include emails ending in @<clientdomain> and/or specific emails. Include your own team emails so you do not lock yourselves out.
  4. Leave "Accept all available identity providers" on (One-time PIN + Google are already account-wide).
  5. Session duration 24 hours. Create.

Phase F: Verify

  • Incognito → https://<slug>.dalayon.work → should hit the Access login.
  • Log in with an allowed email → site loads.
  • Incognito → client-<slug>.pages.dev → should also require login.
  • (Optional) a non-listed email → should be denied.

Phase G: Branding

Match the site to the client's brand so reports feel native. The template already handles the light/dark toggle, footer credit, and universal CSS fixes. Three brand-specific steps remain:

  1. Logo + favicon. Save the client's logo to docs/assets/ and uncomment theme.logo / theme.favicon in mkdocs.yml. Add a small square favicon.svg (brand-color tile + initials).
  2. Colors. In docs/stylesheets/extra.css, uncomment the per-client brand section and set the --brand-* variables and the light/dark scheme blocks.
  3. Fonts. Uncomment theme.font in mkdocs.yml and the heading @import block in extra.css.

Branding heuristics:

  • Header color follows the logo. Light or vivid logo → dark brand-colored header. Dark or black logo → white/light header (copy the .md-header override from client-sundaenco).
  • Palette comes from the client's own assets. Pull their logo, read its colors. If no hex codes are exposed, derive them from the logo.
  • Light-only vs light+dark is a brand judgment: playful and editorial brands went light-only; the rest kept the toggle.
  • Fonts match brand character: serif for fashion/editorial, condensed/industrial for rugged, geometric sans for tech.

Worked examples: client-sample (cleanest), client-theladcollective (dark header, multi-region), client-megansalmon (light header), client-hardhat (dark + hi-vis accent).

Phase H: Populate the dashboards

The template ships interactive, brand-aware widgets (Overview KPIs, Conversion funnel + weekly chart, Site Performance vitals chart, Activity Log, CRO rounds). They stay empty until data exists. To populate them, follow Data Refresh: drop exports into team-data/, set the currency in tools/build_overview_data.py, run the builder, then author the static page content from the same merged data.

Add CRO rounds as you run them (docs/cro/round-<n>/ + a nav block + a linked row in cro/index.md). See CRO Delivery.

When the engagement ends

Follow Client Offboarding.