Multi-step verification wizard
A four-step public form collects name, contact details, room code, and a proof photo. Each browser attempt carries an idempotency key so a dropped response can be retried without creating a duplicate application.
Resident verification application
Verifies new residents of a Frankfurt student residence before they receive the WhatsApp-group invite, replacing an Apps Script workflow no successor could maintain.
No durable email delivery, an occupancy model that could silently evict legitimate residents, and the WhatsApp invite link hardcoded in the script. Every change needed the original developer.
The public landing page opens with the residence name and the verification proposition, beside a schematic view of the campus houses.
Four steps set the expectation before anyone starts: contact details, the room code, the nameplate photo, and a review by a real person. Nothing is published and no ID document is ever requested.
Typing a room code resolves it against the unit inventory in real time. C033 resolves to a confirmed ground-floor room; the 3D picker button becomes available once a code is entered.
The optional 3D picker opens at campus level. Nine houses are drawn and eight are open for registration; Haus J joins them with 149 rooms in October 2026. An applicant who does not know their code navigates down to house, floor and room.
After entering a code, the picker opens at the WG and room level, confirming the resolved unit in context. The 3D view is lazy-loaded so a failed WebGL environment never blocks registration.
Step 3 asks for a photo of the internal intercom nameplate directly outside the applicant's WG or room. A privacy note states how long the photo is kept and when it is deleted.
A four-step public form collects name, contact details, room code, and a proof photo. Each browser attempt carries an idempotency key so a dropped response can be retried without creating a duplicate application.
Every outbound message is written to a D1 table before it is sent. A scheduled Worker claims, delivers, and retries each message independently, so a failed send is observable and correctable.
Approving an applicant compares the active resident count against the unit's configured capacity. Displacement fires only when the count would be exceeded, and only after an explicit confirmation naming who is affected.
An optional Three.js campus schematic lets applicants navigate from house to floor to room. The picker is dynamically imported so WebGL failure on an older device never prevents registration.
Houses, units, admin roles, and the WhatsApp invite link are all editable in the admin UI. A rotated invite link or a new building is a settings change that any super admin can make without touching the database or the codebase.
Problems we hit
The v1 system normalised and zero-padded room codes, treating C33 and C033 as the same unit. C33 is the 3rd WG on floor 3; C033 is a distinct ground-floor room. Padding them together silently corrupted occupancy records.
Codes are stored and matched verbatim. An exact lookup against the units table runs first; shape-based parsing fires only on a miss. The difference between C33 and C033 is preserved at every stage.
In the v1 system, approving a new applicant moved out every existing resident matching the room string. A shared family WG would silently evict a spouse alongside the departing student.
Approval compares active resident count against the unit's configured capacity. Displacement fires only when the count would be exceeded, and only after a confirmation screen naming exactly who is affected.
The v1 authorisation check treated an unknown caller and an empty verifier list the same way: as permitted. Whether someone could approve an application depended on the state of a configuration sheet rather than on an explicit grant.
Authorization fails closed at both layers: Cloudflare Access controls entry to the admin application, and the admins table controls what an authenticated person may do. An empty configuration grants access to nobody.
WhatsApp group invite links are routinely rotated after spam or a group rebuild. In v1 the link was embedded in the script, so rotating it required a code edit and redeployment by the original developer. Every approval between the rotation and the fix sent a dead link.
The WhatsApp invite URL is stored in a settings table and editable in the admin UI. A rotated link is a two-field update that any super admin can make without touching the database or the codebase.
Decisions we made
Never pad or normalise room digits.
`C33` and `C033` identify different places: `C33` is the 3rd WG on floor 3 and `C033` is a distinct ground-floor room. Padding or case-folding them together silently corrupts occupancy history. The unit table is the authority on whether a code exists; the parser only handles input that produces no exact match.
Cloudflare Pages and D1 were chosen over Supabase because Supabase's free tier pauses a project after roughly seven days without traffic.
FDH usage is seasonal, heavy at move-in and near-zero over summer, so every September the first admin would find a paused database. Cloudflare D1 never sleeps, and Time Travel gives point-in-time restore automatically, alongside a daily SQL export to R2 for portability.
The campus schematic is dynamically imported and always optional.
A five-year-old Android or a browser with WebGL disabled must never block registration. Typing a room code remains the primary and fully supported path; the picker is an enhancement for applicants who do not yet know their code.
Contact
For professional opportunities, collaborations, or selected freelance work.