App.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. import {
  24. startAutoSyncListener,
  25. stopAutoSyncListener
  26. } from 'src/watermelondb/features/chat/networkSync';
  27. const IOS_STORE_URL = 'https://apps.apple.com/app/id6502843543';
  28. const ANDROID_STORE_URL =
  29. 'https://play.google.com/store/apps/details?id=com.nomadmania.presentation';
  30. LogBox.ignoreLogs([/defaultProps will be removed/, /IMGElement/]);
  31. const userId = (storage.get('uid', StoreType.STRING) as string) ?? 'not_logged_in';
  32. const token = (storage.get('token', StoreType.STRING) as string) ?? null;
  33. const routingInstrumentation = Sentry.reactNavigationIntegration({
  34. enableTimeToInitialDisplay: true
  35. });
  36. Sentry.init({
  37. dsn: 'https://c9b37005f4be22a17a582603ebc17598@o4507781200543744.ingest.de.sentry.io/4507781253824592',
  38. integrations: [Sentry.reactNativeTracingIntegration({ routingInstrumentation })],
  39. debug: false,
  40. enableNative: true,
  41. enableNativeCrashHandling: true,
  42. ignoreErrors: ['Network Error', 'ECONNABORTED', 'timeout of 10000ms exceeded'],
  43. beforeSend(event, hint) {
  44. if (userId) {
  45. event.user = {
  46. ...event.user,
  47. userId: userId
  48. };
  49. }
  50. const isNonError = hint?.originalException instanceof Error === false;
  51. if (isNonError || event.message?.match(/Non-Error exception captured/)) {
  52. return {
  53. ...event,
  54. message: `Processed Non-Error: ${event.message || 'No message'}`,
  55. level: 'warning',
  56. contexts: {
  57. ...event.contexts,
  58. non_error: {
  59. type: typeof hint?.originalException,
  60. value: JSON.stringify(hint?.originalException)
  61. }
  62. }
  63. };
  64. }
  65. return event;
  66. }
  67. });
  68. const linking = {
  69. prefixes: [API_HOST, 'nomadmania://'],
  70. config: {
  71. screens: {
  72. publicProfileView: '/profile/:userId',
  73. inAppEvent: '/event/:url',
  74. inAppMapTab: '/map/:lon/:lat',
  75. inAppChatsList: '/messages',
  76. inAppMasterRanking: '/master-ranking',
  77. inAppLpiRanking: '/lpi',
  78. inAppInMemoriam: '/in-memoriam',
  79. inAppInHistory: '/travellers-in-history',
  80. inAppUNMaster: '/un-masters',
  81. inAppStatistics: '/statistics',
  82. inAppTriumphs: '/triumphs',
  83. inAppSeriesRanking: '/series-ranking',
  84. inAppMyFriends: '/my-friends',
  85. inAppFixers: '/fixers',
  86. inAppEarth: '/earth',
  87. inAppSeries: '/series',
  88. inAppCountries: '/countries',
  89. inAppRegions: '/regions',
  90. inAppDare: '/dare',
  91. inAppRegionPreview: {
  92. path: 'region/:id',
  93. alias: [
  94. 'region_mqp/:id',
  95. 'region/:id/',
  96. 'region_mqp/:id/',
  97. ],
  98. },
  99. inAppPhotos: '/photos',
  100. inAppTrips2025: '/trips'
  101. }
  102. }
  103. };
  104. const App = () => {
  105. return (
  106. <QueryClientProvider client={queryClient}>
  107. <ErrorProvider>
  108. <InnerApp />
  109. </ErrorProvider>
  110. </QueryClientProvider>
  111. );
  112. };
  113. const InnerApp = () => {
  114. const errorContext = useError();
  115. const navigation = React.useRef(null);
  116. const [isUpdateAvailable, setIsUpdateAvailable] = useState(false);
  117. useEffect(() => {
  118. setupGlobalErrorHandler(navigation);
  119. }, []);
  120. useEffect(() => {
  121. setupInterceptors(errorContext);
  122. }, [errorContext]);
  123. useEffect(() => {
  124. const checkLatestVersion = async () => {
  125. try {
  126. const response = await axios.get(API_URL + '/' + API.LATEST_VERSION, {
  127. headers: {
  128. 'App-Version': APP_VERSION,
  129. Platform: Platform.OS
  130. }
  131. });
  132. const { version } = response.data;
  133. const formatVersion = (versionString: string) => {
  134. return parseInt(versionString.replace(/\./g, ''), 10);
  135. };
  136. const currentVersionInt = formatVersion(APP_VERSION);
  137. const latestVersionInt = formatVersion(version);
  138. if (latestVersionInt > currentVersionInt) {
  139. setIsUpdateAvailable(true);
  140. }
  141. } catch (error) {
  142. console.error('Failed to check latest version:', error);
  143. }
  144. };
  145. checkLatestVersion();
  146. }, []);
  147. useEffect(() => {
  148. if (token) startAutoSyncListener(token);
  149. return () => stopAutoSyncListener();
  150. }, [token]);
  151. const handleUpdatePress = () => {
  152. const storeUrl = Platform.OS === 'ios' ? IOS_STORE_URL : ANDROID_STORE_URL;
  153. Linking.openURL(storeUrl).catch((err) => console.error('Failed to open store URL:', err));
  154. };
  155. return (
  156. <ConnectionProvider>
  157. <RegionProvider>
  158. <NavigationContainer
  159. ref={navigation}
  160. onReady={() => {
  161. routingInstrumentation.registerNavigationContainer(navigation);
  162. }}
  163. linking={linking}
  164. navigationInChildEnabled
  165. >
  166. <Route />
  167. <ConnectionBanner />
  168. <ErrorModal />
  169. <WarningModal
  170. isVisible={isUpdateAvailable}
  171. type="success"
  172. title="Update Available"
  173. message="A new version of the NomadMania app is available. Please update to the latest version."
  174. action={handleUpdatePress}
  175. onClose={() => setIsUpdateAvailable(false)}
  176. />
  177. </NavigationContainer>
  178. </RegionProvider>
  179. </ConnectionProvider>
  180. );
  181. };
  182. export default Sentry.wrap(App);