All work

Dihestia

A fashion store and its admin panel, finished before its backend exists.

Kind
Client project. In development, not launched.
Timeline
August 2025 to now, rebuilt in October 2026
Team
With a UI/UX designer and the .NET backend developers.
Built with
React Router, React, TypeScript, Tailwind CSS, Base UI, Zod, TipTap, MJML, Vitest, Playwright

01Context

Dihestia is a luxury fashion brand. The job was its online store and the admin panel behind it: collections, products with sizes and colours, a bag and favourites, checkout with an embedded payment form, accounts and order history. For the staff there are products, orders, newsletters, legal policies and analytics.

I started in August 2025 and built the first version as a single-page app from the designer's Figma file. In October 2026 I rebuilt it on React Router with server rendering.

The backend is being written by other developers in .NET, and it was not ready. So the question that shaped the project was how to finish a store without one.

02My role

The whole frontend: storefront, admin panel, emails and tests, and the specification the backend is built from.

Not launched yet. The repository is private.

03Finishing a store before its backend exists

A frontend that waits for an API ends up with screens nobody has ever used. A frontend that fakes the API with sample data scattered through its components has to be taken apart when the real one arrives. I wanted neither.

04Decisions

  1. What does the frontend talk to when there is no backend?

    Options
    • Wait for the API
    • Hard-code sample data into the pages
    • A typed interface for each service, with a mock behind it
    Chose
    Every domain, from the catalogue to orders to newsletters, is an interface plus a mock over an in-memory database. Pages only ever call the interface. The real API will be one HTTP implementation per service and a switch in one file.
    Because
    The whole product works today. You can sign up, fill a bag, pay in the provider's test mode and find the order in the admin panel. And the interfaces became the contract: I wrote the backend specification from them, so every field and rule in it is something a screen already depends on.
    It costs
    The mock is a second implementation that has to be kept honest, which is why the project's rule is a test with every service method. And an in-memory database forgets everything when the server restarts.
  2. Who works out the price?

    Options
    • The browser, for speed
    • The server, always
    Chose
    The server owns the truth. The bag summary, the order prices and every permission check are computed on the server. The browser renders and submits.
    Because
    A total computed in the browser is a total a customer can edit. The same thinking is why the admin panel answers 404 to anyone who is not an admin: it should not confirm that it exists.
    It costs
    Every change to the bag is a round trip. Fetchers keep it feeling immediate without moving the arithmetic.
  3. What do you do when the design fails a contrast check?

    Options
    • Ship it as drawn
    • Adjust the colours quietly
    • Hold the minimum and write down each departure
    Chose
    Text contrast is held at 4.5 to 1. That took three departures from the Figma colours: a new grey for muted text on light surfaces, a darker red for error text and badges, and separate ink colours for green and yellow status text. Each is documented beside its token.
    Because
    The instruction was to match Figma exactly. An exception that is written down is still a decision about the design. One that is not is drift.
    It costs
    One failure stays on purpose. A statement on the home page lights up word by word as you scroll, and its dim starting state is the design. It is reported in the audit, not changed.
  4. How do you know a page is still fast?

    Options
    • Run Lighthouse now and then
    • Make the budgets fail
    Chose
    One script fails when a Lighthouse median drops under its floor, another when the bundle grows past its limit, and the browser tests run on six device profiles and again on WebKit.
    Because
    A real case. On a first visit, the largest thing Chrome counts as painted on the home page is the cookie notice, because the full-screen photo is ignored. When the notice faded in from fully transparent, its text stopped counting, a 20 pixel icon became the largest paint, and the mobile figure jumped from about one second to over five. The fix was to start the fade at 0.01 instead of 0. A test guards it now.
    It costs
    Budgets catch regressions. They do not fix the hard part: mobile Lighthouse performance is still between 53 and 79, and the reason is known. About 670 KB arrives before the largest paint, mostly fonts and JavaScript.
  5. How should a legal page change?

    Options
    • Edit the text and deploy
    • Versions, dates and notice
    Chose
    The five legal pages are data: numbered, immutable versions, each with an effective date and a summary of what changed. A material change needs 30 days' notice, only one version can be waiting, and customers can read every past version.
    Because
    A policy that can be edited silently is not much of a promise. I also went through each policy, traced every promise in it to a rule the backend has to keep, and changed the wording wherever the system did not do what the page said.
    It costs
    It is more machinery than five static pages, for a store that has not opened yet.

Going deeper

A content security policy the payment form has to live with

Pages run only the scripts that carry that response's nonce, and the policy is enforced in production builds. No card data is collected or stored by the store at all. Cards live with the payment provider, whose form is embedded in the checkout.

That form is why the checkout allows inline style elements and no other page does. The validation library's compiler is switched off, because the way it probes for support counts as a violation. After any change to the policy or the payment step, a script opens every kind of page in a real browser and fails on a single blocked resource.

Emails the backend can send unchanged

Each email is an MJML design with a plain-text twin, and its variables use Handlebars so the .NET backend can render the same files. In development, everything the store would send lands in an outbox page.

Newsletter pictures point at email-sized copies, never at the originals, some of which were 3 MB. A test fails if a photo is added without its smaller copy.

What the measurements say

These are lab results from my machine, recorded in the repository: Lighthouse 13.6 against a production build, three runs per page.

Desktop performance is 94 to 99 on every audited page. The product page went from 77 to 94, and its layout shift from 0.365 to 0. Mobile performance is 53 to 79, which misses the target. In a real browser, the largest paint on a first visit lands at 0.4 to 0.6 seconds.

The home page also taught me that the score can flip between 93 and 48 on the same build, depending on which paint the simulator picks. Small differences from three runs are not evidence.

A bug only a tunnel shows

Behind an HTTPS tunnel, every form post came back as 400. The tunnel ends the encrypted connection, so the server saw a plain request while the browser's Origin header said secure, and the framework's origin check refused it. The fix was to list the tunnel's host as an allowed origin, and to remember that the list is read at build time.

05The result

What works

  • The storefront and the admin panel are complete against the mock services: browsing, search, favourites, the bag, checkout in the payment provider's test mode, accounts and order history, and admin sections for products, orders, categories, newsletters, policies and analytics.
  • 58 unit test files, and a browser suite on six device profiles with an accessibility pass over every public page.
  • A backend specification written from the frontend's interfaces, down to the request and response models and the business rules.

What is not there

  • Not launched. The real backend is still being built, and nothing here has served a customer.
  • Mobile Lighthouse performance is 53 to 79. The cause is understood and the work is open.
  • Parts of the payment step, 3-D Secure and wallet buttons, have not been exercised.

06Reflection

Writing the specification from a finished frontend turned out to be the most useful thing I did. A screen cannot be vague about what it needs. Every field in that document is there because something on a page breaks without it.

I also stopped trusting a single performance run. If two builds are close, I interleave the runs or I do not draw a conclusion.