@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,204 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import { ViewStyle } from "react-native";
3
+ import {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withTiming,
7
+ withDelay,
8
+ interpolate,
9
+ } from "react-native-reanimated";
10
+ import type { WithTimingConfig, SharedValue } from "react-native-reanimated";
11
+
12
+ import {
13
+ TIMING,
14
+ ENTRY_CONFIGS,
15
+ staggerDelay,
16
+ } from "@/utils/animations/presets";
17
+ import type { EntryAnimation } from "@/utils/animations/presets";
18
+
19
+ // ============================================================================
20
+ // Types
21
+ // ============================================================================
22
+
23
+ export interface UseAnimatedEntryOptions {
24
+ /**
25
+ * Which entry animation to use
26
+ * @default 'fadeIn'
27
+ */
28
+ animation?: EntryAnimation;
29
+
30
+ /**
31
+ * Delay before the animation starts (ms)
32
+ * @default 0
33
+ */
34
+ delay?: number;
35
+
36
+ /**
37
+ * Timing configuration for the animation
38
+ * @default TIMING.normal
39
+ */
40
+ timing?: WithTimingConfig;
41
+
42
+ /**
43
+ * Whether the animation plays automatically on mount
44
+ * @default true
45
+ */
46
+ autoPlay?: boolean;
47
+ }
48
+
49
+ export interface UseAnimatedEntryReturn {
50
+ /** Animated style to spread onto an Animated.View */
51
+ animatedStyle: ViewStyle;
52
+
53
+ /** Manually trigger the entry animation */
54
+ play: () => void;
55
+
56
+ /** Reset animation back to start (progress = 0) */
57
+ reset: () => void;
58
+
59
+ /** Shared value tracking animation progress (0 = start, 1 = done) */
60
+ progress: SharedValue<number>;
61
+ }
62
+
63
+ // ============================================================================
64
+ // Hook Implementation
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Hook for declarative entry animations.
69
+ *
70
+ * Returns an animated style that interpolates opacity, translateX, translateY,
71
+ * and scale from the chosen preset's initial values to their identity values
72
+ * as `progress` goes from 0 to 1.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * function MyComponent() {
77
+ * const { animatedStyle } = useAnimatedEntry({
78
+ * animation: 'slideUp',
79
+ * delay: 200,
80
+ * });
81
+ *
82
+ * return (
83
+ * <Animated.View style={animatedStyle}>
84
+ * <Text>Hello</Text>
85
+ * </Animated.View>
86
+ * );
87
+ * }
88
+ * ```
89
+ */
90
+ export function useAnimatedEntry(
91
+ options: UseAnimatedEntryOptions = {}
92
+ ): UseAnimatedEntryReturn {
93
+ const {
94
+ animation = "fadeIn",
95
+ delay = 0,
96
+ timing = TIMING.normal,
97
+ autoPlay = true,
98
+ } = options;
99
+
100
+ const progress = useSharedValue(0);
101
+ const config = ENTRY_CONFIGS[animation];
102
+
103
+ // Store timing config in a ref to avoid infinite re-renders when
104
+ // callers pass an inline object literal for `timing`.
105
+ const timingRef = useRef(timing);
106
+ timingRef.current = timing;
107
+
108
+ const play = useCallback(() => {
109
+ progress.value = 0;
110
+ if (delay > 0) {
111
+ progress.value = withDelay(delay, withTiming(1, timingRef.current));
112
+ } else {
113
+ progress.value = withTiming(1, timingRef.current);
114
+ }
115
+ }, [delay, progress]);
116
+
117
+ const reset = useCallback(() => {
118
+ progress.value = 0;
119
+ }, [progress]);
120
+
121
+ // Auto-play on mount
122
+ useEffect(() => {
123
+ if (autoPlay) {
124
+ play();
125
+ }
126
+ }, [autoPlay, play]);
127
+
128
+ const animatedStyle = useAnimatedStyle((): ViewStyle => {
129
+ return {
130
+ opacity: interpolate(progress.value, [0, 1], [config.opacity, 1]),
131
+ transform: [
132
+ {
133
+ translateX: interpolate(
134
+ progress.value,
135
+ [0, 1],
136
+ [config.translateX, 0]
137
+ ),
138
+ },
139
+ {
140
+ translateY: interpolate(
141
+ progress.value,
142
+ [0, 1],
143
+ [config.translateY, 0]
144
+ ),
145
+ },
146
+ {
147
+ scale: interpolate(progress.value, [0, 1], [config.scale, 1]),
148
+ },
149
+ ],
150
+ };
151
+ });
152
+
153
+ return { animatedStyle, play, reset, progress };
154
+ }
155
+
156
+ // ============================================================================
157
+ // Staggered Entry
158
+ // ============================================================================
159
+
160
+ export interface UseStaggeredEntryOptions extends Omit<
161
+ UseAnimatedEntryOptions,
162
+ "delay"
163
+ > {
164
+ /**
165
+ * Base delay between each staggered item (ms)
166
+ * @default 50
167
+ */
168
+ staggerDelay?: number;
169
+ }
170
+
171
+ /**
172
+ * Convenience wrapper around `useAnimatedEntry` that adds an index-based
173
+ * stagger delay. Use this for list items that should animate in sequentially.
174
+ *
175
+ * @param index - The item's position in the list (0-based)
176
+ * @param options - Same as useAnimatedEntry options, plus `staggerDelay`
177
+ *
178
+ * @example
179
+ * ```tsx
180
+ * function ListItem({ index }: { index: number }) {
181
+ * const { animatedStyle } = useStaggeredEntry(index, {
182
+ * animation: 'slideUp',
183
+ * staggerDelay: 80,
184
+ * });
185
+ *
186
+ * return (
187
+ * <Animated.View style={animatedStyle}>
188
+ * <Text>Item {index}</Text>
189
+ * </Animated.View>
190
+ * );
191
+ * }
192
+ * ```
193
+ */
194
+ export function useStaggeredEntry(
195
+ index: number,
196
+ options: UseStaggeredEntryOptions = {}
197
+ ): UseAnimatedEntryReturn {
198
+ const { staggerDelay: baseDelay = 50, ...rest } = options;
199
+
200
+ return useAnimatedEntry({
201
+ ...rest,
202
+ delay: staggerDelay(index, baseDelay),
203
+ });
204
+ }
package/hooks/useApi.ts CHANGED
@@ -38,14 +38,15 @@ export const queryKeys = {
38
38
  },
39
39
  posts: {
40
40
  all: ["posts"] as const,
41
- list: (filters: Record<string, unknown>) => ["posts", "list", filters] as const,
41
+ list: (filters: Record<string, unknown>) =>
42
+ ["posts", "list", filters] as const,
42
43
  detail: (id: string) => ["posts", id] as const,
43
44
  },
44
45
  // Add more query keys as needed
45
46
  } as const;
46
47
 
47
48
  // Generic types for API responses
48
- interface PaginatedResponse<T> {
49
+ interface _PaginatedResponse<T> {
49
50
  data: T[];
50
51
  page: number;
51
52
  pageSize: number;
@@ -222,7 +223,7 @@ export function useUpdateUser() {
222
223
  // Generic CRUD Hooks Factory
223
224
  // ===========================================
224
225
 
225
- interface CrudHooksConfig<T, CreateDTO, UpdateDTO> {
226
+ interface CrudHooksConfig<_T, _CreateDTO, _UpdateDTO> {
226
227
  baseKey: readonly string[];
227
228
  endpoint: string;
228
229
  entityName: string;
@@ -264,7 +265,7 @@ interface CrudHooksConfig<T, CreateDTO, UpdateDTO> {
264
265
  export function createCrudHooks<
265
266
  T extends { id: string },
266
267
  CreateDTO = Omit<T, "id">,
267
- UpdateDTO = Partial<T>
268
+ UpdateDTO = Partial<T>,
268
269
  >(config: CrudHooksConfig<T, CreateDTO, UpdateDTO>) {
269
270
  const { baseKey, endpoint, entityName } = config;
270
271
 
package/hooks/useAuth.tsx CHANGED
@@ -128,7 +128,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
128
128
  // Check if tokens are expired
129
129
  if (parsedTokens.expiresAt < Date.now()) {
130
130
  // Try to refresh
131
- const refreshed = await tryRefreshWithToken(parsedTokens.refreshToken);
131
+ const refreshed = await tryRefreshWithToken(
132
+ parsedTokens.refreshToken
133
+ );
132
134
  if (!refreshed) {
133
135
  await clearAuth();
134
136
  return;
@@ -146,7 +148,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
146
148
  }
147
149
  };
148
150
 
149
- const tryRefreshWithToken = async (refreshToken: string): Promise<boolean> => {
151
+ const tryRefreshWithToken = async (
152
+ _refreshToken: string
153
+ ): Promise<boolean> => {
150
154
  try {
151
155
  // TODO: Replace with your actual API call
152
156
  // const response = await api.post('/auth/refresh', { refreshToken });
@@ -190,7 +194,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
190
194
  setUser(null);
191
195
  };
192
196
 
193
- const signIn = useCallback(async (email: string, password: string) => {
197
+ const signIn = useCallback(async (email: string, _password: string) => {
194
198
  try {
195
199
  // TODO: Replace with your actual API call
196
200
  // const response = await api.post('/auth/login', { email, password });