From 692a8156da9b498fa908851e2955c045df212dc7 Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Wed, 13 Aug 2025 18:21:57 +0530 Subject: [PATCH] ai prediction detil screen added --- app/assets/dicom/dicom-viewer.html | 502 ++++++++++++++++++ app/assets/dicom/test-dicom-viewer.html | 443 ++++++++++++++++ .../navigation/AIPredictionStackNavigator.tsx | 4 +- .../screens/AIPredictionDetailScreen.tsx | 6 +- .../PatientCare/screens/PatientsScreen.tsx | 18 +- app/shared/components/DICOM_VIEWER_README.md | 207 ++++++++ app/shared/components/DicomViewer.tsx | 306 +++++++++++ app/shared/components/DicomViewerTest.tsx | 252 +++++++++ app/shared/components/index.ts | 6 + package-lock.json | 15 + package.json | 1 + 11 files changed, 1746 insertions(+), 14 deletions(-) create mode 100644 app/assets/dicom/dicom-viewer.html create mode 100644 app/assets/dicom/test-dicom-viewer.html create mode 100644 app/shared/components/DICOM_VIEWER_README.md create mode 100644 app/shared/components/DicomViewer.tsx create mode 100644 app/shared/components/DicomViewerTest.tsx diff --git a/app/assets/dicom/dicom-viewer.html b/app/assets/dicom/dicom-viewer.html new file mode 100644 index 0000000..7217af0 --- /dev/null +++ b/app/assets/dicom/dicom-viewer.html @@ -0,0 +1,502 @@ + + + + + + + + DICOM Viewer + + + +
+
+
+
Loading DICOM Viewer...
+
+
+ + + + + + diff --git a/app/assets/dicom/test-dicom-viewer.html b/app/assets/dicom/test-dicom-viewer.html new file mode 100644 index 0000000..0f7c363 --- /dev/null +++ b/app/assets/dicom/test-dicom-viewer.html @@ -0,0 +1,443 @@ + + + + + + DICOM Viewer Test + + + +
+

DICOM Viewer Test

+

Test the DICOM viewer functionality in your browser before using it in React Native.

+ +
+

Sample DICOM URLs

+
+
+ Sample 1:
+ LIDC-IDRI-0001 +
+
+ Sample 2:
+ LIDC-IDRI-0001 +
+
+ Sample 3:
+ LIDC-IDRI-0001 +
+
+
+ +
+

Custom DICOM URL

+ + +
+ +
+
Ready to load DICOM image
+
+
Click a sample URL above or enter a custom URL to load a DICOM image
+
+
+ + + +
+
+ + + + diff --git a/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx b/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx index ede10d5..6cf719d 100644 --- a/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx +++ b/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx @@ -13,7 +13,7 @@ import { theme } from '../../../theme'; // Import screens import { AIPredictionsScreen, AIPredictionDetailScreen } from '../screens'; -import { ComingSoonScreen } from '../../../shared/components'; +import { ComingSoonScreen, DicomViewer } from '../../../shared/components'; // Import types import type { AIPredictionStackParamList } from './navigationTypes'; @@ -152,7 +152,7 @@ const AIPredictionStackNavigator: React.FC = () => { {/* AI Prediction Details Screen */} } options={({ navigation, route }) => ({ title: 'Create Suggestion', headerLeft: () => ( diff --git a/app/modules/AIPrediction/screens/AIPredictionDetailScreen.tsx b/app/modules/AIPrediction/screens/AIPredictionDetailScreen.tsx index 06d3be5..633d785 100644 --- a/app/modules/AIPrediction/screens/AIPredictionDetailScreen.tsx +++ b/app/modules/AIPrediction/screens/AIPredictionDetailScreen.tsx @@ -543,8 +543,8 @@ const AIPredictionDetailScreen: React.FC = ({ - - <> + + {/* Enhanced Header */} {renderHeader()} @@ -860,7 +860,7 @@ const AIPredictionDetailScreen: React.FC = ({ - + diff --git a/app/modules/PatientCare/screens/PatientsScreen.tsx b/app/modules/PatientCare/screens/PatientsScreen.tsx index ae0d21c..1e37cf7 100644 --- a/app/modules/PatientCare/screens/PatientsScreen.tsx +++ b/app/modules/PatientCare/screens/PatientsScreen.tsx @@ -470,15 +470,15 @@ const PatientsScreen: React.FC = ({ navigation }) => { /> } // Performance optimizations - removeClippedSubviews={true} - maxToRenderPerBatch={10} - windowSize={10} - initialNumToRender={8} - getItemLayout={(data, index) => ({ - length: 120, // Approximate height of PatientCard - offset: 120 * index, - index, - })} + // removeClippedSubviews={true} + // maxToRenderPerBatch={10} + // windowSize={10} + // initialNumToRender={8} + // getItemLayout={(data, index) => ({ + // length: 120, // Approximate height of PatientCard + // offset: 120 * index, + // index, + // })} /> ); diff --git a/app/shared/components/DICOM_VIEWER_README.md b/app/shared/components/DICOM_VIEWER_README.md new file mode 100644 index 0000000..7ca6e3a --- /dev/null +++ b/app/shared/components/DICOM_VIEWER_README.md @@ -0,0 +1,207 @@ +# DICOM Viewer Component + +## Overview +The DICOM Viewer component is a React Native component that uses WebView to display DICOM medical imaging files. It integrates with Cornerstone.js and Cornerstone WADO Image Loader for robust DICOM file handling. + +## Features +- ✅ WebView-based DICOM rendering +- ✅ Cornerstone.js integration for medical imaging +- ✅ Support for remote DICOM URLs +- ✅ Loading states and error handling +- ✅ Real-time communication with React Native +- ✅ Responsive design for mobile devices + +## Components + +### 1. DicomViewer +The main DICOM viewer component. + +**Props:** +```typescript +interface DicomViewerProps { + dicomUrl: string; // URL to the DICOM file + onError?: (error: string) => void; // Error callback + onLoad?: () => void; // Load success callback +} +``` + +**Usage:** +```typescript +import { DicomViewer } from '../shared/components'; + + console.error('DICOM Error:', error)} + onLoad={() => console.log('DICOM loaded successfully')} +/> +``` + +### 2. DicomViewerTest +A test component for testing different DICOM URLs and debugging issues. + +**Usage:** +```typescript +import { DicomViewerTest } from '../shared/components'; + + +``` + +## How It Works + +### 1. WebView Setup +- Loads a local HTML file (`dicom-viewer.html`) +- Enables JavaScript and DOM storage +- Allows file access and universal access from file URLs + +### 2. Library Loading +- Dynamically loads Cornerstone.js from CDN +- Loads Cornerstone WADO Image Loader +- Initializes the viewer when libraries are ready + +### 3. DICOM Processing +- Receives DICOM URL from React Native via postMessage +- Uses Cornerstone to load and display the DICOM image +- Handles errors and success states + +### 4. Communication +- Sends status messages back to React Native +- Reports loading, success, and error states +- Enables debugging and user feedback + +## Troubleshooting + +### Black Screen Issues + +#### 1. Check Console Logs +Open the React Native debugger and check for: +- WebView loading errors +- JavaScript execution errors +- Network request failures + +#### 2. Verify DICOM URL +- Ensure the URL is accessible from the device +- Check if the URL returns a valid DICOM file +- Verify CORS settings if loading from a different domain + +#### 3. Library Loading Issues +- Check internet connectivity (libraries load from CDN) +- Verify the HTML file path is correct +- Check WebView permissions and settings + +#### 4. Platform-Specific Issues + +**Android:** +- Ensure `allowFileAccess` is enabled +- Check if the HTML file is in the correct assets folder +- Verify WebView permissions in AndroidManifest.xml + +**iOS:** +- Check WebView configuration in Info.plist +- Ensure JavaScript is enabled +- Verify file access permissions + +### Common Error Messages + +#### "Failed to load DICOM viewer libraries" +- Check internet connectivity +- Verify CDN URLs are accessible +- Check WebView JavaScript settings + +#### "Failed to load DICOM image" +- Verify DICOM URL is accessible +- Check if the file is a valid DICOM format +- Ensure the server supports CORS + +#### "Invalid message received from app" +- Check the message format being sent +- Verify the postMessage implementation +- Check WebView message handling + +## Testing + +### 1. Use Sample URLs +The test component includes sample DICOM URLs that are known to work: +- Sample DICOM 1-3 from OHIF examples + +### 2. Test Custom URLs +- Enter your own DICOM URLs +- Test with different file formats +- Verify error handling + +### 3. Debug Mode +- Check console logs in React Native debugger +- Monitor WebView messages +- Use the test component for isolated testing + +## Performance Tips + +### 1. Image Optimization +- Use compressed DICOM files when possible +- Consider implementing progressive loading +- Cache frequently accessed images + +### 2. Memory Management +- Dispose of WebView when not in use +- Monitor memory usage with large DICOM files +- Implement proper cleanup in useEffect + +### 3. Network Optimization +- Use CDN for DICOM files when possible +- Implement retry logic for failed requests +- Consider offline caching for critical images + +## Security Considerations + +### 1. URL Validation +- Validate DICOM URLs before loading +- Implement URL whitelisting if needed +- Sanitize user input for custom URLs + +### 2. WebView Security +- Limit WebView permissions to minimum required +- Implement proper origin whitelisting +- Monitor for malicious content + +### 3. Data Privacy +- Ensure DICOM files don't contain PHI +- Implement proper data handling protocols +- Follow HIPAA compliance guidelines + +## Future Enhancements + +### 1. Offline Support +- Bundle Cornerstone libraries locally +- Implement offline DICOM caching +- Support for local DICOM files + +### 2. Advanced Features +- Multi-planar reconstruction (MPR) +- Measurement tools +- Annotation capabilities +- 3D rendering support + +### 3. Performance Improvements +- WebAssembly integration +- GPU acceleration +- Progressive image loading +- Background processing + +## Support + +For issues and questions: +1. Check this README for common solutions +2. Review console logs and error messages +3. Test with sample URLs first +4. Verify WebView configuration +5. Check platform-specific requirements + +## Dependencies + +- `react-native-webview`: WebView component +- `cornerstone-core`: Medical imaging library +- `cornerstone-wado-image-loader`: DICOM file loader + +## License + +Design & Developed by Tech4Biz Solutions +Copyright (c) Spurrin Innovations. All rights reserved. diff --git a/app/shared/components/DicomViewer.tsx b/app/shared/components/DicomViewer.tsx new file mode 100644 index 0000000..8a2c33d --- /dev/null +++ b/app/shared/components/DicomViewer.tsx @@ -0,0 +1,306 @@ +/* + * File: DicomViewer.tsx + * Description: DICOM viewer component using WebView for medical imaging + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useRef, useEffect, useState } from 'react'; +import { WebView, WebViewMessageEvent } from 'react-native-webview'; +import { Platform, View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native'; + +// Interface for component props +interface DicomViewerProps { + dicomUrl: string; + onError?: (error: string) => void; + onLoad?: () => void; + debugMode?: boolean; +} + +// Interface for WebView reference +interface WebViewRef { + postMessage: (message: string) => void; + reload: () => void; +} + +export default function DicomViewer({ dicomUrl, onError, onLoad, debugMode = false }: DicomViewerProps): React.ReactElement { + const webViewRef = useRef(null); + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + const [debugInfo, setDebugInfo] = useState([]); + const [webViewReady, setWebViewReady] = useState(false); + + // Debug logging function + const debugLog = (message: string) => { + if (debugMode) { + const timestamp = new Date().toLocaleTimeString(); + const logMessage = `[${timestamp}] ${message}`; + console.log(logMessage); + setDebugInfo(prev => [...prev.slice(-9), logMessage]); // Keep last 10 messages + } + }; + + // Handle WebView load events + const handleLoadStart = () => { + debugLog('WebView load started'); + setIsLoading(true); + setHasError(false); + }; + + const handleLoadEnd = () => { + debugLog('WebView load ended'); + setIsLoading(false); + setWebViewReady(true); + onLoad?.(); + }; + + const handleError = (error: any) => { + debugLog(`WebView error: ${JSON.stringify(error)}`); + setIsLoading(false); + setHasError(true); + onError?.(error?.nativeEvent?.description || 'Failed to load DICOM viewer'); + }; + + const handleMessage = (event: WebViewMessageEvent) => { + try { + const message = event.nativeEvent.data; + debugLog(`Message from WebView: ${message}`); + + // Try to parse JSON message + if (typeof message === 'string') { + try { + const parsedMessage = JSON.parse(message); + debugLog(`Parsed message: ${JSON.stringify(parsedMessage)}`); + + if (parsedMessage.type === 'error') { + setHasError(true); + onError?.(parsedMessage.message); + } else if (parsedMessage.type === 'success') { + setHasError(false); + } + } catch (parseError) { + debugLog(`Failed to parse message as JSON: ${parseError}`); + } + } + } catch (error) { + debugLog(`Error handling WebView message: ${error}`); + } + }; + + // Send DICOM URL to WebView when component mounts or URL changes + useEffect(() => { + if (webViewRef.current && dicomUrl && webViewReady) { + debugLog(`Sending DICOM URL to WebView: ${dicomUrl}`); + + // Wait a bit for WebView to be ready + const timer = setTimeout(() => { + if (webViewRef.current) { + try { + webViewRef.current.postMessage(dicomUrl); + debugLog('DICOM URL sent successfully'); + } catch (error) { + debugLog(`Failed to send DICOM URL: ${error}`); + } + } + }, 1000); + + return () => clearTimeout(timer); + } + }, [dicomUrl, webViewReady]); + + // Reload WebView if there's an error + const handleRetry = () => { + debugLog('Retrying WebView load'); + if (webViewRef.current) { + setHasError(false); + setIsLoading(true); + setWebViewReady(false); + webViewRef.current.reload(); + } + }; + + // Clear debug info + const clearDebugInfo = () => { + setDebugInfo([]); + }; + + return ( + + ( + + + Loading DICOM Viewer... + + )} + /> + + {hasError && ( + + Failed to load DICOM viewer + + URL: {dicomUrl} + + + Tap to retry + + + )} + + {debugMode && ( + + + Debug Info + + Clear + + + + {debugInfo.map((info, index) => ( + {info} + ))} + + + + WebView Ready: {webViewReady ? 'Yes' : 'No'} + + + Loading: {isLoading ? 'Yes' : 'No'} + + + Error: {hasError ? 'Yes' : 'No'} + + + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#000', + }, + webview: { + flex: 1, + backgroundColor: '#000', + }, + loadingContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#000', + }, + loadingText: { + color: '#FFF', + marginTop: 16, + fontSize: 16, + }, + errorContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#000', + padding: 20, + }, + errorText: { + color: '#F44336', + fontSize: 18, + textAlign: 'center', + marginBottom: 16, + fontWeight: '600', + }, + errorDetails: { + color: '#FF9800', + fontSize: 14, + textAlign: 'center', + marginBottom: 20, + fontFamily: 'monospace', + }, + retryButton: { + backgroundColor: '#2196F3', + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 8, + }, + retryButtonText: { + color: '#FFFFFF', + fontSize: 16, + fontWeight: '600', + }, + debugContainer: { + position: 'absolute', + top: 10, + right: 10, + backgroundColor: 'rgba(0,0,0,0.9)', + borderRadius: 8, + padding: 10, + maxWidth: 300, + maxHeight: 400, + }, + debugHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 8, + }, + debugTitle: { + color: '#FFFFFF', + fontSize: 14, + fontWeight: '600', + }, + clearButton: { + color: '#2196F3', + fontSize: 12, + textDecorationLine: 'underline', + }, + debugContent: { + maxHeight: 200, + }, + debugText: { + color: '#FFFFFF', + fontSize: 10, + fontFamily: 'monospace', + marginBottom: 2, + }, + debugStatus: { + marginTop: 8, + paddingTop: 8, + borderTopColor: '#333', + borderTopWidth: 1, + }, + debugStatusText: { + color: '#CCC', + fontSize: 10, + marginBottom: 2, + }, +}); + +/* + * End of File: DicomViewer.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ diff --git a/app/shared/components/DicomViewerTest.tsx b/app/shared/components/DicomViewerTest.tsx new file mode 100644 index 0000000..c8c670d --- /dev/null +++ b/app/shared/components/DicomViewerTest.tsx @@ -0,0 +1,252 @@ +/* + * File: DicomViewerTest.tsx + * Description: Test component for DICOM viewer with sample URLs + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ + +import React, { useState } from 'react'; +import { View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, Alert } from 'react-native'; +import DicomViewer from './DicomViewer'; + +// Sample DICOM URLs for testing +const SAMPLE_DICOM_URLS = [ + { + name: 'Sample DICOM 1', + url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm' + }, + { + name: 'Sample DICOM 2', + url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm' + }, + { + name: 'Sample DICOM 3', + url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-003.dcm' + } +]; + +export default function DicomViewerTest(): React.ReactElement { + const [dicomUrl, setDicomUrl] = useState(SAMPLE_DICOM_URLS[0].url); + const [customUrl, setCustomUrl] = useState(''); + + const handleUrlSelect = (url: string) => { + setDicomUrl(url); + setCustomUrl(url); + }; + + const handleCustomUrlSubmit = () => { + if (customUrl.trim()) { + setDicomUrl(customUrl.trim()); + } else { + Alert.alert('Error', 'Please enter a valid URL'); + } + }; + + const handleViewerError = (error: string) => { + console.error('DICOM Viewer Error:', error); + Alert.alert('DICOM Viewer Error', error); + }; + + const handleViewerLoad = () => { + console.log('DICOM Viewer loaded successfully'); + }; + + return ( + + + DICOM Viewer Test + Test different DICOM URLs + + + + Sample DICOM URLs: + {SAMPLE_DICOM_URLS.map((sample, index) => ( + handleUrlSelect(sample.url)} + > + + {sample.name} + + + {sample.url} + + + ))} + + + Custom DICOM URL: + + + + Load + + + + + + Current URL: + {dicomUrl} + + + + + DICOM Viewer: + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#F5F5F5', + }, + header: { + backgroundColor: '#2196F3', + padding: 20, + alignItems: 'center', + }, + title: { + fontSize: 24, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 8, + }, + subtitle: { + fontSize: 16, + color: '#E3F2FD', + }, + urlSection: { + flex: 1, + padding: 16, + }, + sectionTitle: { + fontSize: 18, + fontWeight: '600', + color: '#212121', + marginBottom: 12, + marginTop: 16, + }, + urlButton: { + backgroundColor: '#FFFFFF', + padding: 16, + borderRadius: 8, + marginBottom: 8, + borderWidth: 1, + borderColor: '#E0E0E0', + }, + selectedUrlButton: { + backgroundColor: '#E3F2FD', + borderColor: '#2196F3', + }, + urlButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#212121', + marginBottom: 4, + }, + selectedUrlButtonText: { + color: '#1976D2', + }, + urlText: { + fontSize: 12, + color: '#757575', + fontFamily: 'monospace', + }, + selectedUrlText: { + color: '#1976D2', + }, + customUrlSection: { + marginTop: 16, + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + textInput: { + flex: 1, + backgroundColor: '#FFFFFF', + borderWidth: 1, + borderColor: '#E0E0E0', + borderRadius: 8, + padding: 12, + fontSize: 14, + color: '#212121', + marginRight: 8, + }, + submitButton: { + backgroundColor: '#4CAF50', + paddingHorizontal: 20, + paddingVertical: 12, + borderRadius: 8, + }, + submitButtonText: { + color: '#FFFFFF', + fontSize: 14, + fontWeight: '600', + }, + currentUrlSection: { + marginTop: 16, + backgroundColor: '#FFFFFF', + padding: 16, + borderRadius: 8, + borderWidth: 1, + borderColor: '#E0E0E0', + }, + currentUrl: { + fontSize: 12, + color: '#757575', + fontFamily: 'monospace', + backgroundColor: '#F5F5F5', + padding: 8, + borderRadius: 4, + }, + viewerContainer: { + flex: 2, + backgroundColor: '#000000', + margin: 16, + borderRadius: 8, + overflow: 'hidden', + }, + viewerTitle: { + color: '#FFFFFF', + fontSize: 16, + fontWeight: '600', + padding: 12, + backgroundColor: '#333333', + }, +}); + +/* + * End of File: DicomViewerTest.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ diff --git a/app/shared/components/index.ts b/app/shared/components/index.ts index 1eca321..c126ee9 100644 --- a/app/shared/components/index.ts +++ b/app/shared/components/index.ts @@ -15,6 +15,12 @@ export { CustomModal } from './CustomModal'; // Coming Soon Screen Component export { ComingSoonScreen } from './ComingSoonScreen'; +// DICOM Viewer Component +export { default as DicomViewer } from './DicomViewer'; + +// DICOM Viewer Test Component +export { default as DicomViewerTest } from './DicomViewerTest'; + // ============================================================================ // TYPE EXPORTS // ============================================================================ diff --git a/package-lock.json b/package-lock.json index 524f4dc..7668689 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "react-native-toast-message": "^2.2.1", "react-native-tts": "^4.1.1", "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.15.0", "react-redux": "^9.2.0", "redux-persist": "^6.0.0" }, @@ -12077,6 +12078,20 @@ "node": ">=10" } }, + "node_modules/react-native-webview": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", + "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz", diff --git a/package.json b/package.json index 228bc40..322d181 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-native-toast-message": "^2.2.1", "react-native-tts": "^4.1.1", "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.15.0", "react-redux": "^9.2.0", "redux-persist": "^6.0.0" },