Wanbird IT

Caffina

An online shop for coffee capsules, with everything around it: one-off orders and subscriptions paid through Stripe, customer accounts, and an admin panel for the people who run it.

Kind
Online shop, accounts and admin
Built in
Next.js 16, React 19
Payments
Stripe Checkout
Data
SQLite, Drizzle ORM

Intensity 9 of 12

Espresso Roast

The house espresso. Thick crema, long finish.

Dark roast. Tastes of dark chocolate, toasted hazelnut and brown sugar.

$12.99 for a box of 10 capsules

Drag the stroke along the scale. Caffina's home page starts the same way: pick a strength, see the blend.

A short menu, mild to strong

Caffina sells four blends and nothing else. Each one sits on a scale from 1 to 12 that describes body and roast, not caffeine: Vanilla Latte at 4, Colombian Blend at 6, Espresso Roast at 9 and French Roast at 11. The shop lists them mildest first, everywhere.

The blends have no photos. Each one stores two colours, a foil and a swirl, and the shop draws the capsule from them. A blend added in the admin looks right on every page straight away, and is sharp at any size.

Two colours make a capsule

Pick any two colours, or one of Caffina's four. The saucer takes its tint from the foil, as it does in the shop.

The shop

A home page with the strength picker, a catalogue, a page for each blend and a subscription builder. Paper the colour of aluminium, ink the colour of espresso, and one blue for everything you can press. The warm colour is left to the capsules.

Caffina's home page: the headline Espresso worth getting up for, beside a capsule and the strength picker

From a blend to the door

Seven screens, from choosing a blend to following the order. Each picture is the real page, with demo content.

  1. Choose

    The catalogue shows every blend with its intensity, tasting notes and price. A blend with no boxes left is marked sold out and cannot be bought.

    The catalogue with four blends
  2. Read

    Each blend has its own page: the roast, what it tastes of, how to serve it, and the other three blends underneath.

    The Espresso Roast page
  3. Fill the cart

    The cart opens from the side and is kept in the browser, so it is still there on the next visit. It says how much more to add for free shipping.

    The cart open at the side of the page, with two boxes of Espresso Roast
  4. Or subscribe

    Build a box, choose how often it arrives, and see the price per delivery before signing up. A subscription needs an account, so it can be managed later.

    The subscription builder with three boxes chosen
  5. Pay

    Payment happens on Stripe's own checkout page. Guests can pay for a single order without an account. The shop then confirms the order and its number.

    The Order confirmed page with the order number and what was paid
  6. Follow the order

    In the account, each order shows how far it has got: confirmed, packed, shipped, delivered.

    An order in the customer's account, with three of four progress steps done
  7. Change your mind

    A subscription can be cancelled from the account, and resumed, without writing to anyone.

    The customer's subscription, with the next delivery date and a Cancel subscription button

Try the sums

A box holds 10 capsules. Shipping is a flat $4.95, and free once an order reaches $40. A subscription takes 15% off every box and always ships free. The owner can change all three numbers in the admin.

The browser only says which blends and how many. The server works out the price itself from its own catalogue and checks the stock, so a price changed in the browser changes nothing.

    Your order

    One payment, one order

    After a payment, two things tell the shop about it: a message from Stripe, and the shopper coming back to the confirmation page. Either can arrive first, and either can be repeated. Whichever arrives first records the order, and the other finds it already there.

    Recording an order takes its boxes out of stock, and refunding it puts them back. Stripe's messages also cover what never passes through the confirmation page: subscription renewals, cancellations, and refunds made from Stripe's own dashboard.

    Behind the counter

    The admin panel, for the people who run the shop. The figures and customers are demo data.

    1. Overview

      The last 30 days against the 30 before: revenue, orders, average order and active subscriptions, with the best sellers, the blends running low and the orders waiting to be packed.

      The admin overview: four figures, a revenue chart, work to do and latest orders
    2. Orders

      Every order, searchable by number, email or name and filtered by status. The list can be exported as a CSV file.

      The list of orders with their status and total
    3. One order

      What was bought, where it is going and how far it has got. Changing the status moves the progress the customer sees. A refund goes back through Stripe and returns the boxes to stock.

      One order in the admin: items, address, fulfilment status and a Refund order button
    4. Blends

      Create, edit and archive blends: the price, the boxes in stock, the intensity, the tasting notes, and the two capsule colours with a live preview.

      Editing Espresso Roast, with the capsule colours beside the form
    5. Customers

      Every account, with how many orders it has placed and how much it has spent.

      The list of customers with orders and amount spent
    6. Subscriptions

      Who subscribes to what, how often, and when the next payment is due. A subscription can be cancelled from here too.

      The list of subscriptions with their boxes, frequency and next payment
    7. Settings

      The shipping price, the amount that makes shipping free, the subscription discount, and the stock level that counts as running low.

      The shop settings form

    Locked where it counts

    Access is checked at the point where data is read or changed, not only on the page that links to it. Every admin page and every admin action checks for an admin first, and answers “not found” to anyone else. Customers only ever see their own orders and subscriptions.

    Passwords are stored hashed. A session is a random token in a cookie that scripts cannot read, and the shop keeps only a hash of it. Repeated failed sign-ins lock that email for 15 minutes.

    On a phone

    The same shop and the same admin, at phone width.

    Home page on a phone The catalogue on a phone The French Roast page on a phone, with nine boxes left The cart on a phone The subscription builder on a phone The admin overview on a phone

    Built with

    Shop
    Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4
    Payments
    Stripe Checkout, for single orders and subscriptions
    Data
    SQLite through libSQL, with Drizzle ORM. Zod checks what the server accepts
    Tests
    197 unit and integration tests (Vitest) and 41 end-to-end tests (Playwright), including accessibility checks
    Checks
    Formatting, linting, types and every test run on each pull request and each push to the main branch

    The end-to-end tests pay through a stand-in for Stripe, so they need no account. The project's own notes say to try a real test payment, a refund and a message from Stripe before going live.

    Have something like this to build?

    Send a few lines about what you need. A rough description is enough to start.

    Email contact@wanbird.ma See all work