Skip to main content
Back to Projects
Görev Kahramanı logo

Görev Kahramanı

Web Application

In short

Before
Parents tracked habits with paper charts or apps made for adults; because a child could approve their own reward, the system lost its credibility.
After
The child completes tasks on their own screen, while approval and rewards stay with the parent alone; the app can be installed on a phone and can also open offline.
What changed
Even if the same task is approved twice, the reward is given once; switching to a child profile is checked on the server, and changing the device clock does not earn a streak.
Görev Kahramanı

A parent-controlled web application that supports daily habits of children aged 4–12 through gamification. Kids complete tasks, earn stars and XP, level up their characters, and collect badges. Parents manage tasks, handle approval workflows, and track progress.

Technologies Used

  • Next.js 16
  • TypeScript
  • Supabase
  • Zustand
  • Zod
  • Vitest
  • Framer Motion

Key Features

  • Kid mode: task flow, stars/XP, leveling, and character evolution
  • Parent panel: task management, approve/reject with coaching notes
  • Reward catalog management and reward request system
  • PWA support — offline usage and home screen installation
  • No lost points when updates happen at the same time, plus PIN security

Development Time

2 months

Problem

To track their children's daily habits, parents either use a paper chart or a task app designed for adults. Both break down at the same point: the child can approve their own reward, so the system loses its credibility. The real problem the app had to solve was not the interface — it was the separation of authority.

Users

Two distinct users in a single product. A child aged 4–12: limited reading and writing, touch targets need to be large, reward feedback has to appear instantly. A parent: defines tasks, approves completed ones or sends them back with a coaching note, follows weekly progress — and wants to be certain the child cannot interfere with that flow.

My Role

Sole developer. Product scope, data model, Supabase schema and RLS policies, the Server Action layer, two separate interfaces (child/parent), PWA setup, the KVKK cookie flow, and Vercel deployment.

Architecture

A single Next.js 16 App Router application. The read path is Server Component → lib/dal/*; the write path is Server Action → Zod validation → Supabase. Authorization is three-layered: session checks in proxy.ts, Postgres RLS policies, and an additional guard inside the Server Action. The child context (activeChild) is kept in Zustand; server data is never copied into the store. Streak reconciliation runs as a scheduled job via /api/cron/reconcile-streaks. The system's second client lives in a separate repository: an iOS application written in SwiftUI. That client carries no backend of its own — it connects to the same Supabase schema, so the authorisation rules and RLS policies are identical to the web. Its maturity today is at skeleton level: parent sign-in, child selection and the flow of the panel screens are in place, and the Supabase client configuration sits as an optional loader — without configuration the app does not crash, it runs unconnected and the screens fill with sample data. So the architectural decision is made and verified; the data binding work is not finished.

  1. Web applicationNext.js 16 App Router · PWA
  2. Zustand · activeChildclient state only · server data never copied in
  3. proxy.tssession check
  4. Read pathServer Component → lib/dal/*
  5. Write pathServer Action → Zod validation → guard
  6. Supabase · PostgresRLS policies · verify_child_pin RPC · compare-and-set update
  7. /api/cron/reconcile-streaksscheduled job · streak reconciliation
  8. iOS client · SwiftUIseparate repo · no backend of its own · same Supabase schema · same RLS policies
  9. Three-layer authorization: proxy.ts · Postgres RLS · Server Action guard
A single Next.js application separates the read and write paths, authorization is checked in three layers — proxy.ts, Postgres RLS and a Server Action guard — while the SwiftUI client connects to the same Supabase schema without a backend of its own and streaks are reconciled by a scheduled job.

Key Decisions

  1. CAS-style concurrency protection on star and XP updates

    If a parent approved the same task in two tabs, or a child tapped the button twice, the reward could be written twice. The update is now performed against the expected current value; the second write is rejected.

    Trade-offOn a collision the user has to be shown a "try again" message; there is no silent merge. Compared to a plain UPDATE, that means more code and more failure paths.

  2. PIN verification in a database function, not on the client

    If the child profile PIN reached the browser, a sibling or the child themselves could easily get past it. The verify_child_pin RPC performs the hash comparison on the server and cuts off brute-force attempts by counting consecutive failures.

    Trade-offEvery profile switch costs a network round trip, and profiles cannot be switched while offline.

  3. The client does not compute the streak — a cron reconciles it

    Device clocks and time zones are not trustworthy; a child could earn a streak by changing the device date. The streak is recalculated server-side by a scheduled job.

    Trade-offThe streak can update late, only once the cron has run; the user does not see the change immediately.

Stack Rationale

Supabase was chosen so that the critical part of authorization — that a child can only see their own tasks — could be expressed as a database policy rather than in application code: even if the application layer has a bug, no data leaks. Zod serves as a single schema, used both on Server Action input and on the form side. Zustand holds only short-lived client state; the source of truth for server data is always the server.

Security

Two independent authorization layers: Postgres RLS and an in-Server-Action guard — if one is bypassed, the other stops it. The PIN is hashed and brute-force counted. Cloudflare Turnstile is active on registration, login, and the child→parent switch. The parent action lock is temporarily released through password verification. Security headers and a Report-Only CSP are defined in next.config.ts. Google Analytics loads only after cookie consent is given and only in production (KVKK).

Performance

Installable as a PWA, with a service worker and an /offline fallback page. 26 test files with Vitest, weighted toward the concurrency behaviour of the approval and reward flows. Lighthouse mobile (my own measurement): 100/100 — September 2026. In the same measurement the median LCP is 1666 ms, total blocking time 1 ms and CLS 0.

Results

Active development
2026-03 → 2026-07 · 39 commits
Concurrency guarantee
A double approval cannot produce a double reward
Test files
26
TypeScript source files
139
Platform
Web (PWA) + native iOS client

What I Would Do Differently

Today I would build the concurrency guarantee as a database constraint rather than with CAS in the application layer. On Ustura I solved appointment overlap with a Postgres exclusion constraint; that approach requires less code and moves the guarantee out of the application and into the database itself. Second, instead of leaving the CSP in Report-Only I would switch it to enforce mode before going live — collecting reports does not stop a violation.

Let's find where to start

Tell me what you want to build; I'll tell you up front how long it takes and what the sensible first step is.