Skip to content
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,7 @@ const CONST = {
SYNC: 'sync',
ENABLE_NEW_CATEGORIES: 'enableNewCategories',
EXPORT: 'export',
TENANT_ID: 'tenantID',
IMPORT_CUSTOMERS: 'importCustomers',
IMPORT_TAX_RATES: 'importTaxRates',
INVOICE_STATUS: {
Expand Down
6 changes: 5 additions & 1 deletion src/components/ConnectionLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type ConnectionLayoutProps = {
/** Style of the title text */
titleStyle?: StyleProp<TextStyle> | undefined;

/** Whether to include safe area padding bottom or not */
shouldIncludeSafeAreaPaddingBottom?: boolean;

/** Whether to use ScrollView or not */
shouldUseScrollView?: boolean;
};
Expand Down Expand Up @@ -72,6 +75,7 @@ function ConnectionLayout({
featureName,
contentContainerStyle,
titleStyle,
shouldIncludeSafeAreaPaddingBottom,
shouldUseScrollView = true,
}: ConnectionLayoutProps) {
const {translate} = useLocalize();
Expand All @@ -95,7 +99,7 @@ function ConnectionLayout({
featureName={featureName}
>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
includeSafeAreaPaddingBottom={!!shouldIncludeSafeAreaPaddingBottom}
shouldEnableMaxHeight
testID={displayName}
>
Expand Down
52 changes: 28 additions & 24 deletions src/pages/workspace/accounting/PolicyAccountingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, {useMemo, useRef, useState} from 'react';
import {ActivityIndicator, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import AccountingListSkeletonView from '@components/AccountingListSkeletonView';
import CollapsibleSection from '@components/CollapsibleSection';
import ConfirmModal from '@components/ConfirmModal';
import ConnectToQuickbooksOnlineButton from '@components/ConnectToQuickbooksOnlineButton';
Expand All @@ -12,7 +11,10 @@ import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import type {MenuItemProps} from '@components/MenuItem';
import MenuItem from '@components/MenuItem';
import MenuItemList from '@components/MenuItemList';
import type {OfflineWithFeedbackProps} from '@components/OfflineWithFeedback';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
Expand Down Expand Up @@ -40,6 +42,8 @@ import type {PolicyConnectionName} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';

type MenuItemData = MenuItemProps & {pendingAction?: OfflineWithFeedbackProps['pendingAction']};

type PolicyAccountingPageOnyxProps = {
connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>;
};
Expand Down Expand Up @@ -101,7 +105,7 @@ function accountingIntegrationData(
}
}

function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataFetchNeeded}: PolicyAccountingPageProps) {
function PolicyAccountingPage({policy, connectionSyncProgress}: PolicyAccountingPageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down Expand Up @@ -141,7 +145,7 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
[translate, policyID, isOffline],
);

const connectionsMenuItems: MenuItemProps[] = useMemo(() => {
const connectionsMenuItems: MenuItemData[] = useMemo(() => {
if (isEmptyObject(policy?.connections) && !isSyncInProgress) {
return accountingIntegrations.map((integration) => {
const integrationData = accountingIntegrationData(integration, policyID, translate);
Expand Down Expand Up @@ -212,6 +216,8 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
}
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.getRoute(policyID, currentXeroOrganization?.id ?? ''));
},
pendingAction: policy?.connections?.xero?.config?.pendingFields?.tenantID,
brickRoadIndicator: policy?.connections?.xero?.config?.errorFields?.tenantID ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
},
]
: []),
Expand Down Expand Up @@ -336,30 +342,28 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF
titleStyles={styles.accountSettingsSectionTitle}
childrenStyles={styles.pt5}
>
{isConnectionDataFetchNeeded ? (
<View style={styles.mnh20}>
<AccountingListSkeletonView shouldAnimate />
</View>
) : (
<>
{connectionsMenuItems.map((menuItem) => (
<OfflineWithFeedback pendingAction={menuItem.pendingAction}>
<MenuItem
key={menuItem.title}
brickRoadIndicator={menuItem.brickRoadIndicator}
// eslint-disable-next-line react/jsx-props-no-spreading
{...menuItem}
/>
</OfflineWithFeedback>
))}
{otherIntegrationsItems && (
<CollapsibleSection
title={translate('workspace.accounting.other')}
wrapperStyle={[styles.pr3, styles.mt5, styles.pv3]}
titleStyle={[styles.textNormal, styles.colorMuted]}
textStyle={[styles.flex1, styles.userSelectNone, styles.textNormal, styles.colorMuted]}
>
<MenuItemList
menuItems={connectionsMenuItems}
menuItems={otherIntegrationsItems}
shouldUseSingleExecution
/>
{otherIntegrationsItems && (
<CollapsibleSection
title={translate('workspace.accounting.other')}
wrapperStyle={[styles.pr3, styles.mt5, styles.pv3]}
titleStyle={[styles.textNormal, styles.colorMuted]}
textStyle={[styles.flex1, styles.userSelectNone, styles.textNormal, styles.colorMuted]}
>
<MenuItemList
menuItems={otherIntegrationsItems}
shouldUseSingleExecution
/>
</CollapsibleSection>
)}
</>
</CollapsibleSection>
)}
</Section>
</View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useMemo} from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import ConnectionLayout from '@components/ConnectionLayout';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {updatePolicyConnectionConfig} from '@libs/actions/connections';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {findCurrentXeroOrganization, getXeroTenants} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
Expand All @@ -30,7 +30,8 @@ function XeroOrganizationConfigurationPage({
const {translate} = useLocalize();
const styles = useThemeStyles();
const tenants = useMemo(() => getXeroTenants(policy ?? undefined), [policy]);
const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID);
const xeroConfig = policy?.connections?.xero?.config;
const currentXeroOrganization = findCurrentXeroOrganization(tenants, xeroConfig?.tenantID);

const policyID = policy?.id ?? '';

Expand All @@ -46,33 +47,34 @@ function XeroOrganizationConfigurationPage({
return;
}

updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, 'tenantID', keyForList);
updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.TENANT_ID, keyForList);
Navigation.goBack(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID));
};

return (
<AccessOrNotFoundWrapper
<ConnectionLayout
displayName={XeroOrganizationConfigurationPage.displayName}
headerTitle="workspace.xero.organization"
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
shouldIncludeSafeAreaPaddingBottom
>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={XeroOrganizationConfigurationPage.displayName}
<OfflineWithFeedback
errors={ErrorUtils.getLatestErrorField(xeroConfig ?? {}, CONST.XERO_CONFIG.TENANT_ID)}
errorRowStyles={[styles.ph5, styles.mt2]}
onClose={() => Policy.clearXeroErrorField(policyID, CONST.XERO_CONFIG.TENANT_ID)}
>
<HeaderWithBackButton title={translate('workspace.xero.organization')} />
<ScrollView contentContainerStyle={styles.pb2}>
<Text style={[styles.ph5, styles.pb5]}>{translate('workspace.xero.organizationDescription')}</Text>
<SelectionList
ListItem={RadioListItem}
onSelectRow={saveSelection}
sections={[{data: sections}]}
initiallyFocusedOptionKey={currentXeroOrganization?.id}
/>
</ScrollView>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
<Text style={[styles.ph5, styles.pb5]}>{translate('workspace.xero.organizationDescription')}</Text>
<SelectionList
containerStyle={styles.pb0}
ListItem={RadioListItem}
onSelectRow={saveSelection}
sections={[{data: sections}]}
initiallyFocusedOptionKey={currentXeroOrganization?.id}
/>
</OfflineWithFeedback>
</ConnectionLayout>
);
}

Expand Down