Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xero authentication flow #40277

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cda7dd0
add xero to accounting page
SzymczakJ Apr 11, 2024
6bfcf11
add other integrations section
SzymczakJ Apr 16, 2024
6d5aee1
add types
SzymczakJ Apr 16, 2024
5c04043
Merge branch 'main' into @szymczak/xero-auth-flow
SzymczakJ Apr 16, 2024
74260e0
make connect to integrations work
SzymczakJ Apr 17, 2024
ed73632
fix connections empty object bug
SzymczakJ Apr 17, 2024
b8a7641
make accounting integrations more generative
SzymczakJ Apr 17, 2024
693bd94
add disconnection logic before connecting to another integration
SzymczakJ Apr 18, 2024
664b96b
move ConnectToQBO to components
SzymczakJ Apr 18, 2024
9a55673
Merge branch 'main' into @szymczak/xero-auth-flow
SzymczakJ Apr 18, 2024
a5a0fea
refactor PolicyAccountingPage
SzymczakJ Apr 19, 2024
c94db81
fix PR comments
SzymczakJ Apr 22, 2024
deed5c5
fix type errors
SzymczakJ Apr 22, 2024
9d7f085
remove unneccessary props
SzymczakJ Apr 23, 2024
64a3ab9
Merge branch 'main' into @szymczak/xero-auth-flow
SzymczakJ Apr 23, 2024
a880061
fix PR comments
SzymczakJ Apr 24, 2024
ce61660
Merge branch 'main' into @szymczak/xero-auth-flow
SzymczakJ Apr 24, 2024
c9dcf6e
fix typescript errors
SzymczakJ Apr 24, 2024
3c097f6
Merge branch 'main' into @szymczak/xero-auth-flow
SzymczakJ Apr 24, 2024
6ea7551
add export and advanced pgae navigation
SzymczakJ Apr 25, 2024
9248550
Merge branch 'main' into @szymczak/xero-auth-flow
filip-solecki Apr 26, 2024
75ccad0
fix PR comments
SzymczakJ Apr 26, 2024
4e1d347
fix linter
SzymczakJ Apr 26, 2024
8d24dc0
apply fix for QBO set up crash
cdOut Apr 29, 2024
2ad1e72
apply corrected translations
cdOut Apr 29, 2024
7ceb7c6
Merge branch 'main' into @szymczak/xero-auth-flow
cdOut Apr 29, 2024
04df83f
add the displayName property back to the ConnectToQuickbooksOnlineBut…
cdOut Apr 29, 2024
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
10 changes: 10 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,7 @@ const CONST = {
NAME: {
// Here we will add other connections names when we add support for them
QBO: 'quickbooksOnline',
XERO: 'xero',
},
SYNC_STAGE_NAME: {
STARTING_IMPORT: 'startingImport',
Expand All @@ -1713,6 +1714,15 @@ const CONST = {
QBO_IMPORT_TAX_CODES: 'quickbooksOnlineSyncTaxCodes',
QBO_CHECK_CONNECTION: 'quickbooksOnlineCheckConnection',
JOB_DONE: 'jobDone',
XERO_SYNC_STEP: 'xeroSyncStep',
XERO_SYNC_XERO_REIMBURSED_REPORTS: 'xeroSyncXeroReimbursedReports',
XERO_SYNC_EXPENSIFY_REIMBURSED_REPORTS: 'xeroSyncExpensifyReimbursedReports',
XERO_SYNC_IMPORT_CHART_OF_ACCOUNTS: 'xeroSyncImportChartOfAccounts',
XERO_SYNC_IMPORT_CATEGORIES: 'xeroSyncImportCategories',
XERO_SYNC_IMPORT_TRACKING_CATEGORIES: 'xeroSyncImportTrackingCategories',
XERO_SYNC_IMPORT_CUSTOMERS: 'xeroSyncImportCustomers',
XERO_SYNC_IMPORT_BANK_ACCOUNTS: 'xeroSyncImportBankAccounts',
XERO_SYNC_IMPORT_TAX_RATES: 'xeroSyncImportTaxRates',
},
},
ACCESS_VARIANTS: {
Expand Down
18 changes: 14 additions & 4 deletions src/components/CollapsibleSection/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useState} from 'react';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import {View} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
Expand All @@ -13,9 +14,18 @@ import Collapsible from './Collapsible';
type CollapsibleSectionProps = ChildrenProps & {
/** Title of the Collapsible section */
title: string;

/** Style of title of the collapsible section */
titleStyle?: StyleProp<TextStyle>;

/** Style for the wrapper view */
wrapperStyle?: StyleProp<ViewStyle>;

/** Whether or not to show border between section title and expandable items */
shouldShowSectionBorder?: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure where this part of the design is discussed. But I checked the terms step of payment and the other integrations.

image image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that we should consider adding this border also to 'Other integrations" dropdown?
Screenshot 2024-04-24 at 14 31 41

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about it. I was curious why the property was added if it was not required for the integrations PR. We added for Terms page, which looks unrelated to the current PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, missed that one.
I wanted to reuse CollapsibleSection component and it was matching the design except of this border so I added boolean prop for showing it.
It seemed cleaner for me to add shouldShowSectionBorder?: boolean; and modify LongTermsForm instead of adding default value to shouldShowSectionBorder. WDYT?

};

function CollapsibleSection({title, children}: CollapsibleSectionProps) {
function CollapsibleSection({title, children, titleStyle, wrapperStyle, shouldShowSectionBorder}: CollapsibleSectionProps) {
const theme = useTheme();
const styles = useThemeStyles();
const [isExpanded, setIsExpanded] = useState(false);
Expand All @@ -30,7 +40,7 @@ function CollapsibleSection({title, children}: CollapsibleSectionProps) {
const src = isExpanded ? Expensicons.UpArrow : Expensicons.DownArrow;

return (
<View style={styles.mt4}>
<View style={[styles.mt4, wrapperStyle]}>
<PressableWithFeedback
onPress={toggleSection}
style={[styles.pb4, styles.flexRow]}
Expand All @@ -40,7 +50,7 @@ function CollapsibleSection({title, children}: CollapsibleSectionProps) {
pressDimmingValue={0.2}
>
<Text
style={[styles.flex1, styles.textStrong, styles.userSelectNone]}
style={[styles.flex1, styles.textStrong, styles.userSelectNone, titleStyle]}
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
{title}
Expand All @@ -50,7 +60,7 @@ function CollapsibleSection({title, children}: CollapsibleSectionProps) {
src={src}
/>
</PressableWithFeedback>
<View style={styles.collapsibleSectionBorder} />
{shouldShowSectionBorder && <View style={styles.collapsibleSectionBorder} />}
<Collapsible isOpened={isExpanded}>
<View>{children}</View>
</Collapsible>
Expand Down
90 changes: 64 additions & 26 deletions src/components/ConnectToQuickbooksOnlineButton/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, {useState} from 'react';
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 FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import useLocalize from '@hooks/useLocalize';
import {removePolicyConnection} from '@libs/actions/connections';
import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -21,40 +23,76 @@ type ConnectToQuickbooksOnlineButtonOnyxProps = {

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

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

const authToken = session?.authToken ?? null;

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

return (
<>
<Button
onPress={() => setIsModalOpen(true)}
onPress={() => {
if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
}
setWebViewOpen(true);
}}
text={translate('workspace.accounting.setup')}
small
/>
<Modal
fullscreen
onClose={() => setIsModalOpen(false)}
isVisible={isModalOpen}
type={CONST.MODAL.MODAL_TYPE.CENTERED}
>
<HeaderWithBackButton
title={translate('workspace.accounting.title')}
onBackButtonPress={() => setIsModalOpen(false)}
{shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect && isDisconnectModalOpen && (
<ConfirmModal
title={translate('workspace.accounting.disconnectTitle', CONST.POLICY.CONNECTIONS.NAME.XERO)}
onConfirm={() => {
removePolicyConnection(policyID, integrationToDisconnect);
setIsDisconnectModalOpen(false);
setWebViewOpen(true);
}}
isVisible
onCancel={() => setIsDisconnectModalOpen(false)}
prompt={translate('workspace.accounting.disconnectPrompt', CONST.POLICY.CONNECTIONS.NAME.QBO)}
confirmText={translate('workspace.accounting.disconnect')}
cancelText={translate('common.cancel')}
danger
/>
<FullPageOfflineBlockingView>
<WebView
source={{
uri: getQuickBooksOnlineSetupLink(policyID),
headers: {
Cookie: `authToken=${session?.authToken}`,
},
}}
incognito // 'incognito' prop required for Android, issue here https://github.com/react-native-webview/react-native-webview/issues/1352
startInLoadingState
renderLoading={renderLoading}
)}
{isWebViewOpen && (
<Modal
onClose={() => setWebViewOpen(false)}
fullscreen
isVisible
type={CONST.MODAL.MODAL_TYPE.CENTERED}
>
<HeaderWithBackButton
title={translate('workspace.accounting.title')}
onBackButtonPress={() => setWebViewOpen(false)}
/>
</FullPageOfflineBlockingView>
</Modal>
<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>
)}
</>
);
}
Expand Down
47 changes: 39 additions & 8 deletions src/components/ConnectToQuickbooksOnlineButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
import React from 'react';
import React, {useState} from 'react';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import type {ConnectToQuickbooksOnlineButtonProps} from './types';

function ConnectToQuickbooksOnlineButton({policyID}: ConnectToQuickbooksOnlineButtonProps) {
function ConnectToQuickbooksOnlineButton({policyID, shouldDisconnectIntegrationBeforeConnecting, integrationToDisconnect}: ConnectToQuickbooksOnlineButtonProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {environmentURL} = useEnvironment();
const styles = useThemeStyles();

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

return (
<Button
onPress={() => Link.openLink(getQuickBooksOnlineSetupLink(policyID), environmentURL, false)}
text={translate('workspace.accounting.setup')}
style={styles.justifyContentCenter}
/>
<>
<Button
onPress={() => {
if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
}
Link.openLink(getQuickBooksOnlineSetupLink(policyID), environmentURL);
}}
text={translate('workspace.accounting.setup')}
style={styles.justifyContentCenter}
small
/>
{shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect && isDisconnectModalOpen && (
<ConfirmModal
title={translate('workspace.accounting.disconnectTitle', CONST.POLICY.CONNECTIONS.NAME.XERO)}
isVisible={isDisconnectModalOpen}
onConfirm={() => {
removePolicyConnection(policyID, integrationToDisconnect);
Link.openLink(getQuickBooksOnlineSetupLink(policyID), environmentURL);
setIsDisconnectModalOpen(false);
}}
onCancel={() => setIsDisconnectModalOpen(false)}
prompt={translate('workspace.accounting.disconnectPrompt', CONST.POLICY.CONNECTIONS.NAME.QBO)}
confirmText={translate('workspace.accounting.disconnect')}
cancelText={translate('common.cancel')}
danger
/>
)}
</>
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/components/ConnectToQuickbooksOnlineButton/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type {PolicyConnectionName} from '@src/types/onyx/Policy';

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

// eslint-disable-next-line import/prefer-default-export
Expand Down
101 changes: 101 additions & 0 deletions src/components/ConnectToXeroButton/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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 FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import getXeroSetupLink from '@libs/actions/connections/ConnectToXero';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session} from '@src/types/onyx';
import type {ConnectToXeroButtonProps} from './types';

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

function ConnectToXeroButton({policyID, session, shouldDisconnectIntegrationBeforeConnecting, integrationToDisconnect}: ConnectToXeroButtonProps & ConnectToXeroButtonOnyxProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const webViewRef = useRef<WebView>(null);
const [isWebViewOpen, setWebViewOpen] = useState(false);

const authToken = session?.authToken ?? null;

const renderLoading = () => <FullScreenLoadingIndicator />;
const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);

return (
<>
<Button
onPress={() => {
if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
}
setWebViewOpen(true);
}}
text={translate('workspace.accounting.setup')}
style={styles.justifyContentCenter}
small
/>
{shouldDisconnectIntegrationBeforeConnecting && isDisconnectModalOpen && integrationToDisconnect && (
<ConfirmModal
title={translate('workspace.accounting.disconnectTitle', CONST.POLICY.CONNECTIONS.NAME.QBO)}
onConfirm={() => {
removePolicyConnection(policyID, integrationToDisconnect);
setIsDisconnectModalOpen(false);
setWebViewOpen(true);
}}
isVisible
onCancel={() => setIsDisconnectModalOpen(false)}
prompt={translate('workspace.accounting.disconnectPrompt', CONST.POLICY.CONNECTIONS.NAME.XERO)}
confirmText={translate('workspace.accounting.disconnect')}
cancelText={translate('common.cancel')}
danger
/>
)}
<Modal
onClose={() => setWebViewOpen(false)}
fullscreen
isVisible={isWebViewOpen}
type={CONST.MODAL.MODAL_TYPE.CENTERED}
Copy link
Member

@rushatgabhane rushatgabhane Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This modal type has a bug for android. More details here: #40978 (comment)

>
<HeaderWithBackButton
title={translate('workspace.accounting.title')}
onBackButtonPress={() => setWebViewOpen(false)}
/>
<FullPageOfflineBlockingView>
<WebView
ref={webViewRef}
source={{
uri: getXeroSetupLink(policyID),
headers: {
Cookie: `authToken=${authToken}`,
},
}}
incognito
startInLoadingState
renderLoading={renderLoading}
/>
</FullPageOfflineBlockingView>
</Modal>
</>
);
}

ConnectToXeroButton.displayName = 'ConnectToXeroButton';

export default withOnyx<ConnectToXeroButtonProps & ConnectToXeroButtonOnyxProps, ConnectToXeroButtonOnyxProps>({
session: {
key: ONYXKEYS.SESSION,
},
})(ConnectToXeroButton);
Loading
Loading