create-stackr 0.2.0 → 0.3.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 (127) hide show
  1. package/README.md +10 -0
  2. package/dist/prompts/features.d.ts +1 -1
  3. package/dist/prompts/features.d.ts.map +1 -1
  4. package/dist/prompts/features.js +34 -25
  5. package/dist/prompts/features.js.map +1 -1
  6. package/dist/prompts/index.js +33 -6
  7. package/dist/prompts/index.js.map +1 -1
  8. package/dist/prompts/preset.d.ts.map +1 -1
  9. package/dist/prompts/preset.js +69 -34
  10. package/dist/prompts/preset.js.map +1 -1
  11. package/dist/utils/template.js +1 -1
  12. package/dist/utils/template.js.map +1 -1
  13. package/dist/utils/validation.d.ts.map +1 -1
  14. package/dist/utils/validation.js +43 -1
  15. package/dist/utils/validation.js.map +1 -1
  16. package/package.json +1 -1
  17. package/templates/base/backend/controllers/rest-api/routes/auth.ts.ejs +33 -1
  18. package/templates/base/backend/controllers/rest-api/routes/oauth-web.ts.ejs +6 -0
  19. package/templates/base/backend/domain/user/repository.drizzle.ts +28 -1
  20. package/templates/base/backend/domain/user/repository.prisma.ts +25 -0
  21. package/templates/base/backend/drizzle/{schema.drizzle.ts → schema.drizzle.ts.ejs} +25 -0
  22. package/templates/base/backend/lib/auth.drizzle.ts.ejs +27 -18
  23. package/templates/base/backend/lib/auth.prisma.ts.ejs +24 -18
  24. package/templates/base/backend/package.json.ejs +29 -23
  25. package/templates/base/backend/prisma/schema.prisma.ejs +20 -0
  26. package/templates/base/mobile/app/+not-found.tsx +1 -1
  27. package/templates/base/mobile/app/_layout.tsx.ejs +95 -10
  28. package/templates/base/mobile/package.json.ejs +21 -13
  29. package/templates/base/mobile/src/components/ui/Button.tsx +5 -3
  30. package/templates/base/mobile/src/components/ui/Card.tsx +1 -1
  31. package/templates/base/mobile/src/components/ui/Input.tsx +1 -1
  32. package/templates/base/mobile/src/components/ui/Skeleton.tsx +1 -1
  33. package/templates/base/mobile/src/components/ui/Toast.tsx +106 -0
  34. package/templates/base/mobile/src/components/ui/{IconSymbol.tsx → icon-symbol.tsx} +7 -0
  35. package/templates/base/mobile/src/components/ui/{LoadingSpinner.tsx → loading-spinner.tsx} +1 -1
  36. package/templates/base/mobile/src/components/ui/{OnboardingLayout.tsx → onboarding-layout.tsx} +2 -2
  37. package/templates/base/mobile/src/components/ui/{PaywallLayout.tsx → paywall-layout.tsx} +3 -3
  38. package/templates/base/mobile/src/constants/Theme.ts +3 -3
  39. package/templates/base/mobile/src/context/{ThemeContext.tsx → theme-context.tsx} +2 -2
  40. package/templates/base/mobile/src/lib/auth-client.ts.ejs +18 -0
  41. package/templates/base/mobile/src/services/{sdkInitializer.ts.ejs → sdk-initializer.ts.ejs} +4 -4
  42. package/templates/base/web/.prettierignore +6 -0
  43. package/templates/base/web/.prettierrc +8 -0
  44. package/templates/base/web/eslint.config.mjs +31 -7
  45. package/templates/base/web/next.config.ts +50 -1
  46. package/templates/base/web/package.json.ejs +14 -2
  47. package/templates/base/web/src/app/globals.css +1 -1
  48. package/templates/base/web/src/app/layout.tsx.ejs +2 -0
  49. package/templates/base/web/src/components/auth/protected-route.tsx.ejs +32 -5
  50. package/templates/base/web/src/components/providers/device-session-setup.tsx.ejs +1 -1
  51. package/templates/base/web/src/hooks/use-device-session.ts.ejs +2 -2
  52. package/templates/base/web/src/lib/auth/actions.ts.ejs +438 -15
  53. package/templates/base/web/src/lib/auth/config.ts.ejs +13 -0
  54. package/templates/base/web/src/lib/auth/cookies.ts.ejs +61 -0
  55. package/templates/base/web/src/lib/device/actions.ts.ejs +2 -10
  56. package/templates/base/web/src/lib/device/types.ts +37 -0
  57. package/templates/base/web/src/proxy.ts.ejs +12 -2
  58. package/templates/base/web/src/store/{deviceSession.store.ts.ejs → device-session-store.ts.ejs} +1 -1
  59. package/templates/features/mobile/auth/app/(auth)/{_layout.tsx → _layout.tsx.ejs} +7 -0
  60. package/templates/features/mobile/auth/app/(auth)/{login.tsx → login.tsx.ejs} +9 -8
  61. package/templates/features/mobile/auth/app/(auth)/{register.tsx → register.tsx.ejs} +9 -8
  62. package/templates/features/mobile/auth/app/(auth)/two-factor-expired.tsx.ejs +88 -0
  63. package/templates/features/mobile/auth/app/(auth)/two-factor.tsx.ejs +259 -0
  64. package/templates/features/mobile/auth/app/(public)/_layout.tsx +9 -0
  65. package/templates/features/mobile/{tabs/app/(tabs)/index.tsx → auth/app/(public)/index.tsx.ejs} +76 -257
  66. package/templates/features/mobile/auth/app/(tabs)/settings/_layout.tsx.ejs +20 -0
  67. package/templates/features/mobile/auth/app/(tabs)/settings/index.tsx.ejs +137 -0
  68. package/templates/features/mobile/auth/app/(tabs)/settings/security/_layout.tsx.ejs +35 -0
  69. package/templates/features/mobile/auth/app/(tabs)/settings/security/index.tsx.ejs +147 -0
  70. package/templates/features/mobile/auth/app/(tabs)/settings/security/set-password.tsx.ejs +140 -0
  71. package/templates/features/mobile/auth/app/(tabs)/settings/security/two-factor-manage.tsx.ejs +366 -0
  72. package/templates/features/mobile/auth/app/(tabs)/settings/security/two-factor-setup.tsx.ejs +317 -0
  73. package/templates/features/mobile/auth/app/account.tsx.ejs +331 -0
  74. package/templates/features/mobile/auth/app/verify-email.tsx.ejs +315 -0
  75. package/templates/features/mobile/auth/components/auth/{LoginForm.tsx.ejs → login-form.tsx.ejs} +15 -3
  76. package/templates/features/mobile/auth/components/auth/protected-screen.tsx.ejs +56 -0
  77. package/templates/features/mobile/auth/components/auth/{RegisterForm.tsx.ejs → register-form.tsx.ejs} +5 -3
  78. package/templates/features/mobile/auth/hooks/{useAuth.ts.ejs → auth.ts.ejs} +255 -1
  79. package/templates/features/mobile/auth/hooks/two-factor-timeout.ts.ejs +56 -0
  80. package/templates/features/mobile/auth/services/{deviceSession.ts → device-session.ts} +2 -10
  81. package/templates/features/mobile/auth/store/{deviceSession.store.ts → device-session-store.ts} +14 -5
  82. package/templates/features/mobile/auth/types/device-session.ts +37 -0
  83. package/templates/features/mobile/onboarding/app/(onboarding)/page-1.tsx.ejs +3 -1
  84. package/templates/features/mobile/onboarding/app/(onboarding)/page-2.tsx.ejs +3 -1
  85. package/templates/features/mobile/onboarding/app/(onboarding)/page-3.tsx.ejs +6 -1
  86. package/templates/features/mobile/paywall/app/paywall.tsx +4 -4
  87. package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx +0 -6
  88. package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx.ejs +60 -0
  89. package/templates/features/mobile/tabs/app/(tabs)/index.tsx.ejs +264 -0
  90. package/templates/features/web/auth/app/(app)/layout.tsx.ejs +8 -22
  91. package/templates/features/web/auth/app/(auth)/login/page.tsx.ejs +19 -3
  92. package/templates/features/web/auth/app/(auth)/login/two-factor/page.tsx.ejs +24 -0
  93. package/templates/features/web/auth/app/(auth)/verify-email/page.tsx.ejs +117 -152
  94. package/templates/features/web/auth/app/{(app) → (protected)}/dashboard/dashboard-client.tsx.ejs +41 -1
  95. package/templates/features/web/auth/app/{(app) → (protected)}/dashboard/page.tsx.ejs +3 -1
  96. package/templates/features/web/auth/app/(protected)/layout.tsx.ejs +67 -0
  97. package/templates/features/web/auth/app/(protected)/settings/security/page.tsx.ejs +19 -0
  98. package/templates/features/web/auth/app/(protected)/settings/security/security-settings.tsx.ejs +73 -0
  99. package/templates/features/web/auth/app/{(app) → (protected)}/settings/sessions/page.tsx.ejs +2 -6
  100. package/templates/features/web/auth/app/auth/callback/route.ts.ejs +5 -1
  101. package/templates/features/web/auth/app/auth/session-expired/route.ts.ejs +39 -0
  102. package/templates/features/web/auth/app/auth/two-factor-expired/route.ts.ejs +22 -0
  103. package/templates/features/web/auth/components/auth/login-form.tsx.ejs +18 -0
  104. package/templates/features/web/auth/components/auth/two-factor-verify.tsx.ejs +173 -0
  105. package/templates/features/web/auth/components/settings/set-password-form.tsx.ejs +123 -0
  106. package/templates/features/web/auth/components/settings/two-factor-manage.tsx.ejs +253 -0
  107. package/templates/features/web/auth/components/settings/two-factor-setup.tsx.ejs +249 -0
  108. package/templates/features/web/auth/components/ui/checkbox.tsx +32 -0
  109. package/templates/features/web/auth/components/ui/dialog.tsx +143 -0
  110. package/templates/features/web/auth/components/ui/input-otp.tsx +71 -0
  111. package/templates/integrations/mobile/adjust/store/{adjust.store.ts → adjust-store.ts} +3 -3
  112. package/templates/integrations/mobile/att/store/{att.store.ts → att-store.ts} +2 -2
  113. package/templates/integrations/mobile/revenuecat/store/{revenuecat.store.ts → revenuecat-store.ts} +1 -1
  114. package/templates/integrations/mobile/scate/store/{scate.store.ts → scate-store.ts} +1 -1
  115. package/templates/base/mobile/src/components/ui/index.ts +0 -6
  116. package/templates/base/mobile/src/store/index.ts.ejs +0 -18
  117. package/templates/base/web/src/lib/auth/index.ts.ejs +0 -40
  118. package/templates/features/mobile/auth/components/auth/index.ts +0 -2
  119. package/templates/features/mobile/auth/hooks/index.ts.ejs +0 -1
  120. /package/templates/base/mobile/src/services/{errorService.ts → error-service.ts} +0 -0
  121. /package/templates/base/mobile/src/store/{ui.store.ts → ui-store.ts} +0 -0
  122. /package/templates/features/web/auth/app/{(app) → (protected)}/settings/sessions/sessions-client.tsx.ejs +0 -0
  123. /package/templates/integrations/mobile/adjust/services/{adjustService.ts.ejs → adjust-service.ts.ejs} +0 -0
  124. /package/templates/integrations/mobile/att/services/{attService.ts → att-service.ts} +0 -0
  125. /package/templates/integrations/mobile/att/services/{trackingPermissions.ts → tracking-permissions.ts} +0 -0
  126. /package/templates/integrations/mobile/revenuecat/services/{revenuecatService.ts.ejs → revenuecat-service.ts.ejs} +0 -0
  127. /package/templates/integrations/mobile/scate/services/{scateService.ts.ejs → scate-service.ts.ejs} +0 -0
@@ -0,0 +1,315 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ StyleSheet,
7
+ Pressable,
8
+ Alert,
9
+ KeyboardAvoidingView,
10
+ Platform,
11
+ SafeAreaView,
12
+ } from "react-native";
13
+ import { useLocalSearchParams, router } from "expo-router";
14
+ import { useAppTheme, AppTheme } from "@/context/theme-context";
15
+ import { emailOtp } from "@/lib/auth-client";
16
+ import { useAuth } from "../src/hooks/auth";
17
+
18
+ export default function VerifyEmailScreen() {
19
+ const { email } = useLocalSearchParams<{ email: string }>();
20
+ const theme = useAppTheme();
21
+ const { colors } = theme;
22
+ const { refetch, signOut } = useAuth();
23
+
24
+ const [otp, setOtp] = useState(["", "", "", "", "", ""]);
25
+ const [isVerifying, setIsVerifying] = useState(false);
26
+ const [error, setError] = useState<string | null>(null);
27
+ const [resendDisabled, setResendDisabled] = useState(false);
28
+ const [resendCountdown, setResendCountdown] = useState(0);
29
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
30
+ const inputRefs = useRef<(TextInput | null)[]>([]);
31
+
32
+ const handleChange = (index: number, value: string) => {
33
+ if (!/^\d*$/.test(value)) return;
34
+
35
+ const newOtp = [...otp];
36
+ newOtp[index] = value.slice(-1);
37
+ setOtp(newOtp);
38
+
39
+ if (value && index < 5) {
40
+ inputRefs.current[index + 1]?.focus();
41
+ }
42
+ };
43
+
44
+ const handleKeyPress = (index: number, key: string) => {
45
+ if (key === "Backspace" && !otp[index] && index > 0) {
46
+ inputRefs.current[index - 1]?.focus();
47
+ }
48
+ };
49
+
50
+ // Auto-verify when complete
51
+ useEffect(() => {
52
+ const code = otp.join("");
53
+ if (code.length === 6 && email && !isVerifying) {
54
+ handleVerify(code);
55
+ }
56
+ }, [otp, email, isVerifying]);
57
+
58
+ // Countdown timer with proper cleanup
59
+ useEffect(() => {
60
+ if (resendCountdown <= 0) return;
61
+
62
+ const timer = setTimeout(() => {
63
+ setResendCountdown((prev) => {
64
+ if (prev <= 1) {
65
+ setResendDisabled(false);
66
+ return 0;
67
+ }
68
+ return prev - 1;
69
+ });
70
+ }, 1000);
71
+
72
+ return () => clearTimeout(timer);
73
+ }, [resendCountdown]);
74
+
75
+ const handleVerify = async (code: string) => {
76
+ if (!email) return;
77
+ setIsVerifying(true);
78
+ setError(null);
79
+
80
+ try {
81
+ const { error: verifyError } = await emailOtp.verifyEmail({
82
+ email,
83
+ otp: code,
84
+ });
85
+
86
+ if (verifyError) {
87
+ setError(verifyError.message || "Invalid code");
88
+ setOtp(["", "", "", "", "", ""]);
89
+ inputRefs.current[0]?.focus();
90
+ setIsVerifying(false);
91
+ return;
92
+ }
93
+
94
+ // Success - refetch session to update emailVerified status, then navigate
95
+ await refetch();
96
+ router.replace("/(tabs)");
97
+ } catch (err) {
98
+ setError("An error occurred");
99
+ setOtp(["", "", "", "", "", ""]);
100
+ inputRefs.current[0]?.focus();
101
+ setIsVerifying(false);
102
+ }
103
+ };
104
+
105
+ const handleResend = async () => {
106
+ if (!email || resendDisabled) return;
107
+
108
+ setResendDisabled(true);
109
+ setResendCountdown(60);
110
+ setError(null);
111
+
112
+ try {
113
+ const { error: sendError } = await emailOtp.sendVerificationOtp({
114
+ email,
115
+ type: "email-verification",
116
+ });
117
+
118
+ if (sendError) {
119
+ Alert.alert("Error", sendError.message || "Failed to resend code");
120
+ }
121
+ } catch (err) {
122
+ Alert.alert("Error", "Failed to resend code");
123
+ }
124
+ };
125
+
126
+ const handleBackToHome = async () => {
127
+ setIsLoggingOut(true);
128
+ try {
129
+ await signOut();
130
+ // Root layout will handle navigation when auth state changes
131
+ } catch (err) {
132
+ Alert.alert("Error", "Failed to sign out");
133
+ setIsLoggingOut(false);
134
+ }
135
+ // Don't reset isLoggingOut on success - component will unmount
136
+ };
137
+
138
+ if (!email) {
139
+ return (
140
+ <SafeAreaView style={[styles.container, { backgroundColor: colors.background }]}>
141
+ <View style={styles.content}>
142
+ <Text style={[styles.title, { color: colors.text }]}>
143
+ Verify your email
144
+ </Text>
145
+ <Text style={[styles.subtitle, { color: colors.textSecondary }]}>
146
+ No email address provided.
147
+ </Text>
148
+ <Pressable
149
+ style={[styles.backButton, { borderColor: colors.border }]}
150
+ onPress={handleBackToHome}
151
+ disabled={isLoggingOut}
152
+ >
153
+ <Text style={[styles.backButtonText, { color: colors.text }]}>
154
+ {isLoggingOut ? "Signing out..." : "Back to home"}
155
+ </Text>
156
+ </Pressable>
157
+ </View>
158
+ </SafeAreaView>
159
+ );
160
+ }
161
+
162
+ return (
163
+ <SafeAreaView style={[styles.container, { backgroundColor: colors.background }]}>
164
+ <KeyboardAvoidingView
165
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
166
+ style={styles.keyboardView}
167
+ >
168
+ <View style={styles.content}>
169
+ <Text style={[styles.title, { color: colors.text }]}>
170
+ Enter verification code
171
+ </Text>
172
+ <Text style={[styles.subtitle, { color: colors.textSecondary }]}>
173
+ We sent a 6-digit code to{"\n"}
174
+ <Text style={{ fontWeight: "600" }}>{email}</Text>
175
+ </Text>
176
+
177
+ <View style={styles.otpContainer}>
178
+ {otp.map((digit, index) => (
179
+ <TextInput
180
+ key={index}
181
+ ref={(el) => (inputRefs.current[index] = el)}
182
+ style={[
183
+ styles.otpInput,
184
+ {
185
+ borderColor: error ? "#dc2626" : colors.border,
186
+ color: colors.text,
187
+ backgroundColor: colors.background,
188
+ },
189
+ ]}
190
+ value={digit}
191
+ onChangeText={(value) => handleChange(index, value)}
192
+ onKeyPress={({ nativeEvent }) =>
193
+ handleKeyPress(index, nativeEvent.key)
194
+ }
195
+ keyboardType="number-pad"
196
+ maxLength={1}
197
+ editable={!isVerifying}
198
+ autoFocus={index === 0}
199
+ selectTextOnFocus
200
+ />
201
+ ))}
202
+ </View>
203
+
204
+ {error && <Text style={styles.error}>{error}</Text>}
205
+
206
+ {isVerifying && (
207
+ <Text style={[styles.verifying, { color: colors.textSecondary }]}>
208
+ Verifying...
209
+ </Text>
210
+ )}
211
+
212
+ <Pressable
213
+ onPress={handleResend}
214
+ disabled={resendDisabled}
215
+ style={styles.resendButton}
216
+ >
217
+ <Text
218
+ style={[
219
+ styles.resend,
220
+ {
221
+ color: resendDisabled ? colors.textSecondary : colors.primary,
222
+ },
223
+ ]}
224
+ >
225
+ {resendDisabled
226
+ ? `Resend code in ${resendCountdown}s`
227
+ : "Didn't receive the code? Resend"}
228
+ </Text>
229
+ </Pressable>
230
+
231
+ <Pressable
232
+ style={[styles.backButton, { borderColor: colors.border }]}
233
+ onPress={handleBackToHome}
234
+ disabled={isLoggingOut}
235
+ >
236
+ <Text style={[styles.backButtonText, { color: colors.text }]}>
237
+ {isLoggingOut ? "Signing out..." : "Back to home"}
238
+ </Text>
239
+ </Pressable>
240
+ </View>
241
+ </KeyboardAvoidingView>
242
+ </SafeAreaView>
243
+ );
244
+ }
245
+
246
+ const styles = StyleSheet.create({
247
+ container: {
248
+ flex: 1,
249
+ },
250
+ keyboardView: {
251
+ flex: 1,
252
+ },
253
+ content: {
254
+ flex: 1,
255
+ padding: 24,
256
+ justifyContent: "center",
257
+ },
258
+ title: {
259
+ fontSize: 24,
260
+ fontWeight: "bold",
261
+ textAlign: "center",
262
+ marginBottom: 8,
263
+ },
264
+ subtitle: {
265
+ fontSize: 16,
266
+ textAlign: "center",
267
+ marginBottom: 32,
268
+ lineHeight: 24,
269
+ },
270
+ otpContainer: {
271
+ flexDirection: "row",
272
+ justifyContent: "center",
273
+ gap: 8,
274
+ marginBottom: 24,
275
+ },
276
+ otpInput: {
277
+ width: 48,
278
+ height: 56,
279
+ borderWidth: 1,
280
+ borderRadius: 8,
281
+ fontSize: 24,
282
+ textAlign: "center",
283
+ fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
284
+ },
285
+ error: {
286
+ color: "#dc2626",
287
+ textAlign: "center",
288
+ marginBottom: 16,
289
+ fontSize: 14,
290
+ },
291
+ verifying: {
292
+ textAlign: "center",
293
+ marginBottom: 16,
294
+ fontSize: 14,
295
+ },
296
+ resendButton: {
297
+ paddingVertical: 12,
298
+ marginBottom: 16,
299
+ },
300
+ resend: {
301
+ textAlign: "center",
302
+ fontSize: 14,
303
+ },
304
+ backButton: {
305
+ borderWidth: 1,
306
+ borderRadius: 8,
307
+ paddingVertical: 12,
308
+ paddingHorizontal: 24,
309
+ },
310
+ backButtonText: {
311
+ textAlign: "center",
312
+ fontSize: 16,
313
+ fontWeight: "500",
314
+ },
315
+ });
@@ -12,10 +12,14 @@ import {
12
12
  TouchableOpacity,
13
13
  <% } %>
14
14
  } from 'react-native';
15
- import { Button, Input } from '../ui';
16
- import { useAuth } from '../../hooks';
15
+ <% if (features.authentication.twoFactor) { %>
16
+ import { router } from 'expo-router';
17
+ <% } %>
18
+ import { Button } from '../ui/button';
19
+ import { Input } from '../ui/input';
20
+ import { useAuth } from '../../hooks/auth';
17
21
  import { isValidEmail } from '../../utils/formatters';
18
- import { useAppTheme, AppTheme } from '@/context/ThemeContext';
22
+ import { useAppTheme, AppTheme } from '@/context/theme-context';
19
23
 
20
24
  interface LoginFormProps {
21
25
  onSuccess?: () => void;
@@ -80,6 +84,14 @@ export const LoginForm: React.FC<LoginFormProps> = ({
80
84
  password,
81
85
  });
82
86
 
87
+ <% if (features.authentication.twoFactor) { %>
88
+ // Check if 2FA is required
89
+ if (result.requiresTwoFactor) {
90
+ router.push('/(auth)/two-factor');
91
+ return;
92
+ }
93
+ <% } %>
94
+
83
95
  if (result.success) {
84
96
  onSuccess?.();
85
97
  } else {
@@ -0,0 +1,56 @@
1
+ import { View, StyleSheet } from 'react-native';
2
+ import { useAuth } from '../../hooks/auth';
3
+ import { LoadingSpinner } from '@/components/ui/loading-spinner';
4
+
5
+ interface ProtectedScreenProps {
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * Client-side screen protection component for mobile
11
+ *
12
+ * Wraps content that should only be visible to authenticated<% if (features.authentication.emailVerification) { %> and verified<% } %> users.
13
+ * Does NOT redirect - the root layout handles all navigation.
14
+ * This component just prevents rendering protected content until auth state is verified.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <ProtectedScreen>
19
+ * <DashboardContent />
20
+ * </ProtectedScreen>
21
+ * ```
22
+ */
23
+ export function ProtectedScreen({ children }: ProtectedScreenProps) {
24
+ const { isAuthenticated, isPending<% if (features.authentication.emailVerification) { %>, user<% } %> } = useAuth();
25
+
26
+ // Show loading state while checking auth
27
+ if (isPending) {
28
+ return (
29
+ <View style={styles.container}>
30
+ <LoadingSpinner size="large" />
31
+ </View>
32
+ );
33
+ }
34
+
35
+ // Don't render content if not authenticated
36
+ if (!isAuthenticated) {
37
+ return null;
38
+ }
39
+
40
+ <% if (features.authentication.emailVerification) { %>
41
+ // Don't render content if email is not verified
42
+ if (user && user.emailVerified === false) {
43
+ return null;
44
+ }
45
+
46
+ <% } %>
47
+ return <>{children}</>;
48
+ }
49
+
50
+ const styles = StyleSheet.create({
51
+ container: {
52
+ flex: 1,
53
+ justifyContent: 'center',
54
+ alignItems: 'center',
55
+ },
56
+ });
@@ -12,10 +12,11 @@ import {
12
12
  TouchableOpacity,
13
13
  <% } %>
14
14
  } from 'react-native';
15
- import { Button, Input } from '../ui';
16
- import { useAuth } from '../../hooks';
15
+ import { Button } from '../ui/button';
16
+ import { Input } from '../ui/input';
17
+ import { useAuth } from '../../hooks/auth';
17
18
  import { isValidEmail } from '../../utils/formatters';
18
- import { useAppTheme, AppTheme } from '@/context/ThemeContext';
19
+ import { useAppTheme, AppTheme } from '@/context/theme-context';
19
20
 
20
21
  interface RegisterFormProps {
21
22
  onSuccess?: () => void;
@@ -125,6 +126,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
125
126
  });
126
127
 
127
128
  if (result.success) {
129
+ // Navigation handled by parent watching isAuthenticated state
128
130
  onSuccess?.();
129
131
  } else {
130
132
  // Show server error via Alert