Skip to content
All work

2025 — 2026· frontend

WhitePenguin

Preschool management for three audiences at once — owners, teachers and parents — including a shared tablet at the front door that strangers use one after another.

Scope

I designed and built the kiosk mode, the admissions pipeline, room activities across all three role surfaces, leads and requests, and the subdomain tenancy that separates one school from another. Colleagues layered UI onto several of those files afterwards, so line-level blame under-credits the logic — but the commits that first introduced each mechanism are mine. One exception worth naming: the offline attendance queue that makes the kiosk survive bad wifi was written later by a colleague. I opened that problem, and someone else solved its hardest half.

Screens

  • The owner's view, for orientation. Open enquiries, waitlist and booked tours are the far end of the admissions pipeline — these counters move because of the two screens that follow.
  • Tours and forms — one of the four admission tabs, all of which are mine. The tabs across the top are the pipeline in order: an enquiry becomes a lead, a lead becomes a booked visit, a visit becomes an admission.
  • Tour slots as a week. The calendar and the list are the same records seen two ways, so a school schedules against what is already booked rather than against its memory.
  • Room activities, which exist three times over — admin, staff and parent. A teacher logs a nap here and the parent reads it from the same record, which is the reason this could not be three separate features.

Problem

A preschool runs on three completely different relationships with the same data. An owner needs billing and compliance; a teacher needs the register and the day's activities; a parent needs to know their child arrived and what they did. Same records, three products. Then there is a fourth surface that is not a product at all — a tablet bolted by the front door, used by a queue of different parents every morning, with no one logged in and school wifi that drops.

Constraints

  • Every school is a separate tenant reached on its own subdomain, and a request has to carry which school it belongs to without the user ever choosing.
  • The kiosk is a shared, unattended, public-facing device. There is no per-user login in the ordinary sense and no assumption that the person in front of it is the same as the last one.
  • School wifi is unreliable and the tablet sits at the door — the worst spot in the building. A missed check-in is a child whose parent is not told they arrived.
  • The records include children's health information and photographs, so who can see what is the product rather than a feature of it.
  • Three role surfaces — admin, staff, parent — plus kiosk, from one codebase and one deployment.

Decisions

The school comes from the host, not from a picker

Tenancy is resolved from the subdomain and carried on every request, so a user never selects which school they are looking at and cannot be in the wrong one by accident. The alternative — a dropdown and an id in application state — makes the tenant a thing the client decides, and the first bug you get is somebody's data appearing under somebody else's letterhead.

Kiosk as a mode inside the app, not a second application

It shares the same routes, services and session plumbing rather than being built and deployed separately. A second app would have meant two release cycles and two copies of the attendance logic drifting apart. The cost is that the kiosk lives inside a build that also contains the admin panel, which is a boundary that then has to be defended deliberately rather than by architecture.

Two layers of identity on one tablet

The device is unlocked once by the school, and each person at it identifies themselves separately — a parent by email and a four-digit PIN or a scanned code, a teacher from the roster. Asking a parent at a doorway to type a password would take longer than the drop-off itself; a device with no identity at all cannot tell you who signed the child in.

Logout flushes before it clears

Ending a kiosk session pushes any pending attendance before wiping the session keys. Clearing first is the obvious order and quietly discards the check-in that was mid-flight — the exact event the whole surface exists to record.

Trade-offs

Gave up

A separate, hardened kiosk application

Bought

One codebase, one release, and attendance logic that cannot drift between two implementations — at the price of a boundary that has to be enforced rather than assumed

Gave up

Passwords at the door

Bought

A four-digit PIN and a queue that moves at drop-off speed. Shorter secrets on a shared device are a real reduction in strength, accepted because the alternative is a system nobody uses.

Gave up

Letting the client pick its tenant

Bought

A school that follows from the address bar, so being in the wrong tenant requires being at the wrong URL rather than making the wrong selection

Results

Live

app.whitepenguin.co

in production

Role surfaces

4

admin, staff, parent, kiosk

Tenancy

subdomain

one school per host

Ratios are given as raw counts rather than percentages. A percentage implies a precision that twenty attempts do not have.

What I got wrong

  • THE KIOSK IS NOT ISOLATED ENOUGH. The device is unlocked with a school admin's session and that session stays live underneath the parent PIN layer, so the tablet by the front door can still reach the admin panel. No server check can fix it — the admin's token is genuinely valid. It needs its own restricted account, a hard idle timeout and a full storage wipe between sessions, and it is the first thing I would change.

Stack

  • Next.js 15
  • React
  • TypeScript
  • TanStack Query
  • Axios
  • Firebase Cloud Messaging
  • Paystack
  • IndexedDB
  • Service Workers