# PRD — myPass-registration

Evidence date: 2026-10-09. Source: `git@bitbucket.org:overdrive/mypass-registration.git`, `master` at `e1a887a` (2023-01-18, "Changed page load").

There is no prior PRD in the repository or in Silicon Ark. Requirements below were recovered from `index.html`, `scripts/core.js`, `domains.js`, `scripts/custom.js`, `_service-worker.js`, and `bitbucket-pipelines.yml`. Provenance is marked on each item.

The page was not executed against the live API for this document. Response fields are those the client reads. Fields the API may return and this client ignores are **Unknown**.

## Product purpose

A visitor applies for event accreditation. The page collects identity, contact, organisation, role, a photo, optional linked events, and terms acceptance, then posts that application to the MyPass API on `footprintapp.net`.

Which event is open depends on the site hostname. The first dot-separated label is sent as `domain` to the styling API. That response selects the event. Provenance: **Implemented**.

The success panel says a confirmation email with the reference number was sent and that the event team will process the application. Provenance: **Documented** in `index.html` (`#response`). Sending mail and processing the application are not implemented in this repository.

## Users and roles

| Role | What they do | Evidence | Provenance |
|---|---|---|---|
| Applicant | Opens the public page and submits one application | `index.html`, `submitRegistration` | Implemented. No login |
| Organisation team lead | Named in the limit-reached message as the person to contact | `#response_event_full` | Documented in copy only. No screen in this repo |
| Event team | Named in the success copy as the people who process the application | `#response`, `#eventTeamName` | Documented in copy. The name shown is `{EVENTNAME} Team` from the submit response |
| Operator of the styling API | Returns event id, form flags, and colours for a domain | `getstyling` | External. Contract is inferred from the client |

No permission model, session, or role check exists in this repository.

## Main workflows

### W1 — Open the form for this hostname

Trigger: browser loads `index.html`. `page` is set to `"registration"` before `scripts/core.js` runs.

1. Theme preloader hides after 150ms (`scripts/custom.js`).
2. `core.js` forces `localStorage['MyPass-Highlight']` to `green` and sets the document background to white.
3. Ethnicity row is hidden.
4. Subdomain is `location.hostname.split('.')[0]`. Examples of what that expression yields: `register` from `register.example.com`; `localhost` from `localhost`. It is not a configured allow-list.
5. If `debug` were `true`, subdomain would be forced to `cpt7smedia`. `debug` is `false` (`d8e47de`).
6. `POST https://footprintapp.net/api/mypass/getstyling` with JSON `{ "domain": subdomain }`, mode `cors`, header `Content-Type: application/json`. No credentials and no auth header.
7. If the body has no `eventID`, or the request fails, the client substitutes `domains.demo` (event id `87`, grey-green styling `#798a7f`). Other keys in `domains.js` are not consulted.
8. `staticEventID` becomes that event id. `form` is kept for later show/hide. Optional `styling` is applied to the mapped elements (colours, `data-highlight`, favicon `href` or image `src`).
9. `doPageLoad` runs `GET https://footprintapp.net/api/mypass/getevent/EventID/{id}`.

On a successful event payload the page:

- Stores the whole JSON in `localStorage['regFormDetails']`.
- Sets both event-name headings from `DATA.AccreditationName`.
- Sets the line under the banner from `AccreditationTitle` (HTML string).
- Sets the banner image from `DATA.AccreBanner` and the page background image from `DATA.MypassBackground`.
- Shows or hides address and city blocks by assigning `RegistrationFields.Address` and `RegistrationFields.City` to CSS `display`.
- Fills category options from `Cats[]` (`ACID`, `Category`) and role options from `Roles[]` (`ARID`, `Role`, `ACID`).
- Injects `DATA.TermsAndConditions` into the terms modal as HTML.
- If `MediaEvent` is the string `"0"`, hides the Events step. Otherwise builds a checkbox per `Linked[]` item (`LID`, `Acc_Secondary`).
- If `DATA.RequireOrganisationReference` is the string `"0"`, hides the reference field and lets the applicant type the organisation name. Otherwise the organisation name stays disabled until lookup fills it.
- If `DATA.CloseDate` is today or earlier in the browser's local timezone, hides the form and shows "Registration for this event is now closed."

Provenance: **Implemented**. The legal meaning of `CloseDate`, `MediaEvent`, and `RequireOrganisationReference` is **Inferred** from these comparisons. Allowed values for the registration-field flags are **Unknown / Requires Confirmation** — they are copied straight into CSS `display`.

### W2 — Personal details

Trigger: applicant completes step Personal and uses NEXT.

Fields on the step: first name, surname, ID or passport number, nationality, date of birth (day, month, year), gender, ethnicity, country calling code, mobile number, email, address line 1, address line 2, town/city, state/province, country, postal code.

NEXT is enabled only when `checkDivInputs('tab-1','nextBtnOne')` finds every `input`, `textarea`, and `select` in `#tab-1` non-empty, skipping `#address2` and any element whose own `style.display` is `none`.

Behaviour that affects what gets stored:

- Typing the first name copies it into Pass Name. Typing the surname appends a space and the surname onto the current Pass Name. It does not rebuild the name from the two fields.
- Changing nationality selects the first country option and the first calling-code option whose visible text contains the nationality string, then shows ethnicity only when the value is exactly `South Africa` and enables that select.
- Mobile shows a green tick when the length is greater than 7. That tick does not gate NEXT. Leading zeros are stripped only at submit.
- Year options are generated from 1930 through the current calendar year, with 1990 preselected. Day defaults to `01`. Month defaults to January. The applicant can leave that date untouched.
- The nationality placeholder option has value `default`, which is non-empty, so NEXT does not require a real nationality.

Theme keyup checks in `custom.js` (email pattern, name pattern) only toggle valid/invalid icons. They do not disable NEXT.

Provenance: **Implemented**.

### W3 — Identity, organisation, role, photo

Trigger: NEXT from Personal calls `setTabActive('2')` and opens `#tab-2`.

Fields: pass name, organisation reference, organisation name, category, role, role description, photo. The phone input is inside an HTML comment and is not posted (`registerUser.phone` is commented out).

Organisation, when a reference is required:

- At exactly 6 characters, `orgCodeSearch` calls `searchOrg`. The search button and the field `change` event call it at any length.
- The value is uppercased and sent as `GET https://footprintapp.net/api/mypass/getorganisation/ORefNumber/{ref}`.
- On success the client writes `json[0].TradingName` into the disabled organisation name and shows the green tick.
- If `json[0].LimitReached == 1`, the form is hidden and the limit panel is shown.
- On failure the error is passed to `loging`, which prints only when `debug` is true. The search control can remain disabled if the failure happens before it is re-enabled. No message is shown.

Category change filters `Roles` to rows whose `ACID` equals the category. A category value of `0` lists every role. Every matching option is marked `selected`; the browser shows the last one.

Photo: `accept="image/*"`. Choosing a file stores a data URL on `registerUser.profileImage`. EDIT PHOTO starts Cropme (viewport 250×280, zoom and rotation). CROP writes the cropped data URL back. Moving to step 3 or 4 calls `cropImage()` when `croptimes` is still 0, which requires Cropme to have been constructed. If the applicant never pressed EDIT PHOTO, `profileImage` is still undefined and that call throws, which stops `setTabActive`. Provenance: **Implemented**. Whether operators expect a crop before submit is **Unknown / Requires Confirmation**.

NEXT uses the same non-empty gate. `#orgRefNumber` is exempt. Organisation name is not, so a failed lookup blocks the step while the name field is empty and disabled.

### W4 — Linked events

Trigger: Identity NEXT, when the Events step was not hidden.

The step lists checkboxes from `Linked`. NEXT stays disabled until at least one is checked (`oncheckEventSubmit`). Checked values are the `LID`s.

When `MediaEvent` is `"0"`, the step is hidden and the client tries to point Identity NEXT at the submit step. It sets `data-bs-target` to `#tab-4` and sets attribute `conclick` (not `onclick`) to `setTabActive('4')`. The markup `onclick` remains `setTabActive('3')`. Provenance: **Implemented**. The screen a non-media applicant actually sees was not run in a browser for this PRD. Treat the mismatch as a defect until someone confirms the intended path.

### W5 — Terms and submit

Trigger: applicant checks "I have read the Terms and Conditions and accept", which enables SUBMIT (`oncheckSubmit`). The terms body is whatever HTML the event payload put in the modal. The link uses `data-menu="menu-call"`, which the theme menu handler opens.

The Covid checkbox is commented out in `index.html` (removed 2023-01-18, commit `1c39a5d`). Submit does not read it. It sets `covidCheck` to `1` always.

SUBMIT hides the buttons, shows a loader, and `POST`s the payload in FR-010 to `https://footprintapp.net/api/mypass/postaccreditation`.

On JSON success:

- `#eventTeamName` becomes `{EVENTNAME} Team`.
- `#eventRefNumber` becomes `REF: {REFNUMBER}`.
- `#contactEmail` is set to the literal `info@mypass.zone`. The response field `EMAIL` is not shown. A previous address, `accreditations@sarugbyevents.co.za`, remains in a comment.
- The form is hidden and `#response` is shown.

CLOSE clears text inputs and reloads the page (`closeClear`).

On request failure, `loging(err)` runs and the loader stays. Buttons stay hidden. Provenance: **Implemented**.

PREVIOUS on this step calls `setTabActive('3')` but its `data-bs-target` is `#tab-2`. Highlight and the collapse panel can disagree. Provenance: **Implemented**.

### W6 — Registration closed

Trigger: event `CloseDate` parses as a date, and that calendar day is before or equal to today in the browser timezone (`dateInPast` zeroes hours on both dates, then tests `close <= today`).

The personal-details block is hidden. The notifications line is "Registration for this event is now closed." The function returns before terms, linked events, and the organisation-reference toggle are applied. Categories may already have been filled. Provenance: **Implemented**.

If `CloseDate` does not parse, the comparison is false and the form stays open. Provenance: **Inferred** from `Date` behaviour. Expected date format is **Unknown / Requires Confirmation**.

### W7 — Organisation limit reached

Trigger: organisation lookup returns `LimitReached == 1`.

The form is hidden. Copy: "The registration limit for your organisation has been reached. Please Contact your orginisation team lead for more information". CLOSE reloads the page. Provenance: **Implemented** (spelling of "orginisation" is the copy in the page).

## Functional requirements

### FR-001 — Resolve the event from the hostname

The system sends the first hostname label to `getstyling` and uses the returned `eventID` for the rest of the visit. If that id is missing or the call fails, it uses event `87` and the demo styling in `domains.js`.

Who: applicant's browser. Trigger: page load. Output: event id in `staticEventID`, then a `getevent` request.

Provenance: **Implemented**. Confidence: high.

`domains.js` also lists `sarugby` 209, `9hour` 346, `local` 87, `rwcsevens` 367, `formulae` 392, `rwcsevensmedia` 388, and `default_old` 87. The current page does not select those keys. `staticEventID` is initialised to `346` and overwritten before `getevent`. Provenance: **Implemented** as unused data.

### FR-002 — Show event content

After `getevent`, the system shows the accreditation name, title, banner, and background, and stores the payload in `localStorage` under `regFormDetails`.

Provenance: **Implemented**. Confidence: high for the fields named in W1. Full payload schema: **Unknown**.

### FR-003 — Collect personal details

The system collects the Personal fields in W2 and blocks NEXT while a visible field other than address line 2 is empty.

Provenance: **Implemented**. The gate does not check email shape, ID shape, a real nationality, or a deliberate date of birth. Confidence: high.

### FR-004 — Show ethnicity for South Africa only

Ethnicity options are African Black (`African_Black`, preselected), White, Indian, Coloured. The row is shown and the select enabled only after nationality becomes `South Africa`. Any other nationality hides the row. The select stays disabled until then.

Page styling may set `#ethnicity_row` to `display: block` from `form.ethnicity` before the applicant picks a nationality. The select can be visible and disabled. Provenance: **Implemented**.

The submitted payload always includes `ethnicity`, including the default, even when the row is hidden. Provenance: **Implemented**.

### FR-005 — Look up the organisation

When the event requires an organisation reference, the system looks up the uppercased reference and fills the trading name. A limit flag replaces the form with the limit panel.

When the event does not require a reference, the applicant types the organisation name.

Provenance: **Implemented**. The reference length rule (auto-search at 6 characters, while a code comment shows a 12-character example `07D40F4D5C91`) is **Requires Confirmation**.

### FR-006 — Collect role and photo

The system lists categories and roles from the event payload and requires a pass name, organisation name, role description, and a selected image file before NEXT. Role description help text is the static sentence in `index.html`. The longer race-crew sentence in `domains.js` (`form.message.role`) is not written into the page.

Provenance: **Implemented**.

### FR-007 — Collect linked events for media events

When `MediaEvent` is not `"0"`, the applicant must tick at least one linked event. The chosen `LID`s are submitted as `eventsLinked`.

Provenance: **Implemented**. The business meaning of `MediaEvent` is **Inferred** from the step being shown. Confidence: medium.

### FR-008 — Stop a closed event

When the close date is today or earlier locally, the system hides the detail form and shows the closed message, and does not offer submit.

Provenance: **Implemented**. Time zone is the browser, not a named zone in this repo. Confidence: high for the comparison; medium for the business intent of "closed on the close date itself".

### FR-009 — Require terms before submit

SUBMIT is enabled only when the terms checkbox is checked. Terms HTML comes from the event payload.

Provenance: **Implemented**. There is no record in this repo of which terms version was accepted, other than `tandcCheck: 1` on the payload.

### FR-010 — Submit the application

The system posts JSON:

| Field | Source |
|---|---|
| `firstName`, `familyName`, `passportNumber`, `nationality`, `email` | Matching inputs |
| `birthday` | `{day}-{month}-{year}` with the values from the three selects |
| `gender` | `F` if Female is checked, otherwise `M` |
| `mobileNumber` | Digits with leading zeros removed |
| `mobileNumberPre` | Selected calling-code value |
| `ethnicity` | Select value, default `African_Black` |
| `address1`, `address2`, `townCity`, `stateProvince`, `country`, `postalCode` | Matching inputs. `address1` is assigned twice |
| `passName`, `orgName`, `roleDescription` | Matching inputs |
| `orgRefNumber` | Uppercased reference |
| `roleCategory`, `roleFunction` | Selected `ACID` and `ARID` |
| `fileUpload` | The file input's path string (browsers use a fake path) |
| `profileImage` | Data URL from file read or from crop, if that ran |
| `tandcCheck` | `1` or `0` |
| `covidCheck` | Always `1` |
| `eventID` | Resolved event id |
| `eventsLinked` | Array of checked `LID`s; empty when none are checked |

Provenance: **Implemented** (`submitRegistration`). Confidence: high.

Gender values on the radios are `1` (Male, default), `0` (Female), `2` (Prefer not to say). Only the Female check changes the posted gender. Prefer not to say is posted as `M`. Provenance: **Implemented**. Whether that encoding is accepted by the API is **Requires Confirmation**.

### FR-011 — Show the reference

On a JSON success response the system shows the event team name, `REF: ` plus `REFNUMBER`, and `info@mypass.zone`, and tells the applicant to wait for processing.

Provenance: **Implemented** for the panel. Email delivery: **not in this repository**.

### FR-012 — Apply event styling

When styling data is present, the system applies `style`, `data-highlight`, and `src`/`href` using the field map in `core.js` (`favicon`, header bars, event name, tab bar, tab labels, and the primary buttons).

Provenance: **Implemented**. A styling key with no map entry is ignored.

### FR-013 — Installable cache

The theme registers `/_service-worker.js` at scope `/`. The worker caches `index.html` under `Cache-Version-v1.309` and answers with the cache when present.

`index.html` also sends `Pragma: no-cache` and `Expires: -1`. Those meta tags do not control the service worker. Provenance: **Implemented**. Which policy wins in production is **Requires Confirmation**.

Install prompts in `custom.js` look for `#menu-install-pwa-android` and `#menu-install-pwa-ios`. Those elements are not in `index.html`, so the prompt blocks do not run. The worker still registers.

## Integrations

| Integration | Direction | Trigger | Data | Auth | Failure |
|---|---|---|---|---|---|
| `POST /api/mypass/getstyling` | Browser → Footprint | Page load | `{ domain }` in, event id, `form`, `styling` out | None in this repo | Demo event 87 |
| `GET /api/mypass/getevent/EventID/{id}` | Browser → Footprint | After styling | Event content listed in W1 | None | `loging` only; form stays at defaults |
| `GET /api/mypass/getorganisation/ORefNumber/{ref}` | Browser → Footprint | 6-character input, change, or search click | First row `TradingName`, `LimitReached` | None | `loging` only |
| `POST /api/mypass/postaccreditation` | Browser → Footprint | SUBMIT | FR-010 JSON in; `EVENTNAME`, `REFNUMBER` displayed | None | Loader remains; no error panel |
| Google Fonts | Browser → Google | Page load | Stylesheet | None | Font fallback is browser-default. Not handled in code |
| S3 + CloudFront | Pipeline → AWS | Push to `master` | Clone directory | Bitbucket variables `REGISTER_PROD_AWS_ACCESS_KEY_ID`, `REGISTER_PROD_AWS_SECRET_ACCESS_KEY`, `REGISTER_PROD_AWS_DEFAULT_REGION`, `REGISTER_PROD_AWS_S3_BUCKET`, `REGISTER_PROD_CLOUDFRONT_DISTRIBUTION_ID` | Not described in the pipeline file |
| `php/contact.php` | Not called | — | Would mail `mail@domain.com` | — | Unused |

Host is `https://` + `qa` + `footprintapp.net`. `qa` is `""`. Provenance: **Implemented**.

## Important constraints

- Public page. Anyone who can open the hostname can post an application. No captcha, rate limit, or auth in this repository.
- Personal data (name, ID or passport, email, mobile, address, photo) is posted as JSON to Footprint and also held in memory. The event JSON is copied to `localStorage`.
- Terms HTML and the accreditation title are assigned with `innerHTML`.
- Client validation is "not empty", plus a mobile length hint. Format rules in `custom.js` are visual.
- `covidCheck` is always `1` after the checkbox was removed.
- Duplicate tab bars exist (`#staticReg*` and `#dynReg*`). `setTabActive` updates both because the step argument is a string and `tabNumber + 1` concatenates (for example `'2' + 1` is `'21'`, which matches `#tab21`).

## Edge cases

| Situation | Behaviour | Provenance |
|---|---|---|
| Styling HTTP or JSON failure | Demo event 87 | Implemented |
| `getevent` failure | Form shell remains; no closed check, no categories | Implemented |
| Nationality left on "Nationality" | Value `default` is submitted | Implemented |
| Date left on defaults | Birthday `01-01-1990` | Implemented |
| Prefer not to say | Posted gender `M` | Implemented |
| Surname edited more than once | Pass name keeps appending | Implemented |
| Address line 2 empty | Allowed | Implemented |
| Hidden fields (`display: none` on the element) | Skipped by the NEXT gate | Implemented |
| Organisation reference not 6 characters | No auto-search; button still searches | Implemented |
| Lookup returns an empty list | Reading `json[0]` throws; catch only logs; search may stay disabled | Inferred from the call order |
| Lookup limit reached | Limit panel, form hidden | Implemented |
| Close date today | Treated as closed | Implemented |
| Close date unparseable | Form stays open | Inferred |
| Submit network error | Loader only | Implemented |
| Non-media event | Events hidden; NEXT handler mismatch | Implemented |
| No EDIT PHOTO before leaving Identity | `cropImage` can throw and abort the step change | Implemented |
| Service worker has an old `index.html` | Cache-first returns it until the cache name changes | Implemented |
| `debug === true` | Subdomain forced to `cpt7smedia` | Implemented, currently off |

## Requires confirmation

1. Production hostname(s) and which domains `getstyling` still recognises.
2. Whether event ids in `domains.js` (87, 209, 346, 367, 388, 392) are still live, and whether `367` is the same RWC Sevens event used by `track.mypass.zone`.
3. Organisation reference length and format.
4. Expected `CloseDate` format and timezone, and whether the close date itself should be closed.
5. Allowed `RegistrationFields.Address` and `.City` values (`block` / `none` or something else).
6. Whether `MediaEvent == "0"` means "not a media accreditation".
7. Whether posting gender `M` for "Prefer not to say", and always sending `covidCheck: 1`, is accepted.
8. Whether the API sends the confirmation email the success panel promises, and whether `info@mypass.zone` is still the contact address (`EMAIL` on the response is ignored).
9. S3 bucket, CloudFront distribution, and whether the `master` pipeline is still active.
10. Intended behaviour when the Events step is hidden (the `conclick` typo).
11. Whether a photo must be cropped, and whether `profileImage` is the field the API stores.
12. Relationship of this form to the unused login / health-pass client in `core_old.js`.
