@croacroa/react-native-template 1.0.0 → 2.0.1
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/.github/workflows/ci.yml +187 -184
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/CHANGELOG.md +106 -106
- package/CONTRIBUTING.md +377 -377
- package/README.md +399 -399
- package/__tests__/components/snapshots.test.tsx +131 -0
- package/__tests__/integration/auth-api.test.tsx +227 -0
- package/__tests__/performance/VirtualizedList.perf.test.tsx +362 -0
- package/app/(public)/onboarding.tsx +5 -5
- package/app.config.ts +45 -2
- package/assets/images/.gitkeep +7 -7
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/SuspenseBoundary.tsx +357 -0
- package/components/providers/index.ts +21 -0
- package/components/ui/Avatar.tsx +316 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Select.tsx +240 -240
- package/components/ui/VirtualizedList.tsx +285 -0
- package/components/ui/index.ts +23 -18
- package/constants/config.ts +97 -54
- 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/hooks/index.ts +27 -25
- package/hooks/useApi.ts +102 -5
- package/hooks/useAuth.tsx +82 -0
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useMFA.ts +499 -0
- package/hooks/useNotifications.ts +39 -0
- package/hooks/useOffline.ts +60 -6
- package/hooks/usePerformance.ts +434 -434
- package/hooks/useTheme.tsx +76 -0
- package/hooks/useUpdates.ts +358 -358
- package/i18n/index.ts +194 -77
- package/i18n/locales/ar.json +101 -0
- package/i18n/locales/de.json +101 -0
- package/i18n/locales/en.json +101 -101
- package/i18n/locales/es.json +101 -0
- package/i18n/locales/fr.json +101 -101
- package/jest.config.js +4 -4
- 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 -0
- package/maestro/flows/mfa-setup.yaml +86 -0
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -0
- package/maestro/flows/offline-sync.yaml +128 -0
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +175 -170
- package/services/analytics.ts +428 -428
- package/services/api.ts +340 -340
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +626 -0
- package/services/index.ts +54 -22
- package/services/security.ts +286 -0
- package/tailwind.config.js +47 -47
- package/utils/accessibility.ts +446 -446
- package/utils/index.ts +52 -43
- package/utils/validation.ts +2 -1
- package/utils/withAccessibility.tsx +272 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview High-performance virtualized list component
|
|
3
|
+
* Wraps @shopify/flash-list for optimal list rendering with large datasets.
|
|
4
|
+
* @module components/ui/VirtualizedList
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useCallback, useMemo } from "react";
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
Text,
|
|
11
|
+
ActivityIndicator,
|
|
12
|
+
RefreshControl,
|
|
13
|
+
StyleSheet,
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import { FlashList, FlashListProps } from "@shopify/flash-list";
|
|
16
|
+
import { useTheme } from "@/hooks/useTheme";
|
|
17
|
+
import { cn } from "@/utils/cn";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Props for VirtualizedList component
|
|
21
|
+
*/
|
|
22
|
+
interface VirtualizedListProps<T> extends Omit<FlashListProps<T>, "renderItem" | "estimatedItemSize"> {
|
|
23
|
+
/**
|
|
24
|
+
* Array of data items to render
|
|
25
|
+
*/
|
|
26
|
+
data: T[];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Render function for each item
|
|
30
|
+
*/
|
|
31
|
+
renderItem: (info: { item: T; index: number }) => React.ReactElement | null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unique key extractor for items
|
|
35
|
+
*/
|
|
36
|
+
keyExtractor: (item: T, index: number) => string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Estimated height of each item for better performance
|
|
40
|
+
* Required for optimal FlashList performance
|
|
41
|
+
*/
|
|
42
|
+
estimatedItemSize?: number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether the list is currently loading
|
|
46
|
+
*/
|
|
47
|
+
isLoading?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether the list is refreshing (pull-to-refresh)
|
|
51
|
+
*/
|
|
52
|
+
isRefreshing?: boolean;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Callback when user pulls to refresh
|
|
56
|
+
*/
|
|
57
|
+
onRefresh?: () => void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether there's more data to load
|
|
61
|
+
*/
|
|
62
|
+
hasMore?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Callback when user scrolls near the end
|
|
66
|
+
*/
|
|
67
|
+
onLoadMore?: () => void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Component to render when list is empty
|
|
71
|
+
*/
|
|
72
|
+
emptyComponent?: React.ReactElement;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Message to show when list is empty
|
|
76
|
+
*/
|
|
77
|
+
emptyMessage?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Component to render at the bottom when loading more
|
|
81
|
+
*/
|
|
82
|
+
loadingMoreComponent?: React.ReactElement;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Additional class name for container
|
|
86
|
+
*/
|
|
87
|
+
className?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Threshold for onEndReached (0 to 1)
|
|
91
|
+
* @default 0.5
|
|
92
|
+
*/
|
|
93
|
+
onEndReachedThreshold?: number;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Whether to show scroll indicator
|
|
97
|
+
* @default false
|
|
98
|
+
*/
|
|
99
|
+
showsVerticalScrollIndicator?: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Draw distance for rendering items outside visible area
|
|
103
|
+
* Higher values = smoother scroll but more memory
|
|
104
|
+
* @default 250
|
|
105
|
+
*/
|
|
106
|
+
drawDistance?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Default empty component
|
|
111
|
+
*/
|
|
112
|
+
function DefaultEmptyComponent({ message }: { message: string }) {
|
|
113
|
+
const { isDark } = useTheme();
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<View className="flex-1 items-center justify-center py-12">
|
|
117
|
+
<Text
|
|
118
|
+
className={cn(
|
|
119
|
+
"text-base",
|
|
120
|
+
isDark ? "text-muted-dark" : "text-muted-light"
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
{message}
|
|
124
|
+
</Text>
|
|
125
|
+
</View>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Default loading more component
|
|
131
|
+
*/
|
|
132
|
+
function DefaultLoadingMoreComponent() {
|
|
133
|
+
return (
|
|
134
|
+
<View className="items-center justify-center py-4">
|
|
135
|
+
<ActivityIndicator size="small" />
|
|
136
|
+
</View>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* High-performance virtualized list component.
|
|
142
|
+
* Optimized for rendering large datasets with minimal memory usage.
|
|
143
|
+
*
|
|
144
|
+
* Features:
|
|
145
|
+
* - Automatic item recycling
|
|
146
|
+
* - Pull-to-refresh support
|
|
147
|
+
* - Infinite scroll (load more)
|
|
148
|
+
* - Empty state handling
|
|
149
|
+
* - Loading states
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* interface User {
|
|
154
|
+
* id: string;
|
|
155
|
+
* name: string;
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* function UserList() {
|
|
159
|
+
* const { data, isLoading, refetch, hasNextPage, fetchNextPage } = useUsers();
|
|
160
|
+
*
|
|
161
|
+
* return (
|
|
162
|
+
* <VirtualizedList<User>
|
|
163
|
+
* data={data}
|
|
164
|
+
* renderItem={({ item }) => <UserCard user={item} />}
|
|
165
|
+
* keyExtractor={(item) => item.id}
|
|
166
|
+
* estimatedItemSize={72}
|
|
167
|
+
* isLoading={isLoading}
|
|
168
|
+
* isRefreshing={isLoading}
|
|
169
|
+
* onRefresh={refetch}
|
|
170
|
+
* hasMore={hasNextPage}
|
|
171
|
+
* onLoadMore={fetchNextPage}
|
|
172
|
+
* emptyMessage="No users found"
|
|
173
|
+
* />
|
|
174
|
+
* );
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export function VirtualizedList<T>({
|
|
179
|
+
data,
|
|
180
|
+
renderItem,
|
|
181
|
+
keyExtractor,
|
|
182
|
+
estimatedItemSize = 50,
|
|
183
|
+
isLoading = false,
|
|
184
|
+
isRefreshing = false,
|
|
185
|
+
onRefresh,
|
|
186
|
+
hasMore = false,
|
|
187
|
+
onLoadMore,
|
|
188
|
+
emptyComponent,
|
|
189
|
+
emptyMessage = "No items found",
|
|
190
|
+
loadingMoreComponent,
|
|
191
|
+
className,
|
|
192
|
+
onEndReachedThreshold = 0.5,
|
|
193
|
+
showsVerticalScrollIndicator = false,
|
|
194
|
+
drawDistance = 250,
|
|
195
|
+
...flashListProps
|
|
196
|
+
}: VirtualizedListProps<T>) {
|
|
197
|
+
const { isDark } = useTheme();
|
|
198
|
+
|
|
199
|
+
// Memoize empty component
|
|
200
|
+
const ListEmptyComponent = useMemo(() => {
|
|
201
|
+
if (isLoading) return null;
|
|
202
|
+
if (emptyComponent) return emptyComponent;
|
|
203
|
+
return <DefaultEmptyComponent message={emptyMessage} />;
|
|
204
|
+
}, [isLoading, emptyComponent, emptyMessage]);
|
|
205
|
+
|
|
206
|
+
// Memoize footer component
|
|
207
|
+
const ListFooterComponent = useCallback(() => {
|
|
208
|
+
if (!hasMore || data.length === 0) return null;
|
|
209
|
+
if (loadingMoreComponent) return loadingMoreComponent;
|
|
210
|
+
return <DefaultLoadingMoreComponent />;
|
|
211
|
+
}, [hasMore, data.length, loadingMoreComponent]);
|
|
212
|
+
|
|
213
|
+
// Handle end reached
|
|
214
|
+
const handleEndReached = useCallback(() => {
|
|
215
|
+
if (hasMore && onLoadMore && !isLoading) {
|
|
216
|
+
onLoadMore();
|
|
217
|
+
}
|
|
218
|
+
}, [hasMore, onLoadMore, isLoading]);
|
|
219
|
+
|
|
220
|
+
// Memoize refresh control
|
|
221
|
+
const refreshControl = useMemo(() => {
|
|
222
|
+
if (!onRefresh) return undefined;
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<RefreshControl
|
|
226
|
+
refreshing={isRefreshing}
|
|
227
|
+
onRefresh={onRefresh}
|
|
228
|
+
tintColor={isDark ? "#94a3b8" : "#64748b"}
|
|
229
|
+
colors={["#3b82f6"]}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
}, [onRefresh, isRefreshing, isDark]);
|
|
233
|
+
|
|
234
|
+
// Loading state
|
|
235
|
+
if (isLoading && data.length === 0) {
|
|
236
|
+
return (
|
|
237
|
+
<View className={cn("flex-1 items-center justify-center", className)}>
|
|
238
|
+
<ActivityIndicator size="large" color="#3b82f6" />
|
|
239
|
+
</View>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<FlashList
|
|
245
|
+
data={data}
|
|
246
|
+
renderItem={renderItem}
|
|
247
|
+
keyExtractor={keyExtractor}
|
|
248
|
+
ListEmptyComponent={ListEmptyComponent}
|
|
249
|
+
ListFooterComponent={ListFooterComponent}
|
|
250
|
+
refreshControl={refreshControl}
|
|
251
|
+
onEndReached={handleEndReached}
|
|
252
|
+
onEndReachedThreshold={onEndReachedThreshold}
|
|
253
|
+
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
254
|
+
// FlashList specific optimizations
|
|
255
|
+
estimatedItemSize={estimatedItemSize}
|
|
256
|
+
drawDistance={drawDistance}
|
|
257
|
+
{...flashListProps}
|
|
258
|
+
contentContainerStyle={[
|
|
259
|
+
styles.container,
|
|
260
|
+
flashListProps.contentContainerStyle,
|
|
261
|
+
]}
|
|
262
|
+
/>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const styles = StyleSheet.create({
|
|
267
|
+
container: {
|
|
268
|
+
flexGrow: 1,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Horizontal virtualized list variant
|
|
274
|
+
*/
|
|
275
|
+
export function HorizontalVirtualizedList<T>(
|
|
276
|
+
props: Omit<VirtualizedListProps<T>, "horizontal">
|
|
277
|
+
) {
|
|
278
|
+
return (
|
|
279
|
+
<VirtualizedList
|
|
280
|
+
{...props}
|
|
281
|
+
horizontal
|
|
282
|
+
showsHorizontalScrollIndicator={false}
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
285
|
+
}
|
package/components/ui/index.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
export { Button } from "./Button";
|
|
2
|
-
export { Input } from "./Input";
|
|
3
|
-
export { Card } from "./Card";
|
|
4
|
-
export { Modal } from "./Modal";
|
|
5
|
-
export { AnimatedButton } from "./AnimatedButton";
|
|
6
|
-
export { AnimatedCard, AnimatedList } from "./AnimatedCard";
|
|
7
|
-
export { Select } from "./Select";
|
|
8
|
-
export type { SelectOption } from "./Select";
|
|
9
|
-
export { Checkbox, CheckboxGroup } from "./Checkbox";
|
|
10
|
-
export { BottomSheet, useBottomSheet } from "./BottomSheet";
|
|
11
|
-
export type { BottomSheetRef } from "./BottomSheet";
|
|
12
|
-
export { Avatar, AvatarGroup } from "./Avatar";
|
|
13
|
-
export { Badge, Chip, CountBadge } from "./Badge";
|
|
14
|
-
export {
|
|
15
|
-
OptimizedImage,
|
|
16
|
-
BackgroundImage,
|
|
17
|
-
ProgressiveImage,
|
|
18
|
-
} from "./OptimizedImage";
|
|
1
|
+
export { Button } from "./Button";
|
|
2
|
+
export { Input } from "./Input";
|
|
3
|
+
export { Card } from "./Card";
|
|
4
|
+
export { Modal } from "./Modal";
|
|
5
|
+
export { AnimatedButton } from "./AnimatedButton";
|
|
6
|
+
export { AnimatedCard, AnimatedList } from "./AnimatedCard";
|
|
7
|
+
export { Select } from "./Select";
|
|
8
|
+
export type { SelectOption } from "./Select";
|
|
9
|
+
export { Checkbox, CheckboxGroup } from "./Checkbox";
|
|
10
|
+
export { BottomSheet, useBottomSheet } from "./BottomSheet";
|
|
11
|
+
export type { BottomSheetRef } from "./BottomSheet";
|
|
12
|
+
export { Avatar, AvatarGroup } from "./Avatar";
|
|
13
|
+
export { Badge, Chip, CountBadge } from "./Badge";
|
|
14
|
+
export {
|
|
15
|
+
OptimizedImage,
|
|
16
|
+
BackgroundImage,
|
|
17
|
+
ProgressiveImage,
|
|
18
|
+
} from "./OptimizedImage";
|
|
19
|
+
export { Skeleton, SkeletonText, SkeletonCircle } from "./Skeleton";
|
|
20
|
+
export {
|
|
21
|
+
VirtualizedList,
|
|
22
|
+
HorizontalVirtualizedList,
|
|
23
|
+
} from "./VirtualizedList";
|
package/constants/config.ts
CHANGED
|
@@ -1,54 +1,97 @@
|
|
|
1
|
-
import Constants from "expo-constants";
|
|
2
|
-
|
|
3
|
-
// Environment detection
|
|
4
|
-
export const IS_DEV = __DEV__;
|
|
5
|
-
export const IS_PREVIEW =
|
|
6
|
-
Constants.expoConfig?.extra?.APP_VARIANT === "preview";
|
|
7
|
-
export const IS_PROD = !IS_DEV && !IS_PREVIEW;
|
|
8
|
-
|
|
9
|
-
// API Configuration
|
|
10
|
-
// TODO: Replace with your actual API URLs
|
|
11
|
-
export const API_URL = IS_PROD
|
|
12
|
-
? "https://api.yourapp.com"
|
|
13
|
-
: IS_PREVIEW
|
|
14
|
-
? "https://staging-api.yourapp.com"
|
|
15
|
-
: "http://localhost:3000";
|
|
16
|
-
|
|
17
|
-
// App Configuration
|
|
18
|
-
export const APP_NAME = Constants.expoConfig?.name || "YourApp";
|
|
19
|
-
export const APP_VERSION = Constants.expoConfig?.version || "1.0.0";
|
|
20
|
-
export const APP_SCHEME = Constants.expoConfig?.scheme || "yourapp";
|
|
21
|
-
|
|
22
|
-
// Feature Flags
|
|
23
|
-
export const FEATURES = {
|
|
24
|
-
ENABLE_ANALYTICS: IS_PROD,
|
|
25
|
-
ENABLE_CRASH_REPORTING: IS_PROD,
|
|
26
|
-
ENABLE_PUSH_NOTIFICATIONS: true,
|
|
27
|
-
ENABLE_BIOMETRIC_AUTH: true,
|
|
28
|
-
ENABLE_PERFORMANCE_MONITORING: IS_DEV || IS_PREVIEW,
|
|
29
|
-
} as const;
|
|
30
|
-
|
|
31
|
-
// Export individual flags for convenience
|
|
32
|
-
export const ENABLE_ANALYTICS = FEATURES.ENABLE_ANALYTICS;
|
|
33
|
-
export const ENABLE_CRASH_REPORTING = FEATURES.ENABLE_CRASH_REPORTING;
|
|
34
|
-
export const ENABLE_PUSH_NOTIFICATIONS = FEATURES.ENABLE_PUSH_NOTIFICATIONS;
|
|
35
|
-
export const ENABLE_BIOMETRIC_AUTH = FEATURES.ENABLE_BIOMETRIC_AUTH;
|
|
36
|
-
export const ENABLE_PERFORMANCE_MONITORING =
|
|
37
|
-
FEATURES.ENABLE_PERFORMANCE_MONITORING;
|
|
38
|
-
|
|
39
|
-
// Timing Constants
|
|
40
|
-
export const TIMING = {
|
|
41
|
-
DEBOUNCE_MS: 300,
|
|
42
|
-
ANIMATION_DURATION_MS: 200,
|
|
43
|
-
TOAST_DURATION_MS: 3000,
|
|
44
|
-
API_TIMEOUT_MS: 30000,
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
|
-
// Storage Keys
|
|
48
|
-
export const STORAGE_KEYS = {
|
|
49
|
-
AUTH_TOKEN: "auth_token",
|
|
50
|
-
USER: "auth_user",
|
|
51
|
-
THEME: "theme_mode",
|
|
52
|
-
ONBOARDING_COMPLETED: "onboarding_completed",
|
|
53
|
-
PUSH_TOKEN: "push_token",
|
|
54
|
-
} as const;
|
|
1
|
+
import Constants from "expo-constants";
|
|
2
|
+
|
|
3
|
+
// Environment detection
|
|
4
|
+
export const IS_DEV = __DEV__;
|
|
5
|
+
export const IS_PREVIEW =
|
|
6
|
+
Constants.expoConfig?.extra?.APP_VARIANT === "preview";
|
|
7
|
+
export const IS_PROD = !IS_DEV && !IS_PREVIEW;
|
|
8
|
+
|
|
9
|
+
// API Configuration
|
|
10
|
+
// TODO: Replace with your actual API URLs
|
|
11
|
+
export const API_URL = IS_PROD
|
|
12
|
+
? "https://api.yourapp.com"
|
|
13
|
+
: IS_PREVIEW
|
|
14
|
+
? "https://staging-api.yourapp.com"
|
|
15
|
+
: "http://localhost:3000";
|
|
16
|
+
|
|
17
|
+
// App Configuration
|
|
18
|
+
export const APP_NAME = Constants.expoConfig?.name || "YourApp";
|
|
19
|
+
export const APP_VERSION = Constants.expoConfig?.version || "1.0.0";
|
|
20
|
+
export const APP_SCHEME = Constants.expoConfig?.scheme || "yourapp";
|
|
21
|
+
|
|
22
|
+
// Feature Flags
|
|
23
|
+
export const FEATURES = {
|
|
24
|
+
ENABLE_ANALYTICS: IS_PROD,
|
|
25
|
+
ENABLE_CRASH_REPORTING: IS_PROD,
|
|
26
|
+
ENABLE_PUSH_NOTIFICATIONS: true,
|
|
27
|
+
ENABLE_BIOMETRIC_AUTH: true,
|
|
28
|
+
ENABLE_PERFORMANCE_MONITORING: IS_DEV || IS_PREVIEW,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
// Export individual flags for convenience
|
|
32
|
+
export const ENABLE_ANALYTICS = FEATURES.ENABLE_ANALYTICS;
|
|
33
|
+
export const ENABLE_CRASH_REPORTING = FEATURES.ENABLE_CRASH_REPORTING;
|
|
34
|
+
export const ENABLE_PUSH_NOTIFICATIONS = FEATURES.ENABLE_PUSH_NOTIFICATIONS;
|
|
35
|
+
export const ENABLE_BIOMETRIC_AUTH = FEATURES.ENABLE_BIOMETRIC_AUTH;
|
|
36
|
+
export const ENABLE_PERFORMANCE_MONITORING =
|
|
37
|
+
FEATURES.ENABLE_PERFORMANCE_MONITORING;
|
|
38
|
+
|
|
39
|
+
// Timing Constants
|
|
40
|
+
export const TIMING = {
|
|
41
|
+
DEBOUNCE_MS: 300,
|
|
42
|
+
ANIMATION_DURATION_MS: 200,
|
|
43
|
+
TOAST_DURATION_MS: 3000,
|
|
44
|
+
API_TIMEOUT_MS: 30000,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
// Storage Keys
|
|
48
|
+
export const STORAGE_KEYS = {
|
|
49
|
+
AUTH_TOKEN: "auth_token",
|
|
50
|
+
USER: "auth_user",
|
|
51
|
+
THEME: "theme_mode",
|
|
52
|
+
ONBOARDING_COMPLETED: "onboarding_completed",
|
|
53
|
+
PUSH_TOKEN: "push_token",
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
// Security Configuration
|
|
57
|
+
export const SECURITY = {
|
|
58
|
+
/**
|
|
59
|
+
* SSL Pinning configuration for enhanced network security.
|
|
60
|
+
* Add your API server's certificate public key hashes here.
|
|
61
|
+
*
|
|
62
|
+
* To generate a pin from your certificate:
|
|
63
|
+
* 1. Get your server's certificate: openssl s_client -connect api.yourapp.com:443
|
|
64
|
+
* 2. Extract public key: openssl x509 -pubkey -noout -in cert.pem
|
|
65
|
+
* 3. Generate hash: openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* SSL_PINS: {
|
|
69
|
+
* "api.yourapp.com": [
|
|
70
|
+
* "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Primary cert
|
|
71
|
+
* "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Backup cert
|
|
72
|
+
* ],
|
|
73
|
+
* }
|
|
74
|
+
*/
|
|
75
|
+
SSL_PINS: {
|
|
76
|
+
// TODO: Add your production API certificate pins
|
|
77
|
+
// "api.yourapp.com": [
|
|
78
|
+
// "sha256/YOUR_CERTIFICATE_HASH_HERE=",
|
|
79
|
+
// ],
|
|
80
|
+
} as Record<string, string[]>,
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Enable SSL pinning in production
|
|
84
|
+
* Set to true once you've configured the SSL_PINS above
|
|
85
|
+
*/
|
|
86
|
+
ENABLE_SSL_PINNING: IS_PROD && false, // Enable when pins are configured
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Request signing configuration
|
|
90
|
+
* Used to sign API requests for added security
|
|
91
|
+
*/
|
|
92
|
+
REQUEST_SIGNING: {
|
|
93
|
+
ENABLED: false,
|
|
94
|
+
ALGORITHM: "sha256",
|
|
95
|
+
HEADER_NAME: "X-Request-Signature",
|
|
96
|
+
},
|
|
97
|
+
} as const;
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
# ADR-001: Use Zustand for State Management
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Accepted
|
|
6
|
-
|
|
7
|
-
## Date
|
|
8
|
-
|
|
9
|
-
2024-01-01
|
|
10
|
-
|
|
11
|
-
## Context
|
|
12
|
-
|
|
13
|
-
We need a state management solution for our React Native application. The requirements are:
|
|
14
|
-
|
|
15
|
-
1. Simple API with minimal boilerplate
|
|
16
|
-
2. Good TypeScript support
|
|
17
|
-
3. Small bundle size (mobile performance matters)
|
|
18
|
-
4. Support for persistence
|
|
19
|
-
5. Devtools support for debugging
|
|
20
|
-
6. No need for complex middleware or actions
|
|
21
|
-
|
|
22
|
-
Options considered:
|
|
23
|
-
|
|
24
|
-
- **Redux Toolkit**: Industry standard, but verbose for our needs
|
|
25
|
-
- **MobX**: Powerful but adds complexity with observables
|
|
26
|
-
- **Jotai**: Atomic state, good for simple cases
|
|
27
|
-
- **Zustand**: Simple, small, flexible
|
|
28
|
-
- **React Context**: Built-in, but can cause performance issues
|
|
29
|
-
|
|
30
|
-
## Decision
|
|
31
|
-
|
|
32
|
-
We chose **Zustand** for global state management.
|
|
33
|
-
|
|
34
|
-
## Rationale
|
|
35
|
-
|
|
36
|
-
1. **Simplicity**: Creating a store is just a function call
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
const useStore = create((set) => ({
|
|
40
|
-
count: 0,
|
|
41
|
-
increment: () => set((s) => ({ count: s.count + 1 })),
|
|
42
|
-
}));
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
2. **Bundle size**: ~1KB gzipped vs Redux's ~7KB
|
|
46
|
-
|
|
47
|
-
3. **TypeScript**: Excellent inference, minimal type annotations needed
|
|
48
|
-
|
|
49
|
-
4. **Persistence**: Easy integration with AsyncStorage via middleware
|
|
50
|
-
|
|
51
|
-
5. **No Providers**: Works outside React components, useful for API services
|
|
52
|
-
|
|
53
|
-
6. **Selective subscriptions**: Components only re-render when selected state changes
|
|
54
|
-
|
|
55
|
-
## Consequences
|
|
56
|
-
|
|
57
|
-
### Positive
|
|
58
|
-
|
|
59
|
-
- Faster development with less boilerplate
|
|
60
|
-
- Smaller bundle size
|
|
61
|
-
- Easy to test stores
|
|
62
|
-
- Can use stores outside React (e.g., in API handlers)
|
|
63
|
-
|
|
64
|
-
### Negative
|
|
65
|
-
|
|
66
|
-
- Less structured than Redux (could lead to inconsistent patterns)
|
|
67
|
-
- Smaller ecosystem than Redux
|
|
68
|
-
- Team needs to establish conventions
|
|
69
|
-
|
|
70
|
-
### Mitigation
|
|
71
|
-
|
|
72
|
-
- Document store patterns in CONTRIBUTING.md
|
|
73
|
-
- Use TypeScript for store definitions
|
|
74
|
-
- Keep stores focused (one concern per store)
|
|
75
|
-
|
|
76
|
-
## References
|
|
77
|
-
|
|
78
|
-
- [Zustand Documentation](https://github.com/pmndrs/zustand)
|
|
79
|
-
- [React Native Performance](https://reactnative.dev/docs/performance)
|
|
1
|
+
# ADR-001: Use Zustand for State Management
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Date
|
|
8
|
+
|
|
9
|
+
2024-01-01
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
We need a state management solution for our React Native application. The requirements are:
|
|
14
|
+
|
|
15
|
+
1. Simple API with minimal boilerplate
|
|
16
|
+
2. Good TypeScript support
|
|
17
|
+
3. Small bundle size (mobile performance matters)
|
|
18
|
+
4. Support for persistence
|
|
19
|
+
5. Devtools support for debugging
|
|
20
|
+
6. No need for complex middleware or actions
|
|
21
|
+
|
|
22
|
+
Options considered:
|
|
23
|
+
|
|
24
|
+
- **Redux Toolkit**: Industry standard, but verbose for our needs
|
|
25
|
+
- **MobX**: Powerful but adds complexity with observables
|
|
26
|
+
- **Jotai**: Atomic state, good for simple cases
|
|
27
|
+
- **Zustand**: Simple, small, flexible
|
|
28
|
+
- **React Context**: Built-in, but can cause performance issues
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
We chose **Zustand** for global state management.
|
|
33
|
+
|
|
34
|
+
## Rationale
|
|
35
|
+
|
|
36
|
+
1. **Simplicity**: Creating a store is just a function call
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const useStore = create((set) => ({
|
|
40
|
+
count: 0,
|
|
41
|
+
increment: () => set((s) => ({ count: s.count + 1 })),
|
|
42
|
+
}));
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **Bundle size**: ~1KB gzipped vs Redux's ~7KB
|
|
46
|
+
|
|
47
|
+
3. **TypeScript**: Excellent inference, minimal type annotations needed
|
|
48
|
+
|
|
49
|
+
4. **Persistence**: Easy integration with AsyncStorage via middleware
|
|
50
|
+
|
|
51
|
+
5. **No Providers**: Works outside React components, useful for API services
|
|
52
|
+
|
|
53
|
+
6. **Selective subscriptions**: Components only re-render when selected state changes
|
|
54
|
+
|
|
55
|
+
## Consequences
|
|
56
|
+
|
|
57
|
+
### Positive
|
|
58
|
+
|
|
59
|
+
- Faster development with less boilerplate
|
|
60
|
+
- Smaller bundle size
|
|
61
|
+
- Easy to test stores
|
|
62
|
+
- Can use stores outside React (e.g., in API handlers)
|
|
63
|
+
|
|
64
|
+
### Negative
|
|
65
|
+
|
|
66
|
+
- Less structured than Redux (could lead to inconsistent patterns)
|
|
67
|
+
- Smaller ecosystem than Redux
|
|
68
|
+
- Team needs to establish conventions
|
|
69
|
+
|
|
70
|
+
### Mitigation
|
|
71
|
+
|
|
72
|
+
- Document store patterns in CONTRIBUTING.md
|
|
73
|
+
- Use TypeScript for store definitions
|
|
74
|
+
- Keep stores focused (one concern per store)
|
|
75
|
+
|
|
76
|
+
## References
|
|
77
|
+
|
|
78
|
+
- [Zustand Documentation](https://github.com/pmndrs/zustand)
|
|
79
|
+
- [React Native Performance](https://reactnative.dev/docs/performance)
|