Skip to content

Commit

Permalink
Merge pull request #45251 from software-mansion-labs/@kosmydel/qbo-ex…
Browse files Browse the repository at this point in the history
…port/auto-sync-errors

[QBO Export] feat: handling auto-sync errors
  • Loading branch information
arosiclair authored Aug 20, 2024
2 parents 892f641 + 451bb3d commit c62f29f
Show file tree
Hide file tree
Showing 19 changed files with 779 additions and 758 deletions.
135 changes: 0 additions & 135 deletions src/components/ConnectToNetSuiteButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,135 +0,0 @@
import React, {useRef, useState} from 'react';
import type {View} from 'react-native';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
import PopoverMenu from '@components/PopoverMenu';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {removePolicyConnection} from '@libs/actions/connections';
import {getAdminPoliciesConnectedToNetSuite} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
import {isControlPolicy} from '@libs/PolicyUtils';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {ConnectToNetSuiteButtonProps} from './types';

function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeConnecting, integrationToDisconnect}: ConnectToNetSuiteButtonProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const policy = usePolicy(policyID);

const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);

const hasPoliciesConnectedToNetSuite = !!getAdminPoliciesConnectedToNetSuite()?.length;
const {isSmallScreenWidth} = useWindowDimensions();
const [isReuseConnectionsPopoverOpen, setIsReuseConnectionsPopoverOpen] = useState(false);
const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
const threeDotsMenuContainerRef = useRef<View>(null);
const connectionOptions = [
{
icon: Expensicons.LinkCopy,
text: translate('workspace.common.createNewConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
{
icon: Expensicons.Copy,
text: translate('workspace.common.reuseExistingConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
];

return (
<>
<Button
onPress={() => {
if (!isControlPolicy(policy)) {
Navigation.navigate(
ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.netsuite.alias, ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID)),
);
return;
}

if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
}

if (!hasPoliciesConnectedToNetSuite) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
return;
}

if (!isSmallScreenWidth) {
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
setReuseConnectionPopoverPosition({
horizontal: x + width,
vertical: y + height,
});
});
}
setIsReuseConnectionsPopoverOpen(true);
}}
text={translate('workspace.accounting.setup')}
style={styles.justifyContentCenter}
small
isDisabled={isOffline}
ref={threeDotsMenuContainerRef}
/>
<PopoverMenu
isVisible={isReuseConnectionsPopoverOpen}
onClose={() => {
setIsReuseConnectionsPopoverOpen(false);
}}
withoutOverlay
menuItems={connectionOptions}
onItemSelected={(item) => {
if (!item?.onSelected) {
return;
}
item.onSelected();
}}
anchorPosition={reuseConnectionPopoverPosition}
anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP}}
anchorRef={threeDotsMenuContainerRef}
/>
{shouldDisconnectIntegrationBeforeConnecting && isDisconnectModalOpen && integrationToDisconnect && (
<AccountingConnectionConfirmationModal
onConfirm={() => {
removePolicyConnection(policyID, integrationToDisconnect);
setIsDisconnectModalOpen(false);

if (!hasPoliciesConnectedToNetSuite) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
return;
}
if (!isSmallScreenWidth) {
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
setReuseConnectionPopoverPosition({
horizontal: x + width,
vertical: y + height,
});
});
}
setIsReuseConnectionsPopoverOpen(true);
}}
integrationToConnect={CONST.POLICY.CONNECTIONS.NAME.NETSUITE}
onCancel={() => setIsDisconnectModalOpen(false)}
/>
)}
</>
);
}

export default ConnectToNetSuiteButton;
86 changes: 86 additions & 0 deletions src/components/ConnectToNetSuiteFlow/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, {useEffect, useState} from 'react';
import * as Expensicons from '@components/Icon/Expensicons';
import PopoverMenu from '@components/PopoverMenu';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {getAdminPoliciesConnectedToNetSuite} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
import {useAccountingContext} from '@pages/workspace/accounting/AccountingContext';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {ConnectToNetSuiteFlowProps} from './types';

function ConnectToNetSuiteFlow({policyID}: ConnectToNetSuiteFlowProps) {
const {translate} = useLocalize();

const hasPoliciesConnectedToNetSuite = !!getAdminPoliciesConnectedToNetSuite()?.length;
const {isSmallScreenWidth} = useWindowDimensions();
const [isReuseConnectionsPopoverOpen, setIsReuseConnectionsPopoverOpen] = useState(false);
const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
const {popoverAnchorRefs} = useAccountingContext();

const threeDotsMenuContainerRef = popoverAnchorRefs?.current?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE];

const connectionOptions = [
{
icon: Expensicons.LinkCopy,
text: translate('workspace.common.createNewConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
{
icon: Expensicons.Copy,
text: translate('workspace.common.reuseExistingConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
];

useEffect(() => {
if (!hasPoliciesConnectedToNetSuite) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
return;
}
setIsReuseConnectionsPopoverOpen(true);
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (threeDotsMenuContainerRef) {
if (!isSmallScreenWidth) {
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
setReuseConnectionPopoverPosition({
horizontal: x + width,
vertical: y + height,
});
});
}

return (
<PopoverMenu
isVisible={isReuseConnectionsPopoverOpen}
onClose={() => {
setIsReuseConnectionsPopoverOpen(false);
}}
withoutOverlay
menuItems={connectionOptions}
onItemSelected={(item) => {
if (!item?.onSelected) {
return;
}
item.onSelected();
}}
anchorPosition={reuseConnectionPopoverPosition}
anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP}}
anchorRef={threeDotsMenuContainerRef}
/>
);
}
}

export default ConnectToNetSuiteFlow;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {PolicyConnectionName} from '@src/types/onyx/Policy';

type ConnectToNetSuiteButtonProps = {
type ConnectToNetSuiteFlowProps = {
policyID: string;
shouldDisconnectIntegrationBeforeConnecting?: boolean;
integrationToDisconnect?: PolicyConnectionName;
};

// eslint-disable-next-line import/prefer-default-export
export type {ConnectToNetSuiteButtonProps};
export type {ConnectToNetSuiteFlowProps};
112 changes: 0 additions & 112 deletions src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,112 +0,0 @@
import React, {useRef, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {WebView} from 'react-native-webview';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import Button from '@components/Button';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import {getQuickbooksOnlineSetupLink} from '@libs/actions/connections/QuickbooksOnline';
import * as PolicyAction from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session} from '@src/types/onyx';
import type {ConnectToQuickbooksOnlineButtonProps} from './types';

type ConnectToQuickbooksOnlineButtonOnyxProps = {
/** Session info for the currently logged in user. */
session: OnyxEntry<Session>;
};

const renderLoading = () => <FullScreenLoadingIndicator />;

function ConnectToQuickbooksOnlineButton({
policyID,
session,
shouldDisconnectIntegrationBeforeConnecting,
integrationToDisconnect,
}: ConnectToQuickbooksOnlineButtonProps & ConnectToQuickbooksOnlineButtonOnyxProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const webViewRef = useRef<WebView>(null);
const [isWebViewOpen, setWebViewOpen] = useState(false);
const {isOffline} = useNetwork();

const authToken = session?.authToken ?? null;

const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);

return (
<>
<Button
onPress={() => {
if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
}
// Since QBO doesn't support Taxes, we should disable them from the LHN when connecting to QBO
PolicyAction.enablePolicyTaxes(policyID, false);
setWebViewOpen(true);
}}
text={translate('workspace.accounting.setup')}
style={styles.justifyContentCenter}
small
isDisabled={isOffline}
/>
{shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect && isDisconnectModalOpen && (
<AccountingConnectionConfirmationModal
onConfirm={() => {
// Since QBO doesn't support Taxes, we should disable them from the LHN when connecting to QBO
PolicyAction.enablePolicyTaxes(policyID, false);
removePolicyConnection(policyID, integrationToDisconnect);
setIsDisconnectModalOpen(false);
setWebViewOpen(true);
}}
integrationToConnect={CONST.POLICY.CONNECTIONS.NAME.QBO}
onCancel={() => setIsDisconnectModalOpen(false)}
/>
)}
{isWebViewOpen && (
<Modal
onClose={() => setWebViewOpen(false)}
fullscreen
isVisible
type={CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE}
>
<HeaderWithBackButton
title={translate('workspace.accounting.title')}
onBackButtonPress={() => setWebViewOpen(false)}
/>
<FullPageOfflineBlockingView>
<WebView
ref={webViewRef}
source={{
uri: getQuickbooksOnlineSetupLink(policyID),
headers: {
Cookie: `authToken=${authToken}`,
},
}}
incognito // 'incognito' prop required for Android, issue here https://github.com/react-native-webview/react-native-webview/issues/1352
startInLoadingState
renderLoading={renderLoading}
/>
</FullPageOfflineBlockingView>
</Modal>
)}
</>
);
}

ConnectToQuickbooksOnlineButton.displayName = 'ConnectToQuickbooksOnlineButton';

export default withOnyx<ConnectToQuickbooksOnlineButtonProps & ConnectToQuickbooksOnlineButtonOnyxProps, ConnectToQuickbooksOnlineButtonOnyxProps>({
session: {
key: ONYXKEYS.SESSION,
},
})(ConnectToQuickbooksOnlineButton);
Loading

0 comments on commit c62f29f

Please sign in to comment.