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,367 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import { useSession, authClient, getCookies } from '../lib/auth-client';
4
+ import Constants from 'expo-constants';
5
+ <% if (features.authentication.providers.google) { %>
6
+ import { GoogleSignin, statusCodes } from '@react-native-google-signin/google-signin';
7
+ <% } %>
8
+ <% if (features.authentication.providers.apple) { %>
9
+ import * as AppleAuthentication from 'expo-apple-authentication';
10
+ <% } %>
11
+
12
+ // Get API URL from Expo config
13
+ const API_URL = Constants.expoConfig?.extra?.apiUrl || 'http://localhost:8080';
14
+
15
+ /**
16
+ * Auth hook using BetterAuth's useSession() directly
17
+ *
18
+ * This hook provides:
19
+ * - Reactive auth state from BetterAuth's SecureStore
20
+ * - Auth actions (signIn, signUp, signOut, etc.)
21
+ * - Loading and error state for UI feedback
22
+ */
23
+ export const useAuth = () => {
24
+ const { data: session, isPending, error: sessionError, refetch } = useSession();
25
+ const [isLoading, setIsLoading] = useState(false);
26
+ const [error, setError] = useState<string | null>(null);
27
+
28
+ // Computed state from BetterAuth session
29
+ const isAuthenticated = !!(session?.user && session?.session);
30
+ const user = session?.user ?? null;
31
+
32
+ // Sign in with email/password
33
+ const signIn = useCallback(async (credentials: { email: string; password: string }) => {
34
+ setIsLoading(true);
35
+ setError(null);
36
+ try {
37
+ const result = await authClient.signIn.email({
38
+ email: credentials.email,
39
+ password: credentials.password,
40
+ });
41
+ if (result.error) {
42
+ throw new Error(result.error.message || 'Sign in failed');
43
+ }
44
+ return { success: true };
45
+ } catch (err) {
46
+ const message = err instanceof Error ? err.message : 'Sign in failed';
47
+ setError(message);
48
+ return { success: false, error: message };
49
+ } finally {
50
+ setIsLoading(false);
51
+ }
52
+ }, []);
53
+
54
+ // Sign up with email/password
55
+ const signUp = useCallback(async (data: { email: string; password: string; name: string }) => {
56
+ setIsLoading(true);
57
+ setError(null);
58
+ try {
59
+ const result = await authClient.signUp.email({
60
+ email: data.email,
61
+ password: data.password,
62
+ name: data.name,
63
+ });
64
+ if (result.error) {
65
+ throw new Error(result.error.message || 'Sign up failed');
66
+ }
67
+ return { success: true };
68
+ } catch (err) {
69
+ const message = err instanceof Error ? err.message : 'Sign up failed';
70
+ setError(message);
71
+ return { success: false, error: message };
72
+ } finally {
73
+ setIsLoading(false);
74
+ }
75
+ }, []);
76
+
77
+ // Sign out
78
+ const signOut = useCallback(async () => {
79
+ setIsLoading(true);
80
+ try {
81
+ await authClient.signOut();
82
+ return { success: true };
83
+ } catch (err) {
84
+ // Still considered success - BetterAuth will clear local state
85
+ return { success: true };
86
+ } finally {
87
+ setIsLoading(false);
88
+ }
89
+ }, []);
90
+ <% if (features.authentication.providers.google) { %>
91
+ /**
92
+ * Google Sign In with native SDK + browser fallback
93
+ * - Native: Uses @react-native-google-signin for best UX (requires dev build)
94
+ * - Fallback: Opens browser for OAuth (works in Expo Go, without Play Services)
95
+ */
96
+ const signInWithGoogle = useCallback(async (
97
+ callbackURL: string = "/(tabs)",
98
+ errorCallbackURL: string = "/(auth)/login"
99
+ ) => {
100
+ setIsLoading(true);
101
+ setError(null);
102
+
103
+ try {
104
+ // Try native sign-in first
105
+ let idToken: string | null = null;
106
+
107
+ try {
108
+ GoogleSignin.configure({
109
+ webClientId: Constants.expoConfig?.extra?.googleOAuth?.webClientId,
110
+ iosClientId: Constants.expoConfig?.extra?.googleOAuth?.iosClientId,
111
+ });
112
+
113
+ await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
114
+ const userInfo = await GoogleSignin.signIn();
115
+ idToken = userInfo.data?.idToken || null;
116
+ } catch (nativeError: any) {
117
+ // Native failed - check if user cancelled
118
+ if (nativeError.code === statusCodes.SIGN_IN_CANCELLED) {
119
+ throw new Error('Sign in cancelled');
120
+ }
121
+
122
+ // Fall back to browser OAuth (Expo Go, no Play Services, etc.)
123
+ console.log('Native Google Sign-In unavailable, using browser fallback');
124
+ const result = await authClient.signIn.social({
125
+ provider: 'google',
126
+ callbackURL,
127
+ errorCallbackURL,
128
+ });
129
+
130
+ if (result.error) {
131
+ throw new Error(result.error.message || 'Google sign in failed');
132
+ }
133
+ return { success: true };
134
+ }
135
+
136
+ // Native succeeded - send ID token to Better Auth
137
+ if (!idToken) {
138
+ throw new Error('No ID token received from Google');
139
+ }
140
+
141
+ const result = await authClient.signIn.social({
142
+ provider: 'google',
143
+ idToken: {
144
+ token: idToken,
145
+ },
146
+ });
147
+
148
+ if (result.error) {
149
+ throw new Error(result.error.message || 'Google sign in failed');
150
+ }
151
+ return { success: true };
152
+ } catch (err: any) {
153
+ const message = err instanceof Error ? err.message : 'Google sign in failed';
154
+ setError(message);
155
+ return { success: false, error: message };
156
+ } finally {
157
+ setIsLoading(false);
158
+ }
159
+ }, []);
160
+ <% } %>
161
+ <% if (features.authentication.providers.apple) { %>
162
+ /**
163
+ * Apple Sign In with native SDK + browser fallback
164
+ * - iOS: Uses expo-apple-authentication for Face ID, native bottom sheet
165
+ * - Android/Fallback: Opens browser for OAuth
166
+ */
167
+ const signInWithApple = useCallback(async (
168
+ callbackURL: string = "/(tabs)",
169
+ errorCallbackURL: string = "/(auth)/login"
170
+ ) => {
171
+ setIsLoading(true);
172
+ setError(null);
173
+
174
+ try {
175
+ // iOS: Try native Apple Sign In
176
+ if (Platform.OS === 'ios') {
177
+ const isAvailable = await AppleAuthentication.isAvailableAsync();
178
+
179
+ if (isAvailable) {
180
+ try {
181
+ const credential = await AppleAuthentication.signInAsync({
182
+ requestedScopes: [
183
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
184
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
185
+ ],
186
+ });
187
+
188
+ if (!credential.identityToken) {
189
+ throw new Error('No identity token received from Apple');
190
+ }
191
+
192
+ const result = await authClient.signIn.social({
193
+ provider: 'apple',
194
+ idToken: {
195
+ token: credential.identityToken,
196
+ },
197
+ });
198
+
199
+ if (result.error) {
200
+ throw new Error(result.error.message || 'Apple sign in failed');
201
+ }
202
+ return { success: true };
203
+ } catch (appleError: any) {
204
+ if (appleError.code === 'ERR_REQUEST_CANCELED') {
205
+ throw new Error('Sign in cancelled');
206
+ }
207
+ throw appleError;
208
+ }
209
+ }
210
+ }
211
+
212
+ // Android or iOS native unavailable: Use browser OAuth
213
+ console.log('Native Apple Sign-In unavailable, using browser fallback');
214
+ const result = await authClient.signIn.social({
215
+ provider: 'apple',
216
+ callbackURL,
217
+ errorCallbackURL,
218
+ });
219
+
220
+ if (result.error) {
221
+ throw new Error(result.error.message || 'Apple sign in failed');
222
+ }
223
+ return { success: true };
224
+ } catch (err: any) {
225
+ const message = err instanceof Error ? err.message : 'Apple sign in failed';
226
+ setError(message);
227
+ return { success: false, error: message };
228
+ } finally {
229
+ setIsLoading(false);
230
+ }
231
+ }, []);
232
+ <% } %>
233
+ <% if (features.authentication.providers.github) { %>
234
+ /**
235
+ * GitHub Sign In - Browser OAuth only (no native SDK exists)
236
+ */
237
+ const signInWithGitHub = useCallback(async (
238
+ callbackURL: string = "/(tabs)",
239
+ errorCallbackURL: string = "/(auth)/login"
240
+ ) => {
241
+ setIsLoading(true);
242
+ setError(null);
243
+
244
+ try {
245
+ const result = await authClient.signIn.social({
246
+ provider: 'github',
247
+ callbackURL,
248
+ errorCallbackURL,
249
+ });
250
+
251
+ if (result.error) {
252
+ throw new Error(result.error.message || 'GitHub sign in failed');
253
+ }
254
+ return { success: true };
255
+ } catch (err: any) {
256
+ const message = err instanceof Error ? err.message : 'GitHub sign in failed';
257
+ setError(message);
258
+ return { success: false, error: message };
259
+ } finally {
260
+ setIsLoading(false);
261
+ }
262
+ }, []);
263
+ <% } %>
264
+
265
+ // Update profile (custom endpoint)
266
+ const updateProfile = useCallback(async (data: { name?: string }) => {
267
+ setIsLoading(true);
268
+ try {
269
+ const response = await fetch(`${API_URL}/api/auth/profile`, {
270
+ method: 'PUT',
271
+ headers: {
272
+ 'Content-Type': 'application/json',
273
+ 'Cookie': getCookies() || '',
274
+ },
275
+ credentials: 'omit',
276
+ body: JSON.stringify(data),
277
+ });
278
+ if (!response.ok) {
279
+ throw new Error('Update failed');
280
+ }
281
+ // Refetch session to get updated user data
282
+ await refetch();
283
+ return { success: true };
284
+ } catch (err) {
285
+ const message = err instanceof Error ? err.message : 'Update failed';
286
+ return { success: false, error: message };
287
+ } finally {
288
+ setIsLoading(false);
289
+ }
290
+ }, [refetch]);
291
+
292
+ // Change password
293
+ const changePassword = useCallback(async (currentPassword: string, newPassword: string) => {
294
+ setIsLoading(true);
295
+ try {
296
+ const result = await authClient.changePassword({
297
+ currentPassword,
298
+ newPassword,
299
+ });
300
+ if (result.error) {
301
+ throw new Error(result.error.message || 'Password change failed');
302
+ }
303
+ return { success: true };
304
+ } catch (err) {
305
+ const message = err instanceof Error ? err.message : 'Password change failed';
306
+ return { success: false, error: message };
307
+ } finally {
308
+ setIsLoading(false);
309
+ }
310
+ }, []);
311
+
312
+ // Delete account (custom endpoint)
313
+ const deleteAccount = useCallback(async () => {
314
+ setIsLoading(true);
315
+ try {
316
+ const response = await fetch(`${API_URL}/api/auth/account`, {
317
+ method: 'DELETE',
318
+ headers: {
319
+ 'Cookie': getCookies() || '',
320
+ },
321
+ credentials: 'omit',
322
+ });
323
+ if (!response.ok) {
324
+ throw new Error('Delete account failed');
325
+ }
326
+ return { success: true };
327
+ } catch (err) {
328
+ const message = err instanceof Error ? err.message : 'Delete account failed';
329
+ return { success: false, error: message };
330
+ } finally {
331
+ setIsLoading(false);
332
+ }
333
+ }, []);
334
+
335
+ const clearError = useCallback(() => setError(null), []);
336
+
337
+ return {
338
+ // State (from BetterAuth's useSession)
339
+ user,
340
+ session: session?.session ?? null,
341
+ isAuthenticated,
342
+ isPending,
343
+
344
+ // Local state for UI feedback
345
+ isLoading,
346
+ error,
347
+
348
+ // Actions
349
+ signIn,
350
+ signUp,
351
+ signOut,
352
+ <% if (features.authentication.providers.google) { %>
353
+ signInWithGoogle,
354
+ <% } %>
355
+ <% if (features.authentication.providers.apple) { %>
356
+ signInWithApple,
357
+ <% } %>
358
+ <% if (features.authentication.providers.github) { %>
359
+ signInWithGitHub,
360
+ <% } %>
361
+ updateProfile,
362
+ changePassword,
363
+ deleteAccount,
364
+ clearError,
365
+ refetch,
366
+ };
367
+ };