Case study
Routine partner work, without an engineer in the loop.
- Role
- Senior Software Engineer
- Focus
- Product shaping · UX · Frontend architecture
- Stack
- Next.js · React · TypeScript · GraphQL · Tailwind
- Status
- In progress
The problem
Everything went through email.
Pharmacy partners had no tool of their own. Looking up an order, correcting a patient record, pulling a report, or rotating an API key meant emailing support and waiting for someone to query the database. Partners couldn't check status, requests had no priority or history, and engineers were in the path of routine work.
Before
- A partner emails support
- Support waits on someone who can query the database, often an engineer
- The answer comes back by email, with no status or history
With the portal
- A partner signs in to the portal
- They look up the order, fix the record, or pull the report themselves
- Support and engineers stay out of routine requests
Shaping
A written pitch before any code.
The team shapes work as a written pitch before building it. I wrote this one: the problem, a proposed solution for each area, the rabbit holes, and what was out of scope. Frontend and backend engineers reviewed it before work started.
Out of scope, on purpose
Gaps named up front
Works without a backend
The key decision
Labeled filters, not a search box.
Stripe, Linear, and Shopify Admin filter with one search bar and removable chips. I weighed three versions of that pattern and kept plain labeled fields, because of who uses this portal.
Who it's for
What research said
When I'd revisit it
Architecture
Open a record, share the link.
Records open as panels over the list instead of on a new page, so people keep their place. The address bar holds which panels are open.
/customers
The list
Filtered and paged, like any other list page.
/customers?customer=42
A record opens over it
The list stays put underneath. Refresh, or send the link, and the same customer is open.
/customers?customer=42&order=7
A related record stacks on top
One of that customer's orders opens as a second panel. Back closes it, and the customer is still there.
What I ruled out
What it costs
The hard part
Rows you could click but not reach.
Every list opened a record when you clicked a row, and none of that worked from a keyboard. The two quick fixes were both wrong.
A tab stop on every row
A button in every row
What I built
Where it stands
Still being built.
Screens for every area in the pitch exist but one. API keys, and the choice of a multi-factor sign-in provider, wait on decisions the pitch flagged at the start.
Areas with working screens
- Customers
- Orders
- Support requests
- Inventory
- Reports
- Webhooks
- Onboarding
- API docs
- API playground
Role
What I actually did
- Wrotethe pitch: the problem, the solution area by area, the rabbit holes, and what was out of scope.
- Reviewedit with frontend and backend engineers before any of it was built.
- Builtmost of the frontend, on Next.js, React, TypeScript, Apollo, and Tailwind.
- Recordedeach real design and architecture choice in a decision log, with the alternatives and what would make me revisit it.
- Testedaccessibility with Playwright and axe-core, including keyboard access, contrast, and reflow.