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.
- package/LICENSE +21 -0
- package/README.md +642 -0
- package/bin/cli.js +12 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/dependencies.d.ts +82 -0
- package/dist/config/dependencies.d.ts.map +1 -0
- package/dist/config/dependencies.js +82 -0
- package/dist/config/dependencies.js.map +1 -0
- package/dist/config/presets.d.ts +3 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +174 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/generators/index.d.ts +40 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +130 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/onboarding.d.ts +8 -0
- package/dist/generators/onboarding.d.ts.map +1 -0
- package/dist/generators/onboarding.js +141 -0
- package/dist/generators/onboarding.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/features.d.ts +14 -0
- package/dist/prompts/features.d.ts.map +1 -0
- package/dist/prompts/features.js +96 -0
- package/dist/prompts/features.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +93 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +6 -0
- package/dist/prompts/onboarding.d.ts.map +1 -0
- package/dist/prompts/onboarding.js +37 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/orm.d.ts +3 -0
- package/dist/prompts/orm.d.ts.map +1 -0
- package/dist/prompts/orm.js +23 -0
- package/dist/prompts/orm.js.map +1 -0
- package/dist/prompts/packageManager.d.ts +2 -0
- package/dist/prompts/packageManager.d.ts.map +1 -0
- package/dist/prompts/packageManager.js +18 -0
- package/dist/prompts/packageManager.js.map +1 -0
- package/dist/prompts/platform.d.ts +3 -0
- package/dist/prompts/platform.d.ts.map +1 -0
- package/dist/prompts/platform.js +21 -0
- package/dist/prompts/platform.js.map +1 -0
- package/dist/prompts/preset.d.ts +4 -0
- package/dist/prompts/preset.d.ts.map +1 -0
- package/dist/prompts/preset.js +165 -0
- package/dist/prompts/preset.js.map +1 -0
- package/dist/prompts/project.d.ts +2 -0
- package/dist/prompts/project.d.ts.map +1 -0
- package/dist/prompts/project.js +27 -0
- package/dist/prompts/project.js.map +1 -0
- package/dist/prompts/sdks.d.ts +2 -0
- package/dist/prompts/sdks.d.ts.map +1 -0
- package/dist/prompts/sdks.js +46 -0
- package/dist/prompts/sdks.js.map +1 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +25 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cleanup.d.ts +5 -0
- package/dist/utils/cleanup.d.ts.map +1 -0
- package/dist/utils/cleanup.js +38 -0
- package/dist/utils/cleanup.js.map +1 -0
- package/dist/utils/copy.d.ts +10 -0
- package/dist/utils/copy.d.ts.map +1 -0
- package/dist/utils/copy.js +53 -0
- package/dist/utils/copy.js.map +1 -0
- package/dist/utils/errors.d.ts +33 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +136 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/git.d.ts +5 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +33 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/package.d.ts +16 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +86 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/system-validation.d.ts +9 -0
- package/dist/utils/system-validation.d.ts.map +1 -0
- package/dist/utils/system-validation.js +31 -0
- package/dist/utils/system-validation.js.map +1 -0
- package/dist/utils/template.d.ts +20 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +234 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +94 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +96 -0
- package/templates/base/backend/.dockerignore.ejs +62 -0
- package/templates/base/backend/.env.example.ejs +116 -0
- package/templates/base/backend/Dockerfile.ejs +142 -0
- package/templates/base/backend/controllers/event-queue/index.ts +20 -0
- package/templates/base/backend/controllers/event-queue/workers/user.ts +39 -0
- package/templates/base/backend/controllers/rest-api/index.ts +48 -0
- package/templates/base/backend/controllers/rest-api/plugins/auth.ts +152 -0
- package/templates/base/backend/controllers/rest-api/plugins/config.ts +64 -0
- package/templates/base/backend/controllers/rest-api/plugins/error-handler.ts +118 -0
- package/templates/base/backend/controllers/rest-api/routes/auth.ts.ejs +180 -0
- package/templates/base/backend/controllers/rest-api/routes/device-sessions.ts +197 -0
- package/templates/base/backend/controllers/rest-api/routes/oauth-web.ts.ejs +375 -0
- package/templates/base/backend/controllers/rest-api/server.ts.ejs +87 -0
- package/templates/base/backend/domain/device-session/repository.drizzle.ts +209 -0
- package/templates/base/backend/domain/device-session/repository.prisma.ts +248 -0
- package/templates/base/backend/domain/device-session/schema.ts +72 -0
- package/templates/base/backend/domain/session/repository.drizzle.ts +72 -0
- package/templates/base/backend/domain/session/repository.prisma.ts +72 -0
- package/templates/base/backend/domain/session/schema.ts +29 -0
- package/templates/base/backend/domain/user/repository.drizzle.ts +127 -0
- package/templates/base/backend/domain/user/repository.prisma.ts +115 -0
- package/templates/base/backend/domain/user/schema.ts +14 -0
- package/templates/base/backend/drizzle/schema.drizzle.ts +111 -0
- package/templates/base/backend/drizzle.config.drizzle.ts +13 -0
- package/templates/base/backend/lib/auth.drizzle.ts.ejs +104 -0
- package/templates/base/backend/lib/auth.prisma.ts.ejs +97 -0
- package/templates/base/backend/lib/constants.ts.ejs +29 -0
- package/templates/base/backend/package.json.ejs +50 -0
- package/templates/base/backend/prisma/schema.prisma.ejs +102 -0
- package/templates/base/backend/prisma.config.prisma.ts +12 -0
- package/templates/base/backend/tsconfig.json +39 -0
- package/templates/base/backend/utils/db.drizzle.ts +41 -0
- package/templates/base/backend/utils/db.prisma.ts +51 -0
- package/templates/base/backend/utils/email.ts.ejs +35 -0
- package/templates/base/backend/utils/errors.ts +348 -0
- package/templates/base/backend/utils/redis.ts.ejs +279 -0
- package/templates/base/mobile/.env.example.ejs +35 -0
- package/templates/base/mobile/.gitignore.ejs +167 -0
- package/templates/base/mobile/app/+not-found.tsx +85 -0
- package/templates/base/mobile/app/_layout.tsx.ejs +71 -0
- package/templates/base/mobile/app.json.ejs +88 -0
- package/templates/base/mobile/assets/images/adaptive-icon.png +0 -0
- package/templates/base/mobile/assets/images/favicon.png +0 -0
- package/templates/base/mobile/assets/images/icon.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_1.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_2.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_3.png +0 -0
- package/templates/base/mobile/assets/images/paywall_image.png +0 -0
- package/templates/base/mobile/assets/images/splash.png +0 -0
- package/templates/base/mobile/eas.json.ejs +49 -0
- package/templates/base/mobile/metro.config.js +9 -0
- package/templates/base/mobile/package.json.ejs +53 -0
- package/templates/base/mobile/src/components/ui/Button.tsx +131 -0
- package/templates/base/mobile/src/components/ui/Card.tsx +68 -0
- package/templates/base/mobile/src/components/ui/IconSymbol.tsx +90 -0
- package/templates/base/mobile/src/components/ui/Input.tsx +142 -0
- package/templates/base/mobile/src/components/ui/LoadingSpinner.tsx +98 -0
- package/templates/base/mobile/src/components/ui/OnboardingLayout.tsx +356 -0
- package/templates/base/mobile/src/components/ui/PaywallLayout.tsx +311 -0
- package/templates/base/mobile/src/components/ui/Skeleton.tsx +58 -0
- package/templates/base/mobile/src/components/ui/index.ts +6 -0
- package/templates/base/mobile/src/constants/Theme.ts +163 -0
- package/templates/base/mobile/src/context/ThemeContext.tsx +157 -0
- package/templates/base/mobile/src/lib/auth-client.ts.ejs +51 -0
- package/templates/base/mobile/src/services/api.ts.ejs +71 -0
- package/templates/base/mobile/src/services/errorService.ts +179 -0
- package/templates/base/mobile/src/services/sdkInitializer.ts.ejs +36 -0
- package/templates/base/mobile/src/store/index.ts.ejs +18 -0
- package/templates/base/mobile/src/store/ui.store.ts +100 -0
- package/templates/base/mobile/src/utils/formatters.ts +105 -0
- package/templates/base/mobile/src/utils/logger.ts +73 -0
- package/templates/base/mobile/src/utils/responsive.ts +234 -0
- package/templates/base/mobile/tsconfig.json +32 -0
- package/templates/base/web/.env.example.ejs +26 -0
- package/templates/base/web/components.json +22 -0
- package/templates/base/web/eslint.config.mjs +18 -0
- package/templates/base/web/next.config.ts +7 -0
- package/templates/base/web/package.json.ejs +35 -0
- package/templates/base/web/postcss.config.mjs +7 -0
- package/templates/base/web/public/.gitkeep +0 -0
- package/templates/base/web/public/file.svg +1 -0
- package/templates/base/web/public/globe.svg +1 -0
- package/templates/base/web/public/next.svg +1 -0
- package/templates/base/web/public/vercel.svg +1 -0
- package/templates/base/web/public/window.svg +1 -0
- package/templates/base/web/src/app/favicon.ico +0 -0
- package/templates/base/web/src/app/globals.css +152 -0
- package/templates/base/web/src/app/layout.tsx.ejs +54 -0
- package/templates/base/web/src/app/page.tsx.ejs +92 -0
- package/templates/base/web/src/components/auth/auth-hydrator.tsx.ejs +19 -0
- package/templates/base/web/src/components/auth/protected-route.tsx.ejs +109 -0
- package/templates/base/web/src/components/providers/device-session-setup.tsx.ejs +56 -0
- package/templates/base/web/src/components/providers/theme-provider.tsx +17 -0
- package/templates/base/web/src/components/theme-toggle.tsx +34 -0
- package/templates/base/web/src/components/ui/button.tsx +62 -0
- package/templates/base/web/src/components/ui/card.tsx +92 -0
- package/templates/base/web/src/components/ui/input.tsx +21 -0
- package/templates/base/web/src/components/ui/label.tsx +24 -0
- package/templates/base/web/src/components/ui/skeleton.tsx +13 -0
- package/templates/base/web/src/components/ui/spinner.tsx +20 -0
- package/templates/base/web/src/hooks/use-device-session.ts.ejs +40 -0
- package/templates/base/web/src/hooks/use-session.ts.ejs +56 -0
- package/templates/base/web/src/lib/auth/actions.ts.ejs +334 -0
- package/templates/base/web/src/lib/auth/config.ts.ejs +65 -0
- package/templates/base/web/src/lib/auth/cookies.ts.ejs +74 -0
- package/templates/base/web/src/lib/auth/index.ts.ejs +40 -0
- package/templates/base/web/src/lib/auth/oauth.ts.ejs +72 -0
- package/templates/base/web/src/lib/auth/pkce.ts.ejs +48 -0
- package/templates/base/web/src/lib/auth/sessions.ts.ejs +135 -0
- package/templates/base/web/src/lib/auth/user-agent.ts.ejs +47 -0
- package/templates/base/web/src/lib/device/actions.ts.ejs +148 -0
- package/templates/base/web/src/lib/device/id.ts.ejs +74 -0
- package/templates/base/web/src/lib/utils.ts +6 -0
- package/templates/base/web/src/proxy.ts.ejs +66 -0
- package/templates/base/web/src/store/auth.store.ts.ejs +89 -0
- package/templates/base/web/src/store/deviceSession.store.ts.ejs +141 -0
- package/templates/base/web/tsconfig.json +34 -0
- package/templates/features/mobile/auth/app/(auth)/_layout.tsx +16 -0
- package/templates/features/mobile/auth/app/(auth)/login.tsx +86 -0
- package/templates/features/mobile/auth/app/(auth)/register.tsx +86 -0
- package/templates/features/mobile/auth/components/auth/LoginForm.tsx.ejs +349 -0
- package/templates/features/mobile/auth/components/auth/RegisterForm.tsx.ejs +407 -0
- package/templates/features/mobile/auth/components/auth/index.ts +2 -0
- package/templates/features/mobile/auth/hooks/index.ts.ejs +1 -0
- package/templates/features/mobile/auth/hooks/useAuth.ts.ejs +367 -0
- package/templates/features/mobile/auth/services/deviceSession.ts +370 -0
- package/templates/features/mobile/auth/store/deviceSession.store.ts +326 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/_layout.tsx.ejs +11 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-1.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-2.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-3.tsx.ejs +60 -0
- package/templates/features/mobile/paywall/app/paywall.tsx +550 -0
- package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx +26 -0
- package/templates/features/mobile/tabs/app/(tabs)/index.tsx +565 -0
- package/templates/features/web/.gitkeep +0 -0
- package/templates/features/web/auth/app/(app)/dashboard/dashboard-client.tsx.ejs +166 -0
- package/templates/features/web/auth/app/(app)/dashboard/page.tsx.ejs +24 -0
- package/templates/features/web/auth/app/(app)/layout.tsx.ejs +43 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/page.tsx.ejs +29 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/sessions-client.tsx.ejs +77 -0
- package/templates/features/web/auth/app/(auth)/forgot-password/page.tsx.ejs +127 -0
- package/templates/features/web/auth/app/(auth)/layout.tsx.ejs +32 -0
- package/templates/features/web/auth/app/(auth)/login/page.tsx.ejs +35 -0
- package/templates/features/web/auth/app/(auth)/register/page.tsx.ejs +19 -0
- package/templates/features/web/auth/app/(auth)/reset-password/page.tsx.ejs +40 -0
- package/templates/features/web/auth/app/(auth)/verify-email/page.tsx.ejs +198 -0
- package/templates/features/web/auth/app/auth/callback/route.ts.ejs +152 -0
- package/templates/features/web/auth/components/auth/login-form.tsx.ejs +100 -0
- package/templates/features/web/auth/components/auth/oauth-buttons.tsx.ejs +126 -0
- package/templates/features/web/auth/components/auth/password-reset-form.tsx.ejs +103 -0
- package/templates/features/web/auth/components/auth/register-form.tsx.ejs +139 -0
- package/templates/features/web/auth/components/settings/session-card.tsx.ejs +132 -0
- package/templates/integrations/mobile/adjust/services/adjustService.ts.ejs +163 -0
- package/templates/integrations/mobile/adjust/store/adjust.store.ts +243 -0
- package/templates/integrations/mobile/att/services/attService.ts +84 -0
- package/templates/integrations/mobile/att/services/trackingPermissions.ts +208 -0
- package/templates/integrations/mobile/att/store/att.store.ts +162 -0
- package/templates/integrations/mobile/revenuecat/services/revenuecatService.ts.ejs +174 -0
- package/templates/integrations/mobile/revenuecat/store/revenuecat.store.ts +286 -0
- package/templates/integrations/mobile/scate/services/scateService.ts.ejs +85 -0
- package/templates/integrations/mobile/scate/store/scate.store.ts +125 -0
- package/templates/integrations/web/.gitkeep +0 -0
- package/templates/shared/.env.example.ejs +21 -0
- package/templates/shared/.gitignore.ejs +145 -0
- package/templates/shared/README.md.ejs +134 -0
- package/templates/shared/docker-compose.prod.yml.ejs +120 -0
- package/templates/shared/docker-compose.yml.ejs +129 -0
- package/templates/shared/scripts/docker-dev.sh.ejs +395 -0
- package/templates/shared/scripts/docker-prod.sh.ejs +542 -0
- 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
|
+
};
|