|
1 | | -# shadcn/ui monorepo template |
| 1 | +<div align="center"> |
2 | 2 |
|
3 | | -This is a TanStack Start monorepo template with shadcn/ui. |
| 3 | +# Qibla |
4 | 4 |
|
5 | | -## Adding components |
| 5 | +**Find your next *salah*, nearby.** |
6 | 6 |
|
7 | | -To add components to your app, run the following command at the root of your `web` app: |
| 7 | +A production-grade mosque finder for iOS, Android, and the web — built as a Bun + Turborepo monorepo with Expo, TanStack Start, oRPC, Drizzle, and Better Auth. |
| 8 | + |
| 9 | +</div> |
| 10 | + |
| 11 | +<!-- |
| 12 | + v2 additions planned for this hero section: |
| 13 | + - Logo / banner (SVG) |
| 14 | + - Badges: build status, license, Expo SDK, Node, Bun |
| 15 | + - App Store / Play Store / TestFlight links when published |
| 16 | +--> |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## About |
| 21 | + |
| 22 | +Qibla helps working professionals and travellers find a nearby mosque, see accurate prayer times, and get notified before each *salah*. Open the app, see mosques around you on a map, tap a pin for times + directions, or use the live compass to face the *qibla*. An admin panel on the web lets operators curate listings, moderate reviews, and manage mosque events. |
| 23 | + |
| 24 | +Launch market: **Dhaka, Bangladesh**. The architecture works anywhere — prayer times come from the AlAdhan API and mosques are stored in Postgres. |
| 25 | + |
| 26 | +## Features |
| 27 | + |
| 28 | +- **Map-first discovery** — full-bleed Google Maps view with bottom-sheet detail, filters, and directions deep-link |
| 29 | +- **Prayer times** — AlAdhan-backed, cached server-side, with next-prayer pill always visible |
| 30 | +- **Qibla compass** — traditional rotating-dial UX with magnetometer smoothing and calibration overlay |
| 31 | +- **Saved mosques** — optimistic heart toggle with a dedicated grid view |
| 32 | +- **Reviews** — write, read, and moderate; ratings recompute server-side |
| 33 | +- **Search + filters** — recent searches (SecureStore), popular-nearby sort, 4-way filters (open now / women / parking / jummah) |
| 34 | +- **Prayer reminders** — daily local notifications per prayer, auto-rescheduled on open |
| 35 | +- **Admin panel** — mosques CRUD, review moderation, users list, dashboard stats |
| 36 | +- **Email + password auth** — Better Auth with an admin role plugin and Expo server plugin for native clients |
| 37 | + |
| 38 | +<!-- |
| 39 | + v2 additions planned for this section: |
| 40 | + - Animated GIF of the compass aligning to the qibla |
| 41 | + - Screenshot grid: map, mosque detail, prayer times, saved, admin dashboard |
| 42 | + - Short demo video link (YouTube / Loom) |
| 43 | +--> |
| 44 | + |
| 45 | +## Tech stack |
| 46 | + |
| 47 | +### Mobile (`apps/mobile`) |
| 48 | + |
| 49 | +- **Expo SDK 54** + **Expo Router** (file-based routing) |
| 50 | +- **React Native 0.81** on React 19 |
| 51 | +- **NativeWind v4** (Tailwind CSS for React Native) |
| 52 | +- **react-native-maps** (Google Maps on both platforms) |
| 53 | +- **@gorhom/bottom-sheet**, **expo-sensors**, **expo-location**, **expo-notifications**, **expo-secure-store** |
| 54 | +- **Better Auth** via `@better-auth/expo` + SecureStore session |
| 55 | +- **TanStack Query** (server state) + **Zustand** (UI state) |
| 56 | +- **oRPC** typed client |
| 57 | + |
| 58 | +### Admin + backend (`apps/admin`) |
| 59 | + |
| 60 | +- **TanStack Start** on **Cloudflare Workers** (Vite + server functions) |
| 61 | +- **oRPC** router (same router consumed by the mobile app over HTTP) |
| 62 | +- **Drizzle ORM** on **Neon Postgres** via `neon-http` |
| 63 | +- **Better Auth** + admin plugin (`admin` | `user` roles) |
| 64 | +- **shadcn/ui** components (radix-vega preset) |
| 65 | +- **TanStack Router**, **TanStack Table**, **TanStack Form** |
| 66 | + |
| 67 | +### Monorepo tooling |
| 68 | + |
| 69 | +- **Bun 1.3+** workspaces with `linker = "hoisted"` (required for Expo's Babel resolver) |
| 70 | +- **Turborepo** for task orchestration + caching |
| 71 | +- **Biome** for lint + format |
| 72 | +- **TypeScript 5.9** end-to-end |
| 73 | + |
| 74 | +## Project structure |
| 75 | + |
| 76 | +``` |
| 77 | +qibla/ |
| 78 | +├── apps/ |
| 79 | +│ ├── admin/ # TanStack Start on Cloudflare Workers — admin UI + oRPC backend |
| 80 | +│ └── mobile/ # Expo app (iOS + Android) |
| 81 | +│ └── features/ # Co-located feature modules: components / hooks / lib / index.ts |
| 82 | +├── packages/ |
| 83 | +│ ├── api/ # oRPC router (public / authed / admin procedures) |
| 84 | +│ ├── api-client/ # createQiblaClient({ baseURL }) — typed RouterClient |
| 85 | +│ ├── auth/ # Better Auth config + Drizzle adapter + admin plugin |
| 86 | +│ ├── db/ # Drizzle schema, Neon-HTTP client, Dhaka fixtures seed |
| 87 | +│ └── ui/ # shadcn components used by the admin app |
| 88 | +├── prd/ # Product docs (overview, architecture, scope, progress) |
| 89 | +├── biome.json |
| 90 | +├── turbo.json |
| 91 | +└── package.json |
| 92 | +``` |
| 93 | + |
| 94 | +## Prerequisites |
| 95 | + |
| 96 | +| Requirement | Version | Notes | |
| 97 | +|---|---|---| |
| 98 | +| **Node.js** | `>= 24` | Enforced via `engines` field | |
| 99 | +| **Bun** | `>= 1.3` | Package manager + workspace runner | |
| 100 | +| **Neon account** | — | Free tier works; needs a Postgres database | |
| 101 | +| **Google Maps API key** | — | Required for Android; iOS optional (falls back to Apple Maps) | |
| 102 | +| **Cloudflare account** | — | Only needed for deploying the admin app + R2 uploads | |
| 103 | +| **EAS account** | — | Only needed for mobile dev-client / store builds | |
| 104 | +| **Mac + physical device on the same Wi-Fi** | — | Recommended for mobile development | |
| 105 | + |
| 106 | +## Getting started |
| 107 | + |
| 108 | +### 1. Clone and install |
| 109 | + |
| 110 | +```bash |
| 111 | +git clone https://github.com/Aqib-Rime/qibla.git |
| 112 | +cd qibla |
| 113 | +bun install |
| 114 | +``` |
| 115 | + |
| 116 | +### 2. Configure environment variables |
| 117 | + |
| 118 | +Copy the example file and fill in your own values: |
| 119 | + |
| 120 | +```bash |
| 121 | +cp .env.example .env.local |
| 122 | +``` |
| 123 | + |
| 124 | +The root `.env.local` holds shared values. The admin app additionally reads server-only values from `apps/admin/.dev.vars` (used by Wrangler / Cloudflare Workers in dev). |
| 125 | + |
| 126 | +Create `apps/admin/.dev.vars`: |
| 127 | + |
| 128 | +```bash |
| 129 | +DATABASE_URL=postgresql://... |
| 130 | +BETTER_AUTH_SECRET=... # openssl rand -base64 32 |
| 131 | +BETTER_AUTH_URL=http://<your-lan-ip>:3000 |
| 132 | +``` |
| 133 | + |
| 134 | +Create `apps/mobile/.env.local`: |
| 135 | + |
| 136 | +```bash |
| 137 | +EXPO_PUBLIC_API_URL=http://<your-lan-ip>:3000 |
| 138 | +``` |
| 139 | + |
| 140 | +> **Why the LAN IP?** The Expo app on your phone talks to the admin Worker over your local network during dev. Find yours with `ipconfig getifaddr en0` on macOS. Better Auth will reject requests from untrusted origins, so this IP must also be in `BETTER_AUTH_TRUSTED_ORIGINS`. |
| 141 | +
|
| 142 | +### 3. Set up the database |
| 143 | + |
| 144 | +```bash |
| 145 | +bun db:push # push Drizzle schema to Neon |
| 146 | +bun db:seed # seed Dhaka mosque fixtures |
| 147 | +bun db:studio # optional — browse data |
| 148 | +``` |
| 149 | + |
| 150 | +### 4. Run the apps |
| 151 | + |
| 152 | +Two terminals: |
| 153 | + |
| 154 | +```bash |
| 155 | +# Terminal 1 — admin Worker on http://<lan-ip>:3000 |
| 156 | +bun run dev:admin |
| 157 | + |
| 158 | +# Terminal 2 — Expo Metro with a dev client |
| 159 | +cd apps/mobile && bun run start --clear |
| 160 | +``` |
| 161 | + |
| 162 | +First time on mobile? You need a dev build before `bun run start` can attach. See **Building the mobile app** below. As a fallback for screens that don't need native plugins: |
| 163 | + |
| 164 | +```bash |
| 165 | +cd apps/mobile && bun run start:go --clear |
| 166 | +``` |
| 167 | + |
| 168 | +> Expo Go does not support `expo-notifications` on SDK 53+ — notifications only work in a dev build. |
| 169 | +
|
| 170 | +### 5. Create the first admin |
| 171 | + |
| 172 | +Open `http://<lan-ip>:3000/initial-setup` and create the bootstrap admin account. Subsequent users default to the `user` role and can be promoted from the admin panel. |
| 173 | + |
| 174 | +## Environment variables |
| 175 | + |
| 176 | +The canonical list lives in [`.env.example`](./.env.example). Summary: |
| 177 | + |
| 178 | +| Variable | Used by | Required | Notes | |
| 179 | +|---|---|---|---| |
| 180 | +| `DATABASE_URL` | admin, db package | Yes | Neon Postgres connection string with `sslmode=require` | |
| 181 | +| `BETTER_AUTH_SECRET` | admin | Yes | `openssl rand -base64 32` | |
| 182 | +| `BETTER_AUTH_URL` | admin | Yes | Public URL of the admin app (LAN IP in dev) | |
| 183 | +| `BETTER_AUTH_TRUSTED_ORIGINS` | admin | Yes | Comma-separated list; include `qibla://` and your LAN IP | |
| 184 | +| `EXPO_PUBLIC_API_URL` | mobile | Yes | Same value as `BETTER_AUTH_URL` in dev | |
| 185 | +| `GOOGLE_MAPS_API_KEY_ANDROID` | mobile | Yes (Android) | From Google Cloud Console | |
| 186 | +| `GOOGLE_MAPS_API_KEY_IOS` | mobile | Optional | Only needed if you want Google Maps on iOS | |
| 187 | +| `CLOUDFLARE_ACCOUNT_ID` | admin deploy | Deploy only | `wrangler whoami` | |
| 188 | +| `CLOUDFLARE_API_TOKEN` | admin deploy | Deploy only | Scoped token for Workers + R2 | |
| 189 | +| `R2_*` | admin | V2 | Photo uploads (see Roadmap) | |
| 190 | + |
| 191 | +## Scripts |
| 192 | + |
| 193 | +Run from the repo root: |
8 | 194 |
|
9 | 195 | ```bash |
10 | | -pnpm dlx shadcn@latest add button -c apps/web |
| 196 | +bun run dev # run everything in parallel via Turbo |
| 197 | +bun run dev:admin # admin only (TanStack Start on Workers) |
| 198 | +bun run build # build every app |
| 199 | +bun run typecheck # tsc --noEmit across the workspace |
| 200 | +bun run lint # biome check |
| 201 | +bun run lint:fix # biome check --fix |
| 202 | +bun run format # biome format --write |
| 203 | + |
| 204 | +bun db:push # push schema |
| 205 | +bun db:generate # generate migrations |
| 206 | +bun db:migrate # apply migrations |
| 207 | +bun db:studio # Drizzle Studio |
| 208 | +bun db:seed # seed Dhaka fixtures |
11 | 209 | ``` |
12 | 210 |
|
13 | | -This will place the ui components in the `packages/ui/src/components` directory. |
| 211 | +Mobile-specific (from `apps/mobile/`): |
| 212 | + |
| 213 | +```bash |
| 214 | +bun run start # Metro for dev client |
| 215 | +bun run start:go # Metro for Expo Go (limited) |
| 216 | +bun run ios # build + launch iOS simulator |
| 217 | +bun run android # build + launch Android emulator |
| 218 | +bun run prebuild # regenerate native folders |
| 219 | +bun run doctor # expo-doctor |
| 220 | +``` |
14 | 221 |
|
15 | | -## Using components |
| 222 | +## Building the mobile app (EAS) |
16 | 223 |
|
17 | | -To use the components in your app, import them from the `ui` package. |
| 224 | +Log in once: |
18 | 225 |
|
19 | | -```tsx |
20 | | -import { Button } from "@workspace/ui/components/button"; |
| 226 | +```bash |
| 227 | +cd apps/mobile |
| 228 | +bunx eas-cli@latest login |
| 229 | +bunx eas-cli@latest init # writes projectId into app.config |
21 | 230 | ``` |
| 231 | + |
| 232 | +Available profiles (defined in [`apps/mobile/eas.json`](./apps/mobile/eas.json)): |
| 233 | + |
| 234 | +```bash |
| 235 | +bun run build:dev:ios # iOS simulator dev client |
| 236 | +bun run build:dev:device # iOS physical device dev client |
| 237 | +bun run build:dev:android # Android APK dev client |
| 238 | +bun run build:preview # internal distribution build (both platforms) |
| 239 | +bun run build:prod # store-ready build (both platforms) |
| 240 | +``` |
| 241 | + |
| 242 | +Install the dev-client artifact on your device once, then every subsequent run is just `bun run start`. |
| 243 | + |
| 244 | +## Deploying the admin app |
| 245 | + |
| 246 | +The admin app runs on Cloudflare Workers via the TanStack Start Workers adapter. |
| 247 | + |
| 248 | +```bash |
| 249 | +cd apps/admin |
| 250 | +bun run build |
| 251 | +bun run deploy # wraps `wrangler deploy` |
| 252 | +``` |
| 253 | + |
| 254 | +Before the first deploy: |
| 255 | + |
| 256 | +1. `wrangler login` |
| 257 | +2. Create a D1-free Workers project (this app uses Neon, not D1) |
| 258 | +3. Mirror your `.dev.vars` into Worker secrets: `wrangler secret put DATABASE_URL`, `BETTER_AUTH_SECRET`, … |
| 259 | +4. Update `BETTER_AUTH_URL` + `EXPO_PUBLIC_API_URL` to the production Worker URL |
| 260 | + |
| 261 | +## Roadmap |
| 262 | + |
| 263 | +### Shipped in V1 |
| 264 | + |
| 265 | +- Mobile: map, mosque detail, prayer times, qibla compass, saved, reviews, search + filter, profile, settings, prayer reminders, onboarding, email + password auth |
| 266 | +- Admin: mosques CRUD, review moderation, users list, dashboard |
| 267 | +- Backend: oRPC with public / authed / admin procedures; Neon Postgres via Drizzle; Better Auth |
| 268 | +- EAS profiles for dev-client, preview, and production builds |
| 269 | + |
| 270 | +### In progress / pending for V1 |
| 271 | + |
| 272 | +- `mosques.nearby` server-side distance filter |
| 273 | +- Events admin (`/_admin/events`) |
| 274 | +- Mosque photo upload to Cloudflare R2 |
| 275 | +- Standalone `reviews.mine` endpoint for profile stats |
| 276 | +- Google Places search integration |
| 277 | +- GitHub Actions CI + first Cloudflare production deploy |
| 278 | + |
| 279 | +### V2 (planned) |
| 280 | + |
| 281 | +- User-submitted mosque listings (`mosque_owner` role) |
| 282 | +- Google / phone-OTP sign-in |
| 283 | +- Bengali / Arabic localization |
| 284 | +- Custom map tiles |
| 285 | +- Prayer calculation method settings (currently hardcoded to Karachi + Hanafi) |
| 286 | +- **README v2**: logo, screenshots, demo GIF, store links, build-status badges |
| 287 | + |
| 288 | +See [`prd/v1-scope.md`](./prd/v1-scope.md) and [`prd/v2-scope.md`](./prd/v2-scope.md) for the full scope and [`prd/progress.md`](./prd/progress.md) for the live status snapshot. |
| 289 | + |
| 290 | +## Gotchas |
| 291 | + |
| 292 | +A few non-obvious things worth knowing before you dig in (full list in [`prd/progress.md`](./prd/progress.md)): |
| 293 | + |
| 294 | +- **Bun isolated linker breaks Expo's Babel** — keep `linker = "hoisted"` in `bunfig.toml`. |
| 295 | +- **NativeWind v4 requires Tailwind v3** — pinned at the workspace root; admin's Tailwind v4 nests inside `apps/admin/node_modules`. |
| 296 | +- **NativeWind content glob must include `features/` + `lib/`** — otherwise classes used only inside feature folders silently stop rendering. |
| 297 | +- **SDK 54 in a monorepo** needs `autolinkingModuleResolution: true` in `app.config.ts`. |
| 298 | +- **Native clients don't send `Origin`** — Better Auth needs `trustedOrigins: ["qibla://", ...]` and the `expo()` server plugin. |
| 299 | +- **`vite dev` needs `--host`** for physical-device testing — already configured in `apps/admin/package.json`. |
| 300 | +- **`bunx expo install <pkg>` must run from `apps/mobile`** — the root has no Expo SDK. |
| 301 | + |
| 302 | +## Contributing |
| 303 | + |
| 304 | +Contributions are welcome. For anything non-trivial, please open an issue first so we can align on scope. |
| 305 | + |
| 306 | +1. Fork and create a feature branch |
| 307 | +2. Run `bun install` |
| 308 | +3. Make your changes and keep `bun run typecheck` + `bun run lint` clean |
| 309 | +4. Open a PR against `main` |
| 310 | + |
| 311 | +<!-- |
| 312 | + v2 addition: CONTRIBUTING.md with coding conventions, PR checklist, and release process. |
| 313 | +--> |
| 314 | + |
| 315 | +## License |
| 316 | + |
| 317 | +Released under the [MIT License](./LICENSE). See the `LICENSE` file for the full text. |
| 318 | + |
| 319 | +## Acknowledgments |
| 320 | + |
| 321 | +- **AlAdhan API** — prayer time calculations |
| 322 | +- **Neon** — serverless Postgres |
| 323 | +- **Cloudflare** — Workers, KV, R2 |
| 324 | +- **Expo** + **TanStack** + **Better Auth** + **oRPC** + **Drizzle** + **shadcn/ui** — the stack that makes this possible |
| 325 | + |
| 326 | +--- |
| 327 | + |
| 328 | +Built by [@Aqib-Rime](https://github.com/Aqib-Rime). |
0 commit comments