Step contest
Logging a day should take two taps and a number. Behind that simple screen is a role system, a live leaderboard derived from raw entries, and a privacy model that the database enforces rather than the UI.
- Type
- Personal
- Status
- In use
- When
- 2026
- Role
- Design and engineering
- Next.js 16
- TypeScript
- Firebase Auth
- Firestore
- Cloud Storage
- Cloud Functions
- Netlify
- PWA
Decisions worth a look
- Emails are never visible to other participants. Leaderboard data is denormalised onto participant documents, and user documents are readable only by their owner and site admins.
- Private evidence is split into its own collection, and Storage rules check Firestore with a cross-service lookup, so even a leaked image URL is unreadable.
- Entries lock at midnight on the last day in the contest's own time zone, compared against server time, so a device with the wrong clock cannot buy extra days.
- Lifetime totals use an atomic increment, so two phones logging at once cannot overwrite each other.
- One hourly scheduled function sends evening reminders at 7pm in each contest's time zone and prunes dead push tokens as it goes.
Roles
A person can hold any combination of these across any number of contests.
| Role | Scope | Granted by |
|---|---|---|
| Site admin | Whole instance | Another site admin (the first is set by hand) |
| Contest admin | One contest | The contest creator, or another contest admin |
| Participant | One contest | Joining with an invite code |
The leaderboard
The leaderboard is derived live from entry documents with Firestore snapshots, so it updates the moment anyone logs, edits or deletes. Alongside the ranking there is a cumulative race chart, streaks, the gap to the person above you, and highlights such as longest streak and biggest single day.
Each highlight goes to a different person on purpose, so the top spot is not the only thing worth winning.
Privacy, enforced by the rules files
Two promises are kept by Firestore and Storage security rules, not by hiding buttons.
- Each entry is split into a public part (date and step count, which the leaderboard needs) and an evidence part (note and screenshot).
- Whether evidence is public is chosen when a contest is created and cannot be changed afterwards, because people upload screenshots on that promise.
- Edits append to a changelog every member can read, including corrections made by admins after the contest closes.
Cost
For a group of friends it runs at no monthly cost. Screenshots are downscaled to 1600px and re-encoded as JPEG in the browser before upload, which keeps each one under roughly 300 KB and the whole project inside the free tiers.