# Features — ClickLearnerPortal

Evidence date: 2026-10-19. Source: `git@bitbucket.org:overdrive/clicklearnerportal.git`, `master` at `2cb0ae5` (2023-02-07, single commit).

There is no changelog and no second commit, so "recently changed" cannot be separated from "what the initial commit contains". Nothing in git is marked deprecated. Commented-out code is listed as present but not on the live path.

## Current (on the live path)

| Feature | What it does | Where |
|---|---|---|
| Learner sign-in page | Title "Click Foundation Learner Portal". Username and password. Show/hide password. Reading Eggs hint. Open-material button. | `ClientApp/src/pages/learner/LearnerLoginPage.js`, `App.js` routes `/` and `/learner/login` |
| Home is the login page | `HomePage` renders `LearnerLoginPage` and adds no other UI. | `pages/HomePage.js` |
| Open material link | On mount, `GET api/programme/999` and uses `loginURL` as the button href. | `LearnerLoginPage.js`, `Controllers/ProgrammeController.cs` |
| Learner login API | Anonymous POST. Looks up the username, compares the password, returns a redirect URL, clears the password on the response. | `Controllers/LearnerController.cs` `login`, `Repositories/LearnerRepository.cs` |
| QUIZ redirect | When the redirect programme is `QUIZ`, appends learner id, source quiz id, and end date, and calls `pLearnerStartedQuiz`. | `LearnerController.Login`, `Repositories/TAORepository.cs` |
| Programme read API | Anonymous GET by integer id. | `Controllers/ProgrammeController.cs` |
| Click home learning page | COVID banner, Reading Eggs / Matific / Reflective Learning links, how-to PDF, contact mail. | `pages/ClickHomeLearningPage.js` |
| Quiz-finished callback | If the page query says the test is finished, POST the learner id and quiz id. | `ClickHomeLearningPage.js`, `Controllers/TAOController.cs` |
| Mobenzi ID lookup | Anonymous GET. Shared-secret query token. Returns HTML that `postMessage`s the procedure result to the parent window. | `Controllers/MobenziController.cs` `mobenzi-lookup-parent-id` |
| Mobenzi form intake | Anonymous POST. Reads a `submissionjson=` body and stores it. No token. | `MobenziController.cs` `mobenzi-form-submission-received` |
| Quiz learner name API | Anonymous GET. Shared-secret query parameter. Returns learner id and name. Comment: "Used by Mobenzi". | `Controllers/QuizController.cs` |
| SMS callback | Anonymous POST. Parses an iTouch-shaped notification or reply and inserts it. | `Controllers/NotificationController.cs` |
| SPA host | Development runs the React dev server. Publish runs `npm install` and `npm run build` and ships `ClientApp/build`. | `ClickLearnerPortal.csproj`, `Startup.cs` |
| Request logging | Serilog to the debug sink and a daily file under `c:/websites/learnerportal/logs/`. | `Program.cs`, `Startup.cs`, `appsettings.json` |
| Production error page | Non-development errors use `/Error` (Razor page, request id, no exception detail). | `Pages/Error.cshtml` |
| Analytics and help widget | Google Analytics and Intercom, both off when the development env file blanks the flags. | `ClientApp/public/index.html`, `ClientApp/.env`, `ClientApp/.env.development` |
| Static cache headers | `web.config` sets `Cache-Control: no-cache` and a 30-second client cache on static content. | `web.config` |

## Present in the tree, not on the live path

| Item | Evidence | Reading |
|---|---|---|
| Account authenticate | The whole of `Controllers/AccountController.cs` is a block comment. It references `this.identityService`, which is not a field. | Not compiled. Would not build if uncommented as it stands |
| JWT issuance | `Jwt` section and `AddJwtBearer` exist. `Expires` is `3600`. No action creates a token. `Models/Authenticated.cs` and `Models/IdentityUser.cs` (with `PasswordHash`) have no callers outside that comment. | Registered middleware, unused by live actions |
| Client session gate | `PrivateRoute.js` imports `services/AuthenticationService`, which is not in the tree. `App.js` does not use `PrivateRoute`. | Dead file. A build that imported it would fail |
| Learner search | `LearnerController.Search` and `LearnerRepository.Search` are block comments. `Models/Learner.cs` (school, grade, username, password) has no live caller. | Not compiled |
| Multiple-username rejection | The `learners.Count() > 1` branch in `Login` is commented. The first row is used. | Not active |
| Quiz `401` | `QuizController` contains a commented `HttpResponseException` with status 401. The live path throws `InvalidOperationException`. | Not active |
| `HttpResponseExceptionFilter` | Registered globally. Nothing on the live path throws `HttpResponseException`. | No current effect |
| `AppSettings:AppID` | Bound to `AppSettingsOptions`. No constructor injects it. Value in config is `7BB92BC7-F734-4D63-AEE4-F62DF0248F1E`. | Config only |
| `dotnet-ef` 3.1.0 | `.config/dotnet-tools.json`. No context and no migrations. | Tool manifest only |
| Library Manager | `libman.json` has an empty `libraries` array. | No client libraries from LibMan |
| `Dapper.Contrib` | Package reference. No `using Dapper.Contrib` in the project. | Unused package |
| `Microsoft.Data.SqlClient` | Package reference. Connection classes use Npgsql. `ProgrammeRepository.cs` imports SqlClient and does not use it. | Unused on the live path |
| npm packages `formik`, `reactstrap-formik`, `yup`, `oidc-client`, `rxjs` | In `package.json`. No import in `src`. | Unused by the pages that exist |
| Service worker | `index.js` calls `unregister()`. `registerServiceWorker()` is commented. | Not registered |
| `learner.zip` | `ClientApp/src/pages/learner.zip` contains `learner/LearnerLoginPage.js` and `.css`. The app imports the files beside the zip, not the zip. The zipped JS still renders four images (Reading Eggs, Feed the Monster, CareUp, Matific). The live JS does not. The zipped CSS has no textual diff against the live CSS beyond line endings. | Older snapshot of the login page, committed beside it |
| COVID programme images | `public/images/covid19/` holds careup, feed-the-monster, matific, reading-eggs, and a second matific gif. The live login page and the home-learning page do not reference them. The zip's login page does. | Assets with no live importer |
| Production IIS rewrite file | `web.production.config` redirects HTTP to HTTPS and `/support` toward `/admin/...`. No `.pubxml` references it. This app has no `/support` route. The rule titled for `/account/login` matches URL `support`, which the earlier rule already handles with `stopProcessing`. | File only. Applied or not: **Unknown** |
| Empty folders in the project file | `Services`, `Extensions`, and `ClientApp\src\services` are `Folder` includes. They are not in the git file list. | IDE placeholders |

## Changed

No commit after the initial commit exists on `origin/master`, so there is no in-repository history of a change. The zip-versus-live login page is the only in-tree sign that the login layout once showed programme logos and then stopped. Both versions were committed together. Provenance: **Inferred** from the diff, not from a commit message.

## Planned

No issue list, TODO product note, or roadmap file. `Dispose` methods contain the standard "TODO: free unmanaged resources" comment from the IDE template. That is not a product plan.

`ConfigureAppServices` in `Startup.cs` is empty.

## Deprecated / removed

Nothing is marked deprecated. The SQL Server connection code is commented inside `DWSqlConnection.cs` and `StagingSqlConnection.cs` ("removed sql adaptor and used postgres adaptor"). The commented lines are the removed path. The Npgsql path is the live one.

## UI routes

| Path | Component | Auth in the client |
|---|---|---|
| `/` | `HomePage` → `LearnerLoginPage` | None |
| `/learner/login` | `LearnerLoginPage` | None |
| `/click-home-learning` | `ClickHomeLearningPage` | None |
| anything else under the SPA | falls through to `HomePage` because `/` has no `exact` | None |

`react-router-dom` v5 `Switch` matches the first route. `/` is last, so it is the fallback.

## HTTP surface (live)

| Method | Path | Auth |
|---|---|---|
| POST | `api/learner/login` | Anonymous |
| GET | `api/programme/{id}` | Anonymous |
| GET | `api/quiz/learnerdetail` | Anonymous plus shared query secret |
| POST | `api/tao/tao-form-submission-received` | Anonymous |
| GET | `mobenzi-lookup-parent-id` | Anonymous plus shared query secret |
| POST | `mobenzi-form-submission-received` | Anonymous |
| POST | `api/notification/sms` | Anonymous |
| GET | `/Error` | Razor page, used as the exception handler outside Development |

There is no `api/account` route while `AccountController.cs` stays commented out.
