A sports activity tracking web app for logging workouts, comparing performance across time periods, and visualizing trends with interactive charts. Built with a modern React stack, Azure Static Web Apps, and Firebase for authentication and data storage.
- Dashboard — Quick activity logging with autocomplete, stat cards (total, last 7/30 days, last activity), recent activity feed with clickable navigation, and first-time onboarding with category picker
- Activity List — Full activity history with search, date range filtering, pagination, month grouping, inline editing via dialogs, and bulk operations (rename, reassign category, delete by category)
- Charts — Stacked bar charts and multi-ring pie charts for activity distribution, with group-by-category toggle and date range filtering
- Compare — Side-by-side comparison of activity periods (month vs month, year vs year) with trend line charts and metric cards (total, most popular, most active day/month)
- Settings — Category management (add/edit/delete with activity reassignment), activity name management (rename, assign to category), and chart display preferences
- Export / Import — Full data export to JSON and import from JSON for backup and migration
- PWA — Installable as a Progressive Web App with offline support via service worker
- Dark mode — Dark/light theme toggle, persisted in user preferences
- React 19
- TypeScript
- React Router v7 with SPA in framework mode
- TanStack React Query for server state management, caching, and optimistic updates
- TanStack Form for form state and validation
- Tailwind CSS v4 with
@tailwindcss/viteplugin and CSS-first configuration - shadcn/ui components built on Radix UI primitives
- Chart.js with
react-chartjs-2for data visualization - Vite as build tool with HMR, TypeScript checker, and PWA plugin
- Storybook 10 with play functions for component and interaction testing
- Lucide React for icons, Sonner for toast notifications, cmdk for command palette, date-fns for date utilities
- Azure Functions (Node.js 22) — serverless API with 19 functions covering CRUD for activities, categories, preferences, bulk operations (rename, reassign, delete by category), and data export/import
- Firebase Admin SDK for ID token verification and Realtime Database access
- Rate limiting middleware (per-user, per-function)
- Firebase Auth for client-side authentication (Google sign-in provider)
- Firebase Realtime Database as the primary data store
- Azure Static Web Apps for hosting with integrated serverless API routing
bun install # Install dependencies
bun run dev # Start frontend (:3000) and API (:7071)The app is split into two workspaces (/client and /api) managed as a bun monorepo.
- In local development, Vite proxies
/api/*requests from the frontend (port 3000) to the Azure Functions backend (port 7071) - In production, Azure Static Web Apps handles the routing natively — the client and API are deployed together, and
/api/*requests are routed to the Functions backend automatically without any proxy
- Client: Firebase Auth handles sign-in (Google provider). The auth state is managed via
AuthContextand a route loader redirects unauthenticated users to/login - API: Each function extracts the Firebase ID token from the
x-auth-tokenheader, verifies it via the Admin SDK, and uses the resultinguserIdto scope all database operations
The data model keeps the client simple by pushing business logic to the API:
- Activities are stored as flat records keyed by Firebase push IDs, each containing
name,date, and optional fields (description,intensity,timeSpent) - Categories each contain an
activityNamesarray — the single source of truth for which activities belong to which category, plus metadata (name,active,description) - Enrichment on read: The API enriches activities with computed
categoryIdandactivefields based on the category lookup before returning them to the client. This means the client never needs to perform the category ↔ activity join — it receives ready-to-render data - User preferences (e.g., theme, chart grouping) are stored per-user and merged with defaults on read
- Export/Import: The
exportDataendpoint returns the complete user dataset (activities, categories, preferences) as a single JSON payload;importDatareplaces it atomically
- Server state: TanStack React Query manages all API data with automatic caching, background refetching, and cache invalidation on mutations
- Client state: React Router loaders pre-fetch data, client actions handle mutations (edit, delete, bulk ops) via
useFetcher - UI state: Component-local state with
useState; theme preference synced to<html>class for Tailwind dark mode
The frontend uses Storybook's test addon as the primary testing strategy — stories with play functions serve as both living documentation and executable tests. This approach provides component-level testing that runs in a real browser, striking the right balance between the isolation of unit tests and the confidence of end-to-end tests.
- Stories are run as Vitest tests via
@storybook/addon-vitest, executing in a real Chromium browser through@vitest/browser-playwright - MSW (Mock Service Worker) is used extensively to mock all API responses at the network level, providing realistic test data without a backend. MSW handlers cover all 19 API endpoints with representative datasets
- API tests use Vitest with an in-memory Firebase mock for fast, isolated database operation testing
cd client && bun run test # Storybook play function tests via Vitest + Playwright
cd api && bun run test # API unit tests (Vitest)
cd client && bun run storybook # Start Storybook on :6006 for interactive development/client React frontend (Vite + React Router)
/src/app Routes, root layout, global CSS
/src/components Reusable components (ui/, forms/, navigation/, etc.)
/src/pages Page components (Welcome, Charts, Compare, Settings)
/src/data React Query hooks, query options, types
/src/auth Firebase auth context and service
/src/mocks MSW handlers and Storybook decorators
/src/utils Helpers (colors, icons, cn)
/api Azure Functions backend
/functions Individual function endpoints (19 functions)
/database Firebase DB abstraction layer with enrichment logic
/utils Shared types, validators, rate limiter
/scripts Migration and utility scripts
bun run --filter '*' build # Build both frontend and APIbun run lint # ESLint with --max-warnings=0Prettier and ESLint run automatically on staged files via lint-staged + Husky pre-commit hook.
The app is deployed on Azure Static Web Apps with CI/CD via GitHub Actions (.github/workflows/azure-static-web-apps.yml).
- Install —
bun install - Firebase config — Decodes
API_FIREBASE_CONFIGsecret intoapi/firebase/firebaseConfig.json - Typecheck —
bun run --filter api typecheck - Build —
bun run --filter '*' build(client →client/build/client, API →api/dist) - Deploy — Uses
@azure/static-web-apps-clito deploy:- Push to
main→ production environment - Pull requests → preview environment (auto-created, auto-destroyed on PR close)
- Push to
- Static content served from
client/build/client - API functions served from
api/dist(Node.js 22) /api/*routes automatically routed to the Functions backend by Azure SWA — no proxy or routing config needed in production