diff --git a/src/CONST.ts b/src/CONST.ts index 9bba8d509642..7d0580e63827 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1372,6 +1372,14 @@ const CONST = { PROVINCIAL_TAX_POSTING_ACCOUNT: 'provincialTaxPostingAccount', ALLOW_FOREIGN_CURRENCY: 'allowForeignCurrency', EXPORT_TO_NEXT_OPEN_PERIOD: 'exportToNextOpenPeriod', + TOKEN_INPUT_STEP_NAMES: ['1', '2,', '3', '4', '5'], + TOKEN_INPUT_STEP_KEYS: { + 0: 'installBundle', + 1: 'enableTokenAuthentication', + 2: 'enableSoapServices', + 3: 'createAccessToken', + 4: 'enterCredentials', + }, IMPORT_FIELDS: ['departments', 'classes', 'locations', 'customers', 'jobs'], IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], SYNC_OPTIONS: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 709347fa71cd..d31a47ccbb57 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -559,6 +559,8 @@ const ONYXKEYS = { ISSUE_NEW_EXPENSIFY_CARD_FORM_DRAFT: 'issueNewExpensifyCardFormDraft', SAGE_INTACCT_CREDENTIALS_FORM: 'sageIntacctCredentialsForm', SAGE_INTACCT_CREDENTIALS_FORM_DRAFT: 'sageIntacctCredentialsFormDraft', + NETSUITE_TOKEN_INPUT_FORM: 'netsuiteTokenInputForm', + NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft', }, } as const; @@ -622,6 +624,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; [ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm; + [ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a70d6e7502ae..e9a88d3d2bb9 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -960,6 +960,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/accounting/netsuite/subsidiary-selector', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/subsidiary-selector` as const, }, + POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT: { + route: 'settings/workspaces/:policyID/accounting/netsuite/token-input', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/token-input` as const, + }, POLICY_ACCOUNTING_NETSUITE_IMPORT: { route: 'settings/workspaces/:policyID/accounting/netsuite/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/import` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e12ccfdab072..aa881cd3d6fc 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -272,6 +272,7 @@ const SCREENS = { XERO_EXPORT_PREFERRED_EXPORTER_SELECT: 'Workspace_Accounting_Xero_Export_Preferred_Exporter_Select', XERO_BILL_PAYMENT_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Bill_Payment_Account_Selector', XERO_EXPORT_BANK_ACCOUNT_SELECT: 'Policy_Accounting_Xero_Export_Bank_Account_Select', + NETSUITE_TOKEN_INPUT: 'Policy_Accounting_NetSuite_Token_Input', NETSUITE_SUBSIDIARY_SELECTOR: 'Policy_Accounting_NetSuite_Subsidiary_Selector', NETSUITE_IMPORT: 'Policy_Accounting_NetSuite_Import', NETSUITE_EXPORT: 'Policy_Accounting_NetSuite_Export', diff --git a/src/components/ConnectToNetSuiteButton/index.tsx b/src/components/ConnectToNetSuiteButton/index.tsx index fc948503a127..a0cd36671117 100644 --- a/src/components/ConnectToNetSuiteButton/index.tsx +++ b/src/components/ConnectToNetSuiteButton/index.tsx @@ -26,8 +26,7 @@ function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeCon return; } - // TODO: Will be updated to new token input page - Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR.getRoute(policyID)); + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID)); }} text={translate('workspace.accounting.setup')} style={styles.justifyContentCenter} @@ -39,8 +38,7 @@ function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeCon onConfirm={() => { removePolicyConnection(policyID, integrationToDisconnect); - // TODO: Will be updated to new token input page - Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR.getRoute(policyID)); + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID)); setIsDisconnectModalOpen(false); }} integrationToConnect={CONST.POLICY.CONNECTIONS.NAME.NETSUITE} diff --git a/src/components/ConnectionLayout.tsx b/src/components/ConnectionLayout.tsx index adb607c8e98b..dc8638f018d4 100644 --- a/src/components/ConnectionLayout.tsx +++ b/src/components/ConnectionLayout.tsx @@ -9,7 +9,6 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {TranslationPaths} from '@src/languages/types'; -import type {Route} from '@src/ROUTES'; import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy'; import HeaderWithBackButton from './HeaderWithBackButton'; import ScreenWrapper from './ScreenWrapper'; @@ -20,9 +19,6 @@ type ConnectionLayoutProps = { /** Used to set the testID for tests */ displayName: string; - /* The route on back button press */ - onBackButtonPressRoute?: Route; - /** Header title to be translated for the connection component */ headerTitle?: TranslationPaths; @@ -64,6 +60,12 @@ type ConnectionLayoutProps = { /** Name of the current connection */ connectionName: ConnectionName; + + /** Block the screen when the connection is not empty */ + reverseConnectionEmptyCheck?: boolean; + + /** Handler for back button press */ + onBackButtonPress?: () => void; }; type ConnectionLayoutContentProps = Pick; @@ -81,7 +83,6 @@ function ConnectionLayoutContent({title, titleStyle, children, titleAlreadyTrans function ConnectionLayout({ displayName, - onBackButtonPressRoute, headerTitle, children, title, @@ -96,6 +97,8 @@ function ConnectionLayout({ shouldUseScrollView = true, headerTitleAlreadyTranslated, titleAlreadyTranslated, + reverseConnectionEmptyCheck = false, + onBackButtonPress = () => Navigation.goBack(), }: ConnectionLayoutProps) { const {translate} = useLocalize(); @@ -120,7 +123,7 @@ function ConnectionLayout({ policyID={policyID} accessVariants={accessVariants} featureName={featureName} - shouldBeBlocked={isConnectionEmpty} + shouldBeBlocked={reverseConnectionEmptyCheck ? !isConnectionEmpty : isConnectionEmpty} > Navigation.goBack(onBackButtonPressRoute)} + onBackButtonPress={onBackButtonPress} /> {shouldUseScrollView ? ( {renderSelectionContent} diff --git a/src/components/InteractiveStepSubHeader.tsx b/src/components/InteractiveStepSubHeader.tsx index 20b3f6bc79a4..d8899a317df5 100644 --- a/src/components/InteractiveStepSubHeader.tsx +++ b/src/components/InteractiveStepSubHeader.tsx @@ -25,6 +25,9 @@ type InteractiveStepSubHeaderProps = { type InteractiveStepSubHeaderHandle = { /** Move to the next step */ moveNext: () => void; + + /** Move to the previous step */ + movePrevious: () => void; }; const MIN_AMOUNT_FOR_EXPANDING = 3; @@ -45,6 +48,9 @@ function InteractiveStepSubHeader({stepNames, startStepIndex = 0, onStepSelected moveNext: () => { setCurrentStep((actualStep) => actualStep + 1); }, + movePrevious: () => { + setCurrentStep((actualStep) => actualStep - 1); + }, }), [], ); diff --git a/src/languages/en.ts b/src/languages/en.ts index 9da1359fcfe5..73919281bd5b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2298,6 +2298,37 @@ export default { noItemsFoundDescription: 'Add invoice items in NetSuite and sync the connection again.', noSubsidiariesFound: 'No subsidiaries found', noSubsidiariesFoundDescription: 'Add the subsidiary in NetSuite and sync the connection again.', + tokenInput: { + title: 'NetSuite setup', + formSteps: { + installBundle: { + title: 'Install the Expensify bundle', + description: 'In NetSuite, go to *Customization > SuiteBundler > Search & Install Bundles* > search for "Expensify" > install the bundle.', + }, + enableTokenAuthentication: { + title: 'Enable token-based authentication', + description: 'In NetSuite, go to *Setup > Company > Enable Features > SuiteCloud* > enable *token-based authentication*.', + }, + enableSoapServices: { + title: 'Enable SOAP web services', + description: 'In NetSuite, go to *Setup > Company > Enable Features > SuiteCloud* > enable *SOAP Web Services*.', + }, + createAccessToken: { + title: 'Create an access token', + description: + 'In NetSuite, go to *Setup > Users/Roles > Access Tokens* > create an access token for the "Expensify" app and either the "Expensify Integration" or "Administrator" role.\n\n*Important:* Make sure you save the *Token ID* and *Token Secret* from this step. You\'ll need it for the next step.', + }, + enterCredentials: { + title: 'Enter your NetSuite credentials', + formInputs: { + netSuiteAccountID: 'NetSuite Account ID', + netSuiteTokenID: 'Token ID', + netSuiteTokenSecret: 'Token Secret', + }, + netSuiteAccountIDDescription: 'In NetSuite, go to *Setup > Integration > SOAP Web Services Preferences*.', + }, + }, + }, import: { expenseCategories: 'Expense categories', expenseCategoriesDescription: 'NetSuite expense categories import into Expensify as categories.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 696a4be69f7d..6eed3aca80a1 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2331,6 +2331,37 @@ export default { noItemsFoundDescription: 'Añade artículos de factura en NetSuite y sincroniza la conexión de nuevo.', noSubsidiariesFound: 'No se ha encontrado subsidiarias', noSubsidiariesFoundDescription: 'Añade la subsidiaria en NetSuite y sincroniza de nuevo la conexión.', + tokenInput: { + title: 'Netsuite configuración', + formSteps: { + installBundle: { + title: 'Instala el paquete de Expensify', + description: 'En NetSuite, ir a *Personalización > SuiteBundler > Buscar e Instalar Paquetes* > busca "Expensify" > instala el paquete.', + }, + enableTokenAuthentication: { + title: 'Habilitar la autenticación basada en token', + description: 'En NetSuite, ir a *Configuración > Empresa > Habilitar Funciones > SuiteCloud* > activar *autenticación basada en token*.', + }, + enableSoapServices: { + title: 'Habilitar servicios web SOAP', + description: 'En NetSuite, ir a *Configuración > Empresa > Habilitar funciones > SuiteCloud* > habilitar *Servicios Web SOAP*.', + }, + createAccessToken: { + title: 'Crear un token de acceso', + description: + 'En NetSuite, ir a *Configuración > Usuarios/Roles > Tokens de Acceso* > crear un token de acceso para la aplicación "Expensify" y tambiém para el rol de "Integración Expensify" o "Administrador".\n\n*Importante:* Asegúrese de guardar el ID y el secreto del Token en este paso. Los necesitará para el siguiente paso.', + }, + enterCredentials: { + title: 'Ingresa tus credenciales de NetSuite', + formInputs: { + netSuiteAccountID: 'ID de Cuenta NetSuite', + netSuiteTokenID: 'ID de Token', + netSuiteTokenSecret: 'Secreto de Token', + }, + netSuiteAccountIDDescription: 'En NetSuite, ir a *Configuración > Integración > Preferencias de Servicios Web SOAP*.', + }, + }, + }, import: { expenseCategories: 'Categorías de gastos', expenseCategoriesDescription: 'Las categorías de gastos de NetSuite se importan a Expensify como categorías.', @@ -2343,7 +2374,7 @@ export default { }, importTaxDescription: 'Importar grupos de impuestos desde NetSuite', importCustomFields: { - customSegments: 'Segmentos/registros personalizado', + customSegments: 'Segmentos/registros personalizados', customLists: 'Listas personalizado', }, }, diff --git a/src/libs/API/parameters/ConnectPolicyToNetSuiteParams.ts b/src/libs/API/parameters/ConnectPolicyToNetSuiteParams.ts new file mode 100644 index 000000000000..2143ca1b039c --- /dev/null +++ b/src/libs/API/parameters/ConnectPolicyToNetSuiteParams.ts @@ -0,0 +1,8 @@ +type ConnectPolicyToNetSuiteParams = { + policyID: string; + netSuiteAccountID: string; + netSuiteTokenID: string; + netSuiteTokenSecret: string; +}; + +export default ConnectPolicyToNetSuiteParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 25a27cefb6ce..569a725cfcb3 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -237,6 +237,7 @@ export type {default as DeleteMoneyRequestOnSearchParams} from './DeleteMoneyReq export type {default as HoldMoneyRequestOnSearchParams} from './HoldMoneyRequestOnSearchParams'; export type {default as UnholdMoneyRequestOnSearchParams} from './UnholdMoneyRequestOnSearchParams'; export type {default as UpdateNetSuiteSubsidiaryParams} from './UpdateNetSuiteSubsidiaryParams'; +export type {default as ConnectPolicyToNetSuiteParams} from './ConnectPolicyToNetSuiteParams'; export type {default as CreateWorkspaceReportFieldParams} from './CreateWorkspaceReportFieldParams'; export type {default as OpenPolicyExpensifyCardsPageParams} from './OpenPolicyExpensifyCardsPageParams'; export type {default as RequestExpensifyCardLimitIncreaseParams} from './RequestExpensifyCardLimitIncreaseParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index fdee63b086a9..42c5ff6cd7f8 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -253,6 +253,7 @@ const WRITE_COMMANDS = { UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD: 'UpdateNetSuiteExportToNextOpenPeriod', REQUEST_EXPENSIFY_CARD_LIMIT_INCREASE: 'RequestExpensifyCardLimitIncrease', CONNECT_POLICY_TO_SAGE_INTACCT: 'ConnectPolicyToSageIntacct', + CONNECT_POLICY_TO_NETSUITE: 'ConnectPolicyToNetSuite', CLEAR_OUTSTANDING_BALANCE: 'ClearOutstandingBalance', } as const; @@ -495,6 +496,7 @@ type WriteCommandParameters = { // Netsuite parameters [WRITE_COMMANDS.UPDATE_NETSUITE_SUBSIDIARY]: Parameters.UpdateNetSuiteSubsidiaryParams; + [WRITE_COMMANDS.CONNECT_POLICY_TO_NETSUITE]: Parameters.ConnectPolicyToNetSuiteParams; // Workspace report field parameters [WRITE_COMMANDS.CREATE_WORKSPACE_REPORT_FIELD]: Parameters.CreateWorkspaceReportFieldParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 4fd6251ec644..da577d17fe07 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -320,6 +320,8 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/xero/advanced/XeroBillPaymentAccountSelectorPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR]: () => require('../../../../pages/workspace/accounting/netsuite/NetSuiteSubsidiarySelector').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: () => + require('../../../../pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: () => require('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: () => require('../../../../pages/workspace/accounting/netsuite/export/NetSuiteExportConfigurationPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PREFERRED_EXPORTER_SELECT]: () => diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 3baa0f9f6889..5c9275cfab17 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -56,6 +56,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR, SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_BANK_ACCOUNT_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PREFERRED_EXPORTER_SELECT, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 3f4896e0c5d2..65fd44139456 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -354,6 +354,7 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PREFERRED_EXPORTER_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_PREFERRED_EXPORTER_SELECT.route}, [SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_BILL_PAYMENT_ACCOUNT_SELECTOR.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR.route}, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT.route}, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_EXPORT]: { path: ROUTES.POLICY_ACCOUNTING_NETSUITE_EXPORT.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ac9710b65d19..f838ac7a8603 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -416,6 +416,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR]: { policyID: string; }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: { + policyID: string; + }; [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: { policyID: string; }; diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index 4d1a6617c253..1664e0ff4fbd 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -2,6 +2,7 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as API from '@libs/API'; +import type {ConnectPolicyToNetSuiteParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import CONST from '@src/CONST'; @@ -14,6 +15,25 @@ type SubsidiaryParam = { subsidiary: string; }; +function connectPolicyToNetSuite(policyID: string, credentials: Omit) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`, + value: { + stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.NETSUITE_SYNC_CONNECTION, + connectionName: CONST.POLICY.CONNECTIONS.NAME.NETSUITE, + timestamp: new Date().toISOString(), + }, + }, + ]; + const parameters: ConnectPolicyToNetSuiteParams = { + policyID, + ...credentials, + }; + API.write(WRITE_COMMANDS.CONNECT_POLICY_TO_NETSUITE, parameters, {optimisticData}); +} + function updateNetSuiteOnyxData( policyID: string, settingName: TSettingName, @@ -449,4 +469,5 @@ export { updateNetSuiteProvincialTaxPostingAccount, updateNetSuiteAllowForeignCurrency, updateNetSuiteExportToNextOpenPeriod, + connectPolicyToNetSuite, }; diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx new file mode 100644 index 000000000000..f885d2dc9b70 --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx @@ -0,0 +1,88 @@ +import React, {useRef} from 'react'; +import type {ForwardedRef} from 'react'; +import {View} from 'react-native'; +import ConnectionLayout from '@components/ConnectionLayout'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSubHeader'; +import useSubStep from '@hooks/useSubStep'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import CONST from '@src/CONST'; +import NetSuiteTokenInputForm from './substeps/NetSuiteTokenInputForm'; +import NetSuiteTokenSetupContent from './substeps/NetSuiteTokenSetupContent'; + +const staticContentSteps = Array(4).fill(NetSuiteTokenSetupContent); +const tokenInputSteps: Array> = [...staticContentSteps, NetSuiteTokenInputForm]; + +function NetSuiteTokenInputPage({policy}: WithPolicyConnectionsProps) { + const policyID = policy?.id ?? '-1'; + const styles = useThemeStyles(); + const ref: ForwardedRef = useRef(null); + + const submit = () => { + Navigation.goBack(); + }; + + const { + componentToRender: SubStep, + isEditing, + nextScreen, + prevScreen, + screenIndex, + moveTo, + } = useSubStep({bodyContent: tokenInputSteps, startFrom: 0, onFinished: submit}); + + const handleBackButtonPress = () => { + if (screenIndex === 0) { + Navigation.goBack(); + return; + } + ref.current?.movePrevious(); + prevScreen(); + }; + + const handleNextScreen = () => { + ref.current?.moveNext(); + nextScreen(); + }; + + return ( + + + + + + + + + ); +} + +NetSuiteTokenInputPage.displayName = 'NetSuiteTokenInputPage'; + +export default withPolicyConnections(NetSuiteTokenInputPage); diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx new file mode 100644 index 000000000000..08e2ab9460ec --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx @@ -0,0 +1,93 @@ +import {ExpensiMark} from 'expensify-common'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {connectPolicyToNetSuite} from '@libs/actions/connections/NetSuiteCommands'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/NetSuiteTokenInputForm'; + +const parser = new ExpensiMark(); + +function NetSuiteTokenInputForm({onNext, policyID}: SubStepProps & {policyID: string}) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const formInputs = Object.values(INPUT_IDS); + + const validate = useCallback( + (formValues: FormOnyxValues) => { + const errors: FormInputErrors = {}; + + formInputs.forEach((formInput) => { + if (formValues[formInput]) { + return; + } + ErrorUtils.addErrorMessage(errors, formInput, translate('common.error.fieldRequired')); + }); + return errors; + }, + [formInputs, translate], + ); + + const connectPolicy = useCallback( + (formValues: FormOnyxValues) => { + connectPolicyToNetSuite(policyID, formValues); + onNext(); + }, + [onNext, policyID], + ); + + return ( + + {translate(`workspace.netsuite.tokenInput.formSteps.enterCredentials.title`)} + + + {formInputs.map((formInput) => ( + + + {formInput === INPUT_IDS.NETSUITE_ACCOUNT_ID && ( + + ${parser.replace( + translate(`workspace.netsuite.tokenInput.formSteps.enterCredentials.${formInput}Description`), + )}`} + /> + + )} + + ))} + + + ); +} + +NetSuiteTokenInputForm.displayName = 'NetSuiteTokenInputForm'; +export default NetSuiteTokenInputForm; diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx new file mode 100644 index 000000000000..f1b9ed258d7d --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx @@ -0,0 +1,46 @@ +import {ExpensiMark} from 'expensify-common'; +import React from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import FixedFooter from '@components/FixedFooter'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; + +const parser = new ExpensiMark(); + +function NetSuiteTokenSetupContent({onNext, screenIndex}: SubStepProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const stepKeys = CONST.NETSUITE_CONFIG.TOKEN_INPUT_STEP_KEYS; + const currentStepKey = stepKeys[(screenIndex ?? 0) as keyof typeof stepKeys]; + + const titleKey = `workspace.netsuite.tokenInput.formSteps.${currentStepKey}.title` as TranslationPaths; + const description = `workspace.netsuite.tokenInput.formSteps.${currentStepKey}.description` as TranslationPaths; + + return ( + + {translate(titleKey)} + + ${parser.replace(translate(description))}`} /> + + +