create-stackr 0.2.0

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 (274) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +642 -0
  3. package/bin/cli.js +12 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +113 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config/dependencies.d.ts +82 -0
  9. package/dist/config/dependencies.d.ts.map +1 -0
  10. package/dist/config/dependencies.js +82 -0
  11. package/dist/config/dependencies.js.map +1 -0
  12. package/dist/config/presets.d.ts +3 -0
  13. package/dist/config/presets.d.ts.map +1 -0
  14. package/dist/config/presets.js +174 -0
  15. package/dist/config/presets.js.map +1 -0
  16. package/dist/generators/index.d.ts +40 -0
  17. package/dist/generators/index.d.ts.map +1 -0
  18. package/dist/generators/index.js +130 -0
  19. package/dist/generators/index.js.map +1 -0
  20. package/dist/generators/onboarding.d.ts +8 -0
  21. package/dist/generators/onboarding.d.ts.map +1 -0
  22. package/dist/generators/onboarding.js +141 -0
  23. package/dist/generators/onboarding.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +65 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/prompts/features.d.ts +14 -0
  29. package/dist/prompts/features.d.ts.map +1 -0
  30. package/dist/prompts/features.js +96 -0
  31. package/dist/prompts/features.js.map +1 -0
  32. package/dist/prompts/index.d.ts +3 -0
  33. package/dist/prompts/index.d.ts.map +1 -0
  34. package/dist/prompts/index.js +93 -0
  35. package/dist/prompts/index.js.map +1 -0
  36. package/dist/prompts/onboarding.d.ts +6 -0
  37. package/dist/prompts/onboarding.d.ts.map +1 -0
  38. package/dist/prompts/onboarding.js +37 -0
  39. package/dist/prompts/onboarding.js.map +1 -0
  40. package/dist/prompts/orm.d.ts +3 -0
  41. package/dist/prompts/orm.d.ts.map +1 -0
  42. package/dist/prompts/orm.js +23 -0
  43. package/dist/prompts/orm.js.map +1 -0
  44. package/dist/prompts/packageManager.d.ts +2 -0
  45. package/dist/prompts/packageManager.d.ts.map +1 -0
  46. package/dist/prompts/packageManager.js +18 -0
  47. package/dist/prompts/packageManager.js.map +1 -0
  48. package/dist/prompts/platform.d.ts +3 -0
  49. package/dist/prompts/platform.d.ts.map +1 -0
  50. package/dist/prompts/platform.js +21 -0
  51. package/dist/prompts/platform.js.map +1 -0
  52. package/dist/prompts/preset.d.ts +4 -0
  53. package/dist/prompts/preset.d.ts.map +1 -0
  54. package/dist/prompts/preset.js +165 -0
  55. package/dist/prompts/preset.js.map +1 -0
  56. package/dist/prompts/project.d.ts +2 -0
  57. package/dist/prompts/project.d.ts.map +1 -0
  58. package/dist/prompts/project.js +27 -0
  59. package/dist/prompts/project.js.map +1 -0
  60. package/dist/prompts/sdks.d.ts +2 -0
  61. package/dist/prompts/sdks.d.ts.map +1 -0
  62. package/dist/prompts/sdks.js +46 -0
  63. package/dist/prompts/sdks.js.map +1 -0
  64. package/dist/types/index.d.ts +77 -0
  65. package/dist/types/index.d.ts.map +1 -0
  66. package/dist/types/index.js +25 -0
  67. package/dist/types/index.js.map +1 -0
  68. package/dist/utils/cleanup.d.ts +5 -0
  69. package/dist/utils/cleanup.d.ts.map +1 -0
  70. package/dist/utils/cleanup.js +38 -0
  71. package/dist/utils/cleanup.js.map +1 -0
  72. package/dist/utils/copy.d.ts +10 -0
  73. package/dist/utils/copy.d.ts.map +1 -0
  74. package/dist/utils/copy.js +53 -0
  75. package/dist/utils/copy.js.map +1 -0
  76. package/dist/utils/errors.d.ts +33 -0
  77. package/dist/utils/errors.d.ts.map +1 -0
  78. package/dist/utils/errors.js +136 -0
  79. package/dist/utils/errors.js.map +1 -0
  80. package/dist/utils/git.d.ts +5 -0
  81. package/dist/utils/git.d.ts.map +1 -0
  82. package/dist/utils/git.js +33 -0
  83. package/dist/utils/git.js.map +1 -0
  84. package/dist/utils/logger.d.ts +9 -0
  85. package/dist/utils/logger.d.ts.map +1 -0
  86. package/dist/utils/logger.js +22 -0
  87. package/dist/utils/logger.js.map +1 -0
  88. package/dist/utils/package.d.ts +16 -0
  89. package/dist/utils/package.d.ts.map +1 -0
  90. package/dist/utils/package.js +86 -0
  91. package/dist/utils/package.js.map +1 -0
  92. package/dist/utils/system-validation.d.ts +9 -0
  93. package/dist/utils/system-validation.d.ts.map +1 -0
  94. package/dist/utils/system-validation.js +31 -0
  95. package/dist/utils/system-validation.js.map +1 -0
  96. package/dist/utils/template.d.ts +20 -0
  97. package/dist/utils/template.d.ts.map +1 -0
  98. package/dist/utils/template.js +234 -0
  99. package/dist/utils/template.js.map +1 -0
  100. package/dist/utils/validation.d.ts +8 -0
  101. package/dist/utils/validation.d.ts.map +1 -0
  102. package/dist/utils/validation.js +94 -0
  103. package/dist/utils/validation.js.map +1 -0
  104. package/package.json +96 -0
  105. package/templates/base/backend/.dockerignore.ejs +62 -0
  106. package/templates/base/backend/.env.example.ejs +116 -0
  107. package/templates/base/backend/Dockerfile.ejs +142 -0
  108. package/templates/base/backend/controllers/event-queue/index.ts +20 -0
  109. package/templates/base/backend/controllers/event-queue/workers/user.ts +39 -0
  110. package/templates/base/backend/controllers/rest-api/index.ts +48 -0
  111. package/templates/base/backend/controllers/rest-api/plugins/auth.ts +152 -0
  112. package/templates/base/backend/controllers/rest-api/plugins/config.ts +64 -0
  113. package/templates/base/backend/controllers/rest-api/plugins/error-handler.ts +118 -0
  114. package/templates/base/backend/controllers/rest-api/routes/auth.ts.ejs +180 -0
  115. package/templates/base/backend/controllers/rest-api/routes/device-sessions.ts +197 -0
  116. package/templates/base/backend/controllers/rest-api/routes/oauth-web.ts.ejs +375 -0
  117. package/templates/base/backend/controllers/rest-api/server.ts.ejs +87 -0
  118. package/templates/base/backend/domain/device-session/repository.drizzle.ts +209 -0
  119. package/templates/base/backend/domain/device-session/repository.prisma.ts +248 -0
  120. package/templates/base/backend/domain/device-session/schema.ts +72 -0
  121. package/templates/base/backend/domain/session/repository.drizzle.ts +72 -0
  122. package/templates/base/backend/domain/session/repository.prisma.ts +72 -0
  123. package/templates/base/backend/domain/session/schema.ts +29 -0
  124. package/templates/base/backend/domain/user/repository.drizzle.ts +127 -0
  125. package/templates/base/backend/domain/user/repository.prisma.ts +115 -0
  126. package/templates/base/backend/domain/user/schema.ts +14 -0
  127. package/templates/base/backend/drizzle/schema.drizzle.ts +111 -0
  128. package/templates/base/backend/drizzle.config.drizzle.ts +13 -0
  129. package/templates/base/backend/lib/auth.drizzle.ts.ejs +104 -0
  130. package/templates/base/backend/lib/auth.prisma.ts.ejs +97 -0
  131. package/templates/base/backend/lib/constants.ts.ejs +29 -0
  132. package/templates/base/backend/package.json.ejs +50 -0
  133. package/templates/base/backend/prisma/schema.prisma.ejs +102 -0
  134. package/templates/base/backend/prisma.config.prisma.ts +12 -0
  135. package/templates/base/backend/tsconfig.json +39 -0
  136. package/templates/base/backend/utils/db.drizzle.ts +41 -0
  137. package/templates/base/backend/utils/db.prisma.ts +51 -0
  138. package/templates/base/backend/utils/email.ts.ejs +35 -0
  139. package/templates/base/backend/utils/errors.ts +348 -0
  140. package/templates/base/backend/utils/redis.ts.ejs +279 -0
  141. package/templates/base/mobile/.env.example.ejs +35 -0
  142. package/templates/base/mobile/.gitignore.ejs +167 -0
  143. package/templates/base/mobile/app/+not-found.tsx +85 -0
  144. package/templates/base/mobile/app/_layout.tsx.ejs +71 -0
  145. package/templates/base/mobile/app.json.ejs +88 -0
  146. package/templates/base/mobile/assets/images/adaptive-icon.png +0 -0
  147. package/templates/base/mobile/assets/images/favicon.png +0 -0
  148. package/templates/base/mobile/assets/images/icon.png +0 -0
  149. package/templates/base/mobile/assets/images/onboarding_page_1.png +0 -0
  150. package/templates/base/mobile/assets/images/onboarding_page_2.png +0 -0
  151. package/templates/base/mobile/assets/images/onboarding_page_3.png +0 -0
  152. package/templates/base/mobile/assets/images/paywall_image.png +0 -0
  153. package/templates/base/mobile/assets/images/splash.png +0 -0
  154. package/templates/base/mobile/eas.json.ejs +49 -0
  155. package/templates/base/mobile/metro.config.js +9 -0
  156. package/templates/base/mobile/package.json.ejs +53 -0
  157. package/templates/base/mobile/src/components/ui/Button.tsx +131 -0
  158. package/templates/base/mobile/src/components/ui/Card.tsx +68 -0
  159. package/templates/base/mobile/src/components/ui/IconSymbol.tsx +90 -0
  160. package/templates/base/mobile/src/components/ui/Input.tsx +142 -0
  161. package/templates/base/mobile/src/components/ui/LoadingSpinner.tsx +98 -0
  162. package/templates/base/mobile/src/components/ui/OnboardingLayout.tsx +356 -0
  163. package/templates/base/mobile/src/components/ui/PaywallLayout.tsx +311 -0
  164. package/templates/base/mobile/src/components/ui/Skeleton.tsx +58 -0
  165. package/templates/base/mobile/src/components/ui/index.ts +6 -0
  166. package/templates/base/mobile/src/constants/Theme.ts +163 -0
  167. package/templates/base/mobile/src/context/ThemeContext.tsx +157 -0
  168. package/templates/base/mobile/src/lib/auth-client.ts.ejs +51 -0
  169. package/templates/base/mobile/src/services/api.ts.ejs +71 -0
  170. package/templates/base/mobile/src/services/errorService.ts +179 -0
  171. package/templates/base/mobile/src/services/sdkInitializer.ts.ejs +36 -0
  172. package/templates/base/mobile/src/store/index.ts.ejs +18 -0
  173. package/templates/base/mobile/src/store/ui.store.ts +100 -0
  174. package/templates/base/mobile/src/utils/formatters.ts +105 -0
  175. package/templates/base/mobile/src/utils/logger.ts +73 -0
  176. package/templates/base/mobile/src/utils/responsive.ts +234 -0
  177. package/templates/base/mobile/tsconfig.json +32 -0
  178. package/templates/base/web/.env.example.ejs +26 -0
  179. package/templates/base/web/components.json +22 -0
  180. package/templates/base/web/eslint.config.mjs +18 -0
  181. package/templates/base/web/next.config.ts +7 -0
  182. package/templates/base/web/package.json.ejs +35 -0
  183. package/templates/base/web/postcss.config.mjs +7 -0
  184. package/templates/base/web/public/.gitkeep +0 -0
  185. package/templates/base/web/public/file.svg +1 -0
  186. package/templates/base/web/public/globe.svg +1 -0
  187. package/templates/base/web/public/next.svg +1 -0
  188. package/templates/base/web/public/vercel.svg +1 -0
  189. package/templates/base/web/public/window.svg +1 -0
  190. package/templates/base/web/src/app/favicon.ico +0 -0
  191. package/templates/base/web/src/app/globals.css +152 -0
  192. package/templates/base/web/src/app/layout.tsx.ejs +54 -0
  193. package/templates/base/web/src/app/page.tsx.ejs +92 -0
  194. package/templates/base/web/src/components/auth/auth-hydrator.tsx.ejs +19 -0
  195. package/templates/base/web/src/components/auth/protected-route.tsx.ejs +109 -0
  196. package/templates/base/web/src/components/providers/device-session-setup.tsx.ejs +56 -0
  197. package/templates/base/web/src/components/providers/theme-provider.tsx +17 -0
  198. package/templates/base/web/src/components/theme-toggle.tsx +34 -0
  199. package/templates/base/web/src/components/ui/button.tsx +62 -0
  200. package/templates/base/web/src/components/ui/card.tsx +92 -0
  201. package/templates/base/web/src/components/ui/input.tsx +21 -0
  202. package/templates/base/web/src/components/ui/label.tsx +24 -0
  203. package/templates/base/web/src/components/ui/skeleton.tsx +13 -0
  204. package/templates/base/web/src/components/ui/spinner.tsx +20 -0
  205. package/templates/base/web/src/hooks/use-device-session.ts.ejs +40 -0
  206. package/templates/base/web/src/hooks/use-session.ts.ejs +56 -0
  207. package/templates/base/web/src/lib/auth/actions.ts.ejs +334 -0
  208. package/templates/base/web/src/lib/auth/config.ts.ejs +65 -0
  209. package/templates/base/web/src/lib/auth/cookies.ts.ejs +74 -0
  210. package/templates/base/web/src/lib/auth/index.ts.ejs +40 -0
  211. package/templates/base/web/src/lib/auth/oauth.ts.ejs +72 -0
  212. package/templates/base/web/src/lib/auth/pkce.ts.ejs +48 -0
  213. package/templates/base/web/src/lib/auth/sessions.ts.ejs +135 -0
  214. package/templates/base/web/src/lib/auth/user-agent.ts.ejs +47 -0
  215. package/templates/base/web/src/lib/device/actions.ts.ejs +148 -0
  216. package/templates/base/web/src/lib/device/id.ts.ejs +74 -0
  217. package/templates/base/web/src/lib/utils.ts +6 -0
  218. package/templates/base/web/src/proxy.ts.ejs +66 -0
  219. package/templates/base/web/src/store/auth.store.ts.ejs +89 -0
  220. package/templates/base/web/src/store/deviceSession.store.ts.ejs +141 -0
  221. package/templates/base/web/tsconfig.json +34 -0
  222. package/templates/features/mobile/auth/app/(auth)/_layout.tsx +16 -0
  223. package/templates/features/mobile/auth/app/(auth)/login.tsx +86 -0
  224. package/templates/features/mobile/auth/app/(auth)/register.tsx +86 -0
  225. package/templates/features/mobile/auth/components/auth/LoginForm.tsx.ejs +349 -0
  226. package/templates/features/mobile/auth/components/auth/RegisterForm.tsx.ejs +407 -0
  227. package/templates/features/mobile/auth/components/auth/index.ts +2 -0
  228. package/templates/features/mobile/auth/hooks/index.ts.ejs +1 -0
  229. package/templates/features/mobile/auth/hooks/useAuth.ts.ejs +367 -0
  230. package/templates/features/mobile/auth/services/deviceSession.ts +370 -0
  231. package/templates/features/mobile/auth/store/deviceSession.store.ts +326 -0
  232. package/templates/features/mobile/onboarding/app/(onboarding)/_layout.tsx.ejs +11 -0
  233. package/templates/features/mobile/onboarding/app/(onboarding)/page-1.tsx.ejs +52 -0
  234. package/templates/features/mobile/onboarding/app/(onboarding)/page-2.tsx.ejs +52 -0
  235. package/templates/features/mobile/onboarding/app/(onboarding)/page-3.tsx.ejs +60 -0
  236. package/templates/features/mobile/paywall/app/paywall.tsx +550 -0
  237. package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx +26 -0
  238. package/templates/features/mobile/tabs/app/(tabs)/index.tsx +565 -0
  239. package/templates/features/web/.gitkeep +0 -0
  240. package/templates/features/web/auth/app/(app)/dashboard/dashboard-client.tsx.ejs +166 -0
  241. package/templates/features/web/auth/app/(app)/dashboard/page.tsx.ejs +24 -0
  242. package/templates/features/web/auth/app/(app)/layout.tsx.ejs +43 -0
  243. package/templates/features/web/auth/app/(app)/settings/sessions/page.tsx.ejs +29 -0
  244. package/templates/features/web/auth/app/(app)/settings/sessions/sessions-client.tsx.ejs +77 -0
  245. package/templates/features/web/auth/app/(auth)/forgot-password/page.tsx.ejs +127 -0
  246. package/templates/features/web/auth/app/(auth)/layout.tsx.ejs +32 -0
  247. package/templates/features/web/auth/app/(auth)/login/page.tsx.ejs +35 -0
  248. package/templates/features/web/auth/app/(auth)/register/page.tsx.ejs +19 -0
  249. package/templates/features/web/auth/app/(auth)/reset-password/page.tsx.ejs +40 -0
  250. package/templates/features/web/auth/app/(auth)/verify-email/page.tsx.ejs +198 -0
  251. package/templates/features/web/auth/app/auth/callback/route.ts.ejs +152 -0
  252. package/templates/features/web/auth/components/auth/login-form.tsx.ejs +100 -0
  253. package/templates/features/web/auth/components/auth/oauth-buttons.tsx.ejs +126 -0
  254. package/templates/features/web/auth/components/auth/password-reset-form.tsx.ejs +103 -0
  255. package/templates/features/web/auth/components/auth/register-form.tsx.ejs +139 -0
  256. package/templates/features/web/auth/components/settings/session-card.tsx.ejs +132 -0
  257. package/templates/integrations/mobile/adjust/services/adjustService.ts.ejs +163 -0
  258. package/templates/integrations/mobile/adjust/store/adjust.store.ts +243 -0
  259. package/templates/integrations/mobile/att/services/attService.ts +84 -0
  260. package/templates/integrations/mobile/att/services/trackingPermissions.ts +208 -0
  261. package/templates/integrations/mobile/att/store/att.store.ts +162 -0
  262. package/templates/integrations/mobile/revenuecat/services/revenuecatService.ts.ejs +174 -0
  263. package/templates/integrations/mobile/revenuecat/store/revenuecat.store.ts +286 -0
  264. package/templates/integrations/mobile/scate/services/scateService.ts.ejs +85 -0
  265. package/templates/integrations/mobile/scate/store/scate.store.ts +125 -0
  266. package/templates/integrations/web/.gitkeep +0 -0
  267. package/templates/shared/.env.example.ejs +21 -0
  268. package/templates/shared/.gitignore.ejs +145 -0
  269. package/templates/shared/README.md.ejs +134 -0
  270. package/templates/shared/docker-compose.prod.yml.ejs +120 -0
  271. package/templates/shared/docker-compose.yml.ejs +129 -0
  272. package/templates/shared/scripts/docker-dev.sh.ejs +395 -0
  273. package/templates/shared/scripts/docker-prod.sh.ejs +542 -0
  274. package/templates/shared/scripts/setup.sh.ejs +979 -0
@@ -0,0 +1,88 @@
1
+ {
2
+ "expo": {
3
+ "name": "<%= projectName %>",
4
+ "slug": "<%= projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-') %>",
5
+ "scheme": "<%= appScheme %>",
6
+ "version": "1.0.0",
7
+ "orientation": "portrait",
8
+ "icon": "./assets/images/icon.png",
9
+ "userInterfaceStyle": "automatic",
10
+ "splash": {
11
+ "image": "./assets/images/splash.png",
12
+ "resizeMode": "contain",
13
+ "backgroundColor": "#ffffff"
14
+ },
15
+ "assetBundlePatterns": [
16
+ "**/*"
17
+ ],
18
+ "ios": {
19
+ "supportsTablet": true,
20
+ "bundleIdentifier": "com.yourcompany.<%= projectName.toLowerCase().replace(/[^a-z0-9]/g, '') %>"<% if (features.authentication.providers.apple) { %>,
21
+ "usesAppleSignIn": true<% } %><% if (integrations.att.enabled) { %>,
22
+ "infoPlist": {
23
+ "NSUserTrackingUsageDescription": "This identifier will be used to deliver personalized ads to you."
24
+ }<% } %>
25
+ },
26
+ "android": {
27
+ "adaptiveIcon": {
28
+ "foregroundImage": "./assets/images/adaptive-icon.png",
29
+ "backgroundColor": "#ffffff"
30
+ },
31
+ "package": "com.yourcompany.<%= projectName.toLowerCase().replace(/[^a-z0-9]/g, '') %>"<% if (integrations.adjust.enabled || integrations.scate.enabled) { %>,
32
+ "permissions": [
33
+ "android.permission.INTERNET",
34
+ "android.permission.ACCESS_NETWORK_STATE"
35
+ ]<% } %>
36
+ },
37
+ "web": {
38
+ "bundler": "metro",
39
+ "output": "static",
40
+ "favicon": "./assets/images/favicon.png"
41
+ },
42
+ "plugins": [
43
+ "expo-router"<% if (integrations.att.enabled) { %>,
44
+ "expo-tracking-transparency"<% } %><% if (features.authentication.enabled) { %>,
45
+ "expo-secure-store"<% } %><% if (features.authentication.providers.google) { %>,
46
+ [
47
+ "@react-native-google-signin/google-signin",
48
+ {
49
+ "iosUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID"
50
+ }
51
+ ]<% } %><% if (features.authentication.providers.apple) { %>,
52
+ "expo-apple-authentication"<% } %>
53
+ ],
54
+ "experiments": {
55
+ "typedRoutes": true
56
+ },
57
+ "extra": {
58
+ "router": {
59
+ "origin": false
60
+ },
61
+ "eas": {
62
+ "projectId": "your-eas-project-id-here"
63
+ },
64
+ "apiUrl": "http://localhost:8080",
65
+ "features": {
66
+ "onboarding": {
67
+ "enabled": <%= features.onboarding.enabled %>
68
+ }
69
+ }<% if (features.authentication.providers.google) { %>,
70
+ "googleOAuth": {
71
+ "webClientId": "YOUR_GOOGLE_WEB_CLIENT_ID",
72
+ "iosClientId": "YOUR_GOOGLE_IOS_CLIENT_ID",
73
+ "androidClientId": "YOUR_GOOGLE_ANDROID_CLIENT_ID"
74
+ }<% } %><% if (integrations.revenueCat.enabled) { %>,
75
+ "revenueCat": {
76
+ "iosKey": "<%= integrations.revenueCat.iosKey || 'YOUR_IOS_API_KEY_HERE' %>",
77
+ "androidKey": "<%= integrations.revenueCat.androidKey || 'YOUR_ANDROID_API_KEY_HERE' %>"
78
+ }<% } %><% if (integrations.adjust.enabled) { %>,
79
+ "adjust": {
80
+ "appToken": "<%= integrations.adjust.appToken || 'YOUR_ADJUST_APP_TOKEN_HERE' %>",
81
+ "environment": "<%= integrations.adjust.environment || 'sandbox' %>"
82
+ }<% } %><% if (integrations.scate.enabled) { %>,
83
+ "scate": {
84
+ "apiKey": "<%= integrations.scate.apiKey || 'YOUR_SCATE_API_KEY_HERE' %>"
85
+ }<% } %>
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "cli": {
3
+ "version": ">= 13.2.0",
4
+ "appVersionSource": "remote"
5
+ },
6
+ "build": {
7
+ "development": {
8
+ "developmentClient": true,
9
+ "distribution": "internal",
10
+ "ios": {
11
+ "simulator": true
12
+ },
13
+ "android": {
14
+ "buildType": "apk"
15
+ }
16
+ },
17
+ "preview": {
18
+ "distribution": "internal",
19
+ "ios": {
20
+ "simulator": false,
21
+ "buildConfiguration": "Release"
22
+ },
23
+ "android": {
24
+ "buildType": "apk"
25
+ }
26
+ },
27
+ "production": {
28
+ "ios": {
29
+ "buildConfiguration": "Release"
30
+ },
31
+ "android": {
32
+ "buildType": "app-bundle"
33
+ }
34
+ }
35
+ },
36
+ "submit": {
37
+ "production": {
38
+ "ios": {
39
+ "appleId": "your.apple.id@example.com",
40
+ "ascAppId": "your-app-store-connect-app-id",
41
+ "appleTeamId": "YOUR_TEAM_ID"
42
+ },
43
+ "android": {
44
+ "serviceAccountKeyPath": "./service-account-key.json",
45
+ "track": "production"
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,9 @@
1
+ const { getDefaultConfig } = require('expo/metro-config');
2
+
3
+ const config = getDefaultConfig(__dirname);
4
+
5
+ // Required for BetterAuth package resolution
6
+ // Enables package exports which BetterAuth uses for module resolution
7
+ config.resolver.unstable_enablePackageExports = true;
8
+
9
+ module.exports = config;
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "<%= projectName %>-mobile",
3
+ "version": "1.0.0",
4
+ "description": "React Native mobile app for <%= projectName %>",
5
+ "main": "expo-router/entry",
6
+ "scripts": {
7
+ "start": "expo start",
8
+ "android": "expo run:android",
9
+ "ios": "expo run:ios",
10
+ "web": "expo start --web",
11
+ "lint": "expo lint",
12
+ "type-check": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@expo/vector-icons": "^15.0.3",
16
+ "@react-native-async-storage/async-storage": "2.2.0",
17
+ "expo": "~54.0.0",
18
+ "expo-application": "~7.0.7",
19
+ "expo-constants": "~18.0.10",
20
+ "expo-font": "~14.0.9",
21
+ "expo-linear-gradient": "~15.0.7",
22
+ "expo-linking": "~8.0.9",
23
+ "expo-router": "~6.0.15",
24
+ "expo-splash-screen": "~31.0.11",
25
+ "expo-status-bar": "~3.0.8",
26
+ "expo-symbols": "~1.0.7",
27
+ "react": "19.1.0",
28
+ "react-native": "0.81.5",
29
+ "react-native-safe-area-context": "~5.6.0",
30
+ "react-native-screens": "~4.16.0",
31
+ "zustand": "^5.0.5"<% if (features.authentication.enabled) { %>,
32
+ "axios": "^1.9.0",
33
+ "expo-secure-store": "~15.0.7",
34
+ "expo-network": "~8.0.7",
35
+ "better-auth": "^1.4.5",
36
+ "@better-auth/expo": "^1.4.5"<% } %><% if (features.authentication.providers.google || features.authentication.providers.apple || features.authentication.providers.github) { %>,
37
+ "expo-web-browser": "~15.0.7"<% } %><% if (features.authentication.providers.google) { %>,
38
+ "@react-native-google-signin/google-signin": "^13.1.0"<% } %><% if (features.authentication.providers.apple) { %>,
39
+ "expo-apple-authentication": "~7.1.3"<% } %><% if (integrations.revenueCat.enabled) { %>,
40
+ "react-native-purchases": "^9.1.0"<% } %><% if (integrations.adjust.enabled) { %>,
41
+ "react-native-adjust": "^5.4.1"<% } %><% if (integrations.scate.enabled) { %>,
42
+ "scatesdk-react": "^0.4.12"<% } %><% if (integrations.att.enabled) { %>,
43
+ "expo-tracking-transparency": "~5.2.4"<% } %>
44
+ },
45
+ "devDependencies": {
46
+ "@babel/core": "^7.25.2",
47
+ "@types/react": "~19.1.10",
48
+ "eslint": "^9.25.0",
49
+ "eslint-config-expo": "~10.0.0",
50
+ "typescript": "~5.9.2"
51
+ },
52
+ "private": true
53
+ }
@@ -0,0 +1,131 @@
1
+ import React, { useRef, useMemo } from 'react';
2
+ import {
3
+ Pressable,
4
+ Text,
5
+ StyleSheet,
6
+ ActivityIndicator,
7
+ ViewStyle,
8
+ TextStyle,
9
+ PressableProps,
10
+ Animated,
11
+ } from 'react-native';
12
+ import { useAppTheme, AppTheme } from '@/context/ThemeContext';
13
+
14
+ interface ButtonProps extends Omit<PressableProps, 'style'> {
15
+ title: string;
16
+ loading?: boolean;
17
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
18
+ size?: 'small' | 'medium' | 'large';
19
+ fullWidth?: boolean;
20
+ style?: ViewStyle;
21
+ textStyle?: TextStyle;
22
+ }
23
+
24
+ export const Button: React.FC<ButtonProps> = ({
25
+ title,
26
+ loading = false,
27
+ variant = 'primary',
28
+ size = 'medium',
29
+ fullWidth = false,
30
+ disabled,
31
+ style,
32
+ textStyle,
33
+ ...props
34
+ }) => {
35
+ const theme = useAppTheme();
36
+ const styles = useMemo(() => createStyles(theme), [theme]);
37
+
38
+ const isDisabled = disabled || loading;
39
+ const scaleAnim = useRef(new Animated.Value(1)).current;
40
+
41
+ const handlePressIn = () => {
42
+ Animated.spring(scaleAnim, {
43
+ toValue: 0.97,
44
+ useNativeDriver: true,
45
+ speed: 50,
46
+ bounciness: 4,
47
+ }).start();
48
+ };
49
+
50
+ const handlePressOut = () => {
51
+ Animated.spring(scaleAnim, {
52
+ toValue: 1,
53
+ useNativeDriver: true,
54
+ speed: 50,
55
+ bounciness: 4,
56
+ }).start();
57
+ };
58
+
59
+ return (
60
+ <Animated.View style={[
61
+ fullWidth && styles.fullWidth,
62
+ { transform: [{ scale: scaleAnim }] },
63
+ ]}>
64
+ <Pressable
65
+ style={[
66
+ styles.base,
67
+ styles[variant],
68
+ styles[size],
69
+ fullWidth && styles.fullWidth,
70
+ isDisabled && styles.disabled,
71
+ variant === 'primary' && !isDisabled && theme.shadows.button,
72
+ style,
73
+ ]}
74
+ disabled={isDisabled}
75
+ onPressIn={handlePressIn}
76
+ onPressOut={handlePressOut}
77
+ {...props}
78
+ >
79
+ {loading ? (
80
+ <ActivityIndicator
81
+ size="small"
82
+ color={variant === 'primary' ? theme.colors.textInverse : theme.colors.primary}
83
+ />
84
+ ) : (
85
+ <Text style={[
86
+ styles.text,
87
+ styles[`${variant}Text` as keyof typeof styles],
88
+ styles[`${size}Text` as keyof typeof styles],
89
+ isDisabled && styles.disabledText,
90
+ textStyle,
91
+ ]}>
92
+ {title}
93
+ </Text>
94
+ )}
95
+ </Pressable>
96
+ </Animated.View>
97
+ );
98
+ };
99
+
100
+ const createStyles = (theme: AppTheme) => StyleSheet.create({
101
+ base: {
102
+ borderRadius: theme.borderRadius.lg,
103
+ justifyContent: 'center',
104
+ alignItems: 'center',
105
+ borderWidth: 1.5,
106
+ borderColor: 'transparent',
107
+ },
108
+ primary: { backgroundColor: theme.colors.primary },
109
+ secondary: { backgroundColor: theme.colors.backgroundSecondary },
110
+ outline: { backgroundColor: 'transparent', borderColor: theme.colors.borderStrong },
111
+ ghost: { backgroundColor: 'transparent' },
112
+
113
+ small: { paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[2], minHeight: 36 },
114
+ medium: { paddingHorizontal: theme.spacing[4], paddingVertical: theme.spacing[3], minHeight: 48 },
115
+ large: { paddingHorizontal: theme.spacing[6], paddingVertical: theme.spacing[4], minHeight: 56 },
116
+
117
+ fullWidth: { width: '100%' },
118
+ disabled: { opacity: 0.5 },
119
+
120
+ text: { fontWeight: '600', textAlign: 'center' },
121
+ primaryText: { color: theme.colors.textInverse },
122
+ secondaryText: { color: theme.colors.text },
123
+ outlineText: { color: theme.colors.text },
124
+ ghostText: { color: theme.colors.primary },
125
+
126
+ smallText: { fontSize: theme.typography.fontSize.sm },
127
+ mediumText: { fontSize: theme.typography.fontSize.base },
128
+ largeText: { fontSize: theme.typography.fontSize.lg },
129
+
130
+ disabledText: {},
131
+ });
@@ -0,0 +1,68 @@
1
+ import React, { useRef, useMemo } from 'react';
2
+ import { View, Text, StyleSheet, ViewStyle, Pressable, Animated } from 'react-native';
3
+ import { useAppTheme, AppTheme } from '@/context/ThemeContext';
4
+
5
+ interface CardProps {
6
+ children: React.ReactNode;
7
+ title?: string;
8
+ subtitle?: string;
9
+ onPress?: () => void;
10
+ style?: ViewStyle;
11
+ variant?: 'default' | 'outlined';
12
+ }
13
+
14
+ export const Card: React.FC<CardProps> = ({
15
+ children, title, subtitle, onPress, style, variant = 'default',
16
+ }) => {
17
+ const theme = useAppTheme();
18
+ const styles = useMemo(() => createStyles(theme), [theme]);
19
+ const scaleAnim = useRef(new Animated.Value(1)).current;
20
+
21
+ const handlePressIn = () => {
22
+ if (onPress) {
23
+ Animated.spring(scaleAnim, { toValue: 0.98, useNativeDriver: true, speed: 50, bounciness: 4 }).start();
24
+ }
25
+ };
26
+
27
+ const handlePressOut = () => {
28
+ if (onPress) {
29
+ Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true, speed: 50, bounciness: 4 }).start();
30
+ }
31
+ };
32
+
33
+ const Content = (
34
+ <>
35
+ {(title || subtitle) && (
36
+ <View style={styles.header}>
37
+ {title && <Text style={styles.title}>{title}</Text>}
38
+ {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
39
+ </View>
40
+ )}
41
+ <View style={styles.content}>{children}</View>
42
+ </>
43
+ );
44
+
45
+ const cardStyle = [styles.card, styles[variant], style];
46
+
47
+ if (onPress) {
48
+ return (
49
+ <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
50
+ <Pressable style={cardStyle} onPress={onPress} onPressIn={handlePressIn} onPressOut={handlePressOut}>
51
+ {Content}
52
+ </Pressable>
53
+ </Animated.View>
54
+ );
55
+ }
56
+
57
+ return <View style={cardStyle}>{Content}</View>;
58
+ };
59
+
60
+ const createStyles = (theme: AppTheme) => StyleSheet.create({
61
+ card: { backgroundColor: theme.colors.card, borderRadius: theme.borderRadius.xl, overflow: 'hidden' },
62
+ default: { borderWidth: 1, borderColor: theme.colors.borderLight, ...theme.shadows.small },
63
+ outlined: { borderWidth: 1, borderColor: theme.colors.border, backgroundColor: 'transparent' },
64
+ header: { padding: theme.spacing[4], paddingBottom: 0 },
65
+ title: { fontSize: theme.typography.fontSize.lg, fontWeight: '600', color: theme.colors.text, marginBottom: theme.spacing[1] },
66
+ subtitle: { fontSize: theme.typography.fontSize.sm, color: theme.colors.textSecondary },
67
+ content: { padding: theme.spacing[4] },
68
+ });
@@ -0,0 +1,90 @@
1
+ // Cross-platform icon component using MaterialIcons as fallback
2
+ // Supports SF Symbols naming for consistency
3
+
4
+ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
5
+ import { SymbolViewProps, SymbolWeight } from "expo-symbols";
6
+ import { ComponentProps } from "react";
7
+ import { OpaqueColorValue, type StyleProp, type TextStyle } from "react-native";
8
+
9
+ /**
10
+ * SF Symbols to Material Icons mappings
11
+ * - see Material Icons: https://icons.expo.fyi
12
+ * - see SF Symbols: https://developer.apple.com/sf-symbols/
13
+ */
14
+ const MAPPING = {
15
+ "house.fill": "home",
16
+ "paperplane.fill": "send",
17
+ "chevron.right": "chevron-right",
18
+ "chevron.left": "chevron-left",
19
+ "chevron.up": "keyboard-arrow-up",
20
+ "chevron.down": "keyboard-arrow-down",
21
+ "arrow.right": "arrow-forward",
22
+ "arrow.left": "arrow-back",
23
+ magnifyingglass: "search",
24
+ "person.fill": "person",
25
+ "doc.text": "description",
26
+ calendar: "event",
27
+ checkmark: "check",
28
+ "checkmark.circle.fill": "check-circle",
29
+ "xmark": "close",
30
+ "xmark.circle.fill": "cancel",
31
+ gear: "settings",
32
+ "gear.circle": "settings",
33
+ "bell.fill": "notifications",
34
+ "heart.fill": "favorite",
35
+ "star.fill": "star",
36
+ "lock.shield.fill": "security",
37
+ "lock.fill": "lock",
38
+ "eye.fill": "visibility",
39
+ "eye.slash.fill": "visibility-off",
40
+ "envelope.fill": "email",
41
+ "phone.fill": "phone",
42
+ trash: "delete",
43
+ plus: "add",
44
+ "plus.circle.fill": "add-circle",
45
+ "minus.circle.fill": "remove-circle",
46
+ globe: "language",
47
+ "square.and.arrow.up": "share",
48
+ "doc.on.doc": "content-copy",
49
+ "list.bullet": "list",
50
+ "arrow.clockwise": "refresh",
51
+ "arrow.clockwise.circle.fill": "refresh",
52
+ "exclamationmark.triangle.fill": "warning",
53
+ "exclamationmark.circle.fill": "error",
54
+ "info.circle.fill": "info",
55
+ "camera.fill": "camera",
56
+ "photo.fill": "photo",
57
+ "mic.fill": "mic",
58
+ "videocam.fill": "videocam",
59
+ sparkles: "auto-awesome",
60
+ infinity: "all-inclusive",
61
+ } as const;
62
+
63
+ export type IconSymbolName = keyof typeof MAPPING;
64
+
65
+ /**
66
+ * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
67
+ * This ensures a consistent look across platforms, and optimal resource usage.
68
+ * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
69
+ */
70
+ export function IconSymbol({
71
+ name,
72
+ size = 24,
73
+ color,
74
+ style,
75
+ }: {
76
+ name: IconSymbolName;
77
+ size?: number;
78
+ color: string | OpaqueColorValue;
79
+ style?: StyleProp<TextStyle>;
80
+ weight?: SymbolWeight;
81
+ }) {
82
+ return (
83
+ <MaterialIcons
84
+ color={color}
85
+ size={size}
86
+ name={MAPPING[name]}
87
+ style={style}
88
+ />
89
+ );
90
+ }
@@ -0,0 +1,142 @@
1
+ import React, { useState, useRef, useMemo } from 'react';
2
+ import {
3
+ View,
4
+ TextInput,
5
+ Text,
6
+ StyleSheet,
7
+ ViewStyle,
8
+ TextStyle,
9
+ TextInputProps,
10
+ Pressable,
11
+ Animated,
12
+ } from 'react-native';
13
+ import { useAppTheme, AppTheme } from '@/context/ThemeContext';
14
+
15
+ interface InputProps extends Omit<TextInputProps, 'style'> {
16
+ label?: string;
17
+ error?: string;
18
+ hint?: string;
19
+ containerStyle?: ViewStyle;
20
+ inputStyle?: TextStyle;
21
+ leftIcon?: React.ReactNode;
22
+ rightIcon?: React.ReactNode;
23
+ showPasswordToggle?: boolean;
24
+ }
25
+
26
+ export const Input: React.FC<InputProps> = ({
27
+ label,
28
+ error,
29
+ hint,
30
+ containerStyle,
31
+ inputStyle,
32
+ leftIcon,
33
+ rightIcon,
34
+ secureTextEntry,
35
+ showPasswordToggle = false,
36
+ ...props
37
+ }) => {
38
+ const theme = useAppTheme();
39
+ const styles = useMemo(() => createStyles(theme), [theme]);
40
+
41
+ const [isSecure, setIsSecure] = useState(secureTextEntry);
42
+ const [isFocused, setIsFocused] = useState(false);
43
+ const borderAnim = useRef(new Animated.Value(0)).current;
44
+
45
+ const hasError = !!error;
46
+
47
+ const handleFocus = () => {
48
+ setIsFocused(true);
49
+ Animated.timing(borderAnim, {
50
+ toValue: 1,
51
+ duration: theme.timing.fast,
52
+ useNativeDriver: false,
53
+ }).start();
54
+ };
55
+
56
+ const handleBlur = () => {
57
+ setIsFocused(false);
58
+ Animated.timing(borderAnim, {
59
+ toValue: 0,
60
+ duration: theme.timing.fast,
61
+ useNativeDriver: false,
62
+ }).start();
63
+ };
64
+
65
+ const borderColor = borderAnim.interpolate({
66
+ inputRange: [0, 1],
67
+ outputRange: [
68
+ hasError ? theme.colors.error : theme.colors.border,
69
+ hasError ? theme.colors.error : theme.colors.primary,
70
+ ],
71
+ });
72
+
73
+ const actualRightIcon = showPasswordToggle && secureTextEntry ? (
74
+ <Pressable onPress={() => setIsSecure(!isSecure)} style={styles.iconContainer} hitSlop={8}>
75
+ <Text style={styles.toggleText}>{isSecure ? 'Show' : 'Hide'}</Text>
76
+ </Pressable>
77
+ ) : rightIcon ? (
78
+ <View style={styles.iconContainer}>{rightIcon}</View>
79
+ ) : null;
80
+
81
+ return (
82
+ <View style={[styles.container, containerStyle]}>
83
+ {label && (
84
+ <Text style={[styles.label, hasError && styles.errorLabel]}>{label}</Text>
85
+ )}
86
+
87
+ <Animated.View style={[styles.inputContainer, { borderColor }]}>
88
+ {leftIcon && <View style={styles.iconContainer}>{leftIcon}</View>}
89
+ <TextInput
90
+ style={[
91
+ styles.input,
92
+ leftIcon && styles.inputWithLeftIcon,
93
+ actualRightIcon && styles.inputWithRightIcon,
94
+ inputStyle,
95
+ ]}
96
+ secureTextEntry={isSecure}
97
+ onFocus={handleFocus}
98
+ onBlur={handleBlur}
99
+ placeholderTextColor={theme.colors.textMuted}
100
+ selectionColor={theme.colors.primary}
101
+ {...props}
102
+ />
103
+ {actualRightIcon}
104
+ </Animated.View>
105
+
106
+ {error && <Text style={styles.errorText}>{error}</Text>}
107
+ {hint && !error && <Text style={styles.hintText}>{hint}</Text>}
108
+ </View>
109
+ );
110
+ };
111
+
112
+ const createStyles = (theme: AppTheme) => StyleSheet.create({
113
+ container: { marginBottom: theme.spacing[4] },
114
+ label: {
115
+ fontSize: theme.typography.fontSize.sm,
116
+ fontWeight: '500',
117
+ color: theme.colors.text,
118
+ marginBottom: theme.spacing[2],
119
+ },
120
+ errorLabel: { color: theme.colors.error },
121
+ inputContainer: {
122
+ flexDirection: 'row',
123
+ alignItems: 'center',
124
+ borderWidth: 1,
125
+ borderRadius: theme.borderRadius.lg,
126
+ backgroundColor: theme.colors.background,
127
+ minHeight: 48,
128
+ },
129
+ input: {
130
+ flex: 1,
131
+ paddingHorizontal: theme.spacing[4],
132
+ paddingVertical: theme.spacing[3],
133
+ fontSize: theme.typography.fontSize.base,
134
+ color: theme.colors.text,
135
+ },
136
+ inputWithLeftIcon: { paddingLeft: theme.spacing[2] },
137
+ inputWithRightIcon: { paddingRight: theme.spacing[2] },
138
+ iconContainer: { paddingHorizontal: theme.spacing[3], justifyContent: 'center', alignItems: 'center' },
139
+ toggleText: { fontSize: theme.typography.fontSize.sm, color: theme.colors.primary, fontWeight: '600' },
140
+ errorText: { fontSize: theme.typography.fontSize.sm, color: theme.colors.error, marginTop: theme.spacing[1] },
141
+ hintText: { fontSize: theme.typography.fontSize.sm, color: theme.colors.textMuted, marginTop: theme.spacing[1] },
142
+ });