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,407 @@
1
+ <% const hasOAuth = features.authentication.providers.google || features.authentication.providers.apple || features.authentication.providers.github; %>
2
+ import React, { useState, useEffect, useMemo } from 'react';
3
+ import {
4
+ View,
5
+ Text,
6
+ StyleSheet,
7
+ KeyboardAvoidingView,
8
+ Platform,
9
+ ScrollView,
10
+ Alert,
11
+ <% if (hasOAuth) { %>
12
+ TouchableOpacity,
13
+ <% } %>
14
+ } from 'react-native';
15
+ import { Button, Input } from '../ui';
16
+ import { useAuth } from '../../hooks';
17
+ import { isValidEmail } from '../../utils/formatters';
18
+ import { useAppTheme, AppTheme } from '@/context/ThemeContext';
19
+
20
+ interface RegisterFormProps {
21
+ onSuccess?: () => void;
22
+ onSwitchToLogin?: () => void;
23
+ }
24
+
25
+ interface FormData {
26
+ name: string;
27
+ email: string;
28
+ password: string;
29
+ passwordConfirmation: string;
30
+ }
31
+
32
+ interface FormErrors {
33
+ name?: string;
34
+ email?: string;
35
+ password?: string;
36
+ passwordConfirmation?: string;
37
+ }
38
+
39
+ export const RegisterForm: React.FC<RegisterFormProps> = ({
40
+ onSuccess,
41
+ onSwitchToLogin,
42
+ }) => {
43
+ const theme = useAppTheme();
44
+ const styles = useMemo(() => createStyles(theme), [theme]);
45
+
46
+ const [formData, setFormData] = useState<FormData>({
47
+ name: '',
48
+ email: '',
49
+ password: '',
50
+ passwordConfirmation: '',
51
+ });
52
+ const [errors, setErrors] = useState<FormErrors>({});
53
+
54
+ const {
55
+ signUp,
56
+ isLoading,
57
+ clearError,
58
+ <% if (features.authentication.providers.google) { %>
59
+ signInWithGoogle,
60
+ <% } %>
61
+ <% if (features.authentication.providers.apple) { %>
62
+ signInWithApple,
63
+ <% } %>
64
+ <% if (features.authentication.providers.github) { %>
65
+ signInWithGitHub,
66
+ <% } %>
67
+ } = useAuth();
68
+
69
+ // Clear auth error on unmount
70
+ useEffect(() => {
71
+ return () => clearError();
72
+ }, [clearError]);
73
+
74
+ const updateField = (field: keyof FormData, value: string) => {
75
+ setFormData(prev => ({ ...prev, [field]: value }));
76
+
77
+ // Clear error when user starts typing
78
+ if (errors[field]) {
79
+ setErrors(prev => ({ ...prev, [field]: undefined }));
80
+ }
81
+ };
82
+
83
+ const validateForm = (): boolean => {
84
+ const newErrors: FormErrors = {};
85
+
86
+ // Name validation
87
+ if (!formData.name.trim()) {
88
+ newErrors.name = 'Name is required';
89
+ } else if (formData.name.trim().length < 2) {
90
+ newErrors.name = 'Name must be at least 2 characters';
91
+ }
92
+
93
+ // Email validation
94
+ if (!formData.email.trim()) {
95
+ newErrors.email = 'Email is required';
96
+ } else if (!isValidEmail(formData.email.trim())) {
97
+ newErrors.email = 'Please enter a valid email';
98
+ }
99
+
100
+ // Password validation
101
+ if (!formData.password) {
102
+ newErrors.password = 'Password is required';
103
+ } else if (formData.password.length < 6) {
104
+ newErrors.password = 'Password must be at least 6 characters';
105
+ }
106
+
107
+ // Password confirmation validation
108
+ if (!formData.passwordConfirmation) {
109
+ newErrors.passwordConfirmation = 'Password confirmation is required';
110
+ } else if (formData.password !== formData.passwordConfirmation) {
111
+ newErrors.passwordConfirmation = 'Passwords do not match';
112
+ }
113
+
114
+ setErrors(newErrors);
115
+ return Object.keys(newErrors).length === 0;
116
+ };
117
+
118
+ const handleSubmit = async () => {
119
+ if (!validateForm()) return;
120
+
121
+ const result = await signUp({
122
+ name: formData.name.trim(),
123
+ email: formData.email.trim().toLowerCase(),
124
+ password: formData.password,
125
+ });
126
+
127
+ if (result.success) {
128
+ onSuccess?.();
129
+ } else {
130
+ // Show server error via Alert
131
+ Alert.alert('Registration Failed', result.error || 'Please try again.');
132
+ }
133
+ };
134
+ <% if (features.authentication.providers.google) { %>
135
+
136
+ const handleGoogleSignIn = async () => {
137
+ const result = await signInWithGoogle();
138
+ // Don't call onSuccess here - browser OAuth completes asynchronously
139
+ // via deep link callback. The parent screen watches isAuthenticated
140
+ // state and will navigate when the session is established.
141
+ if (result.error && result.error !== 'Sign in cancelled') {
142
+ Alert.alert('Sign In Failed', result.error);
143
+ }
144
+ };
145
+ <% } %>
146
+ <% if (features.authentication.providers.apple) { %>
147
+
148
+ const handleAppleSignIn = async () => {
149
+ const result = await signInWithApple();
150
+ // Don't call onSuccess here - browser OAuth completes asynchronously
151
+ if (result.error && result.error !== 'Sign in cancelled') {
152
+ Alert.alert('Sign In Failed', result.error);
153
+ }
154
+ };
155
+ <% } %>
156
+ <% if (features.authentication.providers.github) { %>
157
+
158
+ const handleGitHubSignIn = async () => {
159
+ const result = await signInWithGitHub();
160
+ // Don't call onSuccess here - browser OAuth completes asynchronously
161
+ if (result.error && result.error !== 'Sign in cancelled') {
162
+ Alert.alert('Sign In Failed', result.error);
163
+ }
164
+ };
165
+ <% } %>
166
+
167
+ return (
168
+ <KeyboardAvoidingView
169
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
170
+ style={styles.container}
171
+ >
172
+ <ScrollView
173
+ contentContainerStyle={styles.scrollContent}
174
+ keyboardShouldPersistTaps="handled"
175
+ >
176
+ <View style={styles.form}>
177
+ <Text style={styles.title}>Create Account</Text>
178
+ <Text style={styles.subtitle}>Sign up to get started</Text>
179
+
180
+ <Input
181
+ label="Full Name"
182
+ value={formData.name}
183
+ onChangeText={(text) => updateField('name', text)}
184
+ placeholder="Enter your full name"
185
+ autoCapitalize="words"
186
+ autoComplete="name"
187
+ error={errors.name}
188
+ containerStyle={styles.inputContainer}
189
+ />
190
+
191
+ <Input
192
+ label="Email"
193
+ value={formData.email}
194
+ onChangeText={(text) => updateField('email', text)}
195
+ placeholder="Enter your email"
196
+ keyboardType="email-address"
197
+ autoCapitalize="none"
198
+ autoComplete="email"
199
+ error={errors.email}
200
+ containerStyle={styles.inputContainer}
201
+ />
202
+
203
+ <Input
204
+ label="Password"
205
+ value={formData.password}
206
+ onChangeText={(text) => updateField('password', text)}
207
+ placeholder="Create a password"
208
+ secureTextEntry
209
+ showPasswordToggle
210
+ autoComplete="password-new"
211
+ error={errors.password}
212
+ hint="Password must be at least 6 characters"
213
+ containerStyle={styles.inputContainer}
214
+ />
215
+
216
+ <Input
217
+ label="Confirm Password"
218
+ value={formData.passwordConfirmation}
219
+ onChangeText={(text) => updateField('passwordConfirmation', text)}
220
+ placeholder="Confirm your password"
221
+ secureTextEntry
222
+ showPasswordToggle
223
+ autoComplete="password-new"
224
+ error={errors.passwordConfirmation}
225
+ containerStyle={styles.inputContainer}
226
+ />
227
+
228
+ <Button
229
+ title="Create Account"
230
+ onPress={handleSubmit}
231
+ loading={isLoading}
232
+ disabled={isLoading}
233
+ style={styles.submitButton}
234
+ />
235
+ <% if (hasOAuth) { %>
236
+
237
+ {/* OAuth Divider */}
238
+ <View style={styles.dividerContainer}>
239
+ <View style={styles.dividerLine} />
240
+ <Text style={styles.dividerText}>or continue with</Text>
241
+ <View style={styles.dividerLine} />
242
+ </View>
243
+
244
+ {/* OAuth Buttons */}
245
+ <View style={styles.oauthContainer}>
246
+ <% if (features.authentication.providers.google) { %>
247
+ <TouchableOpacity
248
+ style={styles.oauthButton}
249
+ onPress={handleGoogleSignIn}
250
+ disabled={isLoading}
251
+ activeOpacity={0.7}
252
+ >
253
+ <Text style={styles.oauthButtonText}>
254
+ <Text style={styles.googleIcon}>G</Text>{' '}Google
255
+ </Text>
256
+ </TouchableOpacity>
257
+ <% } %>
258
+ <% if (features.authentication.providers.apple) { %>
259
+ <TouchableOpacity
260
+ style={styles.oauthButton}
261
+ onPress={handleAppleSignIn}
262
+ disabled={isLoading}
263
+ activeOpacity={0.7}
264
+ >
265
+ <Text style={styles.oauthButtonText}>Apple</Text>
266
+ </TouchableOpacity>
267
+ <% } %>
268
+ <% if (features.authentication.providers.github) { %>
269
+ <TouchableOpacity
270
+ style={styles.oauthButton}
271
+ onPress={handleGitHubSignIn}
272
+ disabled={isLoading}
273
+ activeOpacity={0.7}
274
+ >
275
+ <Text style={styles.oauthButtonText}>GitHub</Text>
276
+ </TouchableOpacity>
277
+ <% } %>
278
+ </View>
279
+ <% } %>
280
+
281
+ {onSwitchToLogin && (
282
+ <View style={styles.switchContainer}>
283
+ <Text style={styles.switchText}>
284
+ Already have an account?{' '}
285
+ </Text>
286
+ <Button
287
+ title="Sign In"
288
+ variant="ghost"
289
+ onPress={onSwitchToLogin}
290
+ style={styles.switchButton}
291
+ textStyle={styles.switchButtonText}
292
+ />
293
+ </View>
294
+ )}
295
+ </View>
296
+ </ScrollView>
297
+ </KeyboardAvoidingView>
298
+ );
299
+ };
300
+
301
+ const createStyles = (theme: AppTheme) => StyleSheet.create({
302
+ container: {
303
+ flex: 1,
304
+ },
305
+
306
+ scrollContent: {
307
+ flexGrow: 1,
308
+ justifyContent: 'center',
309
+ padding: theme.spacing[5],
310
+ },
311
+
312
+ form: {
313
+ width: '100%',
314
+ maxWidth: 400,
315
+ alignSelf: 'center',
316
+ },
317
+
318
+ title: {
319
+ fontSize: theme.typography.fontSize['2xl'],
320
+ fontWeight: 'bold',
321
+ color: theme.colors.text,
322
+ textAlign: 'center',
323
+ marginBottom: theme.spacing[2],
324
+ },
325
+
326
+ subtitle: {
327
+ fontSize: theme.typography.fontSize.base,
328
+ color: theme.colors.textSecondary,
329
+ textAlign: 'center',
330
+ marginBottom: theme.spacing[8],
331
+ },
332
+
333
+ inputContainer: {
334
+ marginBottom: theme.spacing[5],
335
+ },
336
+
337
+ submitButton: {
338
+ marginTop: theme.spacing[2],
339
+ marginBottom: theme.spacing[6],
340
+ },
341
+ <% if (hasOAuth) { %>
342
+
343
+ // Divider
344
+ dividerContainer: {
345
+ flexDirection: 'row',
346
+ alignItems: 'center',
347
+ marginBottom: theme.spacing[6],
348
+ },
349
+ dividerLine: {
350
+ flex: 1,
351
+ height: 1,
352
+ backgroundColor: theme.colors.border,
353
+ },
354
+ dividerText: {
355
+ marginHorizontal: theme.spacing[4],
356
+ fontSize: theme.typography.fontSize.sm,
357
+ color: theme.colors.textSecondary,
358
+ },
359
+
360
+ // OAuth buttons - theme-aware (matches web app pattern)
361
+ oauthContainer: {
362
+ gap: theme.spacing[3],
363
+ marginBottom: theme.spacing[6],
364
+ },
365
+ oauthButton: {
366
+ flexDirection: 'row',
367
+ alignItems: 'center',
368
+ justifyContent: 'center',
369
+ paddingVertical: 14,
370
+ borderRadius: theme.borderRadius.md,
371
+ borderWidth: 1,
372
+ backgroundColor: 'transparent',
373
+ borderColor: theme.colors.border,
374
+ },
375
+ oauthButtonText: {
376
+ fontSize: theme.typography.fontSize.base,
377
+ fontWeight: '600',
378
+ color: theme.colors.text,
379
+ },
380
+ googleIcon: {
381
+ color: theme.colors.primary,
382
+ fontWeight: '700',
383
+ fontSize: 18,
384
+ },
385
+ <% } %>
386
+
387
+ switchContainer: {
388
+ flexDirection: 'row',
389
+ justifyContent: 'center',
390
+ alignItems: 'center',
391
+ },
392
+
393
+ switchText: {
394
+ fontSize: theme.typography.fontSize.base,
395
+ color: theme.colors.textSecondary,
396
+ },
397
+
398
+ switchButton: {
399
+ paddingHorizontal: 0,
400
+ paddingVertical: 0,
401
+ minHeight: 'auto' as unknown as number,
402
+ },
403
+
404
+ switchButtonText: {
405
+ fontSize: theme.typography.fontSize.base,
406
+ },
407
+ });
@@ -0,0 +1,2 @@
1
+ export * from './LoginForm';
2
+ export * from './RegisterForm';
@@ -0,0 +1 @@
1
+ export * from './useAuth';