diff --git a/src/CONST.ts b/src/CONST.ts index 051fe789751d..a007a68f6d39 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6,7 +6,7 @@ import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; import type {Video} from './libs/actions/Report'; import type {MileageRate} from './libs/DistanceRequestUtils'; -import BankAccount from './libs/models/BankAccount'; +import BankAccountState from './libs/models/BankAccountState'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; import type PlaidBankAccount from './types/onyx/PlaidBankAccount'; @@ -5166,7 +5166,7 @@ const CONST = { REIMBURSEMENT_ACCOUNT: { DEFAULT_DATA: { achData: { - state: BankAccount.STATE.SETUP, + state: BankAccountState.SETUP, }, isLoading: false, errorFields: {}, diff --git a/src/libs/Log.ts b/src/libs/Log.ts index 2ccbd1d37882..7cf7d9ff4614 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -12,12 +12,14 @@ import pkg from '../../package.json'; import {addLog, flushAllLogsOnAppLaunch} from './actions/Console'; import {shouldAttachLog} from './Console'; import getPlatform from './getPlatform'; -import * as Network from './Network'; import requireParameters from './requireParameters'; let timeout: NodeJS.Timeout; let shouldCollectLogs = false; +// Dynamic Import to avoid circular dependency +const Network = () => import('./Network'); + Onyx.connect({ key: ONYXKEYS.SHOULD_STORE_LOGS, callback: (val) => { @@ -40,7 +42,9 @@ function LogCommand(parameters: LogCommandParameters): Promise<{requestID: strin // Note: We are forcing Log to run since it requires no authToken and should only be queued when we are offline. // Non-cancellable request: during logout, when requests are cancelled, we don't want to cancel any remaining logs - return Network.post(commandName, {...parameters, forceNetworkRequest: true, canCancel: false}) as Promise<{requestID: string}>; + return Network().then((module) => { + return module.post(commandName, {...parameters, forceNetworkRequest: true, canCancel: false}) as Promise<{requestID: string}>; + }); } // eslint-disable-next-line diff --git a/src/libs/Middleware/Reauthentication.ts b/src/libs/Middleware/Reauthentication.ts index 09a01e821cb2..881e9c9ad314 100644 --- a/src/libs/Middleware/Reauthentication.ts +++ b/src/libs/Middleware/Reauthentication.ts @@ -1,4 +1,3 @@ -import * as Authentication from '@libs/Authentication'; import Log from '@libs/Log'; import * as MainQueue from '@libs/Network/MainQueue'; import * as NetworkStore from '@libs/Network/NetworkStore'; @@ -7,6 +6,9 @@ import * as Request from '@libs/Request'; import CONST from '@src/CONST'; import type Middleware from './types'; +// Dynamic Import to avoid circular dependency +const Authentication = () => import('@libs/Authentication'); + // We store a reference to the active authentication request so that we are only ever making one request to authenticate at a time. let isAuthenticating: Promise | null = null; @@ -15,15 +17,18 @@ function reauthenticate(commandName?: string): Promise { return isAuthenticating; } - isAuthenticating = Authentication.reauthenticate(commandName) - .then((response) => { - isAuthenticating = null; - return response; - }) - .catch((error) => { - isAuthenticating = null; - throw error; - }); + isAuthenticating = Authentication().then((module) => + module + .reauthenticate(commandName) + .then((response) => { + isAuthenticating = null; + return response; + }) + .catch((error) => { + isAuthenticating = null; + throw error; + }), + ); return isAuthenticating; } diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index d54668bf3f69..646e6e6a8e4a 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -4,7 +4,6 @@ import {CommonActions, getPathFromState, StackActions} from '@react-navigation/n import type {OnyxEntry} from 'react-native-onyx'; import Log from '@libs/Log'; import {isCentralPaneName, removePolicyIDParamFromState} from '@libs/NavigationUtils'; -import * as ReportConnection from '@libs/ReportConnection'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; @@ -39,6 +38,9 @@ let pendingRoute: Route | null = null; let shouldPopAllStateOnUP = false; +// Dynamic Import to avoid circular dependency +const ReportConnection = () => import('@libs/ReportConnection'); + /** * Inform the navigation that next time user presses UP we should pop all the state back to LHN. */ @@ -66,8 +68,11 @@ const dismissModal = (reportID?: string, ref = navigationRef) => { originalDismissModal(ref); return; } - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - originalDismissModalWithReport({reportID, ...report}, ref); + + ReportConnection().then((module) => { + const report = module.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + originalDismissModalWithReport({reportID, ...report}, ref); + }); }; // Re-exporting the closeRHPFlow here to fill in default value for navigationRef. The closeRHPFlow isn't defined in this file to avoid cyclic dependencies. const closeRHPFlow = (ref = navigationRef) => originalCloseRHPFlow(ref); diff --git a/src/libs/ReportConnection.ts b/src/libs/ReportConnection.ts index f9f913d59beb..0846c618f455 100644 --- a/src/libs/ReportConnection.ts +++ b/src/libs/ReportConnection.ts @@ -3,10 +3,10 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; import * as PriorityModeActions from './actions/PriorityMode'; -import * as ReportHelperActions from './actions/Report'; -// Dynamic Import to avoid circular dependency +// Dynamic Imports to avoid circular dependencies const UnreadIndicatorUpdaterHelper = () => import('./UnreadIndicatorUpdater'); +const ReportHelperActions = () => import('./actions/Report'); const reportIDToNameMap: Record = {}; let allReports: OnyxCollection; @@ -29,7 +29,9 @@ Onyx.connect({ return; } reportIDToNameMap[report.reportID] = report.reportName ?? report.displayName ?? report.reportID; - ReportHelperActions.handleReportChanged(report); + ReportHelperActions().then((module) => { + module.handleReportChanged(report); + }); }); }, }); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index cb2c1a52e2d2..09fce0e97837 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -5,8 +5,6 @@ import lodashSet from 'lodash/set'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import {getPolicyCategoriesData} from '@libs/actions/Policy/Category'; -import {getPolicyTagsData} from '@libs/actions/Policy/Tag'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -32,6 +30,10 @@ import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getDistanceInMeters from './getDistanceInMeters'; +// Dynamic Imports to avoid circular dependencies +const CategoryActions = () => import('@libs/actions/Policy/Category'); +const TagActions = () => import('@libs/actions/Policy/Tag'); + let allTransactions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -1136,37 +1138,41 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); - const policyCategories = getPolicyCategoriesData(report?.policyID ?? '-1'); - const availableCategories = Object.values(policyCategories) - .filter((category) => differentValues.includes(category.name) && category.enabled && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) - .map((e) => e.name); - - if (!areAllFieldsEqualForKey && policy?.areCategoriesEnabled && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { - change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; - } else if (areAllFieldsEqualForKey) { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; - } - } else if (fieldName === 'tag') { - const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); - const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); - if (isMultiLevelTags) { - if (areAllFieldsEqualForKey || !policy?.areTagsEnabled) { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; - } else { - processChanges(fieldName, transactions, keys); - } - } else { - const differentValues = getDifferentValues(transactions, keys); - const policyTagsObj = Object.values(Object.values(policyTags).at(0)?.tags ?? {}); - const availableTags = policyTagsObj - .filter((tag) => differentValues.includes(tag.name) && tag.enabled && tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) + CategoryActions().then((actions) => { + const policyCategories = actions.getPolicyCategoriesData(report?.policyID ?? '-1'); + const availableCategories = Object.values(policyCategories) + .filter((category) => differentValues.includes(category.name) && category.enabled && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) .map((e) => e.name); - if (!areAllFieldsEqualForKey && policy?.areTagsEnabled && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { - change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; + + if (!areAllFieldsEqualForKey && policy?.areCategoriesEnabled && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { + change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } - } + }); + } else if (fieldName === 'tag') { + TagActions().then((module) => { + const policyTags = module.getPolicyTagsData(report?.policyID ?? '-1'); + const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); + if (isMultiLevelTags) { + if (areAllFieldsEqualForKey || !policy?.areTagsEnabled) { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } else { + processChanges(fieldName, transactions, keys); + } + } else { + const differentValues = getDifferentValues(transactions, keys); + const policyTagsObj = Object.values(Object.values(policyTags).at(0)?.tags ?? {}); + const availableTags = policyTagsObj + .filter((tag) => differentValues.includes(tag.name) && tag.enabled && tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) + .map((e) => e.name); + if (!areAllFieldsEqualForKey && policy?.areTagsEnabled && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { + change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; + } else if (areAllFieldsEqualForKey) { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } + } + }); } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { diff --git a/src/libs/Url.ts b/src/libs/Url.ts index ef595c484bae..dd64de4947d5 100644 --- a/src/libs/Url.ts +++ b/src/libs/Url.ts @@ -1,5 +1,4 @@ import 'react-native-url-polyfill/auto'; -import CONST from '@src/CONST'; import type {Route} from '@src/ROUTES'; /** @@ -71,8 +70,8 @@ function hasURL(text: string) { return urlPattern.test(text); } -function extractUrlDomain(url: string): string | undefined { - const match = String(url).match(CONST.REGEX.DOMAIN_BASE); +function extractUrlDomain(url: string, domainRegex: string): string | undefined { + const match = String(url).match(domainRegex); return match?.[1]; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3b0b43accbc6..d64b9a02bb56 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -34,7 +34,6 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; -import * as FileUtils from '@libs/fileDownload/FileUtils'; import GoogleTagManager from '@libs/GoogleTagManager'; import * as IOUUtils from '@libs/IOUUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; @@ -77,6 +76,9 @@ import * as Report from './Report'; import {getRecentWaypoints, sanitizeRecentWaypoints} from './Transaction'; import * as TransactionEdit from './TransactionEdit'; +// Dynamic Import to avoid circular dependency +const FileUtils = () => import('@libs/fileDownload/FileUtils'); + type IOURequestType = ValueOf; type OneOnOneIOUReport = OnyxTypes.Report | undefined | null; @@ -7952,7 +7954,9 @@ function navigateToStartStepIfScanFileCannotBeRead( } IOUUtils.navigateToStartMoneyRequestStep(requestType, iouType, transactionID, reportID); }; - FileUtils.readFileAsync(receiptPath.toString(), receiptFilename, onSuccess, onFailure, receiptType); + FileUtils().then((module) => { + module.readFileAsync(receiptPath.toString(), receiptFilename, onSuccess, onFailure, receiptType); + }); } /** Save the preferred payment method for a policy */ diff --git a/src/libs/actions/PriorityMode.ts b/src/libs/actions/PriorityMode.ts index 2aca5d9f9de8..0b23a998c1ca 100644 --- a/src/libs/actions/PriorityMode.ts +++ b/src/libs/actions/PriorityMode.ts @@ -1,11 +1,12 @@ import debounce from 'lodash/debounce'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; -import * as ReportConnection from '@libs/ReportConnection'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +const ReportConnection = () => import('@libs/ReportConnection'); + /** * This actions file is used to automatically switch a user into #focus mode when they exceed a certain number of reports. We do this primarily for performance reasons. * Similar to the "Welcome action" we must wait for a number of things to happen when the user signs in or refreshes the page: @@ -77,11 +78,13 @@ function resetHasReadRequiredDataFromStorage() { } function checkRequiredData() { - if (ReportConnection.getAllReports() === undefined || hasTriedFocusMode === undefined || isInFocusMode === undefined || isLoadingReportData) { - return; - } + ReportConnection().then((module) => { + if (module.getAllReports() === undefined || hasTriedFocusMode === undefined || isInFocusMode === undefined || isLoadingReportData) { + return; + } - resolveIsReadyPromise(); + resolveIsReadyPromise(); + }); } function tryFocusModeUpdate() { @@ -98,33 +101,36 @@ function tryFocusModeUpdate() { } const validReports = []; - const allReports = ReportConnection.getAllReports(); - Object.keys(allReports ?? {}).forEach((key) => { - const report = allReports?.[key]; - if (!report) { - return; - } - if (!ReportUtils.isValidReport(report) || !ReportUtils.isReportParticipant(currentUserAccountID ?? -1, report)) { - return; - } + ReportConnection().then((module) => { + const allReports = module.getAllReports(); + Object.keys(allReports ?? {}).forEach((key) => { + const report = allReports?.[key]; + if (!report) { + return; + } - validReports.push(report); - }); + if (!ReportUtils.isValidReport(report) || !ReportUtils.isReportParticipant(currentUserAccountID ?? -1, report)) { + return; + } - const reportCount = validReports.length; - if (reportCount < CONST.REPORT.MAX_COUNT_BEFORE_FOCUS_UPDATE) { - Log.info('Not switching user to optimized focus mode as they do not have enough reports', false, {reportCount}); - return; - } + validReports.push(report); + }); - Log.info('Switching user to optimized focus mode', false, {reportCount, hasTriedFocusMode, isInFocusMode}); + const reportCount = validReports.length; + if (reportCount < CONST.REPORT.MAX_COUNT_BEFORE_FOCUS_UPDATE) { + Log.info('Not switching user to optimized focus mode as they do not have enough reports', false, {reportCount}); + return; + } + + Log.info('Switching user to optimized focus mode', false, {reportCount, hasTriedFocusMode, isInFocusMode}); - // Record that we automatically switched them so we don't ask again. - hasTriedFocusMode = true; + // Record that we automatically switched them so we don't ask again. + hasTriedFocusMode = true; - // Setting this triggers a modal to open and notify the user. - Onyx.set(ONYXKEYS.FOCUS_MODE_NOTIFICATION, true); + // Setting this triggers a modal to open and notify the user. + Onyx.set(ONYXKEYS.FOCUS_MODE_NOTIFICATION, true); + }); }); } diff --git a/src/libs/models/BankAccount.ts b/src/libs/models/BankAccount.ts index a86a30b57e86..87e2efc85931 100644 --- a/src/libs/models/BankAccount.ts +++ b/src/libs/models/BankAccount.ts @@ -3,8 +3,9 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {BankAccountAdditionalData} from '@src/types/onyx/BankAccount'; import type BankAccountJSON from '@src/types/onyx/BankAccount'; +import BankAccountState from './BankAccountState'; -type State = ValueOf; +type State = ValueOf; type ACHData = { routingNumber: string; @@ -20,14 +21,7 @@ type ACHData = { class BankAccount { json: BankAccountJSON; - static STATE = { - PENDING: 'PENDING', - OPEN: 'OPEN', - DELETED: 'DELETED', - LOCKED: 'LOCKED', - SETUP: 'SETUP', - VERIFYING: 'VERIFYING', - }; + static STATE = BankAccountState; constructor(accountJSON: BankAccountJSON) { this.json = accountJSON; diff --git a/src/libs/models/BankAccountState.ts b/src/libs/models/BankAccountState.ts new file mode 100644 index 000000000000..5763fc3980fe --- /dev/null +++ b/src/libs/models/BankAccountState.ts @@ -0,0 +1,10 @@ +const BankAccountState = { + PENDING: 'PENDING', + OPEN: 'OPEN', + DELETED: 'DELETED', + LOCKED: 'LOCKED', + SETUP: 'SETUP', + VERIFYING: 'VERIFYING', +}; + +export default BankAccountState; diff --git a/src/libs/saveLastRoute/index.ios.ts b/src/libs/saveLastRoute/index.ios.ts index 12107937800b..0c00f716adef 100644 --- a/src/libs/saveLastRoute/index.ios.ts +++ b/src/libs/saveLastRoute/index.ios.ts @@ -1,6 +1,10 @@ -import {updateLastRoute} from '@libs/actions/App'; import Navigation from '@libs/Navigation/Navigation'; +// Dynamic Import to avoid circular dependency +const AppActions = () => import('@libs/actions/App'); + export default function saveLastRoute() { - updateLastRoute(Navigation.getActiveRoute()); + AppActions().then(({updateLastRoute}) => { + updateLastRoute(Navigation.getActiveRoute()); + }); } diff --git a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx index d6cb1e71407f..bd824a2aee4b 100644 --- a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx +++ b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx @@ -56,7 +56,7 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC if (!ValidationUtils.isValidWebsite(companyWebsite)) { errors.companyWebsite = translate('bankAccount.error.website'); } else { - const domain = Url.extractUrlDomain(companyWebsite); + const domain = Url.extractUrlDomain(companyWebsite, CONST.REGEX.DOMAIN_BASE); if (!domain || !Str.isValidDomainName(domain)) { errors.companyWebsite = translate('iou.invalidDomainError'); diff --git a/src/pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite.tsx b/src/pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite.tsx index aead9b03f49a..e1b944a57323 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite.tsx @@ -49,7 +49,7 @@ function WorkspaceInvoicingDetailsWebsite({route}: WorkspaceInvoicingDetailsWebs if (!ValidationUtils.isValidWebsite(companyWebsite)) { errors.companyWebsite = translate('bankAccount.error.website'); } else { - const domain = Url.extractUrlDomain(companyWebsite); + const domain = Url.extractUrlDomain(companyWebsite, CONST.REGEX.DOMAIN_BASE); if (!domain || !Str.isValidDomainName(domain)) { errors.companyWebsite = translate('iou.invalidDomainError');