Skip to content

Full-stack Product Delivery

Client work

Three systems I designed and built for clients through SOLAT Technologies. Each one turned on a single decision, so that is what I have written up rather than a feature list.

Some clients are described rather than named here. I have not yet asked their permission to use their names publicly, and I would rather under-claim than assume it.

A Scottish community organisation

Delivered — in handover

Community / non-profit · Next.js · Payload CMS · PostgreSQL · S3 · Resend · Playwright

The problem

The organisation's work — programmes, events, resources, community stories — was scattered across a site with no structure behind it, and every content change needed a developer. They needed a platform their own team could run.

What I did

I led the rebuild end to end: information architecture, the content model, the frontend, the CMS implementation, and the deployment and handover documentation.

Why a self-hosted CMS rather than a hosted one

The requirement that decided this was administrative, not editorial. The organisation needed a nine-role permission model, ownership of a submission queue, an audit log, soft delete, and personal-data access scoped by role. Hosted CMS platforms either put that tier of access control behind enterprise pricing or leave it out, which would have meant building a second admin application alongside the content one.

Payload gave me collection- and field-level access control, native drafts and scheduled publishing, and one deployable codebase sharing types with the frontend. I weighed it against Sanity, which was the original suggestion in the project brief.

I modelled fourteen collections rather than a generic page type, because each one has genuinely different access rules and publishing lifecycles. Treating an event, a policy document and a community story as the same object would have pushed that complexity into the editing experience, which is exactly where a small team cannot absorb it.

What was delivered

  • Fourteen content collections with distinct access rules and draft/publish lifecycles
  • Nine-role permission model with audit logging and soft delete
  • WCAG 2.2 AA checks (Playwright + axe-core) running in CI alongside typecheck, lint and migration-backed builds
  • Migration-driven database workflow and durable media storage

What I would do differently

I left the forms and email pipeline as non-submitting demos until late in the build, because the routing addresses and legal wording sat with the client. Wiring a stub adapter early and surfacing that dependency sooner would have shortened the handover tail considerably.

Screenshot to be added
Screenshot withheld until the client agrees to be named.

A UK beauty studio

Live

Beauty / appointments · Next.js · Supabase · Stripe · Resend · Sentry · Vitest

The problem

The studio ran appointments, deposits and availability by hand. The owner needed a booking experience customers could trust and, more importantly, a system that actually removed the admin rather than adding a nicer front end on top of it.

What I did

Full stack: the customer booking flow, the availability engine, the data model, Stripe deposits, an admin dashboard, Google Calendar sync, and deployment.

Treating availability as the hard problem

Booking looks simple until you write down what makes a slot valid. I ended up with eight rules a proposed start time has to satisfy: it falls on a working day, sits inside opening hours, finishes before closing, respects the daily booking limit, avoids existing bookings plus their buffer, misses blocked dates and partial blocks, matches the service type, and is not in the past.

Those interact. A three-hour service starting at 14:30 is invalid not because 14:30 is closed but because 17:30 is. A slot can be free and still unbookable because it falls in the buffer after the previous appointment.

The subtler problem was time. All wall-clock times are composed in Europe/London and converted to UTC before any overlap maths runs, because London is not a fixed offset — the same 09:00 slot is a different instant in July than in January. Getting that wrong produces bookings that are an hour off for half the year, which is the kind of bug a customer discovers by turning up at the wrong time.

I wrote the engine as a pure function and covered it with nineteen tests, including both British Summer Time and Greenwich Mean Time cases and the exact boundary conditions — a service ending precisely at closing time, a start exactly one buffer after the previous booking. Double bookings and missed appointments cost a small business real money and trust, so this is the part of the system I most wanted to be able to change without fear.

What was delivered

  • End-to-end booking and deposit flow, live in production
  • Availability engine covering eight validity rules, with nineteen tests including DST correctness
  • Admin dashboard across bookings, availability, services, gallery, content and testimonials
  • Google Calendar sync so the owner's existing diary stays authoritative
  • Stripe hosted Checkout, keeping card details inside Stripe's interface
Screenshot to be added
Screenshot withheld until the client agrees to be named.

A made-to-order fashion brand

Live

Fashion / retail · Next.js · React · Framer Motion · static rendering

The problem

The brand had real equity — editorial photography, press coverage, a documented founder story, genuine testimonials — and nowhere that brought it together. What it did not have was a product catalogue in the conventional sense, because pieces are made to order.

What I did

Design and build: information architecture, the collection and lookbook experience, content structure, performance work, and deployment.

Not building the shopping cart

The obvious build was an e-commerce storefront. It would have been the wrong one. These pieces are sold through conversation — sizing, fabric and customisation are settled before anyone commits — so an 'add to bag' button would have interrupted the sale it appeared to be enabling, and a checkout flow with no fixed prices behind it is a worse experience than none.

I built a collection-led catalogue instead: four collections presented as editorial chapters, a lookbook built from the brand's own photography, and direct enquiry paths to email, WhatsApp and Instagram.

The decision is visible in the codebase rather than just described in a deck. There is no cart, checkout, payment or inventory layer, and the legacy /cart and /checkout paths permanently redirect to the collection so old links still land somewhere deliberate. Content comes from two single sources of truth, and collection copy is drawn only from material the brand had already published — where real data did not exist, I simplified the interface rather than inventing product names or prices to fill it.

What was delivered

  • Five-route responsive brand experience, statically rendered
  • Four collections structured as editorial chapters with a full lookbook
  • Server-rendered markup for search and social previews, with reduced-motion support
  • Optimised high-resolution imagery to reduce page weight while preserving visual quality
Screenshot to be added
Screenshot withheld until the client agrees to be named.