Learn by Directing AI
Unit 4

The Patient Portal Frontend

Step 1: Build the authentication UI

The backend is protected. Every API route checks both authentication and authorization. Now you build the interface that Lucia's staff will actually use.

Start with the auth flows: login, registration, and logout. Direct Claude to implement these with a specific constraint:

Build the authentication UI for the patient portal. Login form, registration form, and logout. Auth state must come from the server session -- never store auth tokens or user data in localStorage. After login, the session cookie is set by the server. The frontend reads auth state from a server endpoint, not from client-side storage.

Client-side state and server state are categorically different. The server session is the authority -- it knows who is logged in, what their role is, and whether the session is valid. The client-side state is a reflection of that authority, not a replacement. When the two drift apart -- the client thinks the user is logged in but the session has expired -- stale data bugs appear. The page looks correct but shows yesterday's information.

AI commonly generates auth UIs that store the JWT or user object in localStorage or React state and never re-check with the server. This works in development because sessions don't expire during a 30-minute coding session. In production, where a nurse might leave the portal open for hours between patients, it breaks silently.

Step 2: Build role-aware views

Each role sees a different version of the patient portal. Doctors see clinical notes, medication history, and full patient records. Nurses see care plans, vitals, and appointment scheduling -- but no clinical notes section. Admin sees scheduling, patient contact information, and clinic statistics -- but no clinical data.

Direct Claude to build these views. The role comes from the session. The frontend uses it to determine which components to render. But remember what you proved in Unit 3: these are visual hints, not security. The API routes enforce access regardless of what the frontend shows. A nurse who somehow reaches the clinical notes component still gets a 403 from the API.

After Claude generates the views, log in as each role and verify that each sees only what the access control matrix permits. Switch between doctor, nurse, and admin accounts. The same patient record page should look different depending on who is logged in -- sections present for one role are absent for another.

When you explain the data architecture to a colleague -- or document it for whoever maintains this system after you -- the reasoning matters. Why server-side rendering for user-specific pages? Because the server already knows the user's role and can render only the permitted data, rather than sending everything and hiding it with JavaScript. Why does the frontend check the role at all if the API enforces it? Because showing a nurse a "Clinical Notes" tab that returns a 403 when clicked is a bad experience. The frontend check is UX. The API check is security.

Step 3: Implement error handling and error boundaries

A patient lookup that returns nothing needs a clear message -- not a blank screen, not a spinner that never stops. A session that expires mid-use needs a redirect to login, not a page of stale data. An API error needs context.

Direct Claude to implement error boundaries and loading states across the portal. Watch for two common AI failures:

Error boundaries that catch errors without communicating anything useful. "Something went wrong" is technically an error message, but it fails the user completely. Lucia's staff -- community health workers, nurses who are "brilliant but technology isn't their strength" -- need messages that say what happened and what to do next. "Unable to load patient records. The connection to the clinic database was interrupted. Try again or return to the dashboard."

Optimistic updates without rollback logic. AI commonly generates code that updates the UI immediately (showing the appointment as confirmed) and then sends the API request. If the API fails, the UI still shows the confirmed appointment. In a medical context, a nurse who thinks an appointment is scheduled when it isn't has made a mistake based on incorrect information.

Stale data in a medical context is not a minor inconvenience. A provider at the Santiago clinic who sees medication information that was updated at the Jarabacoa clinic an hour ago may make prescribing decisions based on incomplete data. That was the exact near-miss that prompted Lucia to build this system.

Step 4: Handle client-server data flow

Patient data flows from the PostgreSQL database through the API routes to the frontend. When a record is updated at one clinic, the other clinics need current data. Direct Claude to implement a cache invalidation strategy -- when a patient record is written, any cached version of that record is stale and should be refreshed on the next read.

After the frontend is working, share it with Lucia. Send her a screenshot or demo of the portal with the role-based views. She'll log in and navigate. Her response reveals her priorities: she's relieved that records are accessible from any clinic. But she has two requests.

First, the community health workers. "Could they also enter data from the field? They visit patients at home and right now they write everything on paper and bring it back to the clinic." This adds a mobile data entry dimension. It's a legitimate need, but it's scope expansion. Decide how to handle it -- acknowledge it, note it for a future version, or keep the current scope.

Second, the board reporting. Carlos wants monthly patient volumes per clinic. This was in his original email, and Lucia is now asking directly. Manage scope: is this in your current ticket list or a follow-up?

✓ Check

✓ Check: Log in as each role (doctor, nurse, admin). Verify that each sees only the data their role permits. Then open browser DevTools -- the auth state should come from the session cookie, not from localStorage or a client-side variable.