# CMS module router

This folder resolves **which React component** to render for each CMS module on a page, and **which props** it receives.

Pages are built from CMS data: `page.sections.main.modules[]` → each item has a `name` (e.g. `widget`, `top_list`, `google_preferred_source`). The `Modules` component in `index.js` renders one module at a time.

## File structure

| File | Responsibility |
|------|----------------|
| `index.js` | Page wrapper: title, intro, Suspense, admin bar. Calls `resolveModule()`. |
| `module-registry.js` | Maps CMS `module.name` → component, props, and render rules. |
| `module-constants.js` | Shared lists (sportstake names, modules without Suspense, etc.). |
| `module-props.js` | Extra prop builders for sportstake/lotto modules. |
| `module.module.scss` | Layout styles per module (e.g. `.google_preferred_sourceModule`). |

Component implementations live under `src/components/` (matrix) or `gatsby-core-theme` (core), **not** in this folder.

## Render flow

```
CMS module (name, items, title, …)
        ↓
index.js builds context (page, serverData, …)
        ↓
resolveModule(name, context)  →  module-registry.js
        ↓
null  →  nothing rendered
{ Component, props, meta }  →  render inside wrapper <div>
```

1. **`resolveModule`** looks up `MODULE_REGISTRY[moduleName]`.
2. **`shouldRender`** (optional) can return `false` → module is skipped.
3. Component is either a **direct import** (`component`) or **lazy-loaded** (`load`).
4. **`getProps`** (optional) returns extra props spread onto the component.
5. **`index.js`** wraps the result in the standard module shell (title, intro, Suspense fallback).

## Context (`ctx`)

The second argument to `resolveModule` and to registry helpers is a **context object** (not React Context):

```js
{
  module,           // current CMS module
  page,             // current page
  pageContext,      // full Gatsby page context
  serverData,       // SSR data (sportstake, lotto, search, …)
  socialIcons,      // from site schema
  EEAT,             // site uses EEAT contributors
  isContactUsPage,
  anchorLabel,      // anchor id for the wrapper
}
```

Use `ctx` inside `shouldRender` and `getProps` when you need page or server data.

## Registry entry options

Add one key per CMS module name in `MODULE_REGISTRY` in `module-registry.js`.

| Option | Purpose |
|--------|---------|
| `component` | Already-imported React component (eager, no `lazy`). |
| `load` | `() => import('path/to/component')` for code-splitting. |
| `resolveComponent` | Function `(ctx) => Component` when the component depends on runtime data (e.g. iframe, spotlights). |
| `shouldRender` | `(module, ctx) => boolean`. Return `false` to hide the module. |
| `getProps` | `(ctx) => object` merged into the component props. |
| `suspense: false` | Skip `<Suspense>` wrapper (see `NO_SUSPENSE_MODULES` in constants). |
| `hideModuleHeader: true` | No module title / intro block (see `HIDE_MODULE_HEADER_MODULES`). |
| `sportstakeSuspense: true` | Use taller Suspense fallback (sportstake modules). |

If `resolveModule` returns `null`, the module is not shown (unknown name, failed `shouldRender`, or no component).

## How to add a new module

### 1. Create the component

Example CMS name: `my_banner` → typical path:

```
src/components/molecules/my-banner/index.js
```

The CMS `name` uses **snake_case**; folders often use **kebab-case**. They do not have to match, but matching makes discovery easier.

### 2. Register in `module-registry.js`

**Simple lazy module (most common):**

```js
my_banner: {
  load: () => import('../../../../components/molecules/my-banner'),
},
```

**Eager component (like `content`):**

```js
import MyBanner from '../../../../components/molecules/my-banner';

// in MODULE_REGISTRY:
my_banner: {
  component: MyBanner,
},
```

**With extra props:**

```js
my_banner: {
  load: () => import('../../../../components/molecules/my-banner'),
  getProps: ({ page }) => ({ pageTemplate: page?.template }),
},
```

**Hide default title/intro (banner-only UI):**

```js
my_banner: {
  load: () => import('../../../../components/molecules/my-banner'),
  hideModuleHeader: true,
},
```

Or add `my_banner` to `HIDE_MODULE_HEADER_MODULES` in `module-constants.js`.

**Only render when a condition is met:**

```js
my_banner: {
  load: () => import('../../../../components/molecules/my-banner'),
  shouldRender: (_, ctx) => Boolean(ctx.page?.someField),
},
```

### 3. Add CMS + styles (if needed)

- Add the module in the CMS for the relevant pages/templates.
- Optional: add `.my_bannerModule { … }` in `module.module.scss` for layout overrides.

### 4. Verify

- Add the module on a test page in the CMS.
- Run `gatsby develop` and confirm it renders.
- If nothing appears: check `shouldRender`, `resolveModule` returning `null`, or a lazy import path typo.

## Sportstake / lotto modules

Do **not** add each sportstake/lotto name manually unless it is a one-off.

1. Add the CMS name to the right array in `module-constants.js` (`SPORTSTAKE_FIXTURE_MODULES`, `SPORTSTAKE_RESULT_MODULES`, or `LOTTO_MODULES`).
2. Registry entries for those groups are generated automatically at the bottom of `MODULE_REGISTRY` via `sportstakeFixtureEntries`, `sportstakeResultEntries`, and `lottoEntries`.
3. Shared prop logic lives in `module-props.js` (`getSportstakeFixtureProps`, etc.).

They require `serverData` from `getServerData` / `server-data.js` and only render when data exists.

## Props passed to every module component

Regardless of registry `getProps`, `index.js` always passes:

- `module`, `page`, `pageContext`
- `noModuleIntro`
- `exclOperator`, `isHomepageFirstModule`, `modulePosition`
- Plus anything from `getProps` (or `{ anchorLabel }` by default)

Your component should accept what it needs via `module` / `page` or document extra props in `getProps`.

## Examples in this codebase

| CMS name | Notes |
|----------|--------|
| `google_preferred_source` | Matrix molecule; `hideModuleHeader`; env URL in component. |
| `widget` | Simple `load` entry. |
| `welcome_bonus` | Different folder name (`operator-welcome-bonus`). |
| `iframe` / `game_iframe` | `resolveComponent` picks game iframe vs core iframe. |
| `multiple_contributors` | `shouldRender` + `getProps` for EEAT sites. |
| `sportstake8fixturesweekend` | Listed in `SPORTSTAKE_FIXTURE_MODULES`, not in registry manually. |

## Import paths

- **Matrix components:** `../../../../components/...` (relative from this folder).
- **Core theme:** `gatsby-core-theme/src/components/...`.
- **Aliases:** `~atoms/...`, `~molecules/...` where configured.

Keep new entries consistent with neighbouring modules in `module-registry.js`.

## Related code

- **Where modules are listed on a page:** `gatsby-core-theme` → `Main` → maps over `page.sections.main.modules`.
- **Server data for sportstake/lotto:** `src/gatsby-core-theme/helpers/server-data.js`.
