RWA Customer Portal

A Next.js portal giving customers self-serve dashboards over their own data, with a staff console behind it.

  • 2025–present
  • Professional · RWA

Why there is no measurement

Measured internally at RWA — not mine to publish

No measurement

Why not

This runs in production and its usage is measured internally, but those numbers are RWA's rather than mine and are not mine to publish. I would rather show the gap than publish a figure I cannot let anyone check, or leave the project off the site because the result is inconvenient to evidence.

Problem

Customers needed their own view onto their data — dashboards, benchmarking, reports — without a person at RWA pulling and sending it by hand, and without every customer seeing every other customer's numbers. Internally, the team running the platform needed a different set of controls entirely: who gets access to what, which modules are configured for which account, what support requests are open. Two audiences, two sets of needs, and a single account model has to keep them apart correctly every time.

Approach

One Next.js application, split by route group into a customer-facing area and a staff area, sitting behind a shared auth layer rather than two separate deployments. What a signed-in user can reach is resolved from their account rather than assumed from which URL they typed, and the same session and revocation checks run in front of both halves. Dashboards themselves are largely embedded Tableau views, wrapped in a layer this project owns so sizing, loading state and failure handling are consistent regardless of which view is on screen.

Components

  1. Tenant-scoped dashboards

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    Customer-facing pages — benchmarking, mapping, reporting — built on embedded views and gated so an account only ever resolves to its own data.

  2. Tableau embed layer

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    A wrapper around the Tableau embedding API with its own sizing and fit logic, so every embedded view behaves the same regardless of the page it's on.

  3. Staff console

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    A separate route tree behind the same login for access control, module configuration, targets and support tickets — internal operation of the platform, not customer-facing.

  4. Session and revocation

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    Auth-layer middleware that can force a password change or invalidate a session outright, checked on every request rather than only at sign-in.

  5. Tenant and module catalog

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    A database-backed record of which modules and dashboards a given account can reach, read by the app to decide what to render rather than hard-coded per customer.

  6. Notifications

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    A per-user notification feed with its own validation step, decoupled from whatever produced the notification.

  7. GDPR tooling

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    Export, erase and purge routines for a customer's data, built as a module of their own rather than one-off scripts run by hand.

  8. Rate limiting

    Evidence: MEASURED INTERNALLY — NOT PUBLISHABLE

    A shared request-limiting layer in front of the API routes, keyed off client IP rather than left to each route to implement separately.

Two audiences, one login

The obvious shape for “customers see their data, staff run the platform” is two applications. This is one, split by route group behind a shared auth layer, because the alternative means keeping two session models, two deployments and two versions of “who is allowed to see this” in sync by hand. Access is resolved from the account, not from which half of the URL space someone landed on.

Dashboards as an embedding problem, not a data problem

Most of what a customer sees is an embedded view rather than a page this project renders itself. The work that belongs to this project is what surrounds that: consistent sizing and loading state, a failure path that doesn’t leave a blank rectangle, and gating so an embedded view only ever resolves to the account that’s allowed to see it.

What is deliberately absent here

No client names, no internal metrics, no architecture diagram of an RWA system, and no mention of the sector this platform serves — even though the repository itself is not shy about it. The employer boundary on this site holds regardless of what the code makes convenient to say.