FocusFlow is built as a layered, offline-first single-page application. The React 19 frontend renders one of three role-specific dashboards —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/piratta/gymApp/llms.txt
Use this file to discover all available pages before exploring further.
CoachDashboard, ClientDashboard, or AdminDashboard — based on the authenticated user’s UserRole. All global application state is owned by a single App.tsx root component and flows down as props. Persistence is handled by a two-tier system: Firebase Firestore is the source of truth for cloud-synced data, while safeStorage (a hardened localStorage wrapper with an in-memory fallback) provides transparent offline resilience. Firebase Auth manages session identity, with anonymous sign-in as a zero-friction fallback and Google OAuth for Workspace integrations.
Architecture Layers
Presentation Layer
React 19 (Vite, ESM). Three dashboards rendered based on
UserRole:
CoachDashboard, ClientDashboard, and AdminDashboard. Routing is
performed inline in App.tsx — no third-party router is used; the
authenticated user’s role field determines which component tree is
mounted.State Management
All global state lives in
App.tsx via useState and useEffect.
Firebase data is loaded at startup via loadAllFromCloud() and written
back reactively via saveToCloud() whenever a state slice changes and
isSyncingCloud is false.Persistence Layer
Cloud: Firebase Firestore under the
coaching_data collection, one
document per SYNC_KEY. Offline: localStorage via safeStorage
with automatic in-memory fallback. Dirty-key tracking ensures no write
is silently lost while offline.Authentication
Firebase Auth with
onAuthStateChanged. If no session is found,
signInAnonymously() is called automatically so Firestore rules are
always satisfied. Google OAuth (signInWithGoogleWorkspace) provides
access tokens for Google Drive and Sheets integrations.Component Structure
FocusFlow’s component tree is rooted atApp.tsx, which mounts the appropriate dashboard after authentication resolves.
Root & Auth
App— global state, Firebase bootstrap, sync engineLoginForm— credential entry, password reset flowNotificationCenter— real-time in-app notification bell
Role Dashboards
CoachDashboard— 8-tab coach workspaceClientDashboard— mobile-first athlete workspaceAdminDashboard— platform management console
Shared Feature Components
Shared components in
components/ (root), used by one or both dashboards:RoutineBuilder— drag-and-drop routine editorInteractiveTraining— live workout session runnerPlicometriaModule— skinfold body-composition calculatorAgendaCalendarView— event scheduling calendarProgressCharts— Recharts metric dashboardsCalendarView— athlete’s workout scheduleCoachClientNotes— coach private notes panelSearchableExerciseDropdown— exercise search widget
Coach Tab Components
Namespaced under
components/coach/:AgendaTab— agenda management tabAthleteCompletedSessionsView— completed session historyAthleteReviewsView— client review managementAthleteStatsView— athlete statistics viewBillingTab— billing and subscription managementExercisesTab— exercise library managementProfileTab— coach profile settingsTemplateHandmadeEditor— custom template editor
components/client/:ClientProgressTab— client-side progress overview
All dashboard components receive their data exclusively via props from
App.tsx. No component fetches from Firestore directly — the data layer is
fully centralized in the root component.Data Flow
The following describes the end-to-end lifecycle of data in FocusFlow, from login to cloud persistence.Authentication
main.tsx bootstraps the app. onAuthStateChanged fires — if no user
session exists, signInAnonymously() resolves the whenAuthReady
promise so that Firestore operations can proceed immediately.Cloud Bootstrap
App.tsx calls loadAllFromCloud(), which fires loadFromCloud() in
parallel for all 16 SYNC_KEYS. Each key is read from Firestore with a
15-second timeout, reconciled with the local localStorage backup, and
the merged result is written back to both stores.State Hydration
All 16 state slices —
users, routines, reviews, messages, etc.
— are populated from the resolved cloud data. isSyncingCloud is set to
false, unblocking reactive save effects.Role-Based Rendering
App.tsx inspects currentUser.role and renders the matching dashboard
(CoachDashboard, ClientDashboard, or AdminDashboard), passing
state slices and action handlers as props.User Actions → Reactive Saves
Every user action (e.g. completing a workout, sending a message) updates
the relevant React state slice. A corresponding
useEffect detects the
change (via reference inequality) and calls saveToCloud(key, data).Offline Write Queue
saveToCloud always writes to localStorage first. If the Firestore
write fails or times out, the key is added to the fit_cloud_dirty_keys
set. The dirty count is polled every 1.5 seconds and displayed as a sync
indicator in the header.Technology Choices
React 19 + Vite
Vite’s ESM-native dev server and optimised production build pipeline
provide near-instant HMR. React 19’s concurrent renderer enables
smooth UI during async cloud bootstrap.
TypeScript
All entities are strongly-typed via
src/types.ts. The TypeScript
compiler catches schema mismatches between Firestore payloads and UI
components at build time, not at runtime.Tailwind CSS 4.0
Tailwind 4’s CSS-variable-based design token system powers FocusFlow’s
four visual themes (Dark Cosmic, Emerald Athletic, Cyberpunk Neon,
Iron Crimson) without shipping separate stylesheet bundles.
Firebase Firestore
Firestore’s schemaless NoSQL model suits FocusFlow’s flexible entity
shapes (e.g., variable skinfold fields per protocol). The single-document-
per-collection pattern keeps queries simple and avoids Firestore
read-cost multiplication.
Recharts
Recharts provides composable, React-native SVG chart primitives for
weight evolution, training volume, body measurements, and compliance
rate visualisations, all driven directly from Firestore-loaded arrays.
Motion
Motion (from
motion/react) powers tab transitions, loading skeletons,
and micro-interactions throughout both dashboards with minimal bundle
overhead over a manual CSS approach.