Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ const CONST = {
ADMIN_POLICIES_URL: 'admin_policies',
ADMIN_DOMAINS_URL: 'admin_domains',
INBOX: 'inbox',
POLICY_CONNECTIONS_URL: (policyID: string) => `policy?param={"policyID":"${policyID}"}#connections`,
},

EXPENSIFY_POLICY_DOMAIN: 'expensify-policy',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3276,6 +3276,8 @@ export default {
}
}
},
errorODIntegration: "There's an error with a connection that's been set up in Expensify Classic. ",
goToODToFix: 'Go to Expensiy Classic to fix this issue.',
setup: 'Connect',
lastSync: (relativeDate: string) => `Last synced ${relativeDate}`,
import: 'Import',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3258,6 +3258,8 @@ export default {
}
}
},
errorODIntegration: 'Hay un error con una conexión que se ha configurado en Expensify Classic. ',
goToODToFix: 'Ve a Expensify Classic para solucionar este problema.',
setup: 'Configurar',
lastSync: (relativeDate: string) => `Recién sincronizado ${relativeDate}`,
import: 'Importar',
Expand Down
5 changes: 5 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,10 @@ function hasIntegrationAutoSync(policy: Policy | undefined, connectedIntegration
return (connectedIntegration && policy?.connections?.[connectedIntegration]?.config?.autoSync?.enabled) ?? false;
}

function hasUnsupportedIntegration(policy: Policy | undefined, accountingIntegrations?: ConnectionName[]) {
return !(accountingIntegrations ?? Object.values(CONST.POLICY.CONNECTIONS.NAME)).some((integration) => !!policy?.connections?.[integration]);
}

function getCurrentConnectionName(policy: Policy | undefined): string | undefined {
const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME);
const connectionKey = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]);
Expand Down Expand Up @@ -1097,6 +1101,7 @@ export {
getAllTaxRatesNamesAndKeys as getAllTaxRates,
getTagNamesFromTagsLists,
getDomainNameForPolicy,
hasUnsupportedIntegration,
getWorkflowApprovalsUnavailable,
};

Expand Down
11 changes: 9 additions & 2 deletions src/libs/actions/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ Onyx.connect({
});

function buildOldDotURL(url: string, shortLivedAuthToken?: string): Promise<string> {
const hasHashParams = url.indexOf('#') !== -1;
const hashIndex = url.lastIndexOf('#');
const hasHashParams = hashIndex !== -1;
const hasURLParams = url.indexOf('?') !== -1;
let originURL = url;
let hashParams = '';
if (hasHashParams) {
originURL = url.substring(0, hashIndex);
hashParams = url.substring(hashIndex);
}

const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : '';
const emailParam = `email=${encodeURIComponent(currentUserEmail)}`;
Expand All @@ -51,7 +58,7 @@ function buildOldDotURL(url: string, shortLivedAuthToken?: string): Promise<stri
const oldDotDomain = Url.addTrailingForwardSlash(environmentURL);

// If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters.
return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`;
return `${oldDotDomain}${originURL}${hasURLParams ? '&' : '?'}${params}${hashParams}`;
});
}

Expand Down
58 changes: 44 additions & 14 deletions src/pages/workspace/accounting/PolicyAccountingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import CollapsibleSection from '@components/CollapsibleSection';
import ConfirmModal from '@components/ConfirmModal';
import FormHelpMessage from '@components/FormHelpMessage';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
Expand All @@ -14,6 +15,8 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import ThreeDotsMenu from '@components/ThreeDotsMenu';
import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -24,6 +27,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections';
import * as PolicyUtils from '@libs/PolicyUtils';
import {
areSettingsInErrorFields,
findCurrentXeroOrganization,
Expand All @@ -39,6 +43,7 @@ import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import type {AnchorPosition} from '@styles/index';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand Down Expand Up @@ -93,6 +98,9 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
connectedIntegration === connectionSyncProgress?.connectionName ? connectionSyncProgress : undefined,
);

const hasSyncError = PolicyUtils.hasSyncError(policy, isSyncInProgress);
const hasUnsupportedNDIntegration = PolicyUtils.hasUnsupportedIntegration(policy, accountingIntegrations);

const tenants = useMemo(() => getXeroTenants(policy), [policy]);
const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID);

Expand Down Expand Up @@ -368,7 +376,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
]);

const otherIntegrationsItems = useMemo(() => {
if (isEmptyObject(policy?.connections) && !isSyncInProgress) {
if (isEmptyObject(policy?.connections) && !isSyncInProgress && !(hasUnsupportedNDIntegration && hasSyncError)) {
return;
}
const otherIntegrations = accountingIntegrations.filter(
Expand Down Expand Up @@ -419,6 +427,8 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
isOffline,
startIntegrationFlow,
popoverAnchorRefs,
hasUnsupportedNDIntegration,
hasSyncError,
]);

return (
Expand Down Expand Up @@ -448,20 +458,40 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) {
titleStyles={styles.accountSettingsSectionTitle}
childrenStyles={styles.pt5}
>
{connectionsMenuItems.map((menuItem) => (
<OfflineWithFeedback
pendingAction={menuItem.pendingAction}
key={menuItem.title}
shouldDisableStrikeThrough
>
<MenuItem
brickRoadIndicator={menuItem.brickRoadIndicator}
{!(hasUnsupportedNDIntegration && hasSyncError) &&
connectionsMenuItems.map((menuItem) => (
<OfflineWithFeedback
pendingAction={menuItem.pendingAction}
key={menuItem.title}
// eslint-disable-next-line react/jsx-props-no-spreading
{...menuItem}
/>
</OfflineWithFeedback>
))}
shouldDisableStrikeThrough
>
<MenuItem
brickRoadIndicator={menuItem.brickRoadIndicator}
key={menuItem.title}
// eslint-disable-next-line react/jsx-props-no-spreading
{...menuItem}
/>
</OfflineWithFeedback>
))}
{hasUnsupportedNDIntegration && hasSyncError && (
<FormHelpMessage
isError
shouldShowRedDotIndicator
style={styles.menuItemError}
>
<Text style={[{color: theme.textError}]}>
{translate('workspace.accounting.errorODIntegration')}
<TextLink
onPress={() => {
// Go to Expensify Classic.
Link.openOldDotLink(CONST.OLDDOT_URLS.POLICY_CONNECTIONS_URL(policyID));
}}
>
{translate('workspace.accounting.goToODToFix')}
</TextLink>
</Text>
</FormHelpMessage>
)}
{otherIntegrationsItems && (
<CollapsibleSection
title={translate('workspace.accounting.other')}
Expand Down