Alliya: from an AI-built site to a booking platform
Alliya Massage Therapy Academy, a clinic and training academy in Sweden, had a website made with an AI site builder. It looked finished, but it could not safely take bookings, follow Swedish consumer law or issue student certificates.
01 / 04The home page in the new design, top to bottom: a grey canvas, one white sheet, a jade gradient.
Alliya Massage Therapy Academy is a clinic and training academy in Kista, Stockholm: massage, manual therapy and hijama for chronic pain, and its own certified levels for therapists in training.
- Client
- Alliya Massage Therapy Academy, Sweden
- Role
- Product, design, engineering and releases
- Stack
- React, Vite and Tailwind; Supabase for Postgres, authentication, storage and an edge function; Vercel for hosting, server functions and the daily job; i18next for the four languages
- Timeline
- April to October 2026, in two sprints
- Languages
- Swedish, English, Farsi, Arabic
- HandoverApr 4
- First sprint: repairsJul 20 to Jul 21
- Second sprint: the rebuildOct 3 to Oct 6
- Every screen testedOct 7
Some projects arrive all at once. This one came in seasons, alongside other work: a handover in spring, a first sprint of repairs in summer, and a second sprint in autumn that rebuilt it from the ground up. By the end I had turned it into a production system: a booking engine the database enforces, a legal pack built into the rules, certificates employers can verify with a QR code, and a site and admin in four languages.
A site that looked done
The founder built the first version by describing it to Hostinger Horizons, an AI website builder. In a short time it produced booking, an admin area, reviews and discounts in React with a Supabase database, and it looked like a finished product.
Taking it further was the problem. Every change was another prompt, and the results drifted. When the founder could not get what the academy needed out of it, I was brought in, and in April 2026 the exported code became my starting point.
I didn't change it at first. I read the generated code and its database, worked out with the founder what the clinic and the academy needed, went through the Swedish consumer rules a booking has to follow, and let a design direction settle.
The first sprint went underneath. I fixed what was broken, from the brand's name, which had drifted into four versions across the code and the database, to a header that cut it short. It worked, but the design still felt old-school, and the clinic still wasn't getting what it wanted.
So the second sprint started from the ground up: a new layout, a stable system under it, and new features, from certificates with their own number and QR code, to a page that verifies them, to partner agencies the academy can add.
Before and after
| Area | AI-built site (April) | Now (October) |
|---|---|---|
| Booking rules | Checked in the browser; double bookings possible | Enforced by the database; overlaps impossible |
| Data access | Far wider than the site needed | Visitors read only what the site shows; writes need a confirmed admin |
| Sent from the browser, by anyone | Sent only by the server, once, retried on failure | |
| Consumer law | Not handled | Withdrawal with Swedish holidays, terms, privacy, health-form consent |
| Texts per language | 525 in English, 464 in the others | 1,563, in all four languages |
| Database migrations | 0 | 11, versioned in the repository |
| Automated tests | 0 | 71, plus database test suites |
| Files in the repository | 111 | 240 |
Securing the site, without taking it down
When I inspected the live database in October, the public website could reach far more than it needed. Everything else waited.
The fix went in first, so the old site kept working while the new one was built. A hardening pass followed, after a review of the fix itself, and a final lockdown came once the new booking site was live. Email moved to the server with that, and the contact form can only write to the clinic.
The hotfix and the hardening shipped with rollback scripts, and I checked each step against what a visitor can and cannot do.
A booking engine the database enforces
Quoting a price, listing free times and creating a booking are database functions now, and a constraint in the database makes overlapping bookings impossible. Each booking keeps its own duration, buffer and price, so editing the price list never rewrites history.
Swedish consumer law is part of the rules. The 14-day withdrawal period ends on the right working day, past weekends and Swedish public holidays. There is an "Ångra bokning" withdrawal form, a health form with its own consent that deletes itself on schedule, and payment by wellness allowance (Epassi, Benifex) next to paying at the clinic.
Every booking change goes through the server, a ledger makes sure no email goes out twice, and one daily job retries failures and catches anything missed. One job, because the client's free hosting plan allows one a day.
Anything checked only in the browser can be skipped. Every booking now passes through the database.
An admin for the whole business
Behind the site sits a custom admin panel, and it reaches every part of the business: bookings, services and opening hours, customers, discounts, training videos, certificates and agencies, who has access, the website's own words and photos, and every email the system sends. It wears the same design as the public site, in light and in dark.
The clinic can read the booking rules here: a buffer between sessions, how much notice a booking needs, how far ahead customers can book, how long health forms are kept. A change applies to new bookings. The ones already made keep the terms they were booked with.
The clinic can change its own website here too. The Frontend editor lists what the site really shows, page by page, in each of the four languages, the terms and privacy policy included.
Every booking email has its own template and its own switch: confirmations, reminders, receipts, cancellations, review requests. Saving is honest now, too. An audit found saves the database had quietly refused while the screen said "saved"; every write now checks that something actually changed.
On a phone the tables become cards.
Certificates employers can verify, and nobody can quietly change
From a client brief written in Farsi, I built the certificate system: one fixed design for Level 1 Foundation, Level 2 Advanced and Level 3 Advanced Clinical, plus an Authorized Representative certificate for partner agencies. Agencies are added in the admin, and a public page lists the academy's representatives by country.
Every certificate gets a number like ALLIYA-2026-L1-0001 and a QR code that opens its verification page: active, revoked or expired. Issued certificates cannot be edited or deleted. That was the client's brief. A mistake is fixed by a corrected reissue, and the old one stays on record, linked to the new one.
Privacy is part of verification. Typing the number of a student's certificate shows only their first name and initials; the QR code on the paper shows the full certificate while it is valid; and an admin can erase a person's data on request. The seal and lotus take the level's medal colour, so the level reads at a glance.
A bug from the client's phone
When the client saved a certificate as a PDF on an iPhone, the title came out as a solid brown bar. The gold headings were drawn with a CSS trick that clips a gradient to the letters, and Safari's print engine does not support it. The round seal, drawn as SVG, had printed perfectly in the same file. I redrew the headings as SVG text at exactly the same positions, and changed the print version to white paper instead of the screen's dark frame.
Stress-testing every screen, in four languages
The client reported a language menu cut off on a phone. Rather than fix one menu, I tested everything. I built a browser test that runs the real site against a fake copy of the database, so no live data is touched. It loads every page at widths from 320 to 1280 pixels, in all four languages, in light and dark mode. It opens every menu and dialog, fills in the forms, feeds in oversized, empty and failing data, and walks each page with the keyboard alone. Two independent code reviews added what a browser cannot see.
| Check | Before | After |
|---|---|---|
| Content off-screen or cut off | 1,400+ | 0 |
| Menus and popups off-screen | 1,000+ | 0 |
| Text below contrast standards | 230+ | 0 |
| Keyboard navigation problems | 322 | 0 |
Then the same checks in WebKit, the engine behind Safari on iPhone, on the production build: 112 scenarios across iPhone 13 and iPhone SE sizes, in English and Arabic, with no issues.
The fixes went well beyond the menu. Leaving a page with unsaved edits asks first, the browser's Back button included. An open tab reloads by itself after a new release instead of breaking. Unknown addresses get a proper page instead of a blank screen.
Four languages, two directions
The clinic's customers and students speak Swedish, English, Farsi and Arabic, so the whole site and the whole admin come in all four, with Swedish as the binding version of the legal texts. Every one of the 1,563 texts exists in each language, with the same placeholders.
Right-to-left layouts needed more than mirroring. English text on an Arabic or Farsi page keeps its own direction. Dates in Farsi showed a Gregorian day next to a Persian month, so 4 October read as "4 Mehr" when it is 12 Mehr. Dates, times, prices and phone numbers now stay in Latin digits and in reading order inside right-to-left text.
Many mindsets, one system
In a project like this, the code is rarely the slow part. The slow part is listening, because everyone involved thinks in a different unit. An AI builder thinks in screens, since a screen is what a prompt gives back. The clinic thinks in sessions and care. The academy thinks in levels and proof. Swedish law thinks in days, records and consent.
Much of my work was translation between them: finding where each way of thinking belongs in the system, and keeping any one of them from flattening the others. The booking rules live in the database, where they can't be skipped. The photography reads as care rather than pampering, because the academy teaches therapy. And the certificates never change, because verification is only worth something if they don't.
None of these were dramatic. Each was small, and each was the visible end of something larger. Following them is the curious part of the work. Fixing each one where it starts, not only where it shows, is what turns a site that looks finished into a system that holds.
The calls that shaped it
- No "factory reset". My call: I removed the admin button that deleted every booking. Bookings are legal and bookkeeping records.
- QR codes on the permanent domain. My call: a printed code can never be changed, so it points at the main domain rather than an old address from the design file.
- No address required for no-shows. My call: instead of asking everyone for an address, the agreed policy, still to come, is a fee in the terms, tracking and a booking block for repeat no-shows. Less personal data for the same goal.
- Stay on the free plan. The client's call: one scheduled job a day does reminders, review requests, retries and catch-up, and is safe to run twice.
- A calm, clinical look. My call: a grey canvas, a white sheet and a jade gradient kept at 4.5:1 contrast for white text, with clinical photography instead of spa imagery.
Production discipline on a small project
The first site came from an AI builder, and it looked finished. What turned it into something a clinic can run on was the process around the code: clear requirements, independent review, tests, staged releases, and one person accountable for every change that reached production.
- Every release is a pull request. Each change is described in plain language and merged by me. Merging deploys to production, so a merge is a release: 14 of them so far.
- Database changes as migrations. Versioned files in the repository. The security and certificate changes ship with rollback scripts, and the ones that must not run twice or out of order refuse to.
- Tested before production. Migrations ran first on Postgres in WebAssembly against a model of the live schema, then as dry runs on the real database inside a transaction that is rolled back.
- Nothing live without approval. No change to the live database without an explicit go-ahead, and read-only checks before and after each one.
- Independent review. 48 findings fixed before the booking launch, and four parallel reviews before the certificates.
- Seen in real browsers. Headless Chrome in every language, light and dark, phone and desktop, keyboard only, before and after the fixes; then WebKit on the production build.
Live, and ready for what comes next
The platform runs at www.alliyamassagetherapy.com: online booking with Swedish consumer law built in, server-sent email, an admin in four languages, and certificates with QR verification.
- Next to build: a no-show policy with tracking and a booking block, then SMS reminders.
- In progress with the client: a lawyer's review of the terms and the privacy policy.
The screens were never the hard part. The work was underneath them, and in taking the time to see it first.
The certificate's holder details and the phone screenshots of admin bookings are demo data.












