@usequark/quark-create-app 1.23.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +210 -0
  2. package/package.json +56 -0
  3. package/src/dependabot-config.test.js +128 -0
  4. package/src/deploy/adapters/iac.js +203 -0
  5. package/src/deploy/adapters/index.js +27 -0
  6. package/src/deploy/adapters/railway.js +508 -0
  7. package/src/deploy/adapters/railway.test.js +318 -0
  8. package/src/deploy/contract.js +59 -0
  9. package/src/deploy/deploy.integration.test.js +183 -0
  10. package/src/deploy/deploy.js +430 -0
  11. package/src/deploy/deploy.test.js +105 -0
  12. package/src/deploy/discovery.js +115 -0
  13. package/src/deploy/discovery.test.js +141 -0
  14. package/src/deploy/index.js +38 -0
  15. package/src/deploy/inspect.js +130 -0
  16. package/src/index.js +2920 -0
  17. package/src/mobile-template.test.js +232 -0
  18. package/src/prepare-hooks.test.js +535 -0
  19. package/src/scaffold-guards.test.js +140 -0
  20. package/src/scaffold-output.test.js +228 -0
  21. package/src/template-scripts.test.js +160 -0
  22. package/src/utils.js +77 -0
  23. package/src/utils.test.js +95 -0
  24. package/src/worker-dockerfile.test.js +85 -0
  25. package/templates/base-project/.cursor/rules/quark.mdc +172 -0
  26. package/templates/base-project/.dockerignore +14 -0
  27. package/templates/base-project/.env.railway.example +87 -0
  28. package/templates/base-project/.github/copilot-instructions.md +65 -0
  29. package/templates/base-project/.github/dependabot.yml +31 -0
  30. package/templates/base-project/.github/skills/project-context/SKILL.md +109 -0
  31. package/templates/base-project/.github/workflows/ci.yml +116 -0
  32. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  33. package/templates/base-project/.github/workflows/install-windows.yml +27 -0
  34. package/templates/base-project/.github/workflows/release.yml +67 -0
  35. package/templates/base-project/.railway/railway.ts +40 -0
  36. package/templates/base-project/CLAUDE.md +163 -0
  37. package/templates/base-project/MAIN.md +21 -0
  38. package/templates/base-project/README.md +164 -0
  39. package/templates/base-project/apps/web/.env.example +99 -0
  40. package/templates/base-project/apps/web/Dockerfile +38 -0
  41. package/templates/base-project/apps/web/biome.json +8 -0
  42. package/templates/base-project/apps/web/jsconfig.json +9 -0
  43. package/templates/base-project/apps/web/next.config.js +154 -0
  44. package/templates/base-project/apps/web/package.json +42 -0
  45. package/templates/base-project/apps/web/postcss.config.mjs +7 -0
  46. package/templates/base-project/apps/web/public/.well-known/security.txt +3 -0
  47. package/templates/base-project/apps/web/public/quark.svg +47 -0
  48. package/templates/base-project/apps/web/scripts/prepare-standalone.mjs +71 -0
  49. package/templates/base-project/apps/web/src/app/_components/AsciiPanel.js +103 -0
  50. package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
  51. package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
  52. package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
  53. package/templates/base-project/apps/web/src/app/_components/SyncedPanels.js +212 -0
  54. package/templates/base-project/apps/web/src/app/_components/UmamiReplayRecorder.js +198 -0
  55. package/templates/base-project/apps/web/src/app/_components/UmamiWebVitals.js +39 -0
  56. package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +7 -0
  57. package/templates/base-project/apps/web/src/app/api/auth/apple/route.js +82 -0
  58. package/templates/base-project/apps/web/src/app/api/auth/google/route.js +59 -0
  59. package/templates/base-project/apps/web/src/app/api/auth/refresh/route.js +48 -0
  60. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +63 -0
  61. package/templates/base-project/apps/web/src/app/api/auth/token/route.js +44 -0
  62. package/templates/base-project/apps/web/src/app/api/csrf/route.js +40 -0
  63. package/templates/base-project/apps/web/src/app/api/device/register/route.js +57 -0
  64. package/templates/base-project/apps/web/src/app/api/error-handler.js +23 -0
  65. package/templates/base-project/apps/web/src/app/api/files/[id]/route.js +89 -0
  66. package/templates/base-project/apps/web/src/app/api/files/route.js +120 -0
  67. package/templates/base-project/apps/web/src/app/api/health/route.js +160 -0
  68. package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
  69. package/templates/base-project/apps/web/src/app/api/users/me/route.js +67 -0
  70. package/templates/base-project/apps/web/src/app/api/users/route.js +45 -0
  71. package/templates/base-project/apps/web/src/app/auth/error/page.js +83 -0
  72. package/templates/base-project/apps/web/src/app/auth/forgot-password/page.js +58 -0
  73. package/templates/base-project/apps/web/src/app/auth/provider-loading.js +25 -0
  74. package/templates/base-project/apps/web/src/app/auth/register/RegisterPageClient.js +340 -0
  75. package/templates/base-project/apps/web/src/app/auth/register/page.js +17 -0
  76. package/templates/base-project/apps/web/src/app/auth/signin/SignInPageClient.js +230 -0
  77. package/templates/base-project/apps/web/src/app/auth/signin/page.js +10 -0
  78. package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
  79. package/templates/base-project/apps/web/src/app/global-error.js +58 -0
  80. package/templates/base-project/apps/web/src/app/globals.css +1166 -0
  81. package/templates/base-project/apps/web/src/app/icon.svg +47 -0
  82. package/templates/base-project/apps/web/src/app/layout.js +57 -0
  83. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  84. package/templates/base-project/apps/web/src/app/not-found.js +39 -0
  85. package/templates/base-project/apps/web/src/app/page.js +262 -0
  86. package/templates/base-project/apps/web/src/app/robots.js +23 -0
  87. package/templates/base-project/apps/web/src/app/sitemap.js +14 -0
  88. package/templates/base-project/apps/web/src/lib/analytics/umami-before-send.js +29 -0
  89. package/templates/base-project/apps/web/src/lib/analytics/umami-config.js +95 -0
  90. package/templates/base-project/apps/web/src/lib/analytics/umami-csp.js +24 -0
  91. package/templates/base-project/apps/web/src/lib/analytics/umami-marketing.js +131 -0
  92. package/templates/base-project/apps/web/src/lib/analytics/umami-replay.js +204 -0
  93. package/templates/base-project/apps/web/src/lib/analytics/umami-web-vitals.js +237 -0
  94. package/templates/base-project/apps/web/src/lib/analytics/umami.js +57 -0
  95. package/templates/base-project/apps/web/src/lib/auth-middleware.js +82 -0
  96. package/templates/base-project/apps/web/src/lib/auth-signup.js +5 -0
  97. package/templates/base-project/apps/web/src/lib/auth.js +191 -0
  98. package/templates/base-project/apps/web/src/lib/csrf-cookie.js +11 -0
  99. package/templates/base-project/apps/web/src/lib/jwt.js +84 -0
  100. package/templates/base-project/apps/web/src/lib/load-crm-config.js +35 -0
  101. package/templates/base-project/apps/web/src/lib/normalize-auth-request.js +33 -0
  102. package/templates/base-project/apps/web/src/lib/proxy-auth.js +80 -0
  103. package/templates/base-project/apps/web/src/lib/seo/indexing.js +25 -0
  104. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +74 -0
  105. package/templates/base-project/apps/web/src/lib/sitemap-entries.js +22 -0
  106. package/templates/base-project/apps/web/src/preflight.js +55 -0
  107. package/templates/base-project/apps/web/src/proxy.js +328 -0
  108. package/templates/base-project/biome.json +52 -0
  109. package/templates/base-project/docker-compose.override.yml +17 -0
  110. package/templates/base-project/docker-compose.yml +37 -0
  111. package/templates/base-project/docs/FIRST_FEATURE.md +74 -0
  112. package/templates/base-project/docs/START_HERE.md +70 -0
  113. package/templates/base-project/docs/adr/README.md +42 -0
  114. package/templates/base-project/package.json +58 -0
  115. package/templates/base-project/packages/db/package.json +38 -0
  116. package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +202 -0
  117. package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
  118. package/templates/base-project/packages/db/prisma/schema.prisma +161 -0
  119. package/templates/base-project/packages/db/prisma/seed.js +152 -0
  120. package/templates/base-project/packages/db/prisma.config.js +27 -0
  121. package/templates/base-project/packages/db/src/client.js +75 -0
  122. package/templates/base-project/packages/db/src/connection.js +44 -0
  123. package/templates/base-project/packages/db/src/context.js +275 -0
  124. package/templates/base-project/packages/db/src/index.js +5 -0
  125. package/templates/base-project/packages/db/src/ping.js +66 -0
  126. package/templates/base-project/packages/db/src/queries.js +292 -0
  127. package/templates/base-project/packages/db/src/schemas.js +30 -0
  128. package/templates/base-project/packages/db/src/umami.js +299 -0
  129. package/templates/base-project/pnpm-workspace.yaml +20 -0
  130. package/templates/base-project/scripts/check-loading.mjs +49 -0
  131. package/templates/base-project/scripts/check-standards.mjs +186 -0
  132. package/templates/base-project/scripts/clean-workspace.mjs +479 -0
  133. package/templates/base-project/scripts/dev-preflight.mjs +48 -0
  134. package/templates/base-project/scripts/doctor.js +408 -0
  135. package/templates/base-project/scripts/prepare.js +490 -0
  136. package/templates/base-project/scripts/run-tests.mjs +71 -0
  137. package/templates/base-project/skills/add-dashboard/SKILL.md +28 -0
  138. package/templates/base-project/skills/add-endpoint/SKILL.md +29 -0
  139. package/templates/base-project/skills/add-model/SKILL.md +28 -0
  140. package/templates/base-project/skills/ecommerce/SKILL.md +224 -0
  141. package/templates/base-project/skills/ecommerce-cart/SKILL.md +193 -0
  142. package/templates/base-project/skills/ecommerce-catalog/SKILL.md +179 -0
  143. package/templates/base-project/skills/ecommerce-checkout/SKILL.md +195 -0
  144. package/templates/base-project/skills/i18n/SKILL.md +211 -0
  145. package/templates/base-project/skills/payment/SKILL.md +280 -0
  146. package/templates/base-project/skills/quark-skills/SKILL.md +37 -0
  147. package/templates/base-project/turbo.json +143 -0
  148. package/templates/config/package.json +20 -0
  149. package/templates/config/src/app-url.js +134 -0
  150. package/templates/config/src/environment.js +284 -0
  151. package/templates/config/src/index.js +16 -0
  152. package/templates/config/src/load-config.js +157 -0
  153. package/templates/config/src/rate-limit.js +136 -0
  154. package/templates/config/src/redis.js +43 -0
  155. package/templates/config/src/validate-env.js +532 -0
  156. package/templates/jobs/README.md +52 -0
  157. package/templates/jobs/package.json +9 -0
  158. package/templates/jobs/src/definitions.js +13 -0
  159. package/templates/jobs/src/index.js +1 -0
  160. package/templates/mobile/.env.example +16 -0
  161. package/templates/mobile/CHECKLIST.md +40 -0
  162. package/templates/mobile/README.md +130 -0
  163. package/templates/mobile/app/(app)/_layout.tsx +83 -0
  164. package/templates/mobile/app/(app)/index.tsx +147 -0
  165. package/templates/mobile/app/(app)/profile.tsx +204 -0
  166. package/templates/mobile/app/(app)/settings.tsx +80 -0
  167. package/templates/mobile/app/(auth)/_layout.tsx +10 -0
  168. package/templates/mobile/app/(auth)/sign-in.tsx +140 -0
  169. package/templates/mobile/app/(auth)/sign-up.tsx +178 -0
  170. package/templates/mobile/app/+not-found.tsx +32 -0
  171. package/templates/mobile/app/_layout.tsx +18 -0
  172. package/templates/mobile/app.json +47 -0
  173. package/templates/mobile/assets/adaptive-icon.png +0 -0
  174. package/templates/mobile/assets/icon.png +0 -0
  175. package/templates/mobile/assets/notification-icon.png +0 -0
  176. package/templates/mobile/assets/splash.png +0 -0
  177. package/templates/mobile/biome.json +27 -0
  178. package/templates/mobile/components/AuthGate.tsx +19 -0
  179. package/templates/mobile/components/ThemeProvider.tsx +23 -0
  180. package/templates/mobile/eas.json +29 -0
  181. package/templates/mobile/hooks/use-auth.ts +127 -0
  182. package/templates/mobile/lib/api-client.ts +138 -0
  183. package/templates/mobile/lib/api.ts +83 -0
  184. package/templates/mobile/lib/auth-providers-google.ts +74 -0
  185. package/templates/mobile/lib/auth-providers.ts +74 -0
  186. package/templates/mobile/lib/auth.ts +100 -0
  187. package/templates/mobile/lib/config.ts +14 -0
  188. package/templates/mobile/lib/errors.ts +46 -0
  189. package/templates/mobile/lib/notifications.ts +77 -0
  190. package/templates/mobile/lib/storage.ts +25 -0
  191. package/templates/mobile/lib/theme-tokens.ts +16 -0
  192. package/templates/mobile/package.json +37 -0
  193. package/templates/mobile/tsconfig.json +10 -0
  194. package/templates/pwa/app/_components/PWARegister.js +15 -0
  195. package/templates/pwa/app/manifest.js +32 -0
  196. package/templates/pwa/public/sw.js +72 -0
  197. package/templates/ui/README.md +44 -0
  198. package/templates/ui/package.json +21 -0
  199. package/templates/ui/src/badge.js +23 -0
  200. package/templates/ui/src/button.js +111 -0
  201. package/templates/ui/src/card.js +128 -0
  202. package/templates/ui/src/checkbox.js +20 -0
  203. package/templates/ui/src/container.js +13 -0
  204. package/templates/ui/src/dialog.js +137 -0
  205. package/templates/ui/src/error-banner.js +15 -0
  206. package/templates/ui/src/footer.js +239 -0
  207. package/templates/ui/src/form-field.integration-test.js +142 -0
  208. package/templates/ui/src/form-field.js +50 -0
  209. package/templates/ui/src/index.js +24 -0
  210. package/templates/ui/src/input.js +11 -0
  211. package/templates/ui/src/label.js +14 -0
  212. package/templates/ui/src/lightbox.integration-test.js +167 -0
  213. package/templates/ui/src/lightbox.js +243 -0
  214. package/templates/ui/src/logo.js +52 -0
  215. package/templates/ui/src/navbar.js +602 -0
  216. package/templates/ui/src/password-input.js +31 -0
  217. package/templates/ui/src/rich-text.js +514 -0
  218. package/templates/ui/src/select.js +317 -0
  219. package/templates/ui/src/skeleton.js +10 -0
  220. package/templates/ui/src/spinner.js +31 -0
  221. package/templates/ui/src/table.js +169 -0
  222. package/templates/ui/src/textarea.js +11 -0
  223. package/templates/ui/src/theme-constants.js +24 -0
  224. package/templates/ui/src/theme.js +196 -0
  225. package/templates/ui/src/toast.js +206 -0
  226. package/templates/worker/Dockerfile +64 -0
  227. package/templates/worker/README.md +56 -0
  228. package/templates/worker/package.json +36 -0
  229. package/templates/worker/src/config/pricing.js +46 -0
  230. package/templates/worker/src/handlers/email-job-validation.js +36 -0
  231. package/templates/worker/src/handlers/email.js +98 -0
  232. package/templates/worker/src/handlers/files.js +59 -0
  233. package/templates/worker/src/handlers/index.js +20 -0
  234. package/templates/worker/src/handlers/push-notification.js +234 -0
  235. package/templates/worker/src/index.js +505 -0
  236. package/templates/worker/src/lib/railway.js +438 -0
  237. package/templates/worker/src/lib/tokens.js +81 -0
@@ -0,0 +1,130 @@
1
+ # Quark Mobile App
2
+
3
+ Expo-based mobile client for Quark projects.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # 1. Start the backend (web + worker) from project root
9
+ pnpm dev
10
+
11
+ # 2. In a second terminal — start the mobile app
12
+ cd apps/mobile
13
+ echo 'EXPO_PUBLIC_API_URL=http://localhost:3000' > .env
14
+ npx expo start
15
+ ```
16
+
17
+ ## Review on MacBook (Android Emulator)
18
+
19
+ Requires [Android Studio](https://developer.android.com/studio) with an AVD created.
20
+
21
+ ```bash
22
+ # First time only: install dependencies + generate native dirs
23
+ npx expo prebuild --platform android
24
+
25
+ # Run on Android emulator
26
+ npx expo run:android
27
+
28
+ # Subsequent runs (fast — reuses build cache)
29
+ npx expo run:android
30
+ ```
31
+
32
+ Hot reload works out of the box. Shake menu (`Cmd+M`) to open dev tools.
33
+
34
+ ## Review on Physical Android Phone
35
+
36
+ ### Option A: Expo Go (quickest, no build)
37
+
38
+ 1. Install **Expo Go** from the Play Store
39
+ 2. Run `npx expo start` and scan the QR code
40
+ 3. Limitation: `expo-secure-store` and `expo-notifications` behave differently in Expo Go — use for UI/layout review only
41
+
42
+ ### Option B: Development Build (full fidelity)
43
+
44
+ ```bash
45
+ # First time: generate native dirs + install on device via USB
46
+ npx expo prebuild --platform android
47
+ npx expo run:android --device
48
+
49
+ # Or use EAS Build for a wireless install
50
+ npx eas build --profile development --platform android
51
+ # Download the .apk from the EAS dashboard, install on phone
52
+ ```
53
+
54
+ Development builds support all native modules (`expo-secure-store`, `expo-notifications`) identically to production.
55
+
56
+ ## Commands Reference
57
+
58
+ ### Development
59
+
60
+ | Command | What it does |
61
+ |---|---|
62
+ | `npx expo start` | Start Metro bundler (JS only, fastest) |
63
+ | `npx expo start --clear` | Start with cleared cache |
64
+ | `npx expo run:android` | Build + run on Android emulator/device |
65
+ | `npx expo run:android --device` | Build + run on connected physical device |
66
+ | `npx expo start --tunnel` | Expose local server over the internet (for testing on phone without same WiFi) |
67
+
68
+ ### Production Builds
69
+
70
+ | Command | What it does |
71
+ |---|---|
72
+ | `npx eas build --profile production --platform android` | Build release APK/AAB for Play Store |
73
+ | `npx eas build --profile production --platform ios` | Build release IPA for App Store |
74
+ | `npx eas build --platform all` | Build both platforms |
75
+
76
+ ### OTA Updates
77
+
78
+ | Command | What it does |
79
+ |---|---|
80
+ | `npx eas update --channel staging` | Push JS changes to staging channel |
81
+ | `npx eas update --channel production` | Push JS changes to production (live users) |
82
+
83
+ OTA is instant — no app store review needed for JS-only changes. See `CHECKLIST.md` for what qualifies.
84
+
85
+ ### Scripts (from project root)
86
+
87
+ | Command | What it does |
88
+ |---|---|
89
+ | `pnpm dev` | Start web + worker (API on :3000) |
90
+ | `pnpm build:mobile` | Export mobile app for preview |
91
+
92
+ ## Environment
93
+
94
+ | Variable | Required | Description |
95
+ |---|---|---|
96
+ | `EXPO_PUBLIC_API_URL` | Yes | Backend API URL (e.g. `http://localhost:3000` or `https://your-app.quark.dev`) |
97
+ | `EXPO_PUBLIC_GOOGLE_CLIENT_ID` | Optional | Google Sign-In client ID |
98
+
99
+ Set in `apps/mobile/.env`. The `EXPO_PUBLIC_` prefix is required by Expo for client-side access.
100
+
101
+ ## Architecture
102
+
103
+ - **Expo Router v55** — File-based routing
104
+ - **expo-secure-store** — Secure token storage (never AsyncStorage)
105
+ - **Zod** — All validation at system boundaries
106
+ - **fetch** — HTTP client with auth injection + automatic 401 retry
107
+
108
+ ## Auth Flow
109
+
110
+ 1. Sign in via `/api/auth/token` → receives JWT + refresh token
111
+ 2. Tokens stored in `expo-secure-store`
112
+ 3. API calls include `Authorization: Bearer <token>` header
113
+ 4. On 401, token refresh attempted via `/api/auth/refresh`
114
+ 5. On refresh failure, auth cleared and user redirected to sign-in
115
+
116
+ ## Push Notifications
117
+
118
+ - Token registered on app launch after authentication
119
+ - APNs (iOS) and FCM (Android) handled server-side via worker jobs
120
+ - No Firebase SDK on client or server
121
+
122
+ ## Deep Linking
123
+
124
+ Configure your URL scheme in `app.json` under `expo.scheme`. Expo Router handles file-based deep linking automatically.
125
+
126
+ ## OTA Updates
127
+
128
+ Configured via `expo-updates`. Channels: `development` → `staging` → `production`.
129
+
130
+ See `CHECKLIST.md` for OTA vs store build decisions.
@@ -0,0 +1,83 @@
1
+ import { Ionicons } from "@expo/vector-icons";
2
+ import { Redirect, Tabs } from "expo-router";
3
+ import { Platform } from "react-native";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import { AuthGate } from "../../components/AuthGate";
6
+ import { useTheme } from "../../components/ThemeProvider";
7
+ import { useAuth } from "../../hooks/use-auth";
8
+
9
+ export default function AppLayout() {
10
+ const { isAuthenticated, isLoading } = useAuth();
11
+ const insets = useSafeAreaInsets();
12
+ const theme = useTheme();
13
+ const isDark = theme === "dark";
14
+
15
+ if (isLoading) {
16
+ return null;
17
+ }
18
+
19
+ if (!isAuthenticated) {
20
+ return <Redirect href="/(auth)/sign-in" />;
21
+ }
22
+
23
+ return (
24
+ <AuthGate>
25
+ <Tabs
26
+ screenOptions={{
27
+ headerShown: false,
28
+ tabBarStyle: {
29
+ backgroundColor: isDark ? "#1c1c1e" : "#ffffff",
30
+ borderTopColor: isDark ? "#38383a" : "#e5e5ea",
31
+ paddingBottom: Platform.OS === "ios" ? insets.bottom : 8,
32
+ height: Platform.OS === "ios" ? 88 : 64,
33
+ },
34
+ tabBarActiveTintColor: isDark ? "#fff" : "#000",
35
+ tabBarInactiveTintColor: isDark ? "#888" : "#666",
36
+ }}
37
+ >
38
+ <Tabs.Screen
39
+ name="index"
40
+ options={{
41
+ title: "Home",
42
+ tabBarLabel: "Home",
43
+ tabBarIcon: ({ color, size }) => (
44
+ <Ionicons
45
+ name="home-outline"
46
+ size={size}
47
+ color={color as string}
48
+ />
49
+ ),
50
+ }}
51
+ />
52
+ <Tabs.Screen
53
+ name="profile"
54
+ options={{
55
+ title: "Profile",
56
+ tabBarLabel: "Profile",
57
+ tabBarIcon: ({ color, size }) => (
58
+ <Ionicons
59
+ name="person-outline"
60
+ size={size}
61
+ color={color as string}
62
+ />
63
+ ),
64
+ }}
65
+ />
66
+ <Tabs.Screen
67
+ name="settings"
68
+ options={{
69
+ title: "Settings",
70
+ tabBarLabel: "Settings",
71
+ tabBarIcon: ({ color, size }) => (
72
+ <Ionicons
73
+ name="settings-outline"
74
+ size={size}
75
+ color={color as string}
76
+ />
77
+ ),
78
+ }}
79
+ />
80
+ </Tabs>
81
+ </AuthGate>
82
+ );
83
+ }
@@ -0,0 +1,147 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import {
3
+ ActivityIndicator,
4
+ RefreshControl,
5
+ ScrollView,
6
+ Text,
7
+ View,
8
+ } from "react-native";
9
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
10
+ import { useAuth } from "../../hooks/use-auth";
11
+ import { getProfile } from "../../lib/auth";
12
+ import { useThemeTokens } from "../../lib/theme-tokens";
13
+
14
+ interface UserProfile {
15
+ id: string;
16
+ email: string;
17
+ name: string | null;
18
+ role: string;
19
+ }
20
+
21
+ export default function HomeScreen() {
22
+ const { isAuthenticated } = useAuth();
23
+ const { bgColor, textColor, mutedColor, cardBg, borderColor, isDark } =
24
+ useThemeTokens();
25
+ const insets = useSafeAreaInsets();
26
+ const [profile, setProfile] = useState<UserProfile | null>(null);
27
+ const [loading, setLoading] = useState(true);
28
+ const [refreshing, setRefreshing] = useState(false);
29
+
30
+ const fetchProfile = useCallback(async () => {
31
+ try {
32
+ const data = await getProfile();
33
+ setProfile(data);
34
+ } catch {
35
+ // Profile fetch failed - non-critical
36
+ } finally {
37
+ setLoading(false);
38
+ setRefreshing(false);
39
+ }
40
+ }, []);
41
+
42
+ useEffect(() => {
43
+ if (isAuthenticated) {
44
+ fetchProfile();
45
+ }
46
+ }, [isAuthenticated, fetchProfile]);
47
+
48
+ async function onRefresh() {
49
+ setRefreshing(true);
50
+ await fetchProfile();
51
+ }
52
+
53
+ if (loading) {
54
+ return (
55
+ <View
56
+ style={{
57
+ flex: 1,
58
+ justifyContent: "center",
59
+ alignItems: "center",
60
+ backgroundColor: bgColor,
61
+ }}
62
+ accessibilityLabel="Loading home screen"
63
+ accessibilityRole="progressbar"
64
+ >
65
+ <ActivityIndicator size="large" color={isDark ? "#fff" : "#000"} />
66
+ </View>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <ScrollView
72
+ style={{ flex: 1, backgroundColor: bgColor }}
73
+ contentContainerStyle={{
74
+ padding: 20,
75
+ paddingTop: insets.top + 20,
76
+ }}
77
+ refreshControl={
78
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
79
+ }
80
+ accessibilityLabel="Home screen content"
81
+ >
82
+ <Text
83
+ style={{
84
+ fontSize: 28,
85
+ fontWeight: "bold",
86
+ color: textColor,
87
+ marginBottom: 8,
88
+ }}
89
+ accessibilityRole="header"
90
+ >
91
+ Welcome{profile?.name ? `, ${profile.name}` : ""}
92
+ </Text>
93
+ <Text style={{ fontSize: 16, color: mutedColor, marginBottom: 24 }}>
94
+ {profile?.email || "Signed in to your account"}
95
+ </Text>
96
+
97
+ <View
98
+ style={{
99
+ backgroundColor: cardBg,
100
+ borderRadius: 12,
101
+ padding: 16,
102
+ marginBottom: 16,
103
+ borderWidth: 1,
104
+ borderColor,
105
+ }}
106
+ accessibilityLabel="Account info card"
107
+ accessibilityRole="summary"
108
+ >
109
+ <Text style={{ fontSize: 14, color: mutedColor, marginBottom: 4 }}>
110
+ Account
111
+ </Text>
112
+ <Text style={{ fontSize: 16, color: textColor, fontWeight: "500" }}>
113
+ {profile?.name || "No name set"}
114
+ </Text>
115
+ <Text style={{ fontSize: 14, color: mutedColor, marginTop: 4 }}>
116
+ {profile?.email}
117
+ </Text>
118
+ </View>
119
+
120
+ <View
121
+ style={{
122
+ backgroundColor: cardBg,
123
+ borderRadius: 12,
124
+ padding: 16,
125
+ borderWidth: 1,
126
+ borderColor,
127
+ }}
128
+ accessibilityLabel="Quick actions card"
129
+ accessibilityRole="summary"
130
+ >
131
+ <Text style={{ fontSize: 14, color: mutedColor, marginBottom: 4 }}>
132
+ Role
133
+ </Text>
134
+ <Text
135
+ style={{
136
+ fontSize: 16,
137
+ color: textColor,
138
+ fontWeight: "500",
139
+ textTransform: "capitalize",
140
+ }}
141
+ >
142
+ {profile?.role || "viewer"}
143
+ </Text>
144
+ </View>
145
+ </ScrollView>
146
+ );
147
+ }
@@ -0,0 +1,204 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ ActivityIndicator,
4
+ Alert,
5
+ Pressable,
6
+ ScrollView,
7
+ Text,
8
+ TextInput,
9
+ View,
10
+ } from "react-native";
11
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
12
+ import { useAuth } from "../../hooks/use-auth";
13
+ import { getProfile, updateProfile } from "../../lib/auth";
14
+ import { useThemeTokens } from "../../lib/theme-tokens";
15
+
16
+ interface UserProfile {
17
+ id: string;
18
+ email: string;
19
+ name: string | null;
20
+ role: string;
21
+ }
22
+
23
+ export default function ProfileScreen() {
24
+ const { isAuthenticated } = useAuth();
25
+ const {
26
+ bgColor,
27
+ textColor,
28
+ mutedColor,
29
+ cardBg,
30
+ borderColor,
31
+ inputBg,
32
+ isDark,
33
+ } = useThemeTokens();
34
+ const insets = useSafeAreaInsets();
35
+ const [profile, setProfile] = useState<UserProfile | null>(null);
36
+ const [name, setName] = useState("");
37
+ const [loading, setLoading] = useState(true);
38
+ const [saving, setSaving] = useState(false);
39
+
40
+ useEffect(() => {
41
+ if (isAuthenticated) {
42
+ (async () => {
43
+ try {
44
+ const data = await getProfile();
45
+ setProfile(data);
46
+ setName(data.name || "");
47
+ } catch {
48
+ // Non-critical
49
+ } finally {
50
+ setLoading(false);
51
+ }
52
+ })();
53
+ }
54
+ }, [isAuthenticated]);
55
+
56
+ async function handleSave() {
57
+ if (!name.trim()) {
58
+ Alert.alert("Error", "Name cannot be empty");
59
+ return;
60
+ }
61
+ setSaving(true);
62
+ try {
63
+ const updated = await updateProfile({ name: name.trim() });
64
+ setProfile(updated);
65
+ Alert.alert("Saved", "Profile updated successfully");
66
+ } catch {
67
+ Alert.alert("Error", "Failed to update profile. Please try again.");
68
+ } finally {
69
+ setSaving(false);
70
+ }
71
+ }
72
+
73
+ if (loading) {
74
+ return (
75
+ <View
76
+ style={{
77
+ flex: 1,
78
+ justifyContent: "center",
79
+ alignItems: "center",
80
+ backgroundColor: bgColor,
81
+ }}
82
+ accessibilityLabel="Loading profile"
83
+ accessibilityRole="progressbar"
84
+ >
85
+ <ActivityIndicator size="large" color={isDark ? "#fff" : "#000"} />
86
+ </View>
87
+ );
88
+ }
89
+
90
+ return (
91
+ <ScrollView
92
+ style={{ flex: 1, backgroundColor: bgColor }}
93
+ contentContainerStyle={{
94
+ padding: 20,
95
+ paddingTop: insets.top + 20,
96
+ }}
97
+ accessibilityLabel="Profile screen"
98
+ >
99
+ <Text
100
+ style={{
101
+ fontSize: 28,
102
+ fontWeight: "bold",
103
+ color: textColor,
104
+ marginBottom: 24,
105
+ }}
106
+ accessibilityRole="header"
107
+ >
108
+ Profile
109
+ </Text>
110
+
111
+ <Text style={{ fontSize: 14, color: mutedColor, marginBottom: 8 }}>
112
+ Name
113
+ </Text>
114
+ <TextInput
115
+ value={name}
116
+ onChangeText={setName}
117
+ placeholder="Your name"
118
+ placeholderTextColor={mutedColor}
119
+ autoComplete="name"
120
+ style={{
121
+ backgroundColor: inputBg,
122
+ borderWidth: 1,
123
+ borderColor,
124
+ borderRadius: 8,
125
+ padding: 12,
126
+ marginBottom: 16,
127
+ fontSize: 16,
128
+ color: textColor,
129
+ }}
130
+ accessibilityLabel="Name input"
131
+ accessibilityHint="Enter your display name"
132
+ />
133
+
134
+ <Text style={{ fontSize: 14, color: mutedColor, marginBottom: 8 }}>
135
+ Email
136
+ </Text>
137
+ <View
138
+ style={{
139
+ backgroundColor: cardBg,
140
+ borderWidth: 1,
141
+ borderColor,
142
+ borderRadius: 8,
143
+ padding: 12,
144
+ marginBottom: 16,
145
+ }}
146
+ accessibilityLabel="Email display"
147
+ >
148
+ <Text style={{ fontSize: 16, color: mutedColor }}>
149
+ {profile?.email}
150
+ </Text>
151
+ </View>
152
+
153
+ <Text style={{ fontSize: 14, color: mutedColor, marginBottom: 8 }}>
154
+ Role
155
+ </Text>
156
+ <View
157
+ style={{
158
+ backgroundColor: cardBg,
159
+ borderWidth: 1,
160
+ borderColor,
161
+ borderRadius: 8,
162
+ padding: 12,
163
+ marginBottom: 24,
164
+ }}
165
+ accessibilityLabel="Role display"
166
+ >
167
+ <Text
168
+ style={{
169
+ fontSize: 16,
170
+ color: textColor,
171
+ textTransform: "capitalize",
172
+ }}
173
+ >
174
+ {profile?.role || "viewer"}
175
+ </Text>
176
+ </View>
177
+
178
+ <Pressable
179
+ onPress={handleSave}
180
+ disabled={saving}
181
+ style={{
182
+ backgroundColor: isDark ? "#fff" : "#000",
183
+ padding: 14,
184
+ borderRadius: 8,
185
+ alignItems: "center",
186
+ opacity: saving ? 0.6 : 1,
187
+ }}
188
+ accessibilityLabel="Save profile changes"
189
+ accessibilityRole="button"
190
+ accessibilityState={{ disabled: saving }}
191
+ >
192
+ <Text
193
+ style={{
194
+ color: isDark ? "#000" : "#fff",
195
+ fontSize: 16,
196
+ fontWeight: "600",
197
+ }}
198
+ >
199
+ {saving ? "Saving..." : "Save Changes"}
200
+ </Text>
201
+ </Pressable>
202
+ </ScrollView>
203
+ );
204
+ }
@@ -0,0 +1,80 @@
1
+ import { useRouter } from "expo-router";
2
+ import { Pressable, Text, View } from "react-native";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import { useAuth } from "../../hooks/use-auth";
5
+ import { useThemeTokens } from "../../lib/theme-tokens";
6
+
7
+ export default function SettingsScreen() {
8
+ const router = useRouter();
9
+ const { signOut } = useAuth();
10
+ const { bgColor, textColor, mutedColor, cardBg, borderColor } =
11
+ useThemeTokens();
12
+ const insets = useSafeAreaInsets();
13
+
14
+ async function handleSignOut() {
15
+ await signOut();
16
+ router.replace("/(auth)/sign-in");
17
+ }
18
+
19
+ return (
20
+ <View
21
+ style={{
22
+ flex: 1,
23
+ backgroundColor: bgColor,
24
+ padding: 20,
25
+ paddingTop: insets.top + 20,
26
+ }}
27
+ accessibilityLabel="Settings screen"
28
+ >
29
+ <Text
30
+ style={{
31
+ fontSize: 28,
32
+ fontWeight: "bold",
33
+ color: textColor,
34
+ marginBottom: 24,
35
+ }}
36
+ accessibilityRole="header"
37
+ >
38
+ Settings
39
+ </Text>
40
+
41
+ <View
42
+ style={{
43
+ backgroundColor: cardBg,
44
+ borderRadius: 12,
45
+ padding: 16,
46
+ marginBottom: 16,
47
+ borderWidth: 1,
48
+ borderColor,
49
+ }}
50
+ accessibilityLabel="App info section"
51
+ >
52
+ <Text style={{ fontSize: 14, color: mutedColor, marginBottom: 4 }}>
53
+ App
54
+ </Text>
55
+ <Text style={{ fontSize: 16, color: textColor }}>Quark Mobile</Text>
56
+ <Text style={{ fontSize: 14, color: mutedColor, marginTop: 4 }}>
57
+ Version 0.1.0
58
+ </Text>
59
+ </View>
60
+
61
+ <Pressable
62
+ onPress={handleSignOut}
63
+ style={{
64
+ backgroundColor: "#ff3b30",
65
+ paddingHorizontal: 24,
66
+ paddingVertical: 14,
67
+ borderRadius: 8,
68
+ alignItems: "center",
69
+ }}
70
+ accessibilityLabel="Sign out of your account"
71
+ accessibilityRole="button"
72
+ accessibilityHint="Signs you out and returns to the sign in screen"
73
+ >
74
+ <Text style={{ color: "#fff", fontSize: 16, fontWeight: "600" }}>
75
+ Sign Out
76
+ </Text>
77
+ </Pressable>
78
+ </View>
79
+ );
80
+ }
@@ -0,0 +1,10 @@
1
+ import { Stack } from "expo-router";
2
+
3
+ export default function AuthLayout() {
4
+ return (
5
+ <Stack screenOptions={{ headerShown: false }}>
6
+ <Stack.Screen name="sign-in" />
7
+ <Stack.Screen name="sign-up" />
8
+ </Stack>
9
+ );
10
+ }