# Business rules — reactFrontend

Evidence date: 2026-10-05. Source: `git@bitbucket.org:overdrive/reactfrontend.git`, `master` `25c9bd8` (2020-07-17).

No business-rules document existed in the source repository or in `silicon_ark/reactFrontend/` before this file.

Rules below are what the code enforces or what a comment says it intended to enforce. They are not an approved policy. Each rule is marked **Implemented**, **Inferred**, or **Unknown**.

The fixed password literal in account activation is not copied here. It lives in `src/views/app/pages/user/account-management-content.js`.

## Access

### BR-001 — App pages require a stored user id

**Implemented.** `/app` renders only when Redux `authUser.user` is truthy. The reducer initialises that field from `localStorage.user_id`. Login sets it to the Cognito username. Logout removes it.

The guard does not call Cognito. A leftover `user_id` admits the shell after the Cognito session is gone.

Evidence: `src/App.js` `AuthRoute`, `src/redux/auth/reducer.js`, `src/redux/auth/saga.js`.

### BR-002 — Demo mode is off

**Implemented.** `isDemo` is `false`, so the `authUser || isDemo` clause does not open `/app` to anonymous browsers.

Evidence: `src/constants/defaultValues.js`, `src/App.js`.

### BR-003 — No role check on menu items

**Implemented.** `src/constants/menu.js` entries have no auth or permission field. Insight, Members, Organisations, Tournaments, Squads, Medical, Settings, and the UI kit are all linked for anyone who passed BR-001.

**Unknown / Requires Confirmation:** whether any role was supposed to hide Medical, Settings, or member admin.

### BR-004 — Card permission flags are loaded and not applied

**Implemented.** Member profile load posts `{ GroupID: global.GroupID, ModulePermission: "Members", personId }` to `/footprint/userpermissions`. Rows expose PRIVATE, VIEW, EDIT, CREATE, DELETE, and `CardPermission` as `containerID`.

`canRender` returns `true` always. The function that returns true only when `foundObject["VIEW"]` is set is commented out. Section ids in the calls include:

- `Member_Home_Details`, `Member_Home_CareerHistoryInternational`, `Member_Home_CareerHistoryNational`, `Member_Home_Qualifications`, `Member_Home_TransferHistory`, `Member_Home_MediaHub`
- `Member_Profile_PersonalInformation_General`, `_Identity`, `_Other`
- `Member_Profile_Contact_Address`, `Member_Profile_Contact_NextOfKin`
- `Member_Profile_Organisation_Current`, `Member_Profile_Organisation_History`
- `Member_Profile_Contract_History`, `Member_Profile_Apparel`, `Member_Profile_Social`, `Member_Profile_Bio`, `Member_Profile_TransferHistory`
- `Member_Medical_Insurance`, `Member_Medical_Injuries`
- `Member_Performance_MatchStatistics`, `Member_Performance_Activity`
- `Member_Performance_Sanctions_Infringements`, `Member_Performance_Sanctions_Suspensions`
- `Member_Performance_Assessments_Match`, `_General`, `_Conditioning`, `_SelfAssessment`

**Inferred** from the commented code: the intended rule was "show the card when that permission object has VIEW true". That rule is not what runs. A source comment says other verbs (delete, create) still need to be checked. Whether VIEW-only was an approved business rule is **Unknown / Requires Confirmation**.

Evidence: `src/components/AccessControl/AccessControl.js`, `user-detail.js`, the containers that call `canRender`.

### BR-005 — Group id defaults to 2

**Implemented.** `TopNav` sets `global.GroupID = 2` in the constructor. After `GET /members/{custom:memberid}` returns, it replaces that with `memberDetails.GroupID`. The profile permission post reads `global.GroupID` at request time.

**Inferred:** a permission request that runs before the top bar finishes uses group 2. Not executed.

Evidence: `src/containers/navs/Topnav.js`, `user-detail.js`.

### BR-006 — Group permission save encodes booleans as 0 or 1

**Implemented.** `POST /footprint/savepermissions` sends PRIVATE, VIEW, EDIT, CREATE, DELETE as `0` when the payload value is `false`, otherwise `1`. The same expression is used for `id` (`payload.id == false ? 0 : 1`), so a real id other than `false` is sent as `1`.

**Inferred:** that `id` line does not send the permission row id. **Unknown** whether the API accepts the body.

Evidence: `groupsAndRoles.js` `handleSubmit`.

## Sign-in and passwords

### BR-007 — Login username is not validated as an email

**Implemented.** The login label is the username message. The regex that required an email shape is commented out. The field may not be empty. The password must be longer than 3 characters (the check is `length < 4`).

Evidence: `src/views/user/login.js`.

### BR-008 — Cognito errors are the login error text

**Implemented.** `signIn` is caught and returned as the error object. If that object has `message`, the saga dispatches `loginUserError` with that message and does not navigate. A result without `message` is treated as success, including a result the code does not otherwise inspect.

Evidence: `src/redux/auth/saga.js`.

### BR-009 — Forgot-password success is "Cognito returned delivery details"

**Implemented.** The forgot-password saga opens the reset page only when `forgotPasswordStatus.CodeDeliveryDetails` is set. Otherwise it shows `message`.

Reset success is a falsy return from `forgotPasswordSubmit`. A truthy return is shown as `message`. Cognito's documented success string was not checked in this pass. **Unknown** whether a normal success is falsy.

The new-password page (`/user/new-password`) calls `forgotPasswordSubmit` and always navigates to login. It does not use the saga's falsy check.

Evidence: `src/redux/auth/saga.js`, `src/views/user/new-password.js`.

### BR-010 — New password fields must match when the validator sees both

**Implemented** as a function: if `newPasswordAgain` is non-empty and differs from `newPassword`, the error is "Please check your new password". Empty new passwords are not rejected by this function. No complexity rule (length, character classes) appears in `reset-password.js` or `new-password.js`.

**Inferred:** Formik `Field validate` passes a single value, while the function destructures `verificationCode`, `newPassword`, and `newPasswordAgain` from its argument. The mismatch message may not run. Not executed.

Evidence: `src/views/user/reset-password.js`, `src/views/user/new-password.js`.

### BR-011 — Confirming a sign-up code starts a password reset

**Implemented.** `Auth.confirmSignUp` is followed by `Auth.forgotPassword` for the same email, then `/user/new-password`.

Evidence: `src/views/user/confirm-code.js`.

### BR-012 — Registration is not the Cognito sign-up used by activation

**Implemented** difference: account activation calls `Auth.signUp`. The public register saga calls `createUserWithEmailAndPassword` on the Amplify auth export. The login Register link targets `/register`, which the router sends to `/error`.

**Inferred:** public self-registration does not complete. **Unknown** whether the product was supposed to allow it. The login copy says "If you are not a member, please Register."

Evidence: `src/views/user/login.js`, `src/redux/auth/saga.js`, `account-management-content.js`.

### BR-013 — Activation requires an email and uses a fixed initial password

**Implemented.** If `signInEmailAddress` is missing, the modal tells the operator to set an email under Profile Settings and does not call Cognito.

If it is present, confirm runs `Auth.signUp(email, <fixed password>)`. On `UsernameExistsException` the code calls `Auth.forgotPassword` and does not post a new uuid. In both the new-user path and the exists path it posts member active status `3`.

The operator does not choose the password. The value is omitted from this document.

Evidence: `account-management-content.js`.

### BR-014 — "Active Member" is the only status string treated as active

**Implemented.** While mapping verbose member JSON, `isActive` is true only when `Status == "Active Member"`. Any other status string is inactive in that flag.

Activation posts numeric `status: 3`, not the string "Active Member". **Unknown / Requires Confirmation:** whether `3` and "Active Member" are the same state on the server.

Evidence: `user-detail.js`, `account-management-content.js`.

## Member data

### BR-015 — Member search is the cartesian of type, group, role, and text

**Implemented.** The post body is `{ union, group, role, search }` where union is the selected organisation type's `value`, group is `MemberGroupID`, role is `MemberRoleID`, and search is the text box. The "All" option value is `0`.

After types load, the selected type becomes the second array entry (`organisationTypes[1]`), not "All".

The type list is always requested as `/footprint/types/13`.

Evidence: `user-list.js`.

### BR-016 — Adding a member from the modal does not persist

**Implemented.** Submit logs JSON and alerts "Successfully Submitted Form :)". No POST is made. The Create Member route does not contain a form.

Evidence: `user-list.js` `handleSubmit`, `create-user-page.js`.

### BR-017 — General profile fields are all required

**Implemented** in the client Yup schema for `GeneralForm`:

- Preferred name, full name, surname, sport safety number: required strings.
- Title, country of nationality, country of birth, city of birth, ethnicity, gender, disabled, additional needs: required objects with required `label` and `value`.
- Height and weight: required numbers, and they must be positive.

The submit body sends `title.value`, country and city `.value`, `ethnicity.value`, `gender.value`, `disabled.value`, `additionalNeeds.value`, plus the strings and numbers. `fullName` is assigned twice in the body object. `CognitoUuid` is on the default values and is not in the post body.

Dropdown choices are the arrays in `dropdownLookupData.js`. They are not loaded from the API. Countries include "South Africa", "Windhoek", "Johannesburg", and "Mauritius". Cities include "Cape Town", "Namibia", "Harare", and "Port Louis". Ethnic groups are Black African, Coloured, White, Asian, and "None dominant". Genders are Male, Female, and Other.

**Unknown** whether the API expects these codes.

Evidence: `src/components/forms/Profile/PersonalInfo/GeneralForm.js`, `src/customData/dropdownLookupData.js`.

### BR-018 — Home tab "basic information" does not use the saved profile fields

**Implemented.** The player basic-information card sets nationality to "South African", gender to "Male", height to "27", place of birth to "South Africa", weight to `user.age + " KG"`, current position to "Left Wing", and current team to "Blue Bulls (Contracted: Club)". Age and current organisation use `user.age` and `user.union`.

An official-shaped card exists in the same file. The render path uses the player card. A toggle that switched `dummyUserType` is commented out.

Evidence: `src/views/app/pages/user/contentTabs/home.js`.

### BR-019 — Health passport values on the profile are placeholders

**Implemented.** The mapper sets current status, review date, platform name, member serial, and authorisation to strings that start with `??` (for example a platform name "V-Covid"). They are not read from the verbose payload fields in that block.

Evidence: `user-detail.js` `healthPassportInfo`.

### BR-020 — Lookup data for squad filters is the same static file

**Implemented.** Squad list filters offer years 2013–2020, plus squad types, sports codes, genders, and age groups from `dropdownLookupData`. Choosing one alerts the label and value and stores it. The five squads stay on screen.

Evidence: `squad-list.js`.

## Squads, tournaments, organisations

### BR-021 — Squad and tournament catalogues in the UI are fixed lists

**Implemented.** Squads: Argentina, Australia, England, Italy, South Africa. Each is category "Senior: International", status "Active", `dateFrom` `11/05/2020`, empty `dateTo`.

Tournaments:

| Title | Category | Status |
|---|---|---|
| Castle Lager Incoming Tour | Senior: International | In Progress |
| Guiness Pro 14 | Senior: National | Completed |
| Vodacom Super Hero Sunday | Junior: International | Completed |
| HSBC Sevens Cape Town | Junior: National | Upcoming |

Squad detail always describes "Blue Bulls", type "Competition Squad", tournament "Curie Cup", age group "Senior", 55 players, 8 personnel.

**Unknown / Requires Confirmation:** whether these names are sample data for a presentation (`5e654af` says "Preparing Squad Detail for presentation tomorrow") or the product catalogue.

Evidence: `squad-list.js`, `squad-detail.js`, `tournament-list.js`.

### BR-022 — Organisation list options are fixed

**Implemented** on `/app/pages/organisation/organisation-list`, which is not in the main menu. Types: All Types, Developer, Federation, Union, Club. Organisations: All Organisations, Evolve AV, SA Rugby, Blue Bulls. Changing them updates component state and logs. The table options come from `organisationListDataTableOptions` in `customData`.

Evidence: `organisation-list.js`.

### BR-023 — Organisation dashboard requests id 220

**Implemented.** On mount, axios GET `https://afsxv57muc.execute-api.eu-west-1.amazonaws.com/dev/footprint` is called with a `URLSearchParams` containing `id=220` passed as the axios config object. The JSON is stored as `dataSelection`. The render returns Gogo demo widgets and does not read `dataSelection`.

**Unknown** what id 220 means.

Evidence: `src/views/app/pages/organisation/dashboard.js`.

## Support request

### BR-024 — Forgot-username form validates three fields and does not file a ticket

**Implemented** in the unrouted page. Name and surname are required. Email is required by the schema (the following lines of the schema were not all quoted here; the file uses Yup). Submit alerts success and does not POST. The side copy says the details will log a ticket to a help desk.

**Inferred:** the help-desk sentence is copy, not a call. **Unknown** whether a ticket system was intended.

Evidence: `src/views/user/forgot-username-password.js`.

## What this file does not claim

- Server-side validation, uniqueness, or authorisation. Those would live in the API this repo calls.
- A Cognito password policy. The pool id is in source. The pool's rules are not.
- Legal retention, consent, or medical-data policy. Medical cards exist in the UI. No policy text was found.
- That SA Rugby, Blue Bulls, or the tournament names are licensed content the product is allowed to show. They are strings in the source.
