@croacroa/react-native-template 2.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.
|
@@ -56,7 +56,7 @@ interface ErrorBoundaryProps {
|
|
|
56
56
|
* </ErrorBoundary>
|
|
57
57
|
* ```
|
|
58
58
|
*/
|
|
59
|
-
export class
|
|
59
|
+
export class LocalErrorBoundary extends Component<
|
|
60
60
|
ErrorBoundaryProps,
|
|
61
61
|
ErrorBoundaryState
|
|
62
62
|
> {
|
|
@@ -232,7 +232,7 @@ export function AsyncBoundary({
|
|
|
232
232
|
fallback: loadingFallback,
|
|
233
233
|
}: AsyncBoundaryProps) {
|
|
234
234
|
return (
|
|
235
|
-
<
|
|
235
|
+
<LocalErrorBoundary
|
|
236
236
|
key={resetKey}
|
|
237
237
|
fallback={errorFallback}
|
|
238
238
|
onError={onError}
|
|
@@ -246,7 +246,7 @@ export function AsyncBoundary({
|
|
|
246
246
|
>
|
|
247
247
|
{children}
|
|
248
248
|
</SuspenseBoundary>
|
|
249
|
-
</
|
|
249
|
+
</LocalErrorBoundary>
|
|
250
250
|
);
|
|
251
251
|
}
|
|
252
252
|
|
|
@@ -298,11 +298,11 @@ export function QueryBoundary({
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
return (
|
|
301
|
-
<
|
|
301
|
+
<LocalErrorBoundary fallback={errorFallback}>
|
|
302
302
|
<Suspense fallback={loadingFallback || <DefaultLoadingFallback />}>
|
|
303
303
|
{children}
|
|
304
304
|
</Suspense>
|
|
305
|
-
</
|
|
305
|
+
</LocalErrorBoundary>
|
|
306
306
|
);
|
|
307
307
|
}
|
|
308
308
|
|
|
@@ -351,7 +351,7 @@ export function BoundaryProvider({ children }: { children: ReactNode }) {
|
|
|
351
351
|
|
|
352
352
|
return (
|
|
353
353
|
<BoundaryContext.Provider value={{ resetAll, reportError }}>
|
|
354
|
-
<
|
|
354
|
+
<LocalErrorBoundary key={resetKey}>{children}</LocalErrorBoundary>
|
|
355
355
|
</BoundaryContext.Provider>
|
|
356
356
|
);
|
|
357
357
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Provider components for app-wide functionality
|
|
3
3
|
* @module components/providers
|
|
4
|
+
*
|
|
5
|
+
* Error Boundary Usage:
|
|
6
|
+
* - Use `ErrorBoundary` (from components/ErrorBoundary) for app root with Sentry integration
|
|
7
|
+
* - Use `LocalErrorBoundary` for local async patterns (lighter, no Sentry)
|
|
4
8
|
*/
|
|
5
9
|
|
|
10
|
+
// Main ErrorBoundary with Sentry integration - use at app root
|
|
11
|
+
export { ErrorBoundary, withErrorBoundary } from "../ErrorBoundary";
|
|
12
|
+
|
|
13
|
+
// Local boundaries for async patterns - lighter weight, no Sentry
|
|
6
14
|
export {
|
|
7
|
-
|
|
15
|
+
LocalErrorBoundary,
|
|
8
16
|
SuspenseBoundary,
|
|
9
17
|
AsyncBoundary,
|
|
10
18
|
QueryBoundary,
|
package/hooks/useOffline.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module hooks/useOffline
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useEffect, useState } from "react";
|
|
7
|
+
import { useEffect, useState, useCallback } from "react";
|
|
8
8
|
import NetInfo, { NetInfoState } from "@react-native-community/netinfo";
|
|
9
9
|
import { onlineManager } from "@tanstack/react-query";
|
|
10
10
|
import { toast } from "@/utils/toast";
|
|
@@ -68,9 +68,10 @@ export function useOffline(options: UseOfflineOptions = {}) {
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Hook for tracking pending mutations count.
|
|
71
|
-
*
|
|
71
|
+
* Integrates with the backgroundSync mutation queue.
|
|
72
72
|
*
|
|
73
|
-
* @
|
|
73
|
+
* @param pollingInterval - How often to check the queue (default: 5000ms)
|
|
74
|
+
* @returns Object with pendingCount, hasPending flag, and refresh function
|
|
74
75
|
*
|
|
75
76
|
* @example
|
|
76
77
|
* ```tsx
|
|
@@ -87,13 +88,36 @@ export function useOffline(options: UseOfflineOptions = {}) {
|
|
|
87
88
|
* }
|
|
88
89
|
* ```
|
|
89
90
|
*/
|
|
90
|
-
export function usePendingMutations() {
|
|
91
|
+
export function usePendingMutations(pollingInterval = 5000) {
|
|
91
92
|
const [pendingCount, setPendingCount] = useState(0);
|
|
93
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
94
|
+
|
|
95
|
+
const refresh = useCallback(async () => {
|
|
96
|
+
try {
|
|
97
|
+
const { getMutationQueue } = await import("@/services/backgroundSync");
|
|
98
|
+
const queue = await getMutationQueue();
|
|
99
|
+
setPendingCount(queue.length);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error("[usePendingMutations] Failed to get queue:", error);
|
|
102
|
+
} finally {
|
|
103
|
+
setIsLoading(false);
|
|
104
|
+
}
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
// Initial fetch
|
|
109
|
+
refresh();
|
|
110
|
+
|
|
111
|
+
// Poll for updates
|
|
112
|
+
const interval = setInterval(refresh, pollingInterval);
|
|
113
|
+
|
|
114
|
+
return () => clearInterval(interval);
|
|
115
|
+
}, [refresh, pollingInterval]);
|
|
92
116
|
|
|
93
|
-
// This would need to be integrated with mutation cache
|
|
94
|
-
// For now, return 0 as a placeholder
|
|
95
117
|
return {
|
|
96
118
|
pendingCount,
|
|
97
119
|
hasPending: pendingCount > 0,
|
|
120
|
+
isLoading,
|
|
121
|
+
refresh,
|
|
98
122
|
};
|
|
99
123
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@croacroa/react-native-template",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Production-ready React Native template with Expo, authentication, i18n, offline support, and more",
|
|
5
5
|
"main": "expo-router/entry",
|
|
6
6
|
"author": "Croacroa <contact@croacroa.dev>",
|
package/services/security.ts
CHANGED
|
@@ -199,6 +199,9 @@ export const SSL_PINNING_CONFIG = {
|
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
201
|
* Generate Android network_security_config.xml content
|
|
202
|
+
* Save this to android/app/src/main/res/xml/network_security_config.xml
|
|
203
|
+
* and reference it in AndroidManifest.xml:
|
|
204
|
+
* <application android:networkSecurityConfig="@xml/network_security_config">
|
|
202
205
|
*/
|
|
203
206
|
getAndroidConfig(): string {
|
|
204
207
|
const pinEntries = Object.entries(SECURITY.SSL_PINS)
|
|
@@ -209,7 +212,7 @@ export const SSL_PINNING_CONFIG = {
|
|
|
209
212
|
|
|
210
213
|
return ` <domain-config cleartextTrafficPermitted="false">
|
|
211
214
|
<domain includeSubdomains="true">${domain}</domain>
|
|
212
|
-
<pin-set expiration="
|
|
215
|
+
<pin-set expiration="2026-12-31">
|
|
213
216
|
${pinElements}
|
|
214
217
|
</pin-set>
|
|
215
218
|
</domain-config>`;
|
|
@@ -226,4 +229,58 @@ ${pinElements}
|
|
|
226
229
|
${pinEntries}
|
|
227
230
|
</network-security-config>`;
|
|
228
231
|
},
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate iOS TrustKit configuration dictionary
|
|
235
|
+
* Add this to your AppDelegate.mm or use expo-build-properties plugin
|
|
236
|
+
*
|
|
237
|
+
* For Expo managed workflow, add to app.config.ts:
|
|
238
|
+
* ```
|
|
239
|
+
* plugins: [
|
|
240
|
+
* ["expo-build-properties", {
|
|
241
|
+
* ios: {
|
|
242
|
+
* infoPlist: SSL_PINNING_CONFIG.getIOSInfoPlist()
|
|
243
|
+
* }
|
|
244
|
+
* }]
|
|
245
|
+
* ]
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
getIOSConfig(): Record<string, unknown> {
|
|
249
|
+
const pinnedDomains: Record<string, unknown> = {};
|
|
250
|
+
|
|
251
|
+
Object.entries(SECURITY.SSL_PINS).forEach(([domain, pins]) => {
|
|
252
|
+
pinnedDomains[domain] = {
|
|
253
|
+
TSKIncludeSubdomains: true,
|
|
254
|
+
TSKEnforcePinning: true,
|
|
255
|
+
TSKPublicKeyHashes: pins.map((pin) => pin.replace("sha256/", "")),
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
TSKSwizzleNetworkDelegates: true,
|
|
261
|
+
TSKPinnedDomains: pinnedDomains,
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Generate iOS Info.plist entries for SSL pinning
|
|
267
|
+
*/
|
|
268
|
+
getIOSInfoPlist(): Record<string, unknown> {
|
|
269
|
+
return {
|
|
270
|
+
NSAppTransportSecurity: {
|
|
271
|
+
NSAllowsArbitraryLoads: false,
|
|
272
|
+
NSPinnedDomains: Object.fromEntries(
|
|
273
|
+
Object.entries(SECURITY.SSL_PINS).map(([domain, pins]) => [
|
|
274
|
+
domain,
|
|
275
|
+
{
|
|
276
|
+
NSIncludesSubdomains: true,
|
|
277
|
+
NSPinnedLeafIdentities: pins.map((pin) => ({
|
|
278
|
+
"SPKI-SHA256-BASE64": pin.replace("sha256/", ""),
|
|
279
|
+
})),
|
|
280
|
+
},
|
|
281
|
+
])
|
|
282
|
+
),
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
},
|
|
229
286
|
};
|
package/utils/validation.ts
CHANGED
|
@@ -12,7 +12,8 @@ export const passwordSchema = z
|
|
|
12
12
|
.min(8, "Password must be at least 8 characters")
|
|
13
13
|
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
|
|
14
14
|
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
|
|
15
|
-
.regex(/[0-9]/, "Password must contain at least one number")
|
|
15
|
+
.regex(/[0-9]/, "Password must contain at least one number")
|
|
16
|
+
.regex(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/, "Password must contain at least one special character");
|
|
16
17
|
|
|
17
18
|
export const nameSchema = z
|
|
18
19
|
.string()
|