@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
@@ -0,0 +1,180 @@
1
+ /**
2
+ * @fileoverview Image selection button with preview
3
+ * Shows a dashed placeholder when empty and the selected image with
4
+ * a clear button when an image is chosen.
5
+ * @module components/ui/ImagePickerButton
6
+ */
7
+
8
+ import { View, Text, Pressable, ActivityIndicator } from "react-native";
9
+ import { Image } from "expo-image";
10
+ import { Ionicons } from "@expo/vector-icons";
11
+ import { useTheme } from "@/hooks/useTheme";
12
+ import {
13
+ useImagePicker,
14
+ type UseImagePickerOptions,
15
+ } from "@/hooks/useImagePicker";
16
+ import type { PickedMedia } from "@/services/media/media-picker";
17
+ import { cn } from "@/utils/cn";
18
+
19
+ /**
20
+ * Props for the ImagePickerButton component
21
+ */
22
+ interface ImagePickerButtonProps {
23
+ /** Callback when an image is selected */
24
+ onImageSelected?: (media: PickedMedia) => void;
25
+ /** Size of the button in pixels (default: 100) */
26
+ size?: number;
27
+ /** Placeholder text (default: 'Add Photo') */
28
+ placeholder?: string;
29
+ /** Additional class name for the container */
30
+ className?: string;
31
+ /** Compression options passed to useImagePicker */
32
+ pickerOptions?: UseImagePickerOptions;
33
+ }
34
+
35
+ /**
36
+ * Image picker button with built-in preview.
37
+ * Displays a dashed border placeholder with a camera icon when no image is selected.
38
+ * Shows the selected image with an X button to clear when an image is chosen.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * function ProfileForm() {
43
+ * const [avatar, setAvatar] = useState<PickedMedia | null>(null);
44
+ *
45
+ * return (
46
+ * <ImagePickerButton
47
+ * size={120}
48
+ * placeholder="Profile Photo"
49
+ * onImageSelected={setAvatar}
50
+ * />
51
+ * );
52
+ * }
53
+ * ```
54
+ */
55
+ export function ImagePickerButton({
56
+ onImageSelected,
57
+ size = 100,
58
+ placeholder = "Add Photo",
59
+ className,
60
+ pickerOptions,
61
+ }: ImagePickerButtonProps) {
62
+ const { isDark } = useTheme();
63
+ const { pickFromLibrary, pickFromCamera, selectedMedia, isLoading, clear } =
64
+ useImagePicker(pickerOptions);
65
+
66
+ const handlePress = async () => {
67
+ const result = await pickFromLibrary();
68
+ if (result && onImageSelected) {
69
+ onImageSelected(result);
70
+ }
71
+ };
72
+
73
+ const handleLongPress = async () => {
74
+ const result = await pickFromCamera();
75
+ if (result && onImageSelected) {
76
+ onImageSelected(result);
77
+ }
78
+ };
79
+
80
+ const handleClear = () => {
81
+ clear();
82
+ };
83
+
84
+ // Loading state
85
+ if (isLoading) {
86
+ return (
87
+ <View
88
+ className={cn("items-center justify-center rounded-xl", className)}
89
+ style={{ width: size, height: size }}
90
+ >
91
+ <ActivityIndicator
92
+ size="small"
93
+ color={isDark ? "#94a3b8" : "#64748b"}
94
+ />
95
+ </View>
96
+ );
97
+ }
98
+
99
+ // Image selected state
100
+ if (selectedMedia) {
101
+ return (
102
+ <View
103
+ className={cn("relative", className)}
104
+ style={{ width: size, height: size }}
105
+ >
106
+ <View
107
+ className="overflow-hidden rounded-xl"
108
+ style={{ width: size, height: size }}
109
+ >
110
+ <Image
111
+ source={{ uri: selectedMedia.uri }}
112
+ style={{ width: size, height: size }}
113
+ contentFit="cover"
114
+ transition={200}
115
+ />
116
+ </View>
117
+
118
+ {/* Clear button */}
119
+ <Pressable
120
+ onPress={handleClear}
121
+ className={cn(
122
+ "absolute -right-2 -top-2 z-10 items-center justify-center rounded-full",
123
+ isDark ? "bg-gray-700" : "bg-white"
124
+ )}
125
+ style={{
126
+ width: 24,
127
+ height: 24,
128
+ shadowColor: "#000",
129
+ shadowOffset: { width: 0, height: 1 },
130
+ shadowOpacity: 0.2,
131
+ shadowRadius: 2,
132
+ elevation: 3,
133
+ }}
134
+ >
135
+ <Ionicons
136
+ name="close"
137
+ size={16}
138
+ color={isDark ? "#f87171" : "#ef4444"}
139
+ />
140
+ </Pressable>
141
+ </View>
142
+ );
143
+ }
144
+
145
+ // Empty placeholder state
146
+ return (
147
+ <Pressable
148
+ onPress={handlePress}
149
+ onLongPress={handleLongPress}
150
+ className={cn(
151
+ "items-center justify-center rounded-xl",
152
+ isDark
153
+ ? "border-gray-600 bg-gray-800/50"
154
+ : "border-gray-300 bg-gray-50",
155
+ className
156
+ )}
157
+ style={{
158
+ width: size,
159
+ height: size,
160
+ borderWidth: 2,
161
+ borderStyle: "dashed",
162
+ }}
163
+ >
164
+ <Ionicons
165
+ name="camera-outline"
166
+ size={size * 0.28}
167
+ color={isDark ? "#94a3b8" : "#9ca3af"}
168
+ />
169
+ <Text
170
+ className={cn(
171
+ "mt-1 text-center text-xs",
172
+ isDark ? "text-gray-400" : "text-gray-500"
173
+ )}
174
+ numberOfLines={1}
175
+ >
176
+ {placeholder}
177
+ </Text>
178
+ </Pressable>
179
+ );
180
+ }
@@ -91,16 +91,8 @@ export const AllStates: Story = {
91
91
  placeholder="Enter text..."
92
92
  hint="This is a helpful hint"
93
93
  />
94
- <Input
95
- label="Password"
96
- placeholder="Enter password"
97
- secureTextEntry
98
- />
99
- <Input
100
- label="With Icon"
101
- placeholder="Search..."
102
- leftIcon="search"
103
- />
94
+ <Input label="Password" placeholder="Enter password" secureTextEntry />
95
+ <Input label="With Icon" placeholder="Search..." leftIcon="search" />
104
96
  </View>
105
97
  ),
106
98
  };
@@ -1,11 +1,5 @@
1
1
  import { forwardRef, useState } from "react";
2
- import {
3
- View,
4
- Text,
5
- TextInput,
6
- TextInputProps,
7
- Pressable,
8
- } from "react-native";
2
+ import { View, Text, TextInput, TextInputProps, Pressable } from "react-native";
9
3
  import { Ionicons } from "@expo/vector-icons";
10
4
  import { cn } from "@/utils/cn";
11
5
  import { useTheme } from "@/hooks/useTheme";
@@ -100,9 +94,7 @@ export const Input = forwardRef<TextInput, InputProps>(
100
94
  ) : null}
101
95
  </View>
102
96
 
103
- {error && (
104
- <Text className="mt-1 text-sm text-red-500">{error}</Text>
105
- )}
97
+ {error && <Text className="mt-1 text-sm text-red-500">{error}</Text>}
106
98
 
107
99
  {hint && !error && (
108
100
  <Text className="mt-1 text-sm text-muted-light dark:text-muted-dark">