@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.
- package/.env.example +5 -0
- package/.eslintrc.js +8 -0
- package/.github/workflows/ci.yml +187 -187
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/.github/workflows/npm-publish.yml +57 -0
- package/CHANGELOG.md +195 -106
- package/CONTRIBUTING.md +377 -377
- package/LICENSE +21 -21
- package/README.md +446 -402
- package/__tests__/accessibility/components.test.tsx +285 -0
- package/__tests__/components/Button.test.tsx +2 -4
- package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
- package/__tests__/components/snapshots.test.tsx +131 -131
- package/__tests__/helpers/a11y.ts +54 -0
- package/__tests__/hooks/useAnalytics.test.ts +100 -0
- package/__tests__/hooks/useAnimations.test.ts +70 -0
- package/__tests__/hooks/useAuth.test.tsx +71 -28
- package/__tests__/hooks/useMedia.test.ts +318 -0
- package/__tests__/hooks/usePayments.test.tsx +307 -0
- package/__tests__/hooks/usePermission.test.ts +230 -0
- package/__tests__/hooks/useWebSocket.test.ts +329 -0
- package/__tests__/integration/auth-api.test.tsx +224 -227
- package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
- package/__tests__/services/api.test.ts +24 -6
- package/app/(auth)/home.tsx +11 -9
- package/app/(auth)/profile.tsx +8 -6
- package/app/(auth)/settings.tsx +11 -9
- package/app/(public)/forgot-password.tsx +25 -15
- package/app/(public)/login.tsx +48 -12
- package/app/(public)/onboarding.tsx +5 -5
- package/app/(public)/register.tsx +24 -15
- package/app/_layout.tsx +6 -3
- package/app.config.ts +27 -2
- package/assets/images/.gitkeep +7 -7
- package/assets/images/adaptive-icon.png +0 -0
- package/assets/images/favicon.png +0 -0
- package/assets/images/icon.png +0 -0
- package/assets/images/notification-icon.png +0 -0
- package/assets/images/splash.png +0 -0
- package/components/ErrorBoundary.tsx +73 -28
- package/components/auth/SocialLoginButtons.tsx +168 -0
- package/components/forms/FormInput.tsx +5 -3
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/AnalyticsProvider.tsx +67 -0
- package/components/providers/SuspenseBoundary.tsx +359 -357
- package/components/providers/index.ts +24 -21
- package/components/ui/AnimatedButton.tsx +1 -9
- package/components/ui/AnimatedList.tsx +98 -0
- package/components/ui/AnimatedScreen.tsx +89 -0
- package/components/ui/Avatar.tsx +319 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Button.tsx +11 -3
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/FeatureGate.tsx +57 -0
- package/components/ui/ForceUpdateScreen.tsx +108 -0
- package/components/ui/ImagePickerButton.tsx +180 -0
- package/components/ui/Input.stories.tsx +2 -10
- package/components/ui/Input.tsx +2 -10
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Paywall.tsx +253 -0
- package/components/ui/PermissionGate.tsx +155 -0
- package/components/ui/PurchaseButton.tsx +84 -0
- package/components/ui/Select.tsx +240 -240
- package/components/ui/Skeleton.tsx +3 -1
- package/components/ui/Toast.tsx +427 -418
- package/components/ui/UploadProgress.tsx +189 -0
- package/components/ui/VirtualizedList.tsx +288 -285
- package/components/ui/index.ts +28 -30
- package/constants/config.ts +135 -97
- package/docs/adr/001-state-management.md +79 -79
- package/docs/adr/002-styling-approach.md +130 -130
- package/docs/adr/003-data-fetching.md +155 -155
- package/docs/adr/004-auth-adapter-pattern.md +144 -144
- package/docs/adr/README.md +78 -78
- package/docs/guides/analytics-posthog.md +121 -0
- package/docs/guides/auth-supabase.md +162 -0
- package/docs/guides/feature-flags-launchdarkly.md +150 -0
- package/docs/guides/payments-revenuecat.md +169 -0
- package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
- package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
- package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
- package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
- package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
- package/eas.json +2 -1
- package/hooks/index.ts +70 -40
- package/hooks/useAnimatedEntry.ts +204 -0
- package/hooks/useApi.ts +5 -4
- package/hooks/useAuth.tsx +7 -3
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useChannel.ts +111 -0
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useExperiment.ts +36 -0
- package/hooks/useFeatureFlag.ts +59 -0
- package/hooks/useForceUpdate.ts +91 -0
- package/hooks/useImagePicker.ts +281 -375
- package/hooks/useInAppReview.ts +64 -0
- package/hooks/useMFA.ts +509 -499
- package/hooks/useParallax.ts +142 -0
- package/hooks/usePerformance.ts +434 -434
- package/hooks/usePermission.ts +190 -0
- package/hooks/usePresence.ts +129 -0
- package/hooks/useProducts.ts +36 -0
- package/hooks/usePurchase.ts +103 -0
- package/hooks/useRateLimit.ts +70 -0
- package/hooks/useSubscription.ts +49 -0
- package/hooks/useTrackEvent.ts +52 -0
- package/hooks/useTrackScreen.ts +40 -0
- package/hooks/useUpdates.ts +358 -358
- package/hooks/useUpload.ts +165 -0
- package/hooks/useWebSocket.ts +111 -0
- package/i18n/index.ts +197 -194
- package/i18n/locales/ar.json +170 -101
- package/i18n/locales/de.json +170 -101
- package/i18n/locales/en.json +170 -101
- package/i18n/locales/es.json +170 -101
- package/i18n/locales/fr.json +170 -101
- package/jest.config.js +1 -1
- package/maestro/README.md +113 -113
- package/maestro/config.yaml +35 -35
- package/maestro/flows/login.yaml +62 -62
- package/maestro/flows/mfa-login.yaml +92 -92
- package/maestro/flows/mfa-setup.yaml +86 -86
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -101
- package/maestro/flows/offline-sync.yaml +128 -128
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +188 -176
- package/scripts/generate-placeholders.js +38 -0
- package/services/analytics/adapters/console.ts +50 -0
- package/services/analytics/analytics-adapter.ts +94 -0
- package/services/analytics/types.ts +73 -0
- package/services/analytics.ts +428 -428
- package/services/api.ts +419 -340
- package/services/auth/social/apple.ts +110 -0
- package/services/auth/social/google.ts +159 -0
- package/services/auth/social/social-auth.ts +100 -0
- package/services/auth/social/types.ts +80 -0
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +652 -626
- package/services/feature-flags/adapters/mock.ts +108 -0
- package/services/feature-flags/feature-flag-adapter.ts +174 -0
- package/services/feature-flags/types.ts +79 -0
- package/services/force-update.ts +140 -0
- package/services/index.ts +116 -54
- package/services/media/compression.ts +91 -0
- package/services/media/media-picker.ts +151 -0
- package/services/media/media-upload.ts +160 -0
- package/services/payments/adapters/mock.ts +159 -0
- package/services/payments/payment-adapter.ts +118 -0
- package/services/payments/types.ts +131 -0
- package/services/permissions/permission-manager.ts +284 -0
- package/services/permissions/types.ts +104 -0
- package/services/realtime/types.ts +100 -0
- package/services/realtime/websocket-manager.ts +441 -0
- package/services/security.ts +289 -286
- package/services/sentry.ts +4 -4
- package/stores/appStore.ts +9 -0
- package/stores/notificationStore.ts +3 -1
- package/tailwind.config.js +47 -47
- package/tsconfig.json +37 -13
- package/types/user.ts +1 -1
- package/utils/accessibility.ts +446 -446
- package/utils/animations/presets.ts +182 -0
- package/utils/animations/transitions.ts +62 -0
- package/utils/index.ts +63 -52
- package/utils/toast.ts +9 -2
- package/utils/validation.ts +4 -1
- 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>) =>
|
|
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
|
|
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<
|
|
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(
|
|
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 (
|
|
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,
|
|
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 });
|