1. Overview
A lightweight, mobile-first web app that runs the TUH Vascular Access Workshop end to end.
Participants scan a QR code on their own phone and complete a form in the browser — no app install. The app handles:
- Registration — name, role, email, WhatsApp, hospital + GDPR consent
- Feedback — a one-question-at-a-time wizard, anonymous by default
- QR codes — generated in-app for registration & feedback
- Data dashboard — organiser loads, downloads (CSV) and deletes results
It replaces the old Google Forms + third-party QR workflow. The whole thing is essentially one HTML file plus a small Supabase database.
2. Software used
| Piece | What | Why |
|---|---|---|
| Cloudflare Pages | Static hosting + deploys from GitHub | Free, fast, auto-deploy on every push |
| Supabase | Postgres database + auto REST API | Stores registrations & feedback; no server to run |
| GitHub | Code repo (rcheva/Vascular-WS, private) | Source of truth; push = deploy |
| chevahub.com | Domain / DNS (Cloudflare) | Custom address vascular-ws.chevahub.com |
In the page itself: vanilla JavaScript only — no framework, no build step. The only external bits are the QR library (qrcodejs), the icon font (Tabler), and Google Fonts, all loaded from a CDN.
index.html (the whole app) · details.html (programme page) · guide.html (this page) · tuh-logo-white.png · CLAUDE.md (full technical spec).3. Roles
Student / participant
Scans a QR code → lands on the Registration or Feedback form → submits. They only ever see those two tabs. They never need a login or a key.
Organiser (admin) — you
Sees the extra QR Codes and Data tabs by adding ?admin=1 to the URL. Sets up the workshop, prints QR codes, and pulls/deletes data using a secret key (below).
?admin in the link.4. How it works
Student scans QR → fills form in browser
│
▼
App validates → saves to Supabase (insert-only public key)
│ tagged with the workshop_id
▼
Organiser (Data tab + secret key) → Load → Download CSV / Delete
- Saving is automatic — the page has a baked-in public key that can only insert. Nothing to set up on the day.
- Reading/deleting needs the secret key, which is never in the page — you paste it at runtime, on your own device.
- Every row is stamped with a
workshop_idlikeTUH_2026-10-12, so each edition's data stays separate.
5. The data
Lives in a Supabase project (Renal-Review), inside a dedicated vascular schema, in two tables:
| Table | Holds |
|---|---|
workshop_registrations | name, role, email, whatsapp, centre, consent, timestamp, workshop_id |
workshop_feedback | name (or "Anonymous"), timestamp, workshop_id, and a responses JSON holding every answer of the feedback wizard |
workshop_id = TUH_<date>. It's built from the workshop date and attached to every submission. This is how the app keeps editions apart and lets you download one workshop at a time.
sa-east-1 (Brazil) region — acceptable for low-risk contact data, but note it's not in the EU.6. Run a workshop (day-of)
- Un-pause Supabase if needed (free projects sleep after ~1 week idle). Dashboard → project shows "Paused" → Restore.
- Open admin QR setup:
vascular-ws.chevahub.com/?tab=qr&admin=1 - Set the workshop date + edition. The Workshop ID (e.g.
TUH_2026-10-12) builds itself. - Click Generate, then Download both QR PNGs.
- Print them: Registration QR at the entrance, Feedback QR on tables / at the exit.
- That's it — participants scan and submit; everything saves automatically.
7. Extract data (CSV)
- Open
vascular-ws.chevahub.com/?tab=data&admin=1 - Get your service-role key from Supabase → Project Settings → API Keys → Legacy API keys →
service_role(a long string startingeyJ…). Copy it. - Paste it into the Service-role key field → click Load saved data.
- If more than one edition exists, an edition picker appears — choose the workshop.
- Click Download registrations and Download feedback — two separate CSV files.
service_role key (starts eyJ…), not the newer sb_secret_… one. The secret key bypasses all security — paste it only on your own device; it's kept in memory and cleared when you close the tab.The feedback CSV automatically flattens every wizard answer into its own column.
8. Delete data
On the Data tab, with the service-role key loaded, the red Delete button wipes only the selected workshop's rows. Flow:
- Type the admin passcode (set in the code — ask the organiser / see the private repo).
- Confirm twice.
- Done — registrations + feedback for that
workshop_idare removed.
9. Set up a new edition
Two ways — the first needs no developer:
A. No redeploy (recommended)
In the admin QR tab, set the date + edition and Generate. The QR links embed the new workshop (?w=TUH_<date>). Anyone scanning gets that date/edition and their data is tagged correctly. One deployment serves every future workshop.
B. Redeploy (changes the default)
Edit the WORKSHOP object at the top of the <script> in index.html and push:
const WORKSHOP = {
dateISO: '2026-10-12', // workshop day → builds the workshop_id
edition: '13th',
date: '12 October 2026',
...
};
Use B when you want the plain URL (no params) to represent the current edition.
10. Edit the feedback form
All questions live in one array — FB_QUESTIONS — near the top of the feedback section of index.html. Add, remove, reorder, or reword there; nothing else changes. Step types:
| Type | Use for |
|---|---|
grid | A matrix: several rows sharing one rating scale (satisfaction, venue, quality) |
options | A single multiple-choice question |
text / short | Long / short free text |
twotext | Two text inputs in one step (e.g. two topics) |
unit | Yes/No + unit name + contact |
Answers save into the feedback responses JSON automatically — no database change needed when you tweak questions.
11. Deploy & code changes
- Code lives in GitHub:
rcheva/Vascular-WS(private). - Every push to
mainauto-deploys via Cloudflare Pages. - No build step — Pages just serves the files (output directory
/). - Custom domain
vascular-ws.chevahub.comis attached in the Pages project.
12. Security model
| Key | Where | Can do |
|---|---|---|
| Anon (public) key | Baked in the page | Insert only. Cannot read or delete. Safe to ship. |
| Service-role key | Never in the page — you paste it | Full read + delete. Organiser device only. |
| Admin passcode | In the code | Soft guard on the Delete button — an accident-stopper, not real security. |
Students can only ever add data. Reading and deleting require the secret key that lives nowhere public. Feedback is anonymous unless someone types their name.
13. Maintenance
- Before each workshop: un-pause the Supabase project (free tier sleeps after ~1 week idle). Submissions won't save while paused.
- QR codes: generate with the permanent domain so old printouts keep working.
- Free-tier limit: the Supabase account allows 2 active free projects total; this app shares the
Renal-Reviewproject via its own schema, so it doesn't use a slot. - Backups: download CSVs after each workshop; that's your archive.
14. Recommendations
- Change the admin passcode to something private, and rotate it occasionally.
- Always download CSVs before deleting anything.
- Test the day before: open the registration QR, submit a dummy entry, load it on the Data tab, then delete it.
- Keep it a single file. Resist splitting
index.html— the simplicity is the feature. - If you grow: consider moving the database to an EU region for stricter GDPR, and swapping the service-key reads for a proper Supabase login.
- Roadmap ideas (see
CLAUDE.md): live polls, an organiser dashboard with live averages, WhatsApp follow-ups, attendance certificates.
15. Quick reference
| Thing | Value |
|---|---|
| Live site | vascular-ws.chevahub.com (and vascular-ws.pages.dev) |
| Admin — QR setup | …/?tab=qr&admin=1 |
| Admin — data | …/?tab=data&admin=1 |
| View a past edition's data | add &w=TUH_<date> to the data URL |
| Code repo | github.com/rcheva/Vascular-WS (private) |
| Supabase project | Renal-Review · schema vascular |
| Service-role key | Supabase → Settings → API Keys → Legacy → service_role |
| Full technical spec | CLAUDE.md in the repo |