{"id":50695700,"url":"https://github.com/hngprojects/personal-trainer-mobile-rn","last_synced_at":"2026-06-09T06:08:11.292Z","repository":{"id":356621240,"uuid":"1233355356","full_name":"hngprojects/personal-trainer-mobile-rn","owner":"hngprojects","description":"FitCall.me mobile app — React Native","archived":false,"fork":false,"pushed_at":"2026-06-03T15:23:36.000Z","size":21531,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2026-06-03T16:22:48.037Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hngprojects.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-08T21:46:01.000Z","updated_at":"2026-06-03T15:14:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hngprojects/personal-trainer-mobile-rn","commit_stats":null,"previous_names":["hngprojects/personal-trainer-mobile-rn"],"tags_count":0,"template":false,"template_full_name":"hngprojects/react-native-starter","purl":"pkg:github/hngprojects/personal-trainer-mobile-rn","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-mobile-rn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-mobile-rn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-mobile-rn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-mobile-rn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hngprojects","download_url":"https://codeload.github.com/hngprojects/personal-trainer-mobile-rn/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-mobile-rn/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34093840,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T06:08:10.436Z","updated_at":"2026-06-09T06:08:11.285Z","avatar_url":"https://github.com/hngprojects.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Native Starter\n\nProduction-ready Expo starter for HNG teams using **Vertical Slice Architecture**. Clone and ship — the structure scales, the defaults work out of the box.\n\n## Stack\n\n| Layer           | Choice                                            |\n| --------------- | ------------------------------------------------- |\n| Framework       | Expo SDK 55 + Expo Router v4                      |\n| Language        | TypeScript (strict)                               |\n| Package manager | pnpm (hoisted)                                    |\n| Navigation      | File-based routing with guarded route groups      |\n| State           | Zustand v5 (one store per feature slice)          |\n| Data fetching   | TanStack Query v5 + Axios                         |\n| Forms           | React Hook Form v7 + Zod v3                       |\n| Storage         | expo-secure-store (tokens) · AsyncStorage (prefs) |\n| Theming         | Custom ThemeContext · light / dark / system       |\n| Linting         | ESLint (eslint-config-expo flat)                  |\n| Formatting      | Prettier                                          |\n| CI              | GitHub Actions → APK → Appetize + artifact        |\n\n---\n\n## Quick start\n\n```bash\ngit clone \u003crepo-url\u003e\ncd react-native-starter\npnpm install\npnpm start\n```\n\n**Demo credentials** (no backend needed): `jeffery@logickoder.dev` / `Password1`\n\n\u003e **Expo Go caveat (SDK 55):** The Expo Go versions on the App Store and Google Play may lag behind SDK 55 releases. See [expo/expo#43699](https://github.com/expo/expo/issues/43699).\n\u003e\n\u003e - **Android** — run `pnpm start` then choose **\"Open on Android\"** from the dev menu. This downloads the latest Expo Go build directly to the device, bypassing the Play Store version.\n\u003e - **iOS** — use the [Expo Go TestFlight build](https://testflight.apple.com/join/E2NifhGI) (SDK 55 compatible) instead of the App Store version.\n\u003e - **Recommended** — use a [development build](https://docs.expo.dev/develop/development-builds/introduction/) to avoid Expo Go version constraints entirely.\n\n---\n\n## Project structure\n\n```\nsrc/\n├── app/                    # Expo Router routes (thin screen files only)\n│   ├── _layout.tsx         # Root layout — navigation guards live here\n│   ├── (onboarding)/       # First-launch onboarding flow\n│   ├── (auth)/             # Login + register screens\n│   └── (main)/             # Authenticated app (bottom tabs)\n│\n├── features/               # Vertical slices — one folder per domain\n│   ├── auth/               # JWT auth: api, store, hooks, forms, schemas\n│   ├── onboarding/         # 3-slide pager with skip/complete tracking\n│   └── home/               # Main screen with theme toggle + logout\n│\n├── shared/                 # Cross-cutting concerns (no feature deps)\n│   ├── api/                # Axios client + interceptors + TanStack wrappers\n│   ├── components/         # Button, TextInput, FormField, Screen, Typography…\n│   ├── constants/          # Storage keys, env vars\n│   ├── hooks/              # useAppReady, useColorScheme\n│   ├── storage/            # Typed AsyncStorage + SecureStore wrappers\n│   ├── store/              # createStore factory (Zustand)\n│   └── theme/              # Colors, typography, spacing, ThemeContext\n│\n└── providers/\n    └── AppProviders.tsx    # QueryClientProvider + ThemeProvider composition\n```\n\n---\n\n## Navigation flow\n\n```\nApp launch → useAppReady() hydrates stores from persistent storage\n                │\n                ├─ onboarding not complete  →  (onboarding) group\n                ├─ onboarding done, not logged in  →  (auth) group\n                └─ logged in  →  (main) group\n```\n\nGuards are reactive — completing onboarding or logging in causes the root layout to re-evaluate and navigate automatically. No `router.replace()` calls in screen components.\n\n---\n\n## Adding a new feature slice\n\n1. **Create the folder**\n\n   ```\n   src/features/my-feature/\n   ├── api/          my-feature.api.ts · my-feature.types.ts\n   ├── components/   MyFeatureScreen.tsx\n   ├── hooks/        useMyFeature.ts\n   ├── store/        my-feature.store.ts\n   ├── schemas/      my-feature.schemas.ts   (if forms needed)\n   └── index.ts      (barrel export — public API)\n   ```\n\n2. **Use shared patterns**\n\n   ```ts\n   // Store\n   import { createStore } from '@/shared/store/factory';\n   export const useMyFeatureStore = createStore\u003cState \u0026 Actions\u003e((set) =\u003e ({ ... }));\n\n   // Data fetching\n   import { useApiQuery, useApiMutation } from '@/shared/api/hooks';\n   export function useMyData() {\n     return useApiQuery(['my-data'], myFeatureApi.getData);\n   }\n\n   // Forms\n   import { FormField } from '@/shared/components';\n   \u003cFormField control={control} name=\"title\" label=\"Title\" /\u003e\n   ```\n\n3. **Add a route** (thin file — no logic)\n\n   ```ts\n   // src/app/(main)/my-feature.tsx\n   import { MyFeatureScreen } from '@/features/my-feature';\n   export default MyFeatureScreen;\n   ```\n\n4. **Add a tab** (optional)\n\n   ```ts\n   // src/app/(main)/_layout.tsx — add inside \u003cTabs\u003e\n   \u003cTabs.Screen name=\"my-feature\" options={{ title: 'My Feature' }} /\u003e\n   ```\n\n**Rule:** import only from `@/features/my-feature` (the barrel), never from deep paths inside another feature.\n\n---\n\n## Theming\n\n```ts\nconst { colors, spacing, typography, isDark, mode, setMode } = useTheme();\n\nconst styles = StyleSheet.create({\n  container: { backgroundColor: colors.background, padding: spacing.md },\n  title: { ...typography.h2, color: colors.text },\n});\n```\n\nToggle modes: `'system'` (default) · `'light'` · `'dark'`. Preference persisted to AsyncStorage automatically.\n\n---\n\n## Auth\n\nThe auth slice ships with a dummy implementation — no backend required to run the full flow. Replace `authApi.login` / `authApi.register` / `authApi.refreshTokens` in [src/features/auth/api/auth.api.ts](src/features/auth/api/auth.api.ts) with real Axios calls.\n\nThe Axios client auto-attaches `Authorization: Bearer \u003ctoken\u003e` to every request and handles 401 → token refresh → retry transparently via interceptors in [src/shared/api/client.ts](src/shared/api/client.ts).\n\n---\n\n## Scripts\n\n| Script              | What it does                       |\n| ------------------- | ---------------------------------- |\n| `pnpm start`        | Start Expo dev server              |\n| `pnpm android`      | Open on Android emulator           |\n| `pnpm ios`          | Open on iOS simulator              |\n| `pnpm test`         | Run Jest tests                     |\n| `pnpm test:watch`   | Jest in watch mode                 |\n| `pnpm typecheck`    | TypeScript compile check (no emit) |\n| `pnpm lint`         | ESLint with zero-warning policy    |\n| `pnpm lint:fix`     | ESLint auto-fix                    |\n| `pnpm format`       | Prettier format all files          |\n| `pnpm format:check` | Prettier check (CI-safe)           |\n\n---\n\n## CI — Android build\n\nPush to `main` (or open a PR) → GitHub Actions:\n\n1. Installs deps with pnpm (cached)\n2. Runs `expo prebuild --platform android`\n3. Builds debug APK with Gradle (Gradle cache enabled)\n4. Uploads APK as a workflow artifact (7-day retention)\n5. Uploads to Appetize.io and posts a preview link in the job summary\n\n**Required secret:** `APPETIZE_API_TOKEN` (from your [Appetize dashboard](https://appetize.io)). Appetize step is skipped gracefully if the secret is missing.\n\n---\n\n## Naming conventions\n\n| Entity           | Pattern                        | Example                       |\n| ---------------- | ------------------------------ | ----------------------------- |\n| Store hook       | `use[Feature]Store`            | `useProfileStore`             |\n| Feature hook     | `use[Action]`                  | `useUpdateProfile`            |\n| Screen component | `[Feature]Screen`              | `ProfileScreen`               |\n| API function     | camelCase verb                 | `updateProfile()`             |\n| Zod schema       | `[action]Schema`               | `updateProfileSchema`         |\n| Storage key      | `STORAGE_KEYS.[FEATURE]_[KEY]` | `STORAGE_KEYS.PROFILE_AVATAR` |\n\n---\n\nMade with ❤️ by [Jeffery Orazulike](https://logickoder.dev) and the HNG projects team.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhngprojects%2Fpersonal-trainer-mobile-rn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhngprojects%2Fpersonal-trainer-mobile-rn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhngprojects%2Fpersonal-trainer-mobile-rn/lists"}