Skip to main content

Parent App Guide

This guide covers the navigation, auth, and shell of the parent app — feature behavior lives in Feature Flows.

App overview

The parent app is a React + Vite + Capacitor build (dev port 5176) used by parents and guardians to follow their children's school life: results, attendance, diary, projects (homework, re-skinned), PTM, notice board, class tests, syllabus, analytics, and chat with teachers. Like the other student-facing apps it ships as Android, iOS, and web from the same source and renders mobile-shaped (max-w-xl) on every viewport.

What sets the parent app apart is the multi-child switcher — one parent account is linked to one or more linkedStudents, and the home screen exposes a horizontal pill list to switch context between them. Most data on screens like Attendance, Results, Diary, Projects, and Class Tests is scoped to the currently-selected child via a studentProfileId carried in route state.

Two zones: the public auth shell (/login-method, /login, /forgot-password, /set-password, /reset-password) and the authenticated app (everything else). Anything unmatched redirects to /login-method.

Bottom nav (AppLayout)

Four tabs, no FAB. Used on Home, Results, Messages, and Progress (analytics):

TabPathNotes
Home/dashboardThe HomePage with the multi-child switcher
Results/resultsScoped to selected child
Messages/messagesChat threads — unread badge from chat store
Progress/analyticsTrends, attendance %, score charts

Active state matches paths exactly except Messages, which prefix-matches so /messages/:id keeps the tab highlighted.

Authenticated routes

/dashboard, /results, /diary, /projects, /ptm, /attendance, /notice-board, /class-tests, /analytics, /notifications, /buy-coins, /syllabus (with nested /:subjectId and /:subjectId/chapters/:chapterId), /messages, /messages/:id. All wrapped in ProtectedRoute.

Multi-child switcher

The HomePage (/dashboard) is where the parent picks which child they're looking at. The selected child's studentProfileId is then passed as location.state when navigating to scoped pages — so feature pages read the child id from route state, not from a global "current child" store.

If linkedStudents is empty, the switcher is hidden and the parent sees a degraded home view.

Dashboard landing (/dashboard)

Sticky header with logo + greeting + parent first name, a notifications bell with unread badge (via getNotifications({ limit: 1 })), and a Logout button (clears tokens, navigates to /login-method). Below: child switcher → quick action cards (Results, Diary, Projects, PTM, etc.) → school-services cards (Attendance, Notice Board, Syllabus, Class Tests, Buy Coins, Analytics).

For the active child the home page also shows a 30-day attendance percentage, total class tests, and due-tasks count fetched in parallel from the relevant services.

Authentication

Entry: /login-method

The default unauthenticated screen — full-screen gradient with the Scholiphi logo and a single CTA: Log in. If the user store rehydrates with an existing user or access token, this page redirects to /dashboard after a 50ms grace (waits for Capacitor Preferences async hydration so logged-in users don't flash the login CTA on cold start).

Login (/login)

Email-or-phone + password form. Unlike the teacher and student apps which require an email, the parent app's emailOrPhone field accepts either — useful in markets where parents don't have a school-issued email.

Network-error state shows the actual API base URL so QA can spot misconfigured builds. Auth errors render as a red alert above the submit button with a generic "Email or password incorrect" message.

Forgot password (/forgot-password)

Email-only form. On submit, fires the reset request and swaps to a success state — even on API failure it shows success (intentional, to avoid leaking which emails are registered). Note: parent app does not ship a separate Maintenance / NotFound / ServerError / Blocked page — those code-paths fall back to the catch-all Navigate to /login-method.

Set / reset password

  • /set-password — used for first-time password setup via an emailed link with ?token=.... Enforces password ≥ 8 chars, ≥ 1 number, ≥ 1 special character, plus confirm match.
  • /reset-password — same shape, used after the forgot-password email link.

No registration

Parents are not self-signup users. Their accounts are provisioned by the school admin and linked to one or more student profiles; the parent's first interaction is the password-set email link.

Profile & settings

There is no dedicated /profile route in the current build. Instead, profile-adjacent affordances live on the home page:

  • LogoutLogOut icon button in the home header, clears the user store and navigates to /login-method.
  • Profile data — surfaced inline (greeting + parent name; school name on the school info banner; linked children in the switcher).
  • Avatar — child avatars come from each linkedStudent.profilePicture; the parent's own photo isn't surfaced anywhere user-facing.

Error & utility pages

The parent app does not currently ship dedicated maintenance / 404 / 500 / blocked pages — unmatched routes hard-redirect to /login-method. Native deep-linking errors (e.g. payment cancel from the external checkout browser) surface as Sonner toasts via the appUrlOpen listener in App.tsx.

ScenarioBehavior
Unknown route<Navigate to="/login-method" replace />
Payment success deep-linkToast: Payment successful. {N} coins added to your child's wallet.
Payment failed deep-linkToast: "Payment failed. Please try again."
Payment cancelledToast: "Payment cancelled"
401 on a protected routeProtectedRoute redirects to the login flow

If a global maintenance state needs to be shown, the parent app currently has no UI for it — see edge cases.

Edge cases & things to test

  • Single-child parent: linkedStudents has exactly one entry — does the switcher hide or render as a single (selected) pill? Verify the selected child still drives child-scoped fetches.
  • No-children parent: linkedStudents is empty — home should not crash; quick actions and school services should be either hidden or render an empty/CTA state, not throw.
  • Multi-child different schools: a parent linked to children at two different schools — does the school info banner update when switching? Are child-scoped fetches scoped correctly across schools?
  • Switcher → deep page → back: select child A, navigate to /results, hit back, select child B, navigate to /results again — does the page reload with B's data, or does cached state from A leak?
  • Push deep-link with multi-child: a "homework assigned" push for child B arrives — tapping it should land on the right child's project view, not the currently-selected child's.
  • Email-or-phone login: phone with country code, phone without country code, email — confirm the API accepts each variant; client-side validation only checks "min length 1".
  • Forgot-password obscure email: submit a non-existent email — UI shows success regardless. Confirm no real email is sent and no error leaks.
  • Set-password with expired token: open /set-password?token=expired — expect a clean error state, not a silent success.
  • Cold start hydration: kill app, reopen — /login-method must not flash before the user-store rehydrates and redirects logged-in users to /dashboard.
  • External checkout return: complete a coin purchase, then come back via deep-link — toast must fire exactly once even if appUrlOpen is invoked twice (the lastHandledUrl guard).
  • Logout from home header: confirm tokens cleared, socket disconnected, chat store reset, push token deregistered.
  • Bottom nav on Messages thread: open /messages/:id — the Messages tab must stay highlighted (prefix match).
  • Maintenance state with no UI: simulate the global maintenance flag — what does the parent see? (No dedicated screen exists; this is a known gap.)