Home/Work/Saint Laurens
INDEPENDENT / CASE STUDY · 2026

SAINT
LAURENS

A full-stack online sporting goods store: catalogue, cart, card payments, orders and an admin area, in one Next.js app.
01 — About the projectScroll to explore ↓
Saint Laurens home page hero with the headline and Shop Now button
Saint Laurens Sporting Goods — home page.

A shop has to get one thing exactly right: when someone pays, the order, the stock and the receipt all agree.

The store is a single Next.js application. Pages read the database directly, changes go through server actions, Postgres holds the data, Stripe takes payment and Resend sends the confirmation email.

The customer journey runs from catalogue to cart, checkout, payment, order and fulfilment. Money is stored as whole cents everywhere and only turned into rand when it is displayed.

The server decides
what things cost.

At checkout the cart is reloaded from the database. The browser never supplies prices or quantities, and the shipping rate is looked up on the server, with free shipping above a threshold.

Carts belong to a visitor through a cookie, so they survive a refresh and work without an account.

From browsing
to a paid order.

01CatalogueSERVER PAGES
02CartCOOKIE SESSION
03CheckoutSERVER ACTION
04PaymentSTRIPE
05OrderWEBHOOK

A paid order
should only be
paid once.

Nothing is marked as paid when the browser returns from Stripe. That happens only in the payment webhook, which verifies Stripe's signature first.

The update only matches orders that are not yet paid, inside a transaction. If the same webhook arrives twice, the second finds nothing to change, so stock is only reduced once. The confirmation email is sent afterwards, and a failed email never undoes a paid order.

Saint Laurens home page on a phone
01
Data model
02
Catalogue
03
Cart
04
Checkout
05
Admin

The same shop,
from sport to size.

Shoppers can browse by sport or by brand from the main menu, or filter the full list by gender and brand. Each product has its own size picker, and sizes that are out of stock are greyed out.

The mobile menu carries the same sport and brand shortcuts, so a phone gets the same routes through the catalogue as a desktop.

04 / THE PRINCIPLE

The browser asks.
The server decides.

The shop front
is the small
part.

Next.js 15
Server components and server actions, no separate API.
Prisma + Postgres
Variants carry their own stock and optional price.
Stripe
Payment intents in rand, confirmed by webhook.
Resend + React Email
Order confirmations rendered from components.
Vitest + Playwright
Unit, component, integration and end-to-end tests.
const markedPaid = await tx.order.updateMany({
  where: { id: orderId, paymentStatus: { not: 'paid' } },
  data: { status: 'confirmed', paymentStatus: 'paid' },
})
if (markedPaid.count === 0) return false // already processed

The interesting work in a shop is invisible: what happens when a webhook is replayed, when two people want the last pair of boots, or when an email fails. Those cases decide whether the owner can trust the numbers.

Credits

Type
Independent case study
Role
Design / Full-stack
Year
2026
Repository
andymutale/store
NEXT PROJECT

MICHAEL WOOD

↗
06 / CONTACT

HAVE SOMETHING INTERESTING?