@croacroa/react-native-template 2.1.0 → 3.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 (172) hide show
  1. package/.env.example +5 -0
  2. package/.eslintrc.js +8 -0
  3. package/.github/workflows/ci.yml +187 -187
  4. package/.github/workflows/eas-build.yml +55 -55
  5. package/.github/workflows/eas-update.yml +50 -50
  6. package/.github/workflows/npm-publish.yml +57 -0
  7. package/CHANGELOG.md +195 -106
  8. package/CONTRIBUTING.md +377 -377
  9. package/LICENSE +21 -21
  10. package/README.md +446 -402
  11. package/__tests__/accessibility/components.test.tsx +285 -0
  12. package/__tests__/components/Button.test.tsx +2 -4
  13. package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
  14. package/__tests__/components/snapshots.test.tsx +131 -131
  15. package/__tests__/helpers/a11y.ts +54 -0
  16. package/__tests__/hooks/useAnalytics.test.ts +100 -0
  17. package/__tests__/hooks/useAnimations.test.ts +70 -0
  18. package/__tests__/hooks/useAuth.test.tsx +71 -28
  19. package/__tests__/hooks/useMedia.test.ts +318 -0
  20. package/__tests__/hooks/usePayments.test.tsx +307 -0
  21. package/__tests__/hooks/usePermission.test.ts +230 -0
  22. package/__tests__/hooks/useWebSocket.test.ts +329 -0
  23. package/__tests__/integration/auth-api.test.tsx +224 -227
  24. package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
  25. package/__tests__/services/api.test.ts +24 -6
  26. package/app/(auth)/home.tsx +11 -9
  27. package/app/(auth)/profile.tsx +8 -6
  28. package/app/(auth)/settings.tsx +11 -9
  29. package/app/(public)/forgot-password.tsx +25 -15
  30. package/app/(public)/login.tsx +48 -12
  31. package/app/(public)/onboarding.tsx +5 -5
  32. package/app/(public)/register.tsx +24 -15
  33. package/app/_layout.tsx +6 -3
  34. package/app.config.ts +27 -2
  35. package/assets/images/.gitkeep +7 -7
  36. package/assets/images/adaptive-icon.png +0 -0
  37. package/assets/images/favicon.png +0 -0
  38. package/assets/images/icon.png +0 -0
  39. package/assets/images/notification-icon.png +0 -0
  40. package/assets/images/splash.png +0 -0
  41. package/components/ErrorBoundary.tsx +73 -28
  42. package/components/auth/SocialLoginButtons.tsx +168 -0
  43. package/components/forms/FormInput.tsx +5 -3
  44. package/components/onboarding/OnboardingScreen.tsx +370 -370
  45. package/components/onboarding/index.ts +2 -2
  46. package/components/providers/AnalyticsProvider.tsx +67 -0
  47. package/components/providers/SuspenseBoundary.tsx +359 -357
  48. package/components/providers/index.ts +24 -21
  49. package/components/ui/AnimatedButton.tsx +1 -9
  50. package/components/ui/AnimatedList.tsx +98 -0
  51. package/components/ui/AnimatedScreen.tsx +89 -0
  52. package/components/ui/Avatar.tsx +319 -316
  53. package/components/ui/Badge.tsx +416 -416
  54. package/components/ui/BottomSheet.tsx +307 -307
  55. package/components/ui/Button.tsx +11 -3
  56. package/components/ui/Checkbox.tsx +261 -261
  57. package/components/ui/FeatureGate.tsx +57 -0
  58. package/components/ui/ForceUpdateScreen.tsx +108 -0
  59. package/components/ui/ImagePickerButton.tsx +180 -0
  60. package/components/ui/Input.stories.tsx +2 -10
  61. package/components/ui/Input.tsx +2 -10
  62. package/components/ui/OptimizedImage.tsx +369 -369
  63. package/components/ui/Paywall.tsx +253 -0
  64. package/components/ui/PermissionGate.tsx +155 -0
  65. package/components/ui/PurchaseButton.tsx +84 -0
  66. package/components/ui/Select.tsx +240 -240
  67. package/components/ui/Skeleton.tsx +3 -1
  68. package/components/ui/Toast.tsx +427 -418
  69. package/components/ui/UploadProgress.tsx +189 -0
  70. package/components/ui/VirtualizedList.tsx +288 -285
  71. package/components/ui/index.ts +28 -30
  72. package/constants/config.ts +135 -97
  73. package/docs/adr/001-state-management.md +79 -79
  74. package/docs/adr/002-styling-approach.md +130 -130
  75. package/docs/adr/003-data-fetching.md +155 -155
  76. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  77. package/docs/adr/README.md +78 -78
  78. package/docs/guides/analytics-posthog.md +121 -0
  79. package/docs/guides/auth-supabase.md +162 -0
  80. package/docs/guides/feature-flags-launchdarkly.md +150 -0
  81. package/docs/guides/payments-revenuecat.md +169 -0
  82. package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
  83. package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
  84. package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
  85. package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
  86. package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
  87. package/eas.json +2 -1
  88. package/hooks/index.ts +70 -40
  89. package/hooks/useAnimatedEntry.ts +204 -0
  90. package/hooks/useApi.ts +5 -4
  91. package/hooks/useAuth.tsx +7 -3
  92. package/hooks/useBiometrics.ts +295 -295
  93. package/hooks/useChannel.ts +111 -0
  94. package/hooks/useDeepLinking.ts +256 -256
  95. package/hooks/useExperiment.ts +36 -0
  96. package/hooks/useFeatureFlag.ts +59 -0
  97. package/hooks/useForceUpdate.ts +91 -0
  98. package/hooks/useImagePicker.ts +281 -375
  99. package/hooks/useInAppReview.ts +64 -0
  100. package/hooks/useMFA.ts +509 -499
  101. package/hooks/useParallax.ts +142 -0
  102. package/hooks/usePerformance.ts +434 -434
  103. package/hooks/usePermission.ts +190 -0
  104. package/hooks/usePresence.ts +129 -0
  105. package/hooks/useProducts.ts +36 -0
  106. package/hooks/usePurchase.ts +103 -0
  107. package/hooks/useRateLimit.ts +70 -0
  108. package/hooks/useSubscription.ts +49 -0
  109. package/hooks/useTrackEvent.ts +52 -0
  110. package/hooks/useTrackScreen.ts +40 -0
  111. package/hooks/useUpdates.ts +358 -358
  112. package/hooks/useUpload.ts +165 -0
  113. package/hooks/useWebSocket.ts +111 -0
  114. package/i18n/index.ts +197 -194
  115. package/i18n/locales/ar.json +170 -101
  116. package/i18n/locales/de.json +170 -101
  117. package/i18n/locales/en.json +170 -101
  118. package/i18n/locales/es.json +170 -101
  119. package/i18n/locales/fr.json +170 -101
  120. package/jest.config.js +1 -1
  121. package/maestro/README.md +113 -113
  122. package/maestro/config.yaml +35 -35
  123. package/maestro/flows/login.yaml +62 -62
  124. package/maestro/flows/mfa-login.yaml +92 -92
  125. package/maestro/flows/mfa-setup.yaml +86 -86
  126. package/maestro/flows/navigation.yaml +68 -68
  127. package/maestro/flows/offline-conflict.yaml +101 -101
  128. package/maestro/flows/offline-sync.yaml +128 -128
  129. package/maestro/flows/offline.yaml +60 -60
  130. package/maestro/flows/register.yaml +94 -94
  131. package/package.json +188 -176
  132. package/scripts/generate-placeholders.js +38 -0
  133. package/services/analytics/adapters/console.ts +50 -0
  134. package/services/analytics/analytics-adapter.ts +94 -0
  135. package/services/analytics/types.ts +73 -0
  136. package/services/analytics.ts +428 -428
  137. package/services/api.ts +419 -340
  138. package/services/auth/social/apple.ts +110 -0
  139. package/services/auth/social/google.ts +159 -0
  140. package/services/auth/social/social-auth.ts +100 -0
  141. package/services/auth/social/types.ts +80 -0
  142. package/services/authAdapter.ts +333 -333
  143. package/services/backgroundSync.ts +652 -626
  144. package/services/feature-flags/adapters/mock.ts +108 -0
  145. package/services/feature-flags/feature-flag-adapter.ts +174 -0
  146. package/services/feature-flags/types.ts +79 -0
  147. package/services/force-update.ts +140 -0
  148. package/services/index.ts +116 -54
  149. package/services/media/compression.ts +91 -0
  150. package/services/media/media-picker.ts +151 -0
  151. package/services/media/media-upload.ts +160 -0
  152. package/services/payments/adapters/mock.ts +159 -0
  153. package/services/payments/payment-adapter.ts +118 -0
  154. package/services/payments/types.ts +131 -0
  155. package/services/permissions/permission-manager.ts +284 -0
  156. package/services/permissions/types.ts +104 -0
  157. package/services/realtime/types.ts +100 -0
  158. package/services/realtime/websocket-manager.ts +441 -0
  159. package/services/security.ts +289 -286
  160. package/services/sentry.ts +4 -4
  161. package/stores/appStore.ts +9 -0
  162. package/stores/notificationStore.ts +3 -1
  163. package/tailwind.config.js +47 -47
  164. package/tsconfig.json +37 -13
  165. package/types/user.ts +1 -1
  166. package/utils/accessibility.ts +446 -446
  167. package/utils/animations/presets.ts +182 -0
  168. package/utils/animations/transitions.ts +62 -0
  169. package/utils/index.ts +63 -52
  170. package/utils/toast.ts +9 -2
  171. package/utils/validation.ts +4 -1
  172. package/utils/withAccessibility.tsx +272 -272
@@ -1,7 +1,12 @@
1
1
  import * as SecureStore from "expo-secure-store";
2
2
  import { router } from "expo-router";
3
3
 
4
- import { ApiClient, getTokens, saveTokens, getValidAccessToken } from "@/services/api";
4
+ import {
5
+ ApiClient,
6
+ getTokens,
7
+ saveTokens,
8
+ getValidAccessToken,
9
+ } from "@/services/api";
5
10
 
6
11
  // Mock dependencies
7
12
  jest.mock("@/utils/toast", () => ({
@@ -12,8 +17,13 @@ jest.mock("@/utils/toast", () => ({
12
17
  },
13
18
  }));
14
19
 
20
+ jest.mock("i18next", () => ({
21
+ t: jest.fn((key: string) => key),
22
+ }));
23
+
15
24
  jest.mock("@/constants/config", () => ({
16
25
  API_URL: "https://api.example.com",
26
+ API_CONFIG: { ENABLE_ETAG_CACHE: false },
17
27
  }));
18
28
 
19
29
  const mockSecureStore = SecureStore as jest.Mocked<typeof SecureStore>;
@@ -26,7 +36,7 @@ const mockTokens = {
26
36
  expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour from now
27
37
  };
28
38
 
29
- const expiredTokens = {
39
+ const _expiredTokens = {
30
40
  accessToken: "expired_access_token",
31
41
  refreshToken: "expired_refresh_token",
32
42
  expiresAt: Date.now() - 1000, // Already expired
@@ -262,7 +272,9 @@ describe("ApiClient", () => {
262
272
  "Authentication failed"
263
273
  );
264
274
 
265
- expect(mockSecureStore.deleteItemAsync).toHaveBeenCalledWith("auth_tokens");
275
+ expect(mockSecureStore.deleteItemAsync).toHaveBeenCalledWith(
276
+ "auth_tokens"
277
+ );
266
278
  expect(mockSecureStore.deleteItemAsync).toHaveBeenCalledWith("auth_user");
267
279
  expect(toast.error).toHaveBeenCalledWith(
268
280
  "Session expired",
@@ -446,7 +458,9 @@ describe("ApiClient", () => {
446
458
  text: () => Promise.resolve(""),
447
459
  });
448
460
 
449
- const result = await apiClient.delete("/users/1", { requiresAuth: false });
461
+ const result = await apiClient.delete("/users/1", {
462
+ requiresAuth: false,
463
+ });
450
464
 
451
465
  expect(result).toEqual({});
452
466
  });
@@ -491,7 +505,9 @@ describe("Token Utilities", () => {
491
505
  });
492
506
 
493
507
  it("should return parsed tokens when stored", async () => {
494
- mockSecureStore.getItemAsync.mockResolvedValue(JSON.stringify(mockTokens));
508
+ mockSecureStore.getItemAsync.mockResolvedValue(
509
+ JSON.stringify(mockTokens)
510
+ );
495
511
 
496
512
  const tokens = await getTokens();
497
513
 
@@ -525,7 +541,9 @@ describe("Token Utilities", () => {
525
541
  });
526
542
 
527
543
  it("should return token when not expired", async () => {
528
- mockSecureStore.getItemAsync.mockResolvedValue(JSON.stringify(mockTokens));
544
+ mockSecureStore.getItemAsync.mockResolvedValue(
545
+ JSON.stringify(mockTokens)
546
+ );
529
547
 
530
548
  const token = await getValidAccessToken();
531
549
 
@@ -2,6 +2,7 @@ import { View, Text, ScrollView } from "react-native";
2
2
  import { Link } from "expo-router";
3
3
  import { SafeAreaView } from "react-native-safe-area-context";
4
4
  import { Ionicons } from "@expo/vector-icons";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  import { Card } from "@/components/ui/Card";
7
8
  import { Button } from "@/components/ui/Button";
@@ -11,6 +12,7 @@ import { useTheme } from "@/hooks/useTheme";
11
12
  export default function HomeScreen() {
12
13
  const { user } = useAuth();
13
14
  const { isDark } = useTheme();
15
+ const { t } = useTranslation();
14
16
 
15
17
  return (
16
18
  <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
@@ -19,7 +21,7 @@ export default function HomeScreen() {
19
21
  <View className="mb-6 flex-row items-center justify-between">
20
22
  <View>
21
23
  <Text className="text-muted-light dark:text-muted-dark">
22
- Welcome back,
24
+ {t("home.welcomeBack", { name: user?.name || "User" })}
23
25
  </Text>
24
26
  <Text className="text-2xl font-bold text-text-light dark:text-text-dark">
25
27
  {user?.name || "User"}
@@ -38,7 +40,7 @@ export default function HomeScreen() {
38
40
 
39
41
  {/* Quick Actions */}
40
42
  <Text className="mb-3 text-lg font-semibold text-text-light dark:text-text-dark">
41
- Quick Actions
43
+ {t("home.quickActions")}
42
44
  </Text>
43
45
  <View className="mb-6 flex-row gap-3">
44
46
  <Card className="flex-1 items-center p-4">
@@ -50,7 +52,7 @@ export default function HomeScreen() {
50
52
  />
51
53
  </View>
52
54
  <Text className="text-sm text-text-light dark:text-text-dark">
53
- New Item
55
+ {t("home.newItem")}
54
56
  </Text>
55
57
  </Card>
56
58
  <Card className="flex-1 items-center p-4">
@@ -62,7 +64,7 @@ export default function HomeScreen() {
62
64
  />
63
65
  </View>
64
66
  <Text className="text-sm text-text-light dark:text-text-dark">
65
- Search
67
+ {t("common.search")}
66
68
  </Text>
67
69
  </Card>
68
70
  <Card className="flex-1 items-center p-4">
@@ -74,14 +76,14 @@ export default function HomeScreen() {
74
76
  />
75
77
  </View>
76
78
  <Text className="text-sm text-text-light dark:text-text-dark">
77
- Stats
79
+ {t("home.stats")}
78
80
  </Text>
79
81
  </Card>
80
82
  </View>
81
83
 
82
84
  {/* Recent Activity */}
83
85
  <Text className="mb-3 text-lg font-semibold text-text-light dark:text-text-dark">
84
- Recent Activity
86
+ {t("home.recentActivity")}
85
87
  </Text>
86
88
  <Card className="mb-4 p-4">
87
89
  <View className="items-center py-8">
@@ -91,10 +93,10 @@ export default function HomeScreen() {
91
93
  color={isDark ? "#64748b" : "#94a3b8"}
92
94
  />
93
95
  <Text className="mt-2 text-muted-light dark:text-muted-dark">
94
- No recent activity
96
+ {t("home.noRecentActivity")}
95
97
  </Text>
96
98
  <Text className="mt-1 text-sm text-muted-light dark:text-muted-dark">
97
- Your activity will appear here
99
+ {t("home.activityWillAppear")}
98
100
  </Text>
99
101
  </View>
100
102
  </Card>
@@ -108,7 +110,7 @@ export default function HomeScreen() {
108
110
  color={isDark ? "#f8fafc" : "#0f172a"}
109
111
  style={{ marginRight: 8 }}
110
112
  />
111
- Settings
113
+ {t("settings.title")}
112
114
  </Button>
113
115
  </Link>
114
116
  </ScrollView>
@@ -2,6 +2,7 @@ import { View, Text, ScrollView, Pressable } from "react-native";
2
2
  import { router } from "expo-router";
3
3
  import { SafeAreaView } from "react-native-safe-area-context";
4
4
  import { Ionicons } from "@expo/vector-icons";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  import { Card } from "@/components/ui/Card";
7
8
  import { Button } from "@/components/ui/Button";
@@ -11,6 +12,7 @@ import { useTheme } from "@/hooks/useTheme";
11
12
  export default function ProfileScreen() {
12
13
  const { user, signOut } = useAuth();
13
14
  const { isDark } = useTheme();
15
+ const { t } = useTranslation();
14
16
 
15
17
  const handleSignOut = async () => {
16
18
  await signOut();
@@ -30,7 +32,7 @@ export default function ProfileScreen() {
30
32
  />
31
33
  </Pressable>
32
34
  <Text className="text-xl font-semibold text-text-light dark:text-text-dark">
33
- Profile
35
+ {t("profile.title")}
34
36
  </Text>
35
37
  </View>
36
38
 
@@ -60,7 +62,7 @@ export default function ProfileScreen() {
60
62
  color={isDark ? "#94a3b8" : "#64748b"}
61
63
  />
62
64
  <Text className="ml-3 text-text-light dark:text-text-dark">
63
- Edit Profile
65
+ {t("profile.editProfile")}
64
66
  </Text>
65
67
  </View>
66
68
  <Ionicons
@@ -80,7 +82,7 @@ export default function ProfileScreen() {
80
82
  color={isDark ? "#94a3b8" : "#64748b"}
81
83
  />
82
84
  <Text className="ml-3 text-text-light dark:text-text-dark">
83
- Notifications
85
+ {t("navigation.notifications")}
84
86
  </Text>
85
87
  </View>
86
88
  <Ionicons
@@ -100,7 +102,7 @@ export default function ProfileScreen() {
100
102
  color={isDark ? "#94a3b8" : "#64748b"}
101
103
  />
102
104
  <Text className="ml-3 text-text-light dark:text-text-dark">
103
- Privacy & Security
105
+ {t("profile.privacySecurity")}
104
106
  </Text>
105
107
  </View>
106
108
  <Ionicons
@@ -120,7 +122,7 @@ export default function ProfileScreen() {
120
122
  color={isDark ? "#94a3b8" : "#64748b"}
121
123
  />
122
124
  <Text className="ml-3 text-text-light dark:text-text-dark">
123
- Help & Support
125
+ {t("profile.helpSupport")}
124
126
  </Text>
125
127
  </View>
126
128
  <Ionicons
@@ -143,7 +145,7 @@ export default function ProfileScreen() {
143
145
  color="#ef4444"
144
146
  style={{ marginRight: 8 }}
145
147
  />
146
- <Text className="text-red-500">Sign Out</Text>
148
+ <Text className="text-red-500">{t("auth.signOut")}</Text>
147
149
  </Button>
148
150
  </View>
149
151
  </ScrollView>
@@ -2,6 +2,7 @@ import { View, Text, ScrollView, Pressable, Switch } from "react-native";
2
2
  import { router } from "expo-router";
3
3
  import { SafeAreaView } from "react-native-safe-area-context";
4
4
  import { Ionicons } from "@expo/vector-icons";
5
+ import { useTranslation } from "react-i18next";
5
6
 
6
7
  import { Card } from "@/components/ui/Card";
7
8
  import { useTheme } from "@/hooks/useTheme";
@@ -10,6 +11,7 @@ import { useNotificationStore } from "@/stores/notificationStore";
10
11
  export default function SettingsScreen() {
11
12
  const { isDark, toggleTheme } = useTheme();
12
13
  const { isEnabled, toggleNotifications } = useNotificationStore();
14
+ const { t } = useTranslation();
13
15
 
14
16
  return (
15
17
  <SafeAreaView className="flex-1 bg-background-light dark:bg-background-dark">
@@ -24,14 +26,14 @@ export default function SettingsScreen() {
24
26
  />
25
27
  </Pressable>
26
28
  <Text className="text-xl font-semibold text-text-light dark:text-text-dark">
27
- Settings
29
+ {t("settings.title")}
28
30
  </Text>
29
31
  </View>
30
32
 
31
33
  <View className="px-4 pt-4">
32
34
  {/* Appearance */}
33
35
  <Text className="mb-3 text-sm font-medium uppercase text-muted-light dark:text-muted-dark">
34
- Appearance
36
+ {t("settings.appearance")}
35
37
  </Text>
36
38
  <Card className="mb-6">
37
39
  <View className="flex-row items-center justify-between p-4">
@@ -42,7 +44,7 @@ export default function SettingsScreen() {
42
44
  color={isDark ? "#94a3b8" : "#64748b"}
43
45
  />
44
46
  <Text className="ml-3 text-text-light dark:text-text-dark">
45
- Dark Mode
47
+ {t("settings.darkMode")}
46
48
  </Text>
47
49
  </View>
48
50
  <Switch
@@ -56,7 +58,7 @@ export default function SettingsScreen() {
56
58
 
57
59
  {/* Notifications */}
58
60
  <Text className="mb-3 text-sm font-medium uppercase text-muted-light dark:text-muted-dark">
59
- Notifications
61
+ {t("settings.notifications")}
60
62
  </Text>
61
63
  <Card className="mb-6">
62
64
  <View className="flex-row items-center justify-between p-4">
@@ -67,7 +69,7 @@ export default function SettingsScreen() {
67
69
  color={isDark ? "#94a3b8" : "#64748b"}
68
70
  />
69
71
  <Text className="ml-3 text-text-light dark:text-text-dark">
70
- Push Notifications
72
+ {t("settings.pushNotifications")}
71
73
  </Text>
72
74
  </View>
73
75
  <Switch
@@ -81,7 +83,7 @@ export default function SettingsScreen() {
81
83
 
82
84
  {/* About */}
83
85
  <Text className="mb-3 text-sm font-medium uppercase text-muted-light dark:text-muted-dark">
84
- About
86
+ {t("settings.about")}
85
87
  </Text>
86
88
  <Card className="mb-6">
87
89
  <Pressable className="flex-row items-center justify-between p-4">
@@ -92,7 +94,7 @@ export default function SettingsScreen() {
92
94
  color={isDark ? "#94a3b8" : "#64748b"}
93
95
  />
94
96
  <Text className="ml-3 text-text-light dark:text-text-dark">
95
- App Version
97
+ {t("settings.appVersion")}
96
98
  </Text>
97
99
  </View>
98
100
  <Text className="text-muted-light dark:text-muted-dark">
@@ -110,7 +112,7 @@ export default function SettingsScreen() {
110
112
  color={isDark ? "#94a3b8" : "#64748b"}
111
113
  />
112
114
  <Text className="ml-3 text-text-light dark:text-text-dark">
113
- Terms of Service
115
+ {t("settings.terms")}
114
116
  </Text>
115
117
  </View>
116
118
  <Ionicons
@@ -130,7 +132,7 @@ export default function SettingsScreen() {
130
132
  color={isDark ? "#94a3b8" : "#64748b"}
131
133
  />
132
134
  <Text className="ml-3 text-text-light dark:text-text-dark">
133
- Privacy Policy
135
+ {t("settings.privacy")}
134
136
  </Text>
135
137
  </View>
136
138
  <Ionicons
@@ -1,8 +1,15 @@
1
1
  import { useState } from "react";
2
- import { View, Text, Pressable, KeyboardAvoidingView, Platform } from "react-native";
2
+ import {
3
+ View,
4
+ Text,
5
+ Pressable,
6
+ KeyboardAvoidingView,
7
+ Platform,
8
+ } from "react-native";
3
9
  import { Link, router } from "expo-router";
4
10
  import { SafeAreaView } from "react-native-safe-area-context";
5
11
  import { Ionicons } from "@expo/vector-icons";
12
+ import { useTranslation } from "react-i18next";
6
13
 
7
14
  import { Input } from "@/components/ui/Input";
8
15
  import { Button } from "@/components/ui/Button";
@@ -12,10 +19,11 @@ export default function ForgotPasswordScreen() {
12
19
  const [error, setError] = useState("");
13
20
  const [success, setSuccess] = useState(false);
14
21
  const [isLoading, setIsLoading] = useState(false);
22
+ const { t } = useTranslation();
15
23
 
16
24
  const handleResetPassword = async () => {
17
25
  if (!email) {
18
- setError("Please enter your email");
26
+ setError(t("forgotPassword.emailRequired"));
19
27
  return;
20
28
  }
21
29
 
@@ -27,8 +35,8 @@ export default function ForgotPasswordScreen() {
27
35
  // await api.resetPassword(email);
28
36
  await new Promise((resolve) => setTimeout(resolve, 1000));
29
37
  setSuccess(true);
30
- } catch (err) {
31
- setError("Failed to send reset email. Please try again.");
38
+ } catch {
39
+ setError(t("forgotPassword.sendFailed"));
32
40
  } finally {
33
41
  setIsLoading(false);
34
42
  }
@@ -42,16 +50,16 @@ export default function ForgotPasswordScreen() {
42
50
  <Ionicons name="mail-outline" size={40} color="#22c55e" />
43
51
  </View>
44
52
  <Text className="text-center text-2xl font-bold text-text-light dark:text-text-dark">
45
- Check your email
53
+ {t("forgotPassword.checkEmail")}
46
54
  </Text>
47
55
  <Text className="mt-2 text-center text-muted-light dark:text-muted-dark">
48
- We've sent a password reset link to {email}
56
+ {t("forgotPassword.resetSent", { email })}
49
57
  </Text>
50
58
  <Button
51
59
  onPress={() => router.replace("/(public)/login")}
52
60
  className="mt-8"
53
61
  >
54
- Back to Sign In
62
+ {t("forgotPassword.backToSignIn")}
55
63
  </Button>
56
64
  </View>
57
65
  </SafeAreaView>
@@ -70,15 +78,17 @@ export default function ForgotPasswordScreen() {
70
78
  className="mb-8 flex-row items-center"
71
79
  >
72
80
  <Ionicons name="arrow-back" size={24} color="#64748b" />
73
- <Text className="ml-2 text-muted-light dark:text-muted-dark">Back</Text>
81
+ <Text className="ml-2 text-muted-light dark:text-muted-dark">
82
+ {t("common.back")}
83
+ </Text>
74
84
  </Pressable>
75
85
 
76
86
  <View className="mb-8">
77
87
  <Text className="text-3xl font-bold text-text-light dark:text-text-dark">
78
- Reset password
88
+ {t("forgotPassword.title")}
79
89
  </Text>
80
90
  <Text className="mt-2 text-muted-light dark:text-muted-dark">
81
- Enter your email and we'll send you a reset link
91
+ {t("forgotPassword.subtitle")}
82
92
  </Text>
83
93
  </View>
84
94
 
@@ -90,8 +100,8 @@ export default function ForgotPasswordScreen() {
90
100
 
91
101
  <View className="gap-4">
92
102
  <Input
93
- label="Email"
94
- placeholder="Enter your email"
103
+ label={t("auth.email")}
104
+ placeholder={t("auth.enterEmail")}
95
105
  value={email}
96
106
  onChangeText={setEmail}
97
107
  keyboardType="email-address"
@@ -104,18 +114,18 @@ export default function ForgotPasswordScreen() {
104
114
  isLoading={isLoading}
105
115
  className="mt-4"
106
116
  >
107
- Send Reset Link
117
+ {t("forgotPassword.sendResetLink")}
108
118
  </Button>
109
119
  </View>
110
120
 
111
121
  <View className="mt-8 flex-row justify-center">
112
122
  <Text className="text-muted-light dark:text-muted-dark">
113
- Remember your password?{" "}
123
+ {t("forgotPassword.rememberPassword")}{" "}
114
124
  </Text>
115
125
  <Link href="/(public)/login" asChild>
116
126
  <Pressable>
117
127
  <Text className="font-semibold text-primary-600 dark:text-primary-400">
118
- Sign In
128
+ {t("auth.signIn")}
119
129
  </Text>
120
130
  </Pressable>
121
131
  </Link>
@@ -1,17 +1,26 @@
1
- import { View, Text, Pressable, KeyboardAvoidingView, Platform } from "react-native";
1
+ import {
2
+ View,
3
+ Text,
4
+ Pressable,
5
+ KeyboardAvoidingView,
6
+ Platform,
7
+ } from "react-native";
2
8
  import { Link, router } from "expo-router";
3
9
  import { SafeAreaView } from "react-native-safe-area-context";
4
10
  import { useForm } from "react-hook-form";
5
11
  import { zodResolver } from "@hookform/resolvers/zod";
6
12
  import Animated, { FadeInDown } from "react-native-reanimated";
13
+ import { useTranslation } from "react-i18next";
7
14
 
8
15
  import { FormInput } from "@/components/forms/FormInput";
16
+ import { SocialLoginButtons } from "@/components/auth/SocialLoginButtons";
9
17
  import { AnimatedButton } from "@/components/ui/AnimatedButton";
10
18
  import { useAuth } from "@/hooks/useAuth";
11
19
  import { loginSchema, LoginFormData } from "@/utils/validation";
12
20
 
13
21
  export default function LoginScreen() {
14
22
  const { signIn } = useAuth();
23
+ const { t } = useTranslation();
15
24
 
16
25
  const {
17
26
  control,
@@ -47,10 +56,10 @@ export default function LoginScreen() {
47
56
  className="mb-8"
48
57
  >
49
58
  <Text className="text-3xl font-bold text-text-light dark:text-text-dark">
50
- Welcome back
59
+ {t("auth.welcomeBack")}
51
60
  </Text>
52
61
  <Text className="mt-2 text-muted-light dark:text-muted-dark">
53
- Sign in to your account
62
+ {t("auth.signInToContinue")}
54
63
  </Text>
55
64
  </Animated.View>
56
65
 
@@ -62,8 +71,8 @@ export default function LoginScreen() {
62
71
  <FormInput
63
72
  name="email"
64
73
  control={control}
65
- label="Email"
66
- placeholder="Enter your email"
74
+ label={t("auth.email")}
75
+ placeholder={t("auth.enterEmail")}
67
76
  keyboardType="email-address"
68
77
  autoCapitalize="none"
69
78
  autoComplete="email"
@@ -73,8 +82,8 @@ export default function LoginScreen() {
73
82
  <FormInput
74
83
  name="password"
75
84
  control={control}
76
- label="Password"
77
- placeholder="Enter your password"
85
+ label={t("auth.password")}
86
+ placeholder={t("auth.enterPassword")}
78
87
  secureTextEntry
79
88
  autoComplete="password"
80
89
  leftIcon="lock-closed-outline"
@@ -83,7 +92,7 @@ export default function LoginScreen() {
83
92
  <Link href="/(public)/forgot-password" asChild>
84
93
  <Pressable className="self-end">
85
94
  <Text className="text-primary-600 dark:text-primary-400">
86
- Forgot password?
95
+ {t("auth.forgotPassword")}
87
96
  </Text>
88
97
  </Pressable>
89
98
  </Link>
@@ -93,22 +102,49 @@ export default function LoginScreen() {
93
102
  isLoading={isSubmitting}
94
103
  className="mt-4"
95
104
  >
96
- Sign In
105
+ {t("auth.signIn")}
97
106
  </AnimatedButton>
98
107
  </Animated.View>
99
108
 
100
- {/* Footer */}
109
+ {/* Divider */}
101
110
  <Animated.View
102
111
  entering={FadeInDown.delay(300).springify()}
112
+ className="my-6 flex-row items-center"
113
+ >
114
+ <View className="h-px flex-1 bg-muted-light/30 dark:bg-muted-dark/30" />
115
+ <Text className="mx-4 text-muted-light dark:text-muted-dark">
116
+ {t("socialAuth.orContinueWith")}
117
+ </Text>
118
+ <View className="h-px flex-1 bg-muted-light/30 dark:bg-muted-dark/30" />
119
+ </Animated.View>
120
+
121
+ {/* Social Login */}
122
+ <Animated.View entering={FadeInDown.delay(400).springify()}>
123
+ <SocialLoginButtons
124
+ onSuccess={(result) => {
125
+ if (__DEV__)
126
+ console.log(
127
+ "Social login succeeded:",
128
+ result.provider,
129
+ result.user.email
130
+ );
131
+ // TODO: Send result.idToken to your backend
132
+ }}
133
+ />
134
+ </Animated.View>
135
+
136
+ {/* Footer */}
137
+ <Animated.View
138
+ entering={FadeInDown.delay(500).springify()}
103
139
  className="mt-8 flex-row justify-center"
104
140
  >
105
141
  <Text className="text-muted-light dark:text-muted-dark">
106
- Don't have an account?{" "}
142
+ {t("auth.noAccount")}{" "}
107
143
  </Text>
108
144
  <Link href="/(public)/register" asChild>
109
145
  <Pressable>
110
146
  <Text className="font-semibold text-primary-600 dark:text-primary-400">
111
- Sign Up
147
+ {t("auth.signUp")}
112
148
  </Text>
113
149
  </Pressable>
114
150
  </Link>
@@ -1,5 +1,5 @@
1
- import { OnboardingScreen } from "@/components/onboarding";
2
-
3
- export default function Onboarding() {
4
- return <OnboardingScreen />;
5
- }
1
+ import { OnboardingScreen } from "@/components/onboarding";
2
+
3
+ export default function Onboarding() {
4
+ return <OnboardingScreen />;
5
+ }