Case study
Ankiety
Ankiety (“Surveys”) is a guest-survey app for the reception desk of a guesthouse. No backend, offline, PDF export.
Context
The reception filled everything in on paper. And a guest survey isn't a formality: the health questionnaire is what qualifies a massage as a medical service exempt from VAT, so the form has to be filled in, stored, found, and shown on request. The app's job was to digitize that process — not replace the document, just stop writing it by hand.
What I built
- A start screen with survey type selection (therapeutic massage / spa & treatments) and accessibility toggles.
- A two-step form: client details, then a health questionnaire with a checklist of contraindications.
- A survey preview before printing, identical to what ends up in the PDF.
- PDF generation with a VAT-exempt health declaration, ready for archiving.
- ZIP export bundling the PDFs, a CSV file, and a JSON backup for restoring the app's state.
Crux
Technical decisions
Why doesn't the app have a backend?
Because the survey includes a health questionnaire — blood clots, pregnancy, heart conditions, cancer. That's health data, a special category of personal data under GDPR Article 9, subject to a stricter regime than a plain name.
Having no server isn't a cost-saving move here — it's a way to sidestep that entire class of problems. Surveys stay in the reception's browser IndexedDB, so guest data never leaves the device: there's no database to leak, no server to secure, no data processor to appoint. As a bonus, the app keeps working when the guesthouse's internet drops, and hosting costs nothing, because it's static files.
Why a PWA, not a native app?
Reception installs it straight from the browser — no store, no developer account. Updates land the moment it's reopened — I'm not waiting on review, and I'm not asking anyone to click anything.
Why does export produce a ZIP, not a single PDF?
Reception archives surveys in batches, not one at a time. The bundle
contains PDFs named by client surname and treatment date, a
dane.csv for spreadsheets, and a backup.json for
restoring the app's state. Since the data only lives in the browser, the
backup isn't an extra — it's what makes this architecture honest.
Why did .htaccess need an AddType for .mjs?
Apache on OVH doesn't map a MIME type for .mjs, so the
browser refused to run the pdf.js worker — a module served with an empty
MIME type gets blocked. The second rule disables caching for
sw.js: without it, PWA updates don't land, because the
browser keeps holding onto the old service worker. Neither shows up in a
screenshot, but without them the app simply doesn't work.
What it looks like
Stack
- Vite
- PWA / Workbox
- IndexedDB (idb)
- pdf.js
- JSZip
- Web Share API
- Arima