App.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import 'react-native-gesture-handler';
  2. import 'expo-splash-screen';
  3. import { QueryClientProvider } from '@tanstack/react-query';
  4. import { NavigationContainer } from '@react-navigation/native';
  5. import { queryClient } from 'src/utils/queryClient';
  6. import * as Sentry from '@sentry/react-native';
  7. import Route from './Route';
  8. import { ConnectionProvider } from 'src/contexts/ConnectionContext';
  9. import ConnectionBanner from 'src/components/ConnectionBanner/ConnectionBanner';
  10. import { RegionProvider } from 'src/contexts/RegionContext';
  11. import { ErrorProvider, useError } from 'src/contexts/ErrorContext';
  12. import { useEffect, useState } from 'react';
  13. import { setupInterceptors } from 'src/utils/request';
  14. import { ErrorModal, WarningModal } from 'src/components';
  15. import React from 'react';
  16. import { Linking, Platform } from 'react-native';
  17. import { API_HOST, API_URL, APP_VERSION } from 'src/constants';
  18. import axios from 'axios';
  19. import { API } from 'src/types';
  20. import { LogBox } from 'react-native';
  21. import { storage, StoreType } from 'src/storage';
  22. import { setupGlobalErrorHandler } from 'src/utils/globalErrorHandler';
  23. const IOS_STORE_URL = 'https://apps.apple.com/app/id6502843543';
  24. const ANDROID_STORE_URL =
  25. 'https://play.google.com/store/apps/details?id=com.nomadmania.presentation';
  26. LogBox.ignoreLogs([/defaultProps will be removed/, /IMGElement/]);
  27. const userId = (storage.get('uid', StoreType.STRING) as string) ?? 'not_logged_in';
  28. const routingInstrumentation = Sentry.reactNavigationIntegration({
  29. enableTimeToInitialDisplay: true
  30. });
  31. Sentry.init({
  32. dsn: 'https://c9b37005f4be22a17a582603ebc17598@o4507781200543744.ingest.de.sentry.io/4507781253824592',
  33. integrations: [Sentry.reactNativeTracingIntegration({ routingInstrumentation })],
  34. debug: false,
  35. enableNative: true,
  36. enableNativeCrashHandling: true,
  37. ignoreErrors: ['Network Error', 'ECONNABORTED', 'timeout of 10000ms exceeded'],
  38. beforeSend(event, hint) {
  39. if (userId) {
  40. event.user = {
  41. ...event.user,
  42. userId: userId
  43. };
  44. }
  45. const isNonError = hint?.originalException instanceof Error === false;
  46. if (isNonError || event.message?.match(/Non-Error exception captured/)) {
  47. return {
  48. ...event,
  49. message: `Processed Non-Error: ${event.message || 'No message'}`,
  50. level: 'warning',
  51. contexts: {
  52. ...event.contexts,
  53. non_error: {
  54. type: typeof hint?.originalException,
  55. value: JSON.stringify(hint?.originalException)
  56. }
  57. }
  58. };
  59. }
  60. return event;
  61. }
  62. });
  63. const linking = {
  64. prefixes: [API_HOST, 'nomadmania://'],
  65. config: {
  66. screens: {
  67. publicProfileView: '/profile/:userId',
  68. inAppEvent: '/event/:url',
  69. inAppMapTab: '/map/:lon/:lat'
  70. }
  71. }
  72. };
  73. const App = () => {
  74. return (
  75. <QueryClientProvider client={queryClient}>
  76. <ErrorProvider>
  77. <InnerApp />
  78. </ErrorProvider>
  79. </QueryClientProvider>
  80. );
  81. };
  82. const InnerApp = () => {
  83. const errorContext = useError();
  84. const navigation = React.useRef(null);
  85. const [isUpdateAvailable, setIsUpdateAvailable] = useState(false);
  86. useEffect(() => {
  87. setupGlobalErrorHandler(navigation);
  88. }, []);
  89. useEffect(() => {
  90. setupInterceptors(errorContext);
  91. }, [errorContext]);
  92. useEffect(() => {
  93. const checkLatestVersion = async () => {
  94. try {
  95. const response = await axios.get(API_URL + '/' + API.LATEST_VERSION, {
  96. headers: {
  97. 'App-Version': APP_VERSION,
  98. Platform: Platform.OS
  99. }
  100. });
  101. const { version } = response.data;
  102. const formatVersion = (versionString: string) => {
  103. return parseInt(versionString.replace(/\./g, ''), 10);
  104. };
  105. const currentVersionInt = formatVersion(APP_VERSION);
  106. const latestVersionInt = formatVersion(version);
  107. if (latestVersionInt > currentVersionInt) {
  108. setIsUpdateAvailable(true);
  109. }
  110. } catch (error) {
  111. console.error('Failed to check latest version:', error);
  112. }
  113. };
  114. checkLatestVersion();
  115. }, []);
  116. const handleUpdatePress = () => {
  117. const storeUrl = Platform.OS === 'ios' ? IOS_STORE_URL : ANDROID_STORE_URL;
  118. Linking.openURL(storeUrl).catch((err) => console.error('Failed to open store URL:', err));
  119. };
  120. return (
  121. <ConnectionProvider>
  122. <RegionProvider>
  123. <NavigationContainer
  124. ref={navigation}
  125. onReady={() => {
  126. routingInstrumentation.registerNavigationContainer(navigation);
  127. }}
  128. linking={linking}
  129. navigationInChildEnabled
  130. >
  131. <Route />
  132. <ConnectionBanner />
  133. <ErrorModal />
  134. <WarningModal
  135. isVisible={isUpdateAvailable}
  136. type="success"
  137. title="Update Available"
  138. message="A new version of the NomadMania app is available. Please update to the latest version."
  139. action={handleUpdatePress}
  140. onClose={() => setIsUpdateAvailable(false)}
  141. />
  142. </NavigationContainer>
  143. </RegionProvider>
  144. </ConnectionProvider>
  145. );
  146. };
  147. export default Sentry.wrap(App);