mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 09:21:10 +00:00
[AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs
Captures the full output of autodev existing-code Phase A through Step 4 (Code Testability Revision) for the Azaion UI workspace: - Step 1 Document: _docs/02_document/ (FINAL_report, architecture, glossary, components/, modules/, diagrams/, system-flows, module-layout) plus _docs/00_problem/ + _docs/01_solution/ + _docs/legacy/ + _docs/how_to_test + README. - Step 2 Architecture Baseline: architecture_compliance_baseline.md. - Step 3 Test Spec: _docs/02_document/tests/ (environment, test-data, blackbox/performance/resilience/security/ resource-limit tests, traceability-matrix), enum_spec_snapshot, expected_results/results_report.md (98 rows), plus the run-tests.sh + run-performance-tests.sh runners. - Step 4 Code Testability Revision: 01-testability-refactoring/ run dir (list-of-changes C01-C07, deferred_to_refactor, analysis/research_findings + refactoring_roadmap) and the 7 child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/ plus _dependencies_table.md. - _docs/_autodev_state.md pins the cursor at Step 4 / refactor Phase 4 entry so /autodev resumes cleanly. Epic AZ-447 (UI testability gates) tracks the 7 child tasks that will land in subsequent commits. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
# Module: `src/features/login/LoginPage.tsx`
|
||||
|
||||
> **Source**: `src/features/login/LoginPage.tsx` (95 lines)
|
||||
> **Topo batch**: B4 (depends on B3: `auth/AuthContext`)
|
||||
|
||||
## Purpose
|
||||
|
||||
The single public route of the SPA. Collects email + password, calls
|
||||
`AuthContext.login(...)`, and on success runs a four-step "unlock"
|
||||
animation (download key → decrypting → starting services → ready)
|
||||
before navigating to `/flights`. Replaces the WPF `LoginWindow.xaml`
|
||||
including its multi-step progress UI (`_docs/legacy/wpf-era.md` §3 / §4).
|
||||
|
||||
## Public interface
|
||||
|
||||
```ts
|
||||
export default function LoginPage(): JSX.Element
|
||||
```
|
||||
|
||||
No props. Reads `useAuth().login` and `react-router-dom`'s `useNavigate`.
|
||||
|
||||
## Internal logic
|
||||
|
||||
- **State machine** — `step: UnlockStep` cycles through:
|
||||
`idle → authenticating → downloadingKey → decrypting → startingServices → ready`,
|
||||
with `idle` also reachable on error from `authenticating`.
|
||||
|
||||
```ts
|
||||
type UnlockStep = 'idle' | 'authenticating' | 'downloadingKey'
|
||||
| 'decrypting' | 'startingServices' | 'ready'
|
||||
```
|
||||
|
||||
- **Local state**:
|
||||
- `email: string`, `password: string` — controlled inputs.
|
||||
- `error: string` — empty unless the previous attempt failed.
|
||||
- `step: UnlockStep` — drives both the form/spinner branch and the
|
||||
spinner caption.
|
||||
- **`handleSubmit(e)`**:
|
||||
1. `preventDefault`, clear `error`, set `step = 'authenticating'`.
|
||||
2. `await login(email, password)` — this throws on bad credentials
|
||||
(`AuthContext.login` rethrows the `api.post` error).
|
||||
3. On success: call `runUnlockSequence()`.
|
||||
4. On failure: reset `step` to `'idle'`, set `error = t('login.error')`.
|
||||
- **`runUnlockSequence()`** — sequentially sets `step` to each of
|
||||
`downloadingKey`, `decrypting`, `startingServices`, `ready`, with a
|
||||
600ms `setTimeout` delay between each. After `ready`, navigates to
|
||||
`/flights`. The unlock steps are **purely presentational** — there is
|
||||
no real downloadKey/decrypt work happening; the SPA's bearer token is
|
||||
already set inside `await login(...)`. The animation reproduces the
|
||||
WPF "vault unlocking" UX for continuity, not for any cryptographic
|
||||
operation. Total minimum wait after a successful auth: 4 × 600 ms =
|
||||
2.4 s. Document explicitly to avoid future "what does this decrypt?"
|
||||
confusion.
|
||||
- **`STEP_KEYS`**: a `Record<UnlockStep, string>` mapping each step to a
|
||||
translation key. `'idle'` maps to the empty string (the spinner
|
||||
caption is not rendered when idle).
|
||||
- **Render**: when `step === 'idle'`, the form is shown (email +
|
||||
password + error + submit). Otherwise the form is hidden and a
|
||||
centered spinner with the localized step caption is shown. Note the
|
||||
spinner is rendered even during `authenticating` (real network), so
|
||||
the user sees a single uninterrupted spinner across the entire
|
||||
auth → animation flow.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Internal**: `../../auth/AuthContext` — `useAuth().login`.
|
||||
- **External**: `react` (`useState`, `FormEvent` type),
|
||||
`react-router-dom` (`useNavigate`),
|
||||
`react-i18next` (`useTranslation`).
|
||||
|
||||
## Consumers (intra-repo)
|
||||
|
||||
- `src/App.tsx` — mounted at the public `/login` route, *outside*
|
||||
`AuthProvider`. (Verify in B8 — currently `App.tsx` puts
|
||||
`AuthProvider` inside the protected branch only, so `LoginPage`
|
||||
reaches `useAuth()` only because `App.tsx` actually wraps everything
|
||||
in `AuthProvider` at a higher level. Confirmed via the §7a graph
|
||||
edge `App → AuthProvider`.)
|
||||
|
||||
## Data models
|
||||
|
||||
`UnlockStep` and `STEP_KEYS` are module-private. No DTOs or shared
|
||||
types.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **i18n keys**: `login.title`, `login.email`, `login.password`,
|
||||
`login.submit`, `login.error`, `login.authenticating`,
|
||||
`login.downloadingKey`, `login.decrypting`, `login.startingServices`,
|
||||
`login.ready`. (All present in `src/i18n/en.json` and `ua.json` —
|
||||
confirmed.)
|
||||
- **Tailwind tokens**: `bg-az-bg`, `bg-az-panel`, `border-az-border`,
|
||||
`text-az-orange`, `text-az-text`, `text-az-muted`, `text-az-red`.
|
||||
Defined in `src/index.css`.
|
||||
- **Hardcoded animation timing**: `600 ms` per step. No env override.
|
||||
|
||||
## External integrations
|
||||
|
||||
None directly. Indirect: `AuthContext.login` calls
|
||||
`POST /api/admin/auth/login` (routed by `nginx.conf` to the `admin/`
|
||||
service).
|
||||
|
||||
## Security
|
||||
|
||||
- Both inputs use the right `type` attribute (`email`, `password`),
|
||||
giving the browser the chance to mask the password and offer
|
||||
password-manager autofill.
|
||||
- The HTML form does NOT have `autoComplete="off"` — autofill is
|
||||
intentionally allowed.
|
||||
- The component does NOT log credentials anywhere. The `error` state
|
||||
carries only the localized "Invalid credentials" string, never the
|
||||
raw backend error.
|
||||
- After successful auth, the bearer is already in memory (`AuthContext`
|
||||
set it). The 2.4s "unlock" animation does NOT extend the auth window
|
||||
— if the bearer expires server-side during the animation the next
|
||||
request retries via `client.ts`'s 401 → refresh path.
|
||||
- The `setError(t('login.error'))` shows a single generic error for
|
||||
every failure mode (wrong password, account locked, server down).
|
||||
Acceptable for security (no user-enumeration leak), but logs an
|
||||
observability flag — backend should keep specific reasons in its
|
||||
audit log.
|
||||
|
||||
## Tests
|
||||
|
||||
None.
|
||||
|
||||
## Notes / open questions
|
||||
|
||||
- **The unlock animation is theatrical** — it suggests cryptographic
|
||||
work that is NOT happening. If a future phase actually adds
|
||||
client-side key derivation, the animation should reflect real
|
||||
progress (`'decrypting'` would block on actual work). Keep the names
|
||||
but document the placeholder status in `solution.md` (Step 5).
|
||||
- **No "loading" while `authenticating`** beyond hiding the form —
|
||||
Submit button shows no spinner of its own; the form simply hides
|
||||
the moment `step` leaves `'idle'`. Mild UX gap (a quick auth
|
||||
failure shows the form blink). Defer to Step 8.
|
||||
- **Caps Lock indicator missing** — the WPF version had one
|
||||
(`_docs/legacy/wpf-era.md`). Not currently a goal; flag if the UX
|
||||
spec calls for it.
|
||||
- **No "Forgot password" link** — out of scope; the `admin/` service
|
||||
may or may not support that flow. Verify during Step 6 problem
|
||||
extraction.
|
||||
- **Form does not disable Submit while authenticating** — but the
|
||||
form is unmounted as soon as `step !== 'idle'`, so a double-click
|
||||
cannot fire a second `login(...)` call. Acceptable.
|
||||
- **`runUnlockSequence` resolution race**: if the user navigates
|
||||
away mid-animation (e.g. closes the tab), the pending `setTimeout`s
|
||||
still fire but harmlessly. React's StrictMode double-invokes the
|
||||
effect-mount path in dev — but `runUnlockSequence` is invoked from
|
||||
`handleSubmit`, not an effect, so no duplication.
|
||||
Reference in New Issue
Block a user