From 05f1ad6d0d50a45b188c4d8fccd8f72788205fc6 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 11 Apr 2024 19:19:37 +0800 Subject: [PATCH 001/419] navigate back when delete modal hides --- src/components/MoneyReportHeader.tsx | 18 +++++++++++++----- src/components/MoneyRequestHeader.tsx | 17 +++++++++++++---- src/libs/actions/IOU.ts | 7 +++---- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 14227d6a2051..5d68440266f3 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -13,7 +13,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; +import ROUTES, { Route } from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -83,6 +83,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const navigateBackToAfterDelete = useRef(); + const cancelPayment = useCallback(() => { if (!chatReport) { return; @@ -136,10 +138,10 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money if (requestParentReportAction) { const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, true); - return; + navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, true); + } else { + navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); } - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); } setIsDeleteRequestModalVisible(false); @@ -292,6 +294,12 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money isVisible={isDeleteRequestModalVisible} onConfirm={deleteTransaction} onCancel={() => setIsDeleteRequestModalVisible(false)} + onModalHide={() => { + if (!navigateBackToAfterDelete.current) { + return; + } + Navigation.goBack(navigateBackToAfterDelete.current) + }} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..2aea42d46503 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -14,6 +14,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import ConfirmModal from './ConfirmModal'; @@ -63,6 +64,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const isOnHold = TransactionUtils.isOnHold(transaction); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); + const navigateBackToAfterDelete = useRef(); + // Only the requestor can take delete the request, admins can only edit it. const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID; const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; @@ -72,10 +75,10 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, if (parentReportAction) { const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { - IOU.deleteTrackExpense(parentReport?.reportID ?? '', iouTransactionID, parentReportAction, true); - return; + navigateBackToAfterDelete.current = IOU.deleteTrackExpense(parentReport?.reportID ?? '', iouTransactionID, parentReportAction, true); + } else { + navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); } - IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); } setIsDeleteModalVisible(false); @@ -200,6 +203,12 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, isVisible={isDeleteModalVisible} onConfirm={deleteTransaction} onCancel={() => setIsDeleteModalVisible(false)} + onModalHide={() => { + if (!navigateBackToAfterDelete.current) { + return; + } + Navigation.goBack(navigateBackToAfterDelete.current) + }} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c2d462bbc4a8..ab50e709a972 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4133,13 +4133,12 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor // STEP 7: Navigate the user depending on which page they are on and which resources were deleted if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(iouReport.reportID)); - return; + return ROUTES.REPORT_WITH_ID.getRoute(iouReport.reportID); } if (iouReport?.chatReportID && shouldDeleteIOUReport) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(iouReport.chatReportID)); + return ROUTES.REPORT_WITH_ID.getRoute(iouReport.chatReportID); } } @@ -4304,7 +4303,7 @@ function deleteTrackExpense(chatReportID: string, transactionID: string, reportA // STEP 7: Navigate the user depending on which page they are on and which resources were deleted if (isSingleTransactionView && shouldDeleteTransactionThread) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '')); + return ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? ''); } } From 3b45b392f04b677ca39a59e4b90f3c206c3d5293 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 17 Apr 2024 15:16:23 +0700 Subject: [PATCH 002/419] animate sign-in pages --- assets/animations/Abracadabra.lottie | Bin 0 -> 15694 bytes assets/animations/MagicCode.lottie | Bin 0 -> 21405 bytes src/components/LottieAnimations/index.tsx | 10 ++++++++++ .../ValidateCode/JustSignedInModal.tsx | 13 ++++++++----- .../ValidateCode/ValidateCodeModal.tsx | 13 ++++++++----- src/styles/index.ts | 9 +++++++++ 6 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 assets/animations/Abracadabra.lottie create mode 100644 assets/animations/MagicCode.lottie diff --git a/assets/animations/Abracadabra.lottie b/assets/animations/Abracadabra.lottie new file mode 100644 index 0000000000000000000000000000000000000000..8805aed1944e6a00a0ebcb33496f36a122e9df37 GIT binary patch literal 15694 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 WRgwjV_=g4L@0s)Wp2hc1?*9OB|0t3G literal 0 HcmV?d00001 diff --git a/assets/animations/MagicCode.lottie b/assets/animations/MagicCode.lottie new file mode 100644 index 0000000000000000000000000000000000000000..ea94f1138f97e960e9254b08c8176590cce7a9dd GIT binary patch literal 21405 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 UU{th!O+o(a*?;4q#=o-v0Wb!wc>n+a literal 0 HcmV?d00001 diff --git a/src/components/LottieAnimations/index.tsx b/src/components/LottieAnimations/index.tsx index 18cb9188d60c..c4c76ec8cc95 100644 --- a/src/components/LottieAnimations/index.tsx +++ b/src/components/LottieAnimations/index.tsx @@ -3,6 +3,11 @@ import variables from '@styles/variables'; import type DotLottieAnimation from './types'; const DotLottieAnimations = { + Abracadabra: { + file: require('@assets/animations/Abracadabra.lottie'), + w: 375, + h: 400, + }, FastMoney: { file: require('@assets/animations/FastMoney.lottie'), w: 375, @@ -46,6 +51,11 @@ const DotLottieAnimations = { h: 400, backgroundColor: colors.ice500, }, + MagicCode: { + file: require('@assets/animations/MagicCode.lottie'), + w: 200, + h: 164, + }, Magician: { file: require('@assets/animations/Magician.lottie'), w: 853, diff --git a/src/components/ValidateCode/JustSignedInModal.tsx b/src/components/ValidateCode/JustSignedInModal.tsx index 19e67b0c56fe..923ea14a3f51 100644 --- a/src/components/ValidateCode/JustSignedInModal.tsx +++ b/src/components/ValidateCode/JustSignedInModal.tsx @@ -2,7 +2,8 @@ import React from 'react'; import {View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; +import Lottie from '@components/Lottie'; +import LottieAnimations from '@components/LottieAnimations'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; @@ -22,10 +23,12 @@ function JustSignedInModal({is2FARequired}: JustSignedInModalProps) { - diff --git a/src/components/ValidateCode/ValidateCodeModal.tsx b/src/components/ValidateCode/ValidateCodeModal.tsx index 1e42773c2dc2..07fc8cb7167e 100644 --- a/src/components/ValidateCode/ValidateCodeModal.tsx +++ b/src/components/ValidateCode/ValidateCodeModal.tsx @@ -4,7 +4,8 @@ import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; +import Lottie from '@components/Lottie'; +import LottieAnimations from '@components/LottieAnimations'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; @@ -37,10 +38,12 @@ function ValidateCodeModal({code, accountID, session = {}}: ValidateCodeModalPro - {translate('validateCodeModal.title')} diff --git a/src/styles/index.ts b/src/styles/index.ts index 537038d9f2e1..fee143b2a95c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4294,6 +4294,15 @@ const styles = (theme: ThemeColors) => borderColor: theme.highlightBG, borderWidth: 2, }, + + magicCodeAnimationWidth: { + width: variables.modalTopIconWidth, + }, + + justSignedInModalAnimationHeight: (is2FARequired: boolean) => ({ + height: is2FARequired ? variables.modalTopIconHeight : variables.modalTopBigIconHeight, + }), + moneyRequestViewImage: { ...spacing.mh5, ...spacing.mv3, From 1e0e916ffc25a7573657d1680f3c859fe6ddb326 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 21:49:04 +0200 Subject: [PATCH 003/419] feat: verify identity step setup --- .../VerifyIdentity/VerifyIdentity.tsx | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx diff --git a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx new file mode 100644 index 000000000000..f402f52255ab --- /dev/null +++ b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx @@ -0,0 +1,120 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import Onfido from '@components/Onfido'; +import type {OnfidoData} from '@components/Onfido/types'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Growl from '@libs/Growl'; +import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PersonalBankAccount, WalletOnfido} from '@src/types/onyx'; + +const DEFAULT_WALLET_ONFIDO_DATA = { + loading: false, + hasAcceptedPrivacyPolicy: false, +}; + +type VerifyIdentityOnyxProps = { + /** Reimbursement account from ONYX */ + personalBankAccount: OnyxEntry; + + /** Onfido applicant ID from ONYX */ + onfidoApplicantID: OnyxEntry; + + /** The token required to initialize the Onfido SDK */ + onfidoToken: OnyxEntry; + + /** The wallet onfido data */ + walletOnfidoData: OnyxEntry; +}; + +type VerifyIdentityProps = VerifyIdentityOnyxProps & { + /** Goes to the previous step */ + onBackButtonPress: () => void; +}; + +const ONFIDO_ERROR_DISPLAY_DURATION = 10000; + +function VerifyIdentity({personalBankAccount, onBackButtonPress, onfidoApplicantID, onfidoToken, walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const handleOnfidoSuccess = useCallback( + (onfidoData: OnfidoData) => { + BankAccounts.verifyIdentity({ + onfidoData: JSON.stringify({ + ...onfidoData, + applicantID: walletOnfidoData?.applicantID, + }), + }); + BankAccounts.updateAddPersonalBankAccountDraft({isOnfidoSetupComplete: true}); + }, + [personalBankAccount, onfidoApplicantID], + ); + + const handleOnfidoError = () => { + // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. + Growl.error(translate('onfidoStep.genericError'), ONFIDO_ERROR_DISPLAY_DURATION); + BankAccounts.clearOnfidoToken(); + // BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }; + + const handleOnfidoUserExit = () => { + BankAccounts.clearOnfidoToken(); + // BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }; + + return ( + + + + + + + + + + + + ); +} + +VerifyIdentity.displayName = 'VerifyIdentity'; + +export default withOnyx({ + // @ts-expect-error: ONYXKEYS.PERSONAL_BANK_ACCOUNT is conflicting with ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM + personalBankAccount: { + key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, + }, + onfidoApplicantID: { + key: ONYXKEYS.ONFIDO_APPLICANT_ID, + }, + onfidoToken: { + key: ONYXKEYS.ONFIDO_TOKEN, + }, + walletOnfidoData: { + key: ONYXKEYS.WALLET_ONFIDO, + + // Let's get a new onfido token each time the user hits this flow (as it should only be once) + initWithStoredValues: false, + }, +})(VerifyIdentity); From 43e030b5d42bf5f19bc7dcb93a6cc3d44890ba3c Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 23 Apr 2024 03:32:30 +0100 Subject: [PATCH 004/419] fix(money request): missing tags in violations translations --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/hooks/useViolations.ts | 41 ++++++++++++++++--- src/libs/Violations/ViolationsUtils.ts | 2 + 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 73fc7e9bae6e..9032c8026012 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -162,7 +162,7 @@ function MoneyRequestView({ // A flag for showing tax rate const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy); - const {getViolationsForField} = useViolations(transactionViolations ?? []); + const {getViolationsForField} = useViolations(transactionViolations ?? [], transaction, policy, policyTagList); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 7aadb4ce4ca3..1d25a5e6f3d2 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -1,6 +1,8 @@ import {useCallback, useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; -import type {TransactionViolation, ViolationName} from '@src/types/onyx'; +import type {Policy, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Names of Fields where violations can occur. @@ -48,7 +50,12 @@ const violationFields: Record = { type ViolationsMap = Map; -function useViolations(violations: TransactionViolation[]) { +function useViolations( + violations: TransactionViolation[], + transaction: OnyxEntry = {} as Transaction, + policy: OnyxEntry = {} as Policy, + policyTagList: OnyxEntry = {}, +) { const violationsByField = useMemo((): ViolationsMap => { const filteredViolations = violations.filter((violation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION); const violationGroups = new Map(); @@ -63,10 +70,11 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data?: TransactionViolation['data']) => { const currentViolations = violationsByField.get(field) ?? []; + const firstViolation = currentViolations[0]; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (currentViolations[0]?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { + if (firstViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(firstViolation?.data?.errorIndexes)) { return currentViolations .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) .map((violation) => ({ @@ -78,14 +86,37 @@ function useViolations(violations: TransactionViolation[]) { })); } + // missingTag has special logic because if its data is null, we need to convert it to someTagLevelsRequired + if (firstViolation?.name === 'missingTag' && firstViolation?.data === null) { + const newViolations = + Object.keys(policyTagList ?? {}).length === 1 + ? ViolationsUtils.getTagViolationsForSingleLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}) + : ViolationsUtils.getTagViolationsForMultiLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}); + const newViolation = newViolations.find( + (currentViolation) => currentViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolation?.data?.errorIndexes), + ); + if (newViolation) { + return newViolations + .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) + .map((currentViolation) => ({ + ...currentViolation, + data: { + ...currentViolation.data, + tagName: data?.tagListName, + }, + })); + } + return test; + } + // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + if (firstViolation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && firstViolation?.data?.tagName) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } return currentViolations; }, - [violationsByField], + [policy?.requiresTag, policyTagList, transaction, violations, violationsByField], ); return { diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 42f58be1d699..3565a3d28b9a 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -110,6 +110,8 @@ function getTagViolationsForMultiLevelTags( } const ViolationsUtils = { + getTagViolationsForSingleLevelTags, + getTagViolationsForMultiLevelTags, /** * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction * violations. From b9d8b24bfa1dd3346fe92f82d3e4542b50d98b17 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 23 Apr 2024 03:39:22 +0100 Subject: [PATCH 005/419] refactor: remove unexpected return --- src/hooks/useViolations.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 1d25a5e6f3d2..cf02545e20b0 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -106,7 +106,6 @@ function useViolations( }, })); } - return test; } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on From 987f3f783283cc2be16b12346a601c9971d2a759 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Apr 2024 18:30:56 +0700 Subject: [PATCH 006/419] rename variables --- src/components/ValidateCode/JustSignedInModal.tsx | 4 ++-- src/components/ValidateCode/ValidateCodeModal.tsx | 4 ++-- src/styles/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/ValidateCode/JustSignedInModal.tsx b/src/components/ValidateCode/JustSignedInModal.tsx index 923ea14a3f51..527493c778cb 100644 --- a/src/components/ValidateCode/JustSignedInModal.tsx +++ b/src/components/ValidateCode/JustSignedInModal.tsx @@ -25,8 +25,8 @@ function JustSignedInModal({is2FARequired}: JustSignedInModalProps) { diff --git a/src/components/ValidateCode/ValidateCodeModal.tsx b/src/components/ValidateCode/ValidateCodeModal.tsx index 07fc8cb7167e..a1d5c4ff3faa 100644 --- a/src/components/ValidateCode/ValidateCodeModal.tsx +++ b/src/components/ValidateCode/ValidateCodeModal.tsx @@ -40,8 +40,8 @@ function ValidateCodeModal({code, accountID, session = {}}: ValidateCodeModalPro diff --git a/src/styles/index.ts b/src/styles/index.ts index a579df46b8e4..bb30e5cf279d 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4364,11 +4364,11 @@ const styles = (theme: ThemeColors) => borderWidth: 2, }, - magicCodeAnimationWidth: { + magicCodeAnimation: { width: variables.modalTopIconWidth, }, - justSignedInModalAnimationHeight: (is2FARequired: boolean) => ({ + justSignedInModalAnimation: (is2FARequired: boolean) => ({ height: is2FARequired ? variables.modalTopIconHeight : variables.modalTopBigIconHeight, }), From 0a0cac1e1baa810315b5686c0cfb6332e0c9573e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 24 Apr 2024 22:04:08 +0100 Subject: [PATCH 007/419] chore: apply PR suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 9 +++-- src/hooks/useViolations.ts | 36 ++++--------------- src/libs/PolicyUtils.ts | 8 +++++ src/libs/Violations/ViolationsUtils.ts | 17 +++++++-- src/libs/actions/IOU.ts | 14 ++++++-- src/types/onyx/PolicyTag.ts | 6 ++++ src/types/onyx/TransactionViolation.ts | 1 + 7 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index b9bf70f22a9d..2cb1d1d6ae84 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -174,7 +174,7 @@ function MoneyRequestView({ // A flag for showing tax rate const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy); - const {getViolationsForField} = useViolations(transactionViolations ?? [], transaction, policy, policyTagList); + const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], @@ -467,11 +467,16 @@ function MoneyRequestView({ getErrorForField('tag', { tagListIndex: index, tagListName: name, + policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), }) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined } - error={getErrorForField('tag', {tagListIndex: index, tagListName: name})} + error={getErrorForField('tag', { + tagListIndex: index, + tagListName: name, + policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), + })} /> ))} diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index cf02545e20b0..eb6138799973 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -1,8 +1,6 @@ import {useCallback, useMemo} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; -import type {Policy, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; +import type {TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Names of Fields where violations can occur. @@ -50,12 +48,7 @@ const violationFields: Record = { type ViolationsMap = Map; -function useViolations( - violations: TransactionViolation[], - transaction: OnyxEntry = {} as Transaction, - policy: OnyxEntry = {} as Policy, - policyTagList: OnyxEntry = {}, -) { +function useViolations(violations: TransactionViolation[]) { const violationsByField = useMemo((): ViolationsMap => { const filteredViolations = violations.filter((violation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION); const violationGroups = new Map(); @@ -86,26 +79,9 @@ function useViolations( })); } - // missingTag has special logic because if its data is null, we need to convert it to someTagLevelsRequired - if (firstViolation?.name === 'missingTag' && firstViolation?.data === null) { - const newViolations = - Object.keys(policyTagList ?? {}).length === 1 - ? ViolationsUtils.getTagViolationsForSingleLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}) - : ViolationsUtils.getTagViolationsForMultiLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}); - const newViolation = newViolations.find( - (currentViolation) => currentViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolation?.data?.errorIndexes), - ); - if (newViolation) { - return newViolations - .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) - .map((currentViolation) => ({ - ...currentViolation, - data: { - ...currentViolation.data, - tagName: data?.tagListName, - }, - })); - } + // missingTag has special logic because we have to take into account dependent tags + if (firstViolation?.data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && firstViolation?.data?.tagName === data?.tagListName) { + return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on @@ -115,7 +91,7 @@ function useViolations( return currentViolations; }, - [policy?.requiresTag, policyTagList, transaction, violations, violationsByField], + [violationsByField], ); return { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 731dc5700c8e..3b60e0cba262 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -378,6 +378,13 @@ function getPolicy(policyID: string | undefined): Policy | EmptyObject { return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? {}; } +function hasDependentTags(policy: OnyxEntry, policyTagList: OnyxEntry) { + return ( + !!policy?.hasMultipleTagLists && + Object.values(policyTagList ?? {}).some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter)) + ); +} + export { getActivePolicies, hasAccountingConnections, @@ -421,6 +428,7 @@ export { getSubmitToAccountID, getAdminEmployees, getPolicy, + hasDependentTags, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 3565a3d28b9a..ffa346754d8a 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -56,6 +56,7 @@ function getTagViolationsForMultiLevelTags( transactionViolations: TransactionViolation[], policyRequiresTags: boolean, policyTagList: PolicyTagList, + hasDependentTags: boolean, ): TransactionViolation[] { const policyTagKeys = getSortedTagKeys(policyTagList); const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; @@ -64,6 +65,17 @@ function getTagViolationsForMultiLevelTags( (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, ); + if (hasDependentTags && !updatedTransaction.tag) { + Object.values(policyTagList).forEach((tagList) => { + newTransactionViolations.push({ + name: CONST.VIOLATIONS.MISSING_TAG, + type: CONST.VIOLATION_TYPES.VIOLATION, + data: {tagName: tagList.name}, + }); + }); + return newTransactionViolations; + } + // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. // Otherwise, we put TAG_OUT_OF_POLICY in Onyx (when applicable) const errorIndexes = []; @@ -110,8 +122,6 @@ function getTagViolationsForMultiLevelTags( } const ViolationsUtils = { - getTagViolationsForSingleLevelTags, - getTagViolationsForMultiLevelTags, /** * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction * violations. @@ -123,6 +133,7 @@ const ViolationsUtils = { policyTagList: PolicyTagList, policyRequiresCategories: boolean, policyCategories: PolicyCategories, + hasDependentTags: boolean, ): OnyxUpdate { const isPartialTransaction = TransactionUtils.isPartialMerchant(TransactionUtils.getMerchant(updatedTransaction)) && TransactionUtils.isAmountMissing(updatedTransaction); if (isPartialTransaction) { @@ -168,7 +179,7 @@ const ViolationsUtils = { newTransactionViolations = Object.keys(policyTagList).length === 1 ? getTagViolationsForSingleLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList) - : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList); + : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList, hasDependentTags); } return { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 30ab2688b593..64eb7ba3a645 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -765,7 +765,7 @@ function buildOnyxDataForMoneyRequest( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}, true); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); @@ -1102,7 +1102,15 @@ function buildOnyxDataForTrackExpense( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + [], + !!policy.requiresTag, + policyTagList ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), + ); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); @@ -2054,6 +2062,7 @@ function getUpdateMoneyRequestParams( policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), ), ); failureData.push({ @@ -4375,6 +4384,7 @@ function editRegularMoneyRequest( policyTags, !!policy.requiresCategory, policyCategories, + PolicyUtils.hasDependentTags(policy, policyTags), ); optimisticData.push(updatedViolationsOnyxData); failureData.push({ diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 37e979fb58f6..88b5297dbd38 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -13,6 +13,12 @@ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** A list of errors keyed by microtime */ errors?: OnyxCommon.Errors | null; + + rules?: { + parentTagsFilter?: string; + }; + + parentTagsFilter?: string; }>; type PolicyTags = Record; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 28de4582bd5e..6859c6749567 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -10,6 +10,7 @@ type TransactionViolation = { type: string; name: ViolationName; data?: { + policyHasDependentTagLists?: boolean; rejectedBy?: string; rejectReason?: string; formattedLimit?: string; From a174c60a79bd5e8cd92eb6afd8913e76f60dd4ee Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 25 Apr 2024 22:18:10 +0100 Subject: [PATCH 008/419] fix: tag name missing in error UI --- src/hooks/useViolations.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index eb6138799973..65e17785500d 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -80,8 +80,16 @@ function useViolations(violations: TransactionViolation[]) { } // missingTag has special logic because we have to take into account dependent tags - if (firstViolation?.data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && firstViolation?.data?.tagName === data?.tagListName) { - return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); + if (data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && data?.tagListName) { + return [ + { + ...firstViolation, + data: { + ...firstViolation.data, + tagName: data?.tagListName, + }, + }, + ]; } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on From e343aa68acc529461e7612572b753169ee736c0e Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 22 Apr 2024 11:24:12 +0200 Subject: [PATCH 009/419] new persona added --- src/CONST.ts | 1 + src/libs/actions/Report.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 2e14aa7cf21f..0725fb47a85b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1202,6 +1202,7 @@ const CONST = { CHRONOS: 'chronos@expensify.com', CONCIERGE: 'concierge@expensify.com', CONTRIBUTORS: 'contributors@expensify.com', + EXPENSIFY_PERSONA: 'expensify@expensify.com', FIRST_RESPONDER: 'firstresponders@expensify.com', GUIDES_DOMAIN: 'team.expensify.com', HELP: 'help@expensify.com', diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7542ca12c592..8260b6194b21 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3029,7 +3029,7 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - const targetEmail = CONST.EMAIL.CONCIERGE; + const targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; From 9a58f4fc89739c5a05df7bf8e56d0b1b51904b5d Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 22 Apr 2024 12:49:50 +0200 Subject: [PATCH 010/419] using expensify persona only for odd account ids --- src/libs/actions/Report.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 8260b6194b21..4afdeb5201a4 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3029,7 +3029,12 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - const targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; + let targetEmail: string = CONST.EMAIL.CONCIERGE; + if (currentUserAccountID % 2 === 1) { + // for odd accountID, we will use the expensify persona instead of concierge + targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; + } + const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; From 6da74e4ca18a8963ada6cf7083d7cbb84871ca32 Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 26 Apr 2024 10:56:25 +0200 Subject: [PATCH 011/419] handling new system report type --- src/CONST.ts | 2 +- src/libs/ReportUtils.ts | 21 +++++++++++++++++++-- src/libs/SidebarUtils.ts | 6 ++++++ src/libs/actions/Report.ts | 10 ++++------ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0725fb47a85b..1aaea219d3bd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -53,6 +53,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', + SYSTEM: 'system', } as const; // Explicit type annotation is required @@ -1202,7 +1203,6 @@ const CONST = { CHRONOS: 'chronos@expensify.com', CONCIERGE: 'concierge@expensify.com', CONTRIBUTORS: 'contributors@expensify.com', - EXPENSIFY_PERSONA: 'expensify@expensify.com', FIRST_RESPONDER: 'firstresponders@expensify.com', GUIDES_DOMAIN: 'team.expensify.com', HELP: 'help@expensify.com', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 121518130cb4..1aac5cd716c5 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -978,6 +978,10 @@ function isGroupChat(report: OnyxEntry | Partial): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.GROUP; } +function isSystemChat(report: OnyxEntry | Partial): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.SYSTEM; +} + /** * Only returns true if this is our main 1:1 DM report with Concierge */ @@ -4791,7 +4795,6 @@ function shouldReportBeInOptionList({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.NOTIFICATIONS) || (report?.participantAccountIDs?.length === 0 && !isChatThread(report) && !isPublicRoom(report) && @@ -4800,7 +4803,8 @@ function shouldReportBeInOptionList({ !isMoneyRequestReport(report) && !isTaskReport(report) && !isSelfDM(report) && - !isGroupChat(report)) + !isGroupChat(report) && + !isSystemChat(report)) ) { return false; } @@ -4877,6 +4881,18 @@ function shouldReportBeInOptionList({ return true; } +/** + * Returns the system report from the list of reports. + * TODO: this method may not be necessary if the participants list of the system report is filled correctly + */ +function getSystemChat(): OnyxEntry { + if (!allReports) { + return null; + } + + return Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) ?? null; +} + /** * Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, expense, room, and policy expense chat. */ @@ -6306,6 +6322,7 @@ export { getRoomWelcomeMessage, getRootParentReport, getRouteFromLink, + getSystemChat, getTaskAssigneeChatOnyxData, getTransactionDetails, getTransactionReportName, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index d230f58e46f9..2c52af317be5 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -20,6 +20,7 @@ import * as OptionsListUtils from './OptionsListUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; const visibleReportActionItems: ReportActions = {}; Onyx.connect({ @@ -245,6 +246,11 @@ function getOptionData({ participantAccountIDs = [report.ownerAccountID ?? 0]; } + // TODO: this is added for the testing purposes only - should be removed once participants list of the system report is filled + if (report.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) { + participantAccountIDs = [report.ownerAccountID ?? 0, ...PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.NOTIFICATIONS])]; + } + const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; const hasErrors = Object.keys(result.allReportErrors ?? {}).length !== 0; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4afdeb5201a4..4d5e99966323 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3029,14 +3029,12 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - let targetEmail: string = CONST.EMAIL.CONCIERGE; - if (currentUserAccountID % 2 === 1) { - // for odd accountID, we will use the expensify persona instead of concierge - targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; - } + const isAccountIDOdd = currentUserAccountID % 2 === 1; + const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; - const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); + // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly + const targetChatReport= isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message From aaa3cd4c9bf2b136b430a61f9655308df57e88cc Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 26 Apr 2024 11:35:07 +0200 Subject: [PATCH 012/419] navigating to system chat at the end of the onboarding process added --- src/libs/AccountUtils.ts | 4 +++- src/libs/SidebarUtils.ts | 2 +- src/libs/actions/Report.ts | 18 ++++++++++++++++-- .../BaseOnboardingPersonalDetails.tsx | 7 ++++++- .../OnboardingWork/BaseOnboardingWork.tsx | 7 ++++++- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/libs/AccountUtils.ts b/src/libs/AccountUtils.ts index d903584e15b4..8bc7037e9682 100644 --- a/src/libs/AccountUtils.ts +++ b/src/libs/AccountUtils.ts @@ -5,4 +5,6 @@ import type {Account} from '@src/types/onyx'; const isValidateCodeFormSubmitting = (account: OnyxEntry) => !!account?.isLoading && account.loadingForm === (account.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM); -export default {isValidateCodeFormSubmitting}; +const isAccountIDOddNumber = (accountID: number) => accountID % 2 === 1; + +export default {isValidateCodeFormSubmitting, isAccountIDOddNumber}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2c52af317be5..50d9cea26efe 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -17,10 +17,10 @@ import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; +import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; const visibleReportActionItems: ReportActions = {}; Onyx.connect({ diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4d5e99966323..714cf2182db9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -8,6 +8,7 @@ import Onyx from 'react-native-onyx'; import type {PartialDeep, ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; import type {FileObject} from '@components/AttachmentModal'; +import AccountUtils from '@libs/AccountUtils'; import * as ActiveClientManager from '@libs/ActiveClientManager'; import * as API from '@libs/API'; import type { @@ -1930,6 +1931,18 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA } } +/** + * Navigates to the 1:1 system chat + */ +function navigateToSystemChat() { + // TODO: when system report participants list is filled, we could just use `ReportUtils.getChatByParticipants()` method insted `getSystemChat()` + const systemChatReport = ReportUtils.getSystemChat(); + + if (systemChatReport && systemChatReport.reportID) { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(systemChatReport.reportID)); + } +} + /** Add a policy report (workspace room) optimistically and navigate to it. */ function addPolicyReport(policyReport: ReportUtils.OptimisticChatReport) { const createdReportAction = ReportUtils.buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE); @@ -3029,12 +3042,12 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - const isAccountIDOdd = currentUserAccountID % 2 === 1; + const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly - const targetChatReport= isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); + const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message @@ -3736,6 +3749,7 @@ export { saveReportActionDraftNumberOfLines, deleteReportComment, navigateToConciergeChat, + navigateToSystemChat, addPolicyReport, deleteReport, navigateToConciergeChatAndDeleteReport, diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 7049b04cc293..0082ede92c4f 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -7,6 +7,7 @@ import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; +import {useSession} from '@components/OnyxProvider'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -16,6 +17,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import AccountUtils from '@libs/AccountUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -37,6 +39,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {shouldUseNarrowLayout} = useOnboardingLayout(); const {inputCallbackRef} = useAutoFocusInput(); const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); + const {accountID} = useSession(); useDisableModalDismissOnEscape(); @@ -69,6 +72,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat // Otherwise stay on the chats screen. if (isSmallScreenWidth) { Navigation.navigate(ROUTES.HOME); + } else if (AccountUtils.isAccountIDOddNumber(accountID ?? 0)) { + Report.navigateToSystemChat(); } else { Report.navigateToConciergeChat(); } @@ -79,7 +84,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat Navigation.navigate(ROUTES.WELCOME_VIDEO_ROOT); }, variables.welcomeVideoDelay); }, - [currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected], + [currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected, accountID], ); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 151c1bb35ea2..d2e583d778c2 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -7,6 +7,7 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; +import {useSession} from '@components/OnyxProvider'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -15,6 +16,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import AccountUtils from '@libs/AccountUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -32,6 +34,7 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles, const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useOnboardingLayout(); + const {accountID} = useSession(); useDisableModalDismissOnEscape(); @@ -62,6 +65,8 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles, // Otherwise stay on the chats screen. if (isSmallScreenWidth) { Navigation.navigate(ROUTES.HOME); + } else if (AccountUtils.isAccountIDOddNumber(accountID ?? 0)) { + Report.navigateToSystemChat(); } else { Report.navigateToConciergeChat(); } @@ -72,7 +77,7 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles, Navigation.navigate(ROUTES.WELCOME_VIDEO_ROOT); }, variables.welcomeVideoDelay); }, - [currentUserPersonalDetails.firstName, currentUserPersonalDetails.lastName, currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected], + [currentUserPersonalDetails.firstName, currentUserPersonalDetails.lastName, currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected, accountID], ); const validate = (values: FormOnyxValues<'onboardingWorkForm'>) => { From 282490a17937d7928230d573dc2af8834e5fcd80 Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 26 Apr 2024 11:41:47 +0200 Subject: [PATCH 013/419] addional todo comment added --- src/libs/ReportUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1aac5cd716c5..e58dd3e54b12 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4804,6 +4804,7 @@ function shouldReportBeInOptionList({ !isTaskReport(report) && !isSelfDM(report) && !isGroupChat(report) && + // TODO: this shouldn't be necessary if the system report has participants list filled !isSystemChat(report)) ) { return false; From 72f85755fab249ec89a8e8b674094baec90d261e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 21:41:03 +0100 Subject: [PATCH 014/419] refactor: apply pull request suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 4 +- src/hooks/useViolations.ts | 25 +++---- src/libs/PolicyUtils.ts | 8 +-- src/libs/Violations/ViolationsUtils.ts | 71 ++++++++++++------- src/libs/actions/IOU.ts | 10 ++- src/types/onyx/PolicyTag.ts | 4 ++ src/types/onyx/TransactionViolation.ts | 2 +- 7 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2cb1d1d6ae84..fa3707605044 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -467,7 +467,7 @@ function MoneyRequestView({ getErrorForField('tag', { tagListIndex: index, tagListName: name, - policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), + policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), }) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined @@ -475,7 +475,7 @@ function MoneyRequestView({ error={getErrorForField('tag', { tagListIndex: index, tagListName: name, - policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), + policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), })} /> diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 65e17785500d..578db947ed57 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -63,29 +63,30 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data?: TransactionViolation['data']) => { const currentViolations = violationsByField.get(field) ?? []; - const firstViolation = currentViolations[0]; + const violation = currentViolations[0]; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (firstViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(firstViolation?.data?.errorIndexes)) { + if (violation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(violation?.data?.errorIndexes)) { return currentViolations - .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) - .map((violation) => ({ - ...violation, + .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) + .map((currentViolation) => ({ + ...currentViolation, data: { - ...violation.data, + ...currentViolation.data, tagName: data?.tagListName, }, })); } - // missingTag has special logic because we have to take into account dependent tags - if (data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && data?.tagListName) { + // missingTag has special logic for policies with dependent tags, because only violation is returned for all tags + // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here + if (data?.policyHasDependentTags && violation?.name === 'missingTag' && data?.tagListName) { return [ { - ...firstViolation, + ...violation, data: { - ...firstViolation.data, + ...violation.data, tagName: data?.tagListName, }, }, @@ -93,8 +94,8 @@ function useViolations(violations: TransactionViolation[]) { } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (firstViolation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && firstViolation?.data?.tagName) { - return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); + if (violation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && violation?.data?.tagName) { + return currentViolations.filter((currentViolation) => currentViolation.data?.tagName === data?.tagListName); } return currentViolations; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 657a3387ce7f..374ffa2436a0 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -391,10 +391,10 @@ function canSendInvoice(policies: OnyxCollection): boolean { } function hasDependentTags(policy: OnyxEntry, policyTagList: OnyxEntry) { - return ( - !!policy?.hasMultipleTagLists && - Object.values(policyTagList ?? {}).some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter)) - ); + if (!policy?.hasMultipleTagLists) { + return false; + } + return Object.values(policyTagList ?? {}).some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter)); } export { diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index ffa346754d8a..0ef102318d78 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -49,36 +49,31 @@ function getTagViolationsForSingleLevelTags( } /** - * Calculates some tag levels required and missing tag violations for the given transaction + * Calculates missing tag violations for policies with dependent tags, + * by returning one per tag with its corresponding tagName in the data */ -function getTagViolationsForMultiLevelTags( - updatedTransaction: Transaction, - transactionViolations: TransactionViolation[], - policyRequiresTags: boolean, - policyTagList: PolicyTagList, - hasDependentTags: boolean, -): TransactionViolation[] { - const policyTagKeys = getSortedTagKeys(policyTagList); - const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; - let newTransactionViolations = [...transactionViolations]; - newTransactionViolations = newTransactionViolations.filter( - (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, - ); +function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[]) { + return [ + ...transactionViolations, + ...Object.values(policyTagList).map((tagList) => ({ + name: CONST.VIOLATIONS.MISSING_TAG, + type: CONST.VIOLATION_TYPES.VIOLATION, + data: {tagName: tagList.name}, + })), + ]; +} - if (hasDependentTags && !updatedTransaction.tag) { - Object.values(policyTagList).forEach((tagList) => { - newTransactionViolations.push({ - name: CONST.VIOLATIONS.MISSING_TAG, - type: CONST.VIOLATION_TYPES.VIOLATION, - data: {tagName: tagList.name}, - }); - }); - return newTransactionViolations; - } +/** + * Calculates missing tag violations for policies with independent tags, + * by returning one per tag with its corresponding tagName in the data + */ +function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], policyTagKeys: string[], selectedTags: string[]) { + let newTransactionViolations = [...transactionViolations]; // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. // Otherwise, we put TAG_OUT_OF_POLICY in Onyx (when applicable) const errorIndexes = []; + for (let i = 0; i < policyTagKeys.length; i++) { const isTagRequired = policyTagList[policyTagKeys[i]].required ?? true; const isTagSelected = Boolean(selectedTags[i]); @@ -86,6 +81,7 @@ function getTagViolationsForMultiLevelTags( errorIndexes.push(i); } } + if (errorIndexes.length !== 0) { newTransactionViolations.push({ name: CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, @@ -96,10 +92,12 @@ function getTagViolationsForMultiLevelTags( }); } else { let hasInvalidTag = false; + for (let i = 0; i < policyTagKeys.length; i++) { const selectedTag = selectedTags[i]; const tags = policyTagList[policyTagKeys[i]].tags; const isTagInPolicy = Object.values(tags).some((tag) => tag.name === selectedTag && Boolean(tag.enabled)); + if (!isTagInPolicy) { newTransactionViolations.push({ name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, @@ -112,15 +110,40 @@ function getTagViolationsForMultiLevelTags( break; } } + if (!hasInvalidTag) { newTransactionViolations = reject(newTransactionViolations, { name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, }); } } + return newTransactionViolations; } +/** + * Calculates some tag levels required and missing tag violations for the given transaction + */ +function getTagViolationsForMultiLevelTags( + updatedTransaction: Transaction, + transactionViolations: TransactionViolation[], + policyRequiresTags: boolean, + policyTagList: PolicyTagList, + hasDependentTags: boolean, +): TransactionViolation[] { + const policyTagKeys = getSortedTagKeys(policyTagList); + const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; + const filteredTransactionViolations = transactionViolations.filter( + (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + ); + + if (hasDependentTags && !updatedTransaction.tag) { + return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations); + } + + return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, policyTagKeys, selectedTags); +} + const ViolationsUtils = { /** * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1df688ce3bed..f605663793d2 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -779,7 +779,15 @@ function buildOnyxDataForMoneyRequest( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}, true); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + [], + !!policy.requiresTag, + policyTagList ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), + ); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 88b5297dbd38..9d48b14ff444 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -15,6 +15,10 @@ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors | null; rules?: { + /** + * String representation of regex to match against parent tag. Eg, if San Francisco is a child tag of California + * its parentTagsFilter will be ^California$ + */ parentTagsFilter?: string; }; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 6859c6749567..39d0dc394753 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -10,7 +10,7 @@ type TransactionViolation = { type: string; name: ViolationName; data?: { - policyHasDependentTagLists?: boolean; + policyHasDependentTags?: boolean; rejectedBy?: string; rejectReason?: string; formattedLimit?: string; From 5c7e16ce6ad6aea0268431926dc965e2408c12a9 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 22:24:24 +0100 Subject: [PATCH 015/419] chore(typescript): add missing argument --- tests/unit/ViolationUtilsTest.ts | 58 ++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index b967617918c1..02668da462e0 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -43,7 +43,7 @@ describe('getViolationsOnyxData', () => { }); it('should return an object with correct shape and with empty transactionViolations array', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result).toEqual({ onyxMethod: Onyx.METHOD.SET, @@ -57,7 +57,7 @@ describe('getViolationsOnyxData', () => { {name: 'duplicatedTransaction', type: CONST.VIOLATION_TYPES.VIOLATION}, {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining(transactionViolations)); }); @@ -70,24 +70,32 @@ describe('getViolationsOnyxData', () => { it('should add missingCategory violation if no category is included', () => { transaction.category = undefined; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([missingCategoryViolation, ...transactionViolations])); }); it('should add categoryOutOfPolicy violation when category is not in policy', () => { transaction.category = 'Bananas'; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([categoryOutOfPolicyViolation, ...transactionViolations])); }); it('should not include a categoryOutOfPolicy violation when category is in policy', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).not.toContainEqual(categoryOutOfPolicyViolation); }); it('should not add a category violation when the transaction is partial', () => { const partialTransaction = {...transaction, amount: 0, merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, category: undefined}; - const result = ViolationsUtils.getViolationsOnyxData(partialTransaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData( + partialTransaction, + transactionViolations, + policyRequiresTags, + policyTags, + policyRequiresCategories, + policyCategories, + false, + ); expect(result.value).not.toContainEqual(missingCategoryViolation); }); @@ -98,7 +106,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([categoryOutOfPolicyViolation, ...transactionViolations])); }); @@ -110,7 +118,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([missingCategoryViolation, ...transactionViolations])); }); @@ -122,7 +130,7 @@ describe('getViolationsOnyxData', () => { }); it('should not add any violations when categories are not required', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).not.toContainEqual([categoryOutOfPolicyViolation]); expect(result.value).not.toContainEqual([missingCategoryViolation]); @@ -147,7 +155,7 @@ describe('getViolationsOnyxData', () => { }); it("shouldn't update the transactionViolations if the policy requires tags and the transaction has a tag from the policy", () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(transactionViolations); }); @@ -155,7 +163,7 @@ describe('getViolationsOnyxData', () => { it('should add a missingTag violation if none is provided and policy requires tags', () => { transaction.tag = undefined; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([{...missingTagViolation}])); }); @@ -163,14 +171,22 @@ describe('getViolationsOnyxData', () => { it('should add a tagOutOfPolicy violation when policy requires tags and tag is not in the policy', () => { policyTags = {}; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([]); }); it('should not add a tag violation when the transaction is partial', () => { const partialTransaction = {...transaction, amount: 0, merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, tag: undefined}; - const result = ViolationsUtils.getViolationsOnyxData(partialTransaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData( + partialTransaction, + transactionViolations, + policyRequiresTags, + policyTags, + policyRequiresCategories, + policyCategories, + false, + ); expect(result.value).not.toContainEqual(missingTagViolation); }); @@ -181,7 +197,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([{...tagOutOfPolicyViolation}, ...transactionViolations])); }); @@ -193,7 +209,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([{...missingTagViolation}, ...transactionViolations])); }); @@ -205,7 +221,7 @@ describe('getViolationsOnyxData', () => { }); it('should not add any violations when tags are not required', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).not.toContainEqual([tagOutOfPolicyViolation]); expect(result.value).not.toContainEqual([missingTagViolation]); @@ -260,30 +276,30 @@ describe('getViolationsOnyxData', () => { }; // Test case where transaction has no tags - let result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + let result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([someTagLevelsRequiredViolation]); // Test case where transaction has 1 tag transaction.tag = 'Africa'; someTagLevelsRequiredViolation.data = {errorIndexes: [1, 2]}; - result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([someTagLevelsRequiredViolation]); // Test case where transaction has 2 tags transaction.tag = 'Africa::Project1'; someTagLevelsRequiredViolation.data = {errorIndexes: [1]}; - result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([someTagLevelsRequiredViolation]); // Test case where transaction has all tags transaction.tag = 'Africa:Accounting:Project1'; - result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([]); }); it('should return tagOutOfPolicy when a tag is not enabled in the policy but is set in the transaction', () => { policyTags.Department.tags.Accounting.enabled = false; transaction.tag = 'Africa:Accounting:Project1'; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); const violation = {...tagOutOfPolicyViolation, data: {tagName: 'Department'}}; expect(result.value).toEqual([violation]); }); From 3d6478f10c327d7b57e628b98295a3185124b232 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 22:25:26 +0100 Subject: [PATCH 016/419] chore(tests): add violation case for missing dependent tags --- tests/unit/ViolationUtilsTest.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 02668da462e0..5e8f93a56dbc 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -303,5 +303,13 @@ describe('getViolationsOnyxData', () => { const violation = {...tagOutOfPolicyViolation, data: {tagName: 'Department'}}; expect(result.value).toEqual([violation]); }); + it('should return missingTag when all dependent tags are enabled in the policy but are not set in the transaction', () => { + const missingDepartmentTag = {...missingTagViolation, data: {tagName: 'Department'}}; + const missingRegionTag = {...missingTagViolation, data: {tagName: 'Region'}}; + const missingProjectTag = {...missingTagViolation, data: {tagName: 'Project'}}; + transaction.tag = undefined; + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, true); + expect(result.value).toEqual(expect.arrayContaining([missingDepartmentTag, missingRegionTag, missingProjectTag])); + }); }); }); From 0410d8ff2bb5ac120aeb3bf9fe5d98a5b579132f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 16:01:49 +0200 Subject: [PATCH 017/419] fix: minor fix --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c9213d5460ea..cbce4a48928d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4983,7 +4983,7 @@ function shouldReportBeInOptionList({ !isTaskReport(report) && !isSelfDM(report) && !isGroupChat(report) && - !isInvoiceRoom(report)) && + !isInvoiceRoom(report) && // TODO: this shouldn't be necessary if the system report has participants list filled !isSystemChat(report)) ) { From 179575fb7ff9e91b9f7a87f90162d03191875c09 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 17:03:58 +0200 Subject: [PATCH 018/419] fix: remove comment --- src/libs/ReportUtils.ts | 1 - src/libs/actions/Report.ts | 24 +++++++++++++++---- .../BaseOnboardingPersonalDetails.tsx | 2 ++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cbce4a48928d..967004c7184d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5064,7 +5064,6 @@ function shouldReportBeInOptionList({ /** * Returns the system report from the list of reports. - * TODO: this method may not be necessary if the participants list of the system report is filled correctly */ function getSystemChat(): OnyxEntry { if (!allReports) { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9fe8f8d9fffa..4ef0b9e22d02 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -871,7 +871,11 @@ function openReport( if (isFromDeepLink) { // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, {optimisticData, successData, failureData}).finally(() => { + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, { + optimisticData, + successData, + failureData, + }).finally(() => { Onyx.set(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, false); }); } else { @@ -1851,7 +1855,10 @@ function updateDescription(reportID: string, previousValue: string, newValue: st { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {description: parsedDescription, pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}, + value: { + description: parsedDescription, + pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, }, ]; const failureData: OnyxUpdate[] = [ @@ -1930,7 +1937,6 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA * Navigates to the 1:1 system chat */ function navigateToSystemChat() { - // TODO: when system report participants list is filled, we could just use `ReportUtils.getChatByParticipants()` method insted `getSystemChat()` const systemChatReport = ReportUtils.getSystemChat(); if (systemChatReport && systemChatReport.reportID) { @@ -2215,7 +2221,10 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi // If this notification was delayed and the user saw the message already, don't show it if (action && report?.lastReadTime && report.lastReadTime >= action.created) { - Log.info(`${tag} No notification because the comment was already read`, false, {created: action.created, lastReadTime: report.lastReadTime}); + Log.info(`${tag} No notification because the comment was already read`, false, { + created: action.created, + lastReadTime: report.lastReadTime, + }); return false; } @@ -2243,7 +2252,10 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi const report = allReports?.[reportID] ?? null; if (!report) { - Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", {reportID, reportActionID: reportAction.reportActionID}); + Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", { + reportID, + reportActionID: reportAction.reportActionID, + }); return; } @@ -3027,6 +3039,8 @@ function completeOnboarding( const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; + console.log({currentUserAccountID}); + const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 036408226e71..eb4d4e50ffbd 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -42,6 +42,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {accountID} = useSession(); useDisableModalDismissOnEscape(); + console.log({accountID}); const completeEngagement = useCallback( (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { @@ -67,6 +68,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat }); Navigation.dismissModal(); + console.log({accountID}); // Only navigate to concierge chat when central pane is visible // Otherwise stay on the chats screen. From 9cba84a26366e0eb611ead798c877a0419b5eb4a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 17:12:31 +0200 Subject: [PATCH 019/419] fix: remove unnecessary condition --- src/libs/ReportUtils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 967004c7184d..ed9639ab765a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4983,9 +4983,7 @@ function shouldReportBeInOptionList({ !isTaskReport(report) && !isSelfDM(report) && !isGroupChat(report) && - !isInvoiceRoom(report) && - // TODO: this shouldn't be necessary if the system report has participants list filled - !isSystemChat(report)) + !isInvoiceRoom(report)) ) { return false; } From ba3aa46bd40fbb1be8be752448c2ff254860e463 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 17:25:53 +0200 Subject: [PATCH 020/419] fix: remove console log --- src/libs/actions/Report.ts | 12 ++++++++---- .../BaseOnboardingPersonalDetails.tsx | 2 -- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4ef0b9e22d02..7d7e93463af2 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -351,7 +351,10 @@ function subscribeToReportTypingEvents(reportID: string) { delete typingWatchTimers[reportUserIdentifier]; }, 1500); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { + errorType: error.type, + pusherChannelName, + }); }); } @@ -382,7 +385,10 @@ function subscribeToReportLeavingEvents(reportID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { + errorType: error.type, + pusherChannelName, + }); }); } @@ -3039,8 +3045,6 @@ function completeOnboarding( const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; - console.log({currentUserAccountID}); - const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index eb4d4e50ffbd..036408226e71 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -42,7 +42,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {accountID} = useSession(); useDisableModalDismissOnEscape(); - console.log({accountID}); const completeEngagement = useCallback( (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { @@ -68,7 +67,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat }); Navigation.dismissModal(); - console.log({accountID}); // Only navigate to concierge chat when central pane is visible // Otherwise stay on the chats screen. From 433b7b8051f5e3532dd0b519adbb4c473d78e484 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Wed, 1 May 2024 13:56:54 -0500 Subject: [PATCH 021/419] DOCS: Update and rename Profile.md to Add-profile-photo.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Article rewrite—we're splitting the previous version of this doc into multiple different articles --- .../settings/Add-profile-photo.md | 21 +++++ .../new-expensify/settings/Profile.md | 92 ------------------- 2 files changed, 21 insertions(+), 92 deletions(-) create mode 100644 docs/articles/new-expensify/settings/Add-profile-photo.md delete mode 100644 docs/articles/new-expensify/settings/Profile.md diff --git a/docs/articles/new-expensify/settings/Add-profile-photo.md b/docs/articles/new-expensify/settings/Add-profile-photo.md new file mode 100644 index 000000000000..60e56deaafbc --- /dev/null +++ b/docs/articles/new-expensify/settings/Add-profile-photo.md @@ -0,0 +1,21 @@ +--- +title: Add profile photo +description: Add an image to your profile +--- +
+ +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click your profile image or icon in the bottom left menu. +2. Click the Edit pencil icon next to your profile image or icon and select **Upload Image** to choose a new image from your saved files. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap your profile image or icon at the bottom of the screen. +2. Tap the Edit pencil icon next to your profile image or icon and select **Upload Image** to choose a new image from your saved files. +{% include end-option.html %} + +{% include end-selector.html %} + +
diff --git a/docs/articles/new-expensify/settings/Profile.md b/docs/articles/new-expensify/settings/Profile.md deleted file mode 100644 index 908cf39c7ac6..000000000000 --- a/docs/articles/new-expensify/settings/Profile.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Profile -description: How to manage your Expensify Profile ---- -# Overview -Your Profile in Expensify allows you to: -- Set your public profile photo -- Set a display name -- Manage your contact methods -- Communicate your current status -- Set your pronouns -- Configure your timezone -- Store your personal details (for travel and payment purposes) - -# How to set your public profile photo - -To set or update your profile photo: -1. Go to **Settings > Profile** -2. Tap on the default or your existing profile photo, -3. You can either either upload (to set a new profile photo), remove or view your profile photo - -Your profile photo is visible to all Expensify users. - -# How to set a display name - -To set or update your display name: -1. Go to **Settings > Profile** -2. Tap on **Display name** -3. Set a first name and a last name, then **Save** - -Your display name is public to all Expensify users. - -# How to add or remove contact methods (email address and phone number) - -Your contact methods allow people to contact you (using your email address or phone number), and allow you to forward receipts to receipts@expensify.com from multiple email addresses. - -To manage your contact methods: -1. Go to **Settings > Profile** -2. Tap on **Contact method** -3. Tap **New contact method** to add a new email or phone number - -Your default contact method (email address or phone number) will be visible to "known" users, with whom you have interacted or are part of your team. - -To change the email address or phone number that's displayed on your Expensify account, add a new contact method, then tap on that email address and tap **Set as default**. - -# How to communicate your current status - -You can use your status emoji to communicate your mood, focus or current activity. You can optionally add a status message too! - -To set your status emoji and status message: -1. Go to **Settings > Profile** -2. Tap on **Status** then **Status** -3. Choose a status emoji, and optionally set a status message -4. Tap on **Save** - -Your status emoji will be visible next to your name in Expensify, and your status emoji and status message will appear in your profile (which is public to all Expensify users). On a computer, your status message will also be visible by hovering your mouse over your name. - -You can also remove your current status: -1. Go to **Settings > Profile** -2. Tap on **Status** -3. Tap on **Clear status** - -# How to set your pronouns - -To set your pronouns: -1. Go to **Settings > Profile** -2. Tap on **Pronouns** -3. Search for your preferred pronouns, then tap on your choice - -Your pronouns will be visible to "known" users, with whom you have interacted or are part of your team. - -# How to configure your timezone - -Your timezone is automatically set using an estimation based on your IP address. - -To set your timezone manually: -1. Go to **Settings > Profile** -2. Tap on **Timezone** -3. Disable **Automatically determine your location** -4. Tap on **Timezone** -5. Search for your preferred timezone, then tap on your choice - -Your timezone will be visible to "known" users, with whom you have interacted or are part of your team. - -# How to store your personal details (for travel and payment purposes) - -Your personal details can be used in Expensify for travel and payment purposes. These will not be shared with any other Expensify user. - -To set your timezone manually: -1. Go to **Settings > Profile** -2. Tap on **Personal details** -3. Tap on **Legal name**, **Date of birth**, and **Address** to set your personal details From 5c2e6d2b3f1319d52e79f0726dcc7e671a1f4e8f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 2 May 2024 11:36:37 +0200 Subject: [PATCH 022/419] fix: remove unnecessary change --- src/libs/actions/Report.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7d7e93463af2..270c126fbcad 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -351,10 +351,7 @@ function subscribeToReportTypingEvents(reportID: string) { delete typingWatchTimers[reportUserIdentifier]; }, 1500); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { - errorType: error.type, - pusherChannelName, - }); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); }); } @@ -385,10 +382,7 @@ function subscribeToReportLeavingEvents(reportID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { - errorType: error.type, - pusherChannelName, - }); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); }); } From 36842b8d038974c945751ce7ad51fc026fed7c81 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 2 May 2024 11:42:50 +0200 Subject: [PATCH 023/419] fix: apply requested changes --- src/libs/ReportUtils.ts | 1 - src/libs/SidebarUtils.ts | 5 ----- src/libs/actions/Report.ts | 24 +++++------------------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ed9639ab765a..1f3ececf17bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4973,7 +4973,6 @@ function shouldReportBeInOptionList({ report?.reportName === undefined || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (report?.participantAccountIDs?.length === 0 && !isChatThread(report) && !isPublicRoom(report) && diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3d0b214d8377..2360366dde73 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -245,11 +245,6 @@ function getOptionData({ participantAccountIDs = [report.ownerAccountID ?? 0]; } - // TODO: this is added for the testing purposes only - should be removed once participants list of the system report is filled - if (report.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) { - participantAccountIDs = [report.ownerAccountID ?? 0, ...PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.NOTIFICATIONS])]; - } - const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; const hasErrors = Object.keys(result.allReportErrors ?? {}).length !== 0; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 270c126fbcad..bb3855dd893a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -871,11 +871,7 @@ function openReport( if (isFromDeepLink) { // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, { - optimisticData, - successData, - failureData, - }).finally(() => { + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, {optimisticData, successData, failureData}).finally(() => { Onyx.set(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, false); }); } else { @@ -1855,10 +1851,7 @@ function updateDescription(reportID: string, previousValue: string, newValue: st { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - description: parsedDescription, - pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, - }, + value: {description: parsedDescription, pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}, }, ]; const failureData: OnyxUpdate[] = [ @@ -2221,10 +2214,7 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi // If this notification was delayed and the user saw the message already, don't show it if (action && report?.lastReadTime && report.lastReadTime >= action.created) { - Log.info(`${tag} No notification because the comment was already read`, false, { - created: action.created, - lastReadTime: report.lastReadTime, - }); + Log.info(`${tag} No notification because the comment was already read`, false, {created: action.created, lastReadTime: report.lastReadTime}); return false; } @@ -2252,10 +2242,7 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi const report = allReports?.[reportID] ?? null; if (!report) { - Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", { - reportID, - reportActionID: reportAction.reportActionID, - }); + Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", {reportID, reportActionID: reportAction.reportActionID}); return; } @@ -3040,8 +3027,7 @@ function completeOnboarding( const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; - // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly - const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); + const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message From a6b6593adaf35c4ea1ec5c8282845fc5e3845c17 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 2 May 2024 13:56:59 +0200 Subject: [PATCH 024/419] fix: lint --- src/libs/SidebarUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2360366dde73..c0d0c9020a64 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -18,7 +18,6 @@ import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; -import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; From e29768615c1e43b9708e4048aa0feb64041e6c7f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 3 May 2024 09:29:44 +0100 Subject: [PATCH 025/419] refactor: apply pull request suggestions --- src/hooks/useViolations.ts | 23 +++++++++++------------ src/libs/Violations/ViolationsUtils.ts | 11 ++++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 578db947ed57..6257a9251d08 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -63,30 +63,29 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data?: TransactionViolation['data']) => { const currentViolations = violationsByField.get(field) ?? []; - const violation = currentViolations[0]; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (violation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(violation?.data?.errorIndexes)) { + if (currentViolations[0]?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { return currentViolations - .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) - .map((currentViolation) => ({ - ...currentViolation, + .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) + .map((violation) => ({ + ...violation, data: { - ...currentViolation.data, + ...violation.data, tagName: data?.tagListName, }, })); } - // missingTag has special logic for policies with dependent tags, because only violation is returned for all tags + // missingTag has special logic for policies with dependent tags, because only one violation is returned for all tags // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here - if (data?.policyHasDependentTags && violation?.name === 'missingTag' && data?.tagListName) { + if (data?.policyHasDependentTags && currentViolations[0]?.name === 'missingTag' && data?.tagListName) { return [ { - ...violation, + ...currentViolations[0], data: { - ...violation.data, + ...currentViolations[0].data, tagName: data?.tagListName, }, }, @@ -94,8 +93,8 @@ function useViolations(violations: TransactionViolation[]) { } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (violation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && violation?.data?.tagName) { - return currentViolations.filter((currentViolation) => currentViolation.data?.tagName === data?.tagListName); + if (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } return currentViolations; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 0ef102318d78..c7bcd66aef5e 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -67,7 +67,9 @@ function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transact * Calculates missing tag violations for policies with independent tags, * by returning one per tag with its corresponding tagName in the data */ -function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], policyTagKeys: string[], selectedTags: string[]) { +function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], transaction: Transaction) { + const policyTagKeys = getSortedTagKeys(policyTagList); + const selectedTags = transaction.tag?.split(CONST.COLON) ?? []; let newTransactionViolations = [...transactionViolations]; // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. @@ -131,17 +133,16 @@ function getTagViolationsForMultiLevelTags( policyTagList: PolicyTagList, hasDependentTags: boolean, ): TransactionViolation[] { - const policyTagKeys = getSortedTagKeys(policyTagList); - const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; const filteredTransactionViolations = transactionViolations.filter( - (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + (violation) => + violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY && violation.name !== CONST.VIOLATIONS.MISSING_TAG, ); if (hasDependentTags && !updatedTransaction.tag) { return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations); } - return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, policyTagKeys, selectedTags); + return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, updatedTransaction); } const ViolationsUtils = { From a8ef1c390be0a38c687303101ec3169ddda7de15 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 3 May 2024 10:19:07 +0100 Subject: [PATCH 026/419] chore(typescript): add missing argument --- src/libs/actions/IOU.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7b962e712cf2..e552c5eac7d0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1087,7 +1087,15 @@ function buildOnyxDataForInvoice( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + [], + !!policy.requiresTag, + policyTagList ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), + ); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); From ca6201f94fdf16b0977ea208b746ee0da495dde0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 7 May 2024 15:13:55 +0200 Subject: [PATCH 027/419] refactor: wip - connect all the steps --- .../ModalStackNavigators/index.tsx | 4 +- .../AddBankAccount/AddBankAccount.tsx | 2 +- src/pages/EnablePayments/EnablePayments.tsx | 102 ++++++++++++++++++ .../PersonalInfo/PersonalInfo.tsx | 30 +++--- .../VerifyIdentity/VerifyIdentity.tsx | 16 +-- 5 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 src/pages/EnablePayments/EnablePayments.tsx diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index ce8ea271182d..b9c1e94f0497 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -216,9 +216,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Wallet/ChooseTransferAccountPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: () => require('../../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_REFACTOR]: () => require('../../../../pages/EnablePayments/PersonalInfo/PersonalInfo').default as React.ComponentType, - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_TEMPORARY_TERMS]: () => require('../../../../pages/EnablePayments/FeesAndTerms/FeesAndTerms').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_REFACTOR]: () => require('../../../../pages/EnablePayments/EnablePayments').default as React.ComponentType, [SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType, [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, [SCREENS.SETTINGS.ADD_BANK_ACCOUNT_REFACTOR]: () => require('../../../../pages/EnablePayments/AddBankAccount/AddBankAccount').default as React.ComponentType, diff --git a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx index 79c91af178c3..9fde5456226b 100644 --- a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx +++ b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx @@ -44,7 +44,7 @@ function AddBankAccount({personalBankAccount, plaidData, personalBankAccountDraf if (selectedPlaidBankAccount) { BankAccounts.addPersonalBankAccount(selectedPlaidBankAccount); - Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS); + Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS_REFACTOR); } }, [personalBankAccountDraft?.plaidAccountID, plaidData?.bankAccounts]); diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx new file mode 100644 index 000000000000..6df56afced57 --- /dev/null +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -0,0 +1,102 @@ +import React, {useEffect} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import Navigation from '@libs/Navigation/Navigation'; +import FeesAndTerms from '@pages/EnablePayments/FeesAndTerms/FeesAndTerms'; +import PersonalInfo from '@pages/EnablePayments/PersonalInfo/PersonalInfo'; +import VerifyIdentity from '@pages/EnablePayments/VerifyIdentity/VerifyIdentity'; +import * as Wallet from '@userActions/Wallet'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {UserWallet} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import ActivateStep from './ActivateStep'; +import FailedKYC from './FailedKYC'; + +type EnablePaymentsPageOnyxProps = { + /** The user's wallet */ + userWallet: OnyxEntry; +}; + +type EnablePaymentsPageProps = EnablePaymentsPageOnyxProps; + +function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + + const {isPendingOnfidoResult, hasFailedOnfido} = userWallet ?? {}; + + useEffect(() => { + if (isOffline) { + return; + } + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (isPendingOnfidoResult || hasFailedOnfido) { + Navigation.navigate(ROUTES.SETTINGS_WALLET, CONST.NAVIGATION.TYPE.UP); + return; + } + + Wallet.openEnablePaymentsPage(); + }, [isOffline, isPendingOnfidoResult, hasFailedOnfido]); + + if (isEmptyObject(userWallet)) { + return ; + } + + return ( + + {() => { + if (userWallet?.errorCode === CONST.WALLET.ERROR.KYC) { + return ( + <> + Navigation.goBack(ROUTES.SETTINGS_WALLET)} + /> + + + ); + } + + const currentStep = userWallet?.currentStep || CONST.WALLET.STEP.ADDITIONAL_DETAILS; + + switch (currentStep) { + case CONST.WALLET.STEP.ADDITIONAL_DETAILS: + case CONST.WALLET.STEP.ADDITIONAL_DETAILS_KBA: + return ; + case CONST.WALLET.STEP.ONFIDO: + return ; + case CONST.WALLET.STEP.TERMS: + return ; + case CONST.WALLET.STEP.ACTIVATE: + return ; + default: + return null; + } + }} + + ); +} + +EnablePaymentsPage.displayName = 'EnablePaymentsPage'; + +export default withOnyx({ + userWallet: { + key: ONYXKEYS.USER_WALLET, + + // We want to refresh the wallet each time the user attempts to activate the wallet so we won't use the + // stored values here. + initWithStoredValues: false, + }, +})(EnablePaymentsPage); diff --git a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx index c23b8bcb12fa..22e9a3731581 100644 --- a/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx +++ b/src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx @@ -9,8 +9,7 @@ import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; -// TODO: uncomment after connecting steps https://github.com/Expensify/App/issues/36648 -// import {parsePhoneNumber} from '@libs/PhoneNumber'; +import {parsePhoneNumber} from '@libs/PhoneNumber'; import Navigation from '@navigation/Navigation'; import getInitialSubstepForPersonalInfo from '@pages/EnablePayments/utils/getInitialSubstepForPersonalInfo'; import getSubstepValues from '@pages/EnablePayments/utils/getSubstepValues'; @@ -47,22 +46,19 @@ function PersonalInfoPage({walletAdditionalDetails, walletAdditionalDetailsDraft const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, walletAdditionalDetailsDraft, walletAdditionalDetails), [walletAdditionalDetails, walletAdditionalDetailsDraft]); const submit = () => { - // TODO: uncomment after connecting steps https://github.com/Expensify/App/issues/36648 - // const personalDetails = { - // phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', - // legalFirstName: values?.[PERSONAL_INFO_STEP_KEYS.FIRST_NAME] ?? '', - // legalLastName: values?.[PERSONAL_INFO_STEP_KEYS.LAST_NAME] ?? '', - // addressStreet: values?.[PERSONAL_INFO_STEP_KEYS.STREET] ?? '', - // addressCity: values?.[PERSONAL_INFO_STEP_KEYS.CITY] ?? '', - // addressState: values?.[PERSONAL_INFO_STEP_KEYS.STATE] ?? '', - // addressZip: values?.[PERSONAL_INFO_STEP_KEYS.ZIP_CODE] ?? '', - // dob: values?.[PERSONAL_INFO_STEP_KEYS.DOB] ?? '', - // ssn: values?.[PERSONAL_INFO_STEP_KEYS.SSN_LAST_4] ?? '', - // }; + const personalDetails = { + phoneNumber: (values.phoneNumber && parsePhoneNumber(values.phoneNumber, {regionCode: CONST.COUNTRY.US}).number?.significant) ?? '', + legalFirstName: values?.[PERSONAL_INFO_STEP_KEYS.FIRST_NAME] ?? '', + legalLastName: values?.[PERSONAL_INFO_STEP_KEYS.LAST_NAME] ?? '', + addressStreet: values?.[PERSONAL_INFO_STEP_KEYS.STREET] ?? '', + addressCity: values?.[PERSONAL_INFO_STEP_KEYS.CITY] ?? '', + addressState: values?.[PERSONAL_INFO_STEP_KEYS.STATE] ?? '', + addressZip: values?.[PERSONAL_INFO_STEP_KEYS.ZIP_CODE] ?? '', + dob: values?.[PERSONAL_INFO_STEP_KEYS.DOB] ?? '', + ssn: values?.[PERSONAL_INFO_STEP_KEYS.SSN_LAST_4] ?? '', + }; // Attempt to set the personal details - // Wallet.updatePersonalDetails(personalDetails); - Navigation.goBack(ROUTES.SETTINGS_WALLET); - Wallet.resetWalletAdditionalDetailsDraft(); + Wallet.updatePersonalDetails(personalDetails); }; const startFrom = useMemo(() => getInitialSubstepForPersonalInfo(values), [values]); diff --git a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx index f402f52255ab..40aab5ba8b35 100644 --- a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx @@ -13,6 +13,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Growl from '@libs/Growl'; import * as BankAccounts from '@userActions/BankAccounts'; +import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalBankAccount, WalletOnfido} from '@src/types/onyx'; @@ -36,14 +37,11 @@ type VerifyIdentityOnyxProps = { walletOnfidoData: OnyxEntry; }; -type VerifyIdentityProps = VerifyIdentityOnyxProps & { - /** Goes to the previous step */ - onBackButtonPress: () => void; -}; +type VerifyIdentityProps = VerifyIdentityOnyxProps; const ONFIDO_ERROR_DISPLAY_DURATION = 10000; -function VerifyIdentity({personalBankAccount, onBackButtonPress, onfidoApplicantID, onfidoToken, walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { +function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -72,16 +70,20 @@ function VerifyIdentity({personalBankAccount, onBackButtonPress, onfidoApplicant // BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); }; + const handleBackButtonPress = () => { + Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS); + }; + return ( From 5b9433ce8e8570dca2692606aab76b4a2f34b53e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 9 May 2024 22:18:26 +0100 Subject: [PATCH 028/419] docs: add onyx types descriptions --- .../settings/Wallet/ExpensifyCardPage.tsx | 2 + src/types/onyx/Account.ts | 11 + src/types/onyx/AccountData.ts | 16 + src/types/onyx/Bank.ts | 13 + src/types/onyx/BankAccount.ts | 19 +- src/types/onyx/Beta.ts | 1 + src/types/onyx/BlockedFromConcierge.ts | 1 + src/types/onyx/Card.ts | 60 ++++ src/types/onyx/Console.ts | 7 + src/types/onyx/Credentials.ts | 1 + src/types/onyx/Currency.ts | 2 + src/types/onyx/CustomStatusDraft.ts | 1 + src/types/onyx/DismissedReferralBanners.ts | 10 + src/types/onyx/Download.ts | 1 + src/types/onyx/FrequentlyUsedEmoji.ts | 1 + src/types/onyx/Fund.ts | 44 ++- src/types/onyx/IOU.ts | 91 ++++++ src/types/onyx/IntroSelected.ts | 1 + src/types/onyx/InvitedEmailsToAccountIDs.ts | 1 + src/types/onyx/LastPaymentMethod.ts | 1 + src/types/onyx/LastSelectedDistanceRates.ts | 1 + src/types/onyx/Locale.ts | 1 + src/types/onyx/Login.ts | 2 + src/types/onyx/MapboxAccessToken.ts | 6 + src/types/onyx/Modal.ts | 2 + src/types/onyx/Network.ts | 1 + src/types/onyx/NewGroupChatDraft.ts | 10 + src/types/onyx/Onboarding.ts | 1 + src/types/onyx/OnyxCommon.ts | 12 + src/types/onyx/OnyxUpdatesFromServer.ts | 20 ++ src/types/onyx/PaymentMethod.ts | 12 + src/types/onyx/PersonalBankAccount.ts | 1 + src/types/onyx/PersonalDetails.ts | 7 + src/types/onyx/PlaidBankAccount.ts | 1 + src/types/onyx/PlaidData.ts | 4 + src/types/onyx/Policy.ts | 301 +++++++++++++++++- src/types/onyx/PolicyCategory.ts | 2 + src/types/onyx/PolicyEmployee.ts | 2 + src/types/onyx/PolicyJoinMember.ts | 1 + src/types/onyx/PolicyOwnershipChangeChecks.ts | 8 + src/types/onyx/PolicyTag.ts | 3 + src/types/onyx/PreferredTheme.ts | 1 + src/types/onyx/PriorityMode.ts | 1 + src/types/onyx/PrivatePersonalDetails.ts | 34 ++ src/types/onyx/QuickAction.ts | 2 + src/types/onyx/RecentWaypoint.ts | 1 + src/types/onyx/RecentlyUsedCategories.ts | 1 + src/types/onyx/RecentlyUsedReportFields.ts | 1 + src/types/onyx/RecentlyUsedTags.ts | 1 + src/types/onyx/ReimbursementAccount.ts | 4 + src/types/onyx/Report.ts | 85 ++++- src/types/onyx/ReportAction.ts | 52 +++ src/types/onyx/ReportActionReactions.ts | 4 + src/types/onyx/ReportActionsDraft.ts | 2 + src/types/onyx/ReportActionsDrafts.ts | 2 + src/types/onyx/ReportMetadata.ts | 1 + src/types/onyx/ReportNextStep.ts | 17 + src/types/onyx/ReportUserIsTyping.ts | 1 + src/types/onyx/Request.ts | 32 ++ src/types/onyx/Response.ts | 39 +++ src/types/onyx/ScreenShareRequest.ts | 1 + src/types/onyx/SecurityGroup.ts | 2 + src/types/onyx/SelectedTabRequest.ts | 1 + src/types/onyx/Session.ts | 3 + src/types/onyx/Task.ts | 1 + src/types/onyx/Transaction.ts | 108 ++++++- src/types/onyx/TransactionViolation.ts | 44 +++ src/types/onyx/User.ts | 1 + src/types/onyx/UserLocation.ts | 1 + src/types/onyx/UserWallet.ts | 4 + src/types/onyx/WalletAdditionalDetails.ts | 13 + src/types/onyx/WalletOnfido.ts | 1 + src/types/onyx/WalletStatement.ts | 1 + src/types/onyx/WalletTerms.ts | 1 + src/types/onyx/WalletTransfer.ts | 3 + src/types/onyx/WorkspaceRateAndUnit.ts | 4 +- 76 files changed, 1146 insertions(+), 6 deletions(-) diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index 49686e19852c..2f4135820b08 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -116,6 +116,8 @@ function ExpensifyCardPage({ // eslint-disable-next-line rulesdir/no-thenable-actions-in-views Card.revealVirtualCardDetails(revealedCardID) .then((value) => { + // TODO: Card.revealVirtualCardDetails return type doesn't include TCardDetails, forcing us to type cast it here. + // The return type could be rewritten like Promise setCardsDetails((prevState: Record) => ({...prevState, [revealedCardID]: value as TCardDetails})); setCardsDetailsErrors((prevState) => ({ ...prevState, diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index c53d7ea816f8..95a0f4ce5314 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -4,8 +4,10 @@ import type DismissedReferralBanners from './DismissedReferralBanners'; import type * as OnyxCommon from './OnyxCommon'; import type {TravelSettings} from './TravelSettings'; +/** Two factor authentication steps */ type TwoFactorAuthStep = ValueOf | ''; +/** Model of user account */ type Account = { /** Whether SAML is enabled for the current account */ isSAMLEnabled?: boolean; @@ -55,10 +57,19 @@ type Account = { /** Whether a sign is loading */ isLoading?: boolean; + /** Authentication failure errors */ errors?: OnyxCommon.Errors | null; + + /** Authentication success message */ success?: string; + + /** Whether the two factor authentication codes were copied */ codesAreCopied?: boolean; + + /** Current two factor authentication step */ twoFactorAuthStep?: TwoFactorAuthStep; + + /** Referral banners that the user dismissed */ dismissedReferralBanners?: DismissedReferralBanners; /** Object containing all account information necessary to connect with Spontana */ diff --git a/src/types/onyx/AccountData.ts b/src/types/onyx/AccountData.ts index 124a006ff57c..78c8391ac8ae 100644 --- a/src/types/onyx/AccountData.ts +++ b/src/types/onyx/AccountData.ts @@ -1,15 +1,29 @@ import type {BankName} from './Bank'; import type * as OnyxCommon from './OnyxCommon'; +/** Model of additional bank account data */ type AdditionalData = { + /** Is a Peer-To-Peer debit card */ isP2PDebitCard?: boolean; + + /** Owners that can benefit from this bank account */ beneficialOwners?: string[]; + + /** In which currency is the bank account */ currency?: string; + + /** In which bank is the bank account */ bankName?: BankName; + + // TODO: Confirm this + /** Whether the bank account is local or international */ fieldsType?: string; + + /** In which country is the bank account */ country?: string; }; +/** Model of bank account data */ type AccountData = { /** The masked bank account number */ accountNumber?: string; @@ -38,6 +52,7 @@ type AccountData = { /** All user emails that have access to this bank account */ sharees?: string[]; + /** Institution that processes the account payments */ processor?: string; /** The bankAccountID in the bankAccounts db */ @@ -52,6 +67,7 @@ type AccountData = { /** Any error message to show */ errors?: OnyxCommon.Errors; + /** The debit card ID */ fundID?: number; }; diff --git a/src/types/onyx/Bank.ts b/src/types/onyx/Bank.ts index 72b5fcda4788..3eee283da5c6 100644 --- a/src/types/onyx/Bank.ts +++ b/src/types/onyx/Bank.ts @@ -3,15 +3,28 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; +/** Bank icon configurations */ type BankIcon = { + /** Source of the icon, can be a component or an image */ icon: IconAsset; + + /** Size of the icon */ iconSize?: number; + + /** Height of the icon */ iconHeight?: number; + + /** Width of the icon */ iconWidth?: number; + + /** Icon wrapper styles */ iconStyles?: ViewStyle[]; }; +/** Bank names */ type BankName = ValueOf; + +/** Bank name keys */ type BankNameKey = keyof typeof CONST.BANK_NAMES; export type {BankIcon, BankName, BankNameKey}; diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index d0f80708842c..6ca40f37093d 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -2,15 +2,30 @@ import type CONST from '@src/CONST'; import type AccountData from './AccountData'; import type * as OnyxCommon from './OnyxCommon'; +// TODO: This type is a duplicate of the one present in AccountData.ts +/** Model of additional bank account data */ type AdditionalData = { + /** Is a Peer-To-Peer Debit Card */ isP2PDebitCard?: boolean; + + /** Owners that can benefit from this bank account */ beneficialOwners?: string[]; + + /** In which currency is the bank account */ currency?: string; + + /** In which bank is the bank account */ bankName?: string; + + // TODO: Confirm this + /** Whether the bank account is local or international */ fieldsType?: string; + + /** In which country is the bank account */ country?: string; }; +/** Model of bank account */ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The bank account type */ accountType?: typeof CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT; @@ -18,6 +33,7 @@ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** string like 'Account ending in XXXX' */ description?: string; + /** Determines if the bank account is a default payment method */ isDefault?: boolean; /* Determines if the bank account is a savings account */ @@ -26,7 +42,7 @@ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Date when the 3 micro amounts for validation were supposed to reach the bank account. */ validateCodeExpectedDate?: string; - /** string like 'bankAccount-{}' where is the bankAccountID */ + /** string like 'bankAccount-{\}' where is the bankAccountID */ key?: string; /** Alias for bankAccountID */ @@ -42,6 +58,7 @@ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors; }>; +/** Record of bank accounts, indexed by bankAccountID */ type BankAccountList = Record; export default BankAccount; diff --git a/src/types/onyx/Beta.ts b/src/types/onyx/Beta.ts index 35ed4c804ab8..1c282409aea5 100644 --- a/src/types/onyx/Beta.ts +++ b/src/types/onyx/Beta.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** New Dot Beta features */ type Beta = ValueOf; export default Beta; diff --git a/src/types/onyx/BlockedFromConcierge.ts b/src/types/onyx/BlockedFromConcierge.ts index 4eebd537604c..7274602bee38 100644 --- a/src/types/onyx/BlockedFromConcierge.ts +++ b/src/types/onyx/BlockedFromConcierge.ts @@ -1,3 +1,4 @@ +/** Model of blocked from concierge */ type BlockedFromConcierge = { /** The date that the user will be unblocked */ expiresAt: string; diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 50403b982c0d..4ede25c34be6 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -2,43 +2,103 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; +/** Model of expensify card */ type Card = { + /** Card ID number */ cardID: number; + + /** Current card state */ state: ValueOf; + + /** Bank name */ bank: string; + + /** Available amount to spend */ availableSpend: number; + + /** Domain name */ domainName: string; + + /** Last four Primary Account Number digits */ lastFourPAN?: string; + + /** Determines if the current card was flagged as fraud */ fraud: ValueOf; + + /** Card related error messages */ errors?: OnyxCommon.Errors; + + /** Is card data loading */ isLoading?: boolean; + + /** Additional card data */ nameValuePairs?: { + // TODO: Doesn't seem to be used in app + /** Type of spending limits */ limitType?: ValueOf; + + // TODO: Doesn't seem to be used in app cardTitle?: string; // Used only for admin-issued virtual cards + + // TODO: Doesn't seem to be used in app issuedBy?: number; + + // TODO: Doesn't seem to be used in app hasCustomUnapprovedExpenseLimit?: boolean; + + // TODO: Doesn't seem to be used in app unapprovedExpenseLimit?: number; + + // TODO: Doesn't seem to be used in app feedCountry?: string; + + /** Is a virtual card */ isVirtual?: boolean; + + // TODO: Doesn't seem to be used in app previousState?: number; + + // TODO: Doesn't seem to be used in app + /** Card expiration date */ expirationDate?: string; }; }; +/** Model of expensify card details */ type TCardDetails = { + /** Card Primary Account Number */ pan: string; + + /** Card expiration date */ expiration: string; + + /** Card Verification Value number */ cvv: string; + + // TODO: Doesn't seem to be used in app + /** Card owner address */ address: { + /** Address line 1 */ street: string; + + /** Address line 2 */ street2: string; + + /** City */ city: string; + + /** State */ state: string; + + /** Zip code */ zip: string; + + /** Country */ country: string; }; }; +/** Record of expensify cards, indexed by cardID */ type CardList = Record; export default Card; diff --git a/src/types/onyx/Console.ts b/src/types/onyx/Console.ts index 592d23fecfa0..371782fe9156 100644 --- a/src/types/onyx/Console.ts +++ b/src/types/onyx/Console.ts @@ -1,11 +1,18 @@ import type CONST from '@src/CONST'; +/** Model of a log */ type Log = { + /** Log time */ time: Date; + + /** Log level */ level: keyof typeof CONST.DEBUG_CONSOLE.LEVELS; + + /** Log message */ message: string; }; +/** Record of logs */ type CapturedLogs = Record; export type {Log, CapturedLogs}; diff --git a/src/types/onyx/Credentials.ts b/src/types/onyx/Credentials.ts index 6a22eeb5af89..135cbae5fe76 100644 --- a/src/types/onyx/Credentials.ts +++ b/src/types/onyx/Credentials.ts @@ -1,3 +1,4 @@ +/** Model of user credentials */ type Credentials = { /** The email/phone the user logged in with */ login?: string; diff --git a/src/types/onyx/Currency.ts b/src/types/onyx/Currency.ts index b8d6f8dda88b..69b4203cdbf1 100644 --- a/src/types/onyx/Currency.ts +++ b/src/types/onyx/Currency.ts @@ -1,3 +1,4 @@ +/** Model of currency */ type Currency = { /** Symbol for the currency */ symbol: string; @@ -21,6 +22,7 @@ type Currency = { cacheBurst?: number; }; +/** Record of currencies, index by currency code */ type CurrencyList = Record; export default Currency; diff --git a/src/types/onyx/CustomStatusDraft.ts b/src/types/onyx/CustomStatusDraft.ts index 73c8fa4baa1a..d29061f5b5ed 100644 --- a/src/types/onyx/CustomStatusDraft.ts +++ b/src/types/onyx/CustomStatusDraft.ts @@ -1,3 +1,4 @@ +/** Model of custom status draft */ type CustomStatusDraft = { /** The emoji code of the draft status */ emojiCode?: string; diff --git a/src/types/onyx/DismissedReferralBanners.ts b/src/types/onyx/DismissedReferralBanners.ts index 86937d3bfbaf..92b3da647759 100644 --- a/src/types/onyx/DismissedReferralBanners.ts +++ b/src/types/onyx/DismissedReferralBanners.ts @@ -1,10 +1,20 @@ import type CONST from '@src/CONST'; +/** Model of dismissed referral banners */ type DismissedReferralBanners = { + /** Is 'Submit expense' referral dismissed */ [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SUBMIT_EXPENSE]?: boolean; + + /** Is 'Start chat' referral dismissed */ [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]?: boolean; + + /** Is 'Pay someone' referral dismissed */ [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.PAY_SOMEONE]?: boolean; + + /** Is 'Refer friend' referral dismissed */ [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]?: boolean; + + /** Is 'Share code' referral dismissed */ [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]?: boolean; }; diff --git a/src/types/onyx/Download.ts b/src/types/onyx/Download.ts index 9c6c2f61f716..de9e91064ed1 100644 --- a/src/types/onyx/Download.ts +++ b/src/types/onyx/Download.ts @@ -1,3 +1,4 @@ +/** Model of file download */ type Download = { /** If a file download is happening */ isDownloading: boolean; diff --git a/src/types/onyx/FrequentlyUsedEmoji.ts b/src/types/onyx/FrequentlyUsedEmoji.ts index c8f6a5179fc6..82de844c0c6f 100644 --- a/src/types/onyx/FrequentlyUsedEmoji.ts +++ b/src/types/onyx/FrequentlyUsedEmoji.ts @@ -1,3 +1,4 @@ +/** Model of frequently used emoji */ type FrequentlyUsedEmoji = { /** The emoji code */ code: string; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index a3c0a95849d1..3a527a45db2c 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -2,40 +2,82 @@ import type CONST from '@src/CONST'; import type {BankName} from './Bank'; import type * as OnyxCommon from './OnyxCommon'; +/** Mode of additional debit card account data */ type AdditionalData = { + // TODO: Not used in app explicitly isBillingCard?: boolean; + + /** Is Peer-To-Peer debit card */ isP2PDebitCard?: boolean; }; +/** Model of debit card account data */ type AccountData = { + /** Additional account data */ additionalData?: AdditionalData; + + /** Address name */ addressName?: string; + + /** Address state */ addressState?: string; + + /** Address street */ addressStreet?: string; + + /** Address zip code */ addressZip?: number; + + /** Debit card month */ cardMonth?: number; - /** The masked credit card number */ + /** The masked debit card number */ cardNumber?: string; + /** Debit card year */ cardYear?: number; + + /** Debit card creation date */ created?: string; + + /** Debit card currency */ currency?: string; + + /** Debit card ID number */ fundID?: number; + + /** Debit card bank name */ bank?: BankName; }; +/** Model of debit card fund */ type Fund = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** All data related to the debit card */ accountData?: AccountData; + + /** Debit card type */ accountType?: typeof CONST.PAYMENT_METHODS.DEBIT_CARD; + + /** Debit card description */ description?: string; + + /** String like 'fund-{}' where is the fundID */ key?: string; + + /** Alias for fundID */ methodID?: number; + + /** Debit card title */ title?: string; + + /** Is default debit card */ isDefault?: boolean; + + /** Debit card related error messages */ errors?: OnyxCommon.Errors; }>; +/** Record of debit card funds, indexed by fundID */ type FundList = Record; export default Fund; diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 726b94c5f6d3..29744dac5615 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -2,60 +2,151 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type {Icon} from './OnyxCommon'; +/** Model of IOU participant */ type Participant = { + /** IOU participant account ID */ accountID?: number; + + /** IOU participant login */ login?: string; + + /** IOU participant display name */ displayName?: string; + + /** Is IOU participant associated with policy expense chat */ isPolicyExpenseChat?: boolean; + + /** Is IOU participant associated with is own policy expense chat */ isOwnPolicyExpenseChat?: boolean; + + /** Type of chat associated with IOU participant */ chatType?: ValueOf; + + /** IOU participant report ID */ reportID?: string; + + /** IOU participant policy ID */ policyID?: string; + + /** Is IOU participant selected in list */ selected?: boolean; + + /** Text that IOU participant display name and login, if available, for searching purposes */ searchText?: string; + + /** Additional text shown in lists (participant phone number or display name) */ alternateText?: string; + + /** IOU participant first name */ firstName?: string; + + /** Icons used in lists (participant avatar) */ icons?: Icon[]; + + /** Key to be used in lists (participant account ID) */ keyForList?: string; + + /** IOU participant last name */ lastName?: string; + + /** IOU participant phone number */ phoneNumber?: string; + + /** Text to be displayed in lists (participant display name) */ text?: string; + + /** Is IOU participant selected in list */ isSelected?: boolean; + + /** Is IOU participant the current user */ isSelfDM?: boolean; isSender?: boolean; }; +/** Model of IOU split */ type Split = { + /** IOU split participant email */ email?: string; + + /** IOU split participant amount paid */ amount?: number; + + /** IOU split participant account ID */ accountID?: number; + + /** Chat report ID */ chatReportID?: string; + + /** IOU report ID */ iouReportID?: string; + + /** Report Action ID */ reportActionID?: string; + + /** Transaction ID */ transactionID?: string; + + /** Policy ID */ policyID?: string; + + /** Created chat report action ID */ createdChatReportActionID?: string; + + /** Created IOU report action ID */ createdIOUReportActionID?: string; + + /** Report preview report action ID */ reportPreviewReportActionID?: string; + + /** Transaction thread report ID */ transactionThreadReportID?: string; + + /** Created report action ID for thread */ createdReportActionIDForThread?: string; }; +/** Model of IOU request */ type IOU = { + /** IOU ID */ id: string; + + /** IOU amount */ amount?: number; + /** Selected Currency Code of the current IOU */ currency?: string; + + /** IOU comment */ comment?: string; + + /** IOU category */ category?: string; + + /** IOU merchant */ merchant?: string; + + /** IOU creation date */ created?: string; + + /** IOU receipt file path */ receiptPath?: string; + + /** IOU comment */ receiptFilename?: string; + + /** IOU transaction ID */ transactionID?: string; + + /** IOU participants */ participants?: Participant[]; + + /** IOU tag */ tag?: string; + + /** Is IOU billable */ billable?: boolean; + + /** Is an IOU split request */ isSplitRequest?: boolean; }; diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index 14a0d2f70dfe..6850f651ca2a 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -1,5 +1,6 @@ import type {OnboardingPurposeType} from '@src/CONST'; +/** Model of onboarding */ type IntroSelected = { /** The choice that the user selected in the engagement modal */ choice: OnboardingPurposeType; diff --git a/src/types/onyx/InvitedEmailsToAccountIDs.ts b/src/types/onyx/InvitedEmailsToAccountIDs.ts index 929d21746682..1f233e7c968c 100644 --- a/src/types/onyx/InvitedEmailsToAccountIDs.ts +++ b/src/types/onyx/InvitedEmailsToAccountIDs.ts @@ -1,3 +1,4 @@ +/** Record of workspace invited accountIDs, indexed by login name of inviter */ type InvitedEmailsToAccountIDs = Record; export default InvitedEmailsToAccountIDs; diff --git a/src/types/onyx/LastPaymentMethod.ts b/src/types/onyx/LastPaymentMethod.ts index 677a23fa9586..ea0c644fc730 100644 --- a/src/types/onyx/LastPaymentMethod.ts +++ b/src/types/onyx/LastPaymentMethod.ts @@ -1,3 +1,4 @@ +/** Record of last payment methods, indexed by policy id */ type LastPaymentMethod = Record; export default LastPaymentMethod; diff --git a/src/types/onyx/LastSelectedDistanceRates.ts b/src/types/onyx/LastSelectedDistanceRates.ts index 1db1cf32b160..3b09864db905 100644 --- a/src/types/onyx/LastSelectedDistanceRates.ts +++ b/src/types/onyx/LastSelectedDistanceRates.ts @@ -1,3 +1,4 @@ +/** Record of last selected distance rates, indexed by policy id */ type LastSelectedDistanceRates = Record; export default LastSelectedDistanceRates; diff --git a/src/types/onyx/Locale.ts b/src/types/onyx/Locale.ts index 89d7636009d7..37c340945bf1 100644 --- a/src/types/onyx/Locale.ts +++ b/src/types/onyx/Locale.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** Available locale values */ type Locale = ValueOf; export default Locale; diff --git a/src/types/onyx/Login.ts b/src/types/onyx/Login.ts index fec12da1b8e4..d89b7f2a29a3 100644 --- a/src/types/onyx/Login.ts +++ b/src/types/onyx/Login.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of user login data */ type Login = OnyxCommon.OnyxValueWithOfflineFeedback< { /** Phone/Email associated with user */ @@ -20,6 +21,7 @@ type Login = OnyxCommon.OnyxValueWithOfflineFeedback< 'defaultLogin' | 'validateLogin' | 'addedLogin' | 'deletedLogin' >; +/** Record of user login data, indexed by partnerUserID */ type LoginList = Record; export default Login; diff --git a/src/types/onyx/MapboxAccessToken.ts b/src/types/onyx/MapboxAccessToken.ts index bea23bcf86c4..9af8621d09b4 100644 --- a/src/types/onyx/MapboxAccessToken.ts +++ b/src/types/onyx/MapboxAccessToken.ts @@ -1,6 +1,12 @@ +/** Model of Mapbox access token data */ type MapboxAccessToken = { + /** Mapbox access token */ token: string; + + /** Mapbox access token expiration date */ expiration: string; + + /** Mapbox access error messages */ errors: string[]; }; diff --git a/src/types/onyx/Modal.ts b/src/types/onyx/Modal.ts index 1ea96cd283ce..b4b761cc8677 100644 --- a/src/types/onyx/Modal.ts +++ b/src/types/onyx/Modal.ts @@ -1,3 +1,4 @@ +/** Modal state */ type Modal = { /** Indicates when an Alert modal is about to be visible */ willAlertModalBecomeVisible?: boolean; @@ -8,6 +9,7 @@ type Modal = { /** Indicates if there is a modal currently visible or not */ isVisible?: boolean; + /** Indicates if the modal is a popover */ isPopover?: boolean; }; diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index cdfa7e02c8f6..680c6c468c00 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -1,5 +1,6 @@ import type {NetworkStatus} from '@libs/NetworkConnection'; +/** Model of network state */ type Network = { /** Is the network currently offline or not */ isOffline: boolean; diff --git a/src/types/onyx/NewGroupChatDraft.ts b/src/types/onyx/NewGroupChatDraft.ts index 008475212cbe..9e4ad3c54299 100644 --- a/src/types/onyx/NewGroupChatDraft.ts +++ b/src/types/onyx/NewGroupChatDraft.ts @@ -1,11 +1,21 @@ +/** Selected chat participant */ type SelectedParticipant = { + /** Participant ID */ accountID: number; + + /** Participant login name */ login: string; }; +/** Model of new group chat draft */ type NewGroupChatDraft = { + /** New group chat participants */ participants: SelectedParticipant[]; + + /** New group chat name */ reportName: string | null; + + /** New group chat avatar URI */ avatarUri: string | null; }; export type {SelectedParticipant}; diff --git a/src/types/onyx/Onboarding.ts b/src/types/onyx/Onboarding.ts index 3559dea90450..9860dd93f9ce 100644 --- a/src/types/onyx/Onboarding.ts +++ b/src/types/onyx/Onboarding.ts @@ -1,3 +1,4 @@ +/** Model of onboarding */ type Onboarding = { /** A Boolean that informs whether the user has completed the guided setup onboarding flow */ hasCompletedGuidedSetupFlow: boolean; diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index c4a3afc3e0b9..186801df6e30 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -3,10 +3,13 @@ import type {MaybePhraseKey} from '@libs/Localize'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; +/** Pending onyx actions */ type PendingAction = ValueOf | null; +/** Mapping of form fields with pending actions */ type PendingFields = {[key in Exclude]?: PendingAction}; +/** Offline properties that store information about data that was written while the app was offline */ type OfflineFeedback = { /** The type of action that's pending */ pendingAction?: PendingAction; @@ -15,14 +18,23 @@ type OfflineFeedback = { pendingFields?: PendingFields; }; +/** Onyx data with offline properties that store information about data that was written while the app was offline */ type OnyxValueWithOfflineFeedback = keyof TOnyx extends string ? TOnyx & OfflineFeedback : never; +/** Mapping of form fields with errors */ type ErrorFields = Record; +/** Mapping of form fields with error translation keys and variables */ type Errors = Record; +/** + * Types of avatars + ** avatar - user avatar + ** workspace - workspace avatar + */ type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; +/** Icon properties */ type Icon = { /** Avatar source to display */ source?: AvatarSource; diff --git a/src/types/onyx/OnyxUpdatesFromServer.ts b/src/types/onyx/OnyxUpdatesFromServer.ts index 0877ea6755f8..525fb34e59cd 100644 --- a/src/types/onyx/OnyxUpdatesFromServer.ts +++ b/src/types/onyx/OnyxUpdatesFromServer.ts @@ -3,22 +3,42 @@ import CONST from '@src/CONST'; import type Request from './Request'; import type Response from './Response'; +/** Model of a onyx server update */ type OnyxServerUpdate = OnyxUpdate & { + /** Whether the update should notify UI */ shouldNotify?: boolean; + + /** Whether the update should be shown as a push notification */ shouldShowPushNotification?: boolean; }; +/** Model of a onyx update event */ type OnyxUpdateEvent = { + /** Type of the update event received from the server */ eventType: string; + + /** Collections of data updates */ data: OnyxServerUpdate[]; }; +/** Model of onyx server updates */ type OnyxUpdatesFromServer = { + /** Delivery method of onyx updates */ type: 'https' | 'pusher' | 'airship'; + + /** Last update ID from server */ lastUpdateID: number | string; + + /** Previous update ID from server */ previousUpdateID: number | string; + + /** Request data sent to the server */ request?: Request; + + /** Response data from server */ response?: Response; + + /** Collection of onyx updates */ updates?: OnyxUpdateEvent[]; }; diff --git a/src/types/onyx/PaymentMethod.ts b/src/types/onyx/PaymentMethod.ts index 4b3a4c8986fb..b95f890939eb 100644 --- a/src/types/onyx/PaymentMethod.ts +++ b/src/types/onyx/PaymentMethod.ts @@ -3,12 +3,24 @@ import type IconAsset from '@src/types/utils/IconAsset'; import type BankAccount from './BankAccount'; import type Fund from './Fund'; +/** Model of a payment method */ type PaymentMethod = (BankAccount | Fund) & { + /** Text shown under menu item title */ description: string; + + /** Source of the menu item icon, which can be a component or an image asset */ icon: IconAsset; + + /** Size of the menu item icon */ iconSize?: number; + + /** Height of the menu item icon */ iconHeight?: number; + + /** Width of the menu item icon */ iconWidth?: number; + + /** Icon wrapper styles */ iconStyles?: ViewStyle[]; }; diff --git a/src/types/onyx/PersonalBankAccount.ts b/src/types/onyx/PersonalBankAccount.ts index 3e52a3cf59f3..b36e7754ca23 100644 --- a/src/types/onyx/PersonalBankAccount.ts +++ b/src/types/onyx/PersonalBankAccount.ts @@ -1,6 +1,7 @@ import type {Route} from '@src/ROUTES'; import type * as OnyxCommon from './OnyxCommon'; +/** Model of personal bank account */ type PersonalBankAccount = { /** An error message to display to the user */ errors?: OnyxCommon.Errors; diff --git a/src/types/onyx/PersonalDetails.ts b/src/types/onyx/PersonalDetails.ts index b7c96998080c..939835028392 100644 --- a/src/types/onyx/PersonalDetails.ts +++ b/src/types/onyx/PersonalDetails.ts @@ -2,8 +2,10 @@ import type {AvatarSource} from '@libs/UserUtils'; import type TIMEZONES from '@src/TIMEZONES'; import type * as OnyxCommon from './OnyxCommon'; +/** Selectable timezones */ type SelectedTimezone = (typeof TIMEZONES)[number]; +/** Model of timezone */ type Timezone = { /** Value of selected timezone */ selected?: SelectedTimezone; @@ -12,6 +14,7 @@ type Timezone = { automatic?: boolean; }; +/** Model of user status */ type Status = { /** The emoji code of the status */ emojiCode: string; @@ -23,6 +26,7 @@ type Status = { clearAfter: string; // ISO 8601 format; }; +/** Model of user personal details */ type PersonalDetails = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** ID of the current user from their personal details */ accountID: number; @@ -48,6 +52,7 @@ type PersonalDetails = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Avatar thumbnail URL of the current user from their personal details */ avatarThumbnail?: string; + /** Avatar original file name with extension */ originalFileName?: string; /** Flag to set when Avatar uploading */ @@ -78,11 +83,13 @@ type PersonalDetails = OnyxCommon.OnyxValueWithOfflineFeedback<{ status?: Status; }>; +/** Model of personal details metadata */ type PersonalDetailsMetadata = { /** Whether we are waiting for the data to load via the API */ isLoading?: boolean; }; +/** Record of user personal details, indexed by user id */ type PersonalDetailsList = Record; export default PersonalDetails; diff --git a/src/types/onyx/PlaidBankAccount.ts b/src/types/onyx/PlaidBankAccount.ts index 7620c4aee367..6e59fc015ca4 100644 --- a/src/types/onyx/PlaidBankAccount.ts +++ b/src/types/onyx/PlaidBankAccount.ts @@ -1,3 +1,4 @@ +/** Model of plaid bank account data */ type PlaidBankAccount = { /** Masked account number */ accountNumber: string; diff --git a/src/types/onyx/PlaidData.ts b/src/types/onyx/PlaidData.ts index 8ec93119cbd8..8f8e16324176 100644 --- a/src/types/onyx/PlaidData.ts +++ b/src/types/onyx/PlaidData.ts @@ -1,6 +1,7 @@ import type * as OnyxCommon from './OnyxCommon'; import type PlaidBankAccount from './PlaidBankAccount'; +/** Model of plaid data */ type PlaidData = { /** Name of the bank */ bankName?: string; @@ -14,7 +15,10 @@ type PlaidData = { /** List of plaid bank accounts */ bankAccounts?: PlaidBankAccount[]; + /** Whether the data is being fetched from server */ isLoading?: boolean; + + /** Error messages to show in UI */ errors: OnyxCommon.Errors; }; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 3a322405c6e1..c0f74747d5b0 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -5,48 +5,96 @@ import type * as OnyxTypes from '.'; import type * as OnyxCommon from './OnyxCommon'; import type {WorkspaceTravelSettings} from './TravelSettings'; +/** Distance units */ type Unit = 'mi' | 'km'; +/** Model of policy distance rate */ type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Name of the distance rate */ name?: string; + + /** Amount to be reimbursed per distance unit travelled */ rate?: number; + + /** Currency used to pay the distance rate */ currency?: string; + + /** Generated ID to identify the distance rate */ customUnitRateID?: string; + + /** Whether this distance rate is currently enabled */ enabled?: boolean; + + /** Error messages to show in UI */ errors?: OnyxCommon.Errors; + + /** Form fields that triggered the errors */ errorFields?: OnyxCommon.ErrorFields; }>; +/** Custom unit attributes */ type Attributes = { + /** Distance unit name */ unit: Unit; }; +/** Policy custom unit */ type CustomUnit = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Custom unit name */ name: string; + + /** ID that identifies this custom unit */ customUnitID: string; + + /** Contains custom attributes like unit, for this custom unit */ attributes: Attributes; + + /** Distance rates using this custom unit */ rates: Record; + + /** The default category in which this custom unit is used */ defaultCategory?: string; + + /** Whether this custom unit is enabled */ enabled?: boolean; + + /** Error messages to show in UI */ errors?: OnyxCommon.Errors; + + /** Form fields that triggered errors */ errorFields?: OnyxCommon.ErrorFields; }>; +/** Policy company address data */ type CompanyAddress = { + /** Street address */ addressStreet: string; + + /** City */ city: string; + + /** State */ state: string; + + /** Zip post code */ zipCode: string; + + /** Country code */ country: Country | ''; }; +/** Policy disabled fields */ type DisabledFields = { + /** Whether the default billable field is disabled */ defaultBillable?: boolean; + + /** Whether the reimbursable field is disabled */ reimbursable?: boolean; }; +/** Policy tax rate */ type TaxRate = OnyxCommon.OnyxValueWithOfflineFeedback<{ - /** Name of the a tax rate. */ + /** Name of the tax rate. */ name: string; /** The value of the tax rate. */ @@ -68,8 +116,10 @@ type TaxRate = OnyxCommon.OnyxValueWithOfflineFeedback<{ errorFields?: OnyxCommon.ErrorFields; }>; +/** Record of policy tax rates, indexed by id_{taxRateName} where taxRateName is the name of the tax rate in UPPER_SNAKE_CASE */ type TaxRates = Record; +/** Policy tax rates with default tax rate */ type TaxRatesWithDefault = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Name of the tax */ name: string; @@ -93,40 +143,85 @@ type TaxRatesWithDefault = OnyxCommon.OnyxValueWithOfflineFeedback<{ errorFields?: OnyxCommon.ErrorFields; }>; +/** Connection last synchronization state */ type ConnectionLastSync = { + /** Date when the connection's last successful sync occurred */ successfulDate?: string; + + /** Date when the connection's last failed sync occurred */ errorDate?: string; + + /** Whether the connection's last sync was successful */ isSuccessful: boolean; + + /** Where did the connection's last sync came from */ source: 'DIRECT' | 'EXPENSIFYWEB' | 'EXPENSIFYAPI' | 'AUTOSYNC' | 'AUTOAPPROVE'; }; +/** Financial account (bank account, debit card, etc) */ type Account = { + /** GL code assigned to the financial account */ glCode?: string; + + /** Name of the financial account */ name: string; + + /** Currency of the financial account */ currency: string; + + /** ID assigned to the financial account */ id: string; }; +/** Model of QuickBooks Online employee data */ type Employee = { + /** ID assigned to the employee */ id: string; + + /** Employee's first name */ firstName?: string; + + /** Employee's last name */ lastName?: string; + + /** Employee's display name */ name: string; + + /** Employee's e-mail */ email: string; }; +/** Model of QuickBooks Online vendor data */ type Vendor = { + /** ID assigned to the vendor */ id: string; + + /** Vendor's name */ name: string; + + /** Vendor's currency */ currency: string; + + /** Vendor's e-mail */ email: string; }; +/** Model of QuickBooks Online tax code data */ type TaxCode = { + /** TODO: Not used in app */ totalTaxRateVal: string; + + /** TODO: Not used in app */ simpleName: string; + + /** TODO: Not used in app */ taxCodeRef: string; + + /** TODO: Not used in app */ taxRateRefs: Record; + + /** TODO: Not used in app */ + /** Name of the tax code */ name: string; }; @@ -134,86 +229,190 @@ type TaxCode = { * Data imported from QuickBooks Online. */ type QBOConnectionData = { + /** TODO: I think this value can be changed to `ValueOf` */ + /** Country code */ country: string; + + /** TODO: Doesn't exist in the app */ edition: string; + + /** TODO: Doesn't exist in the app */ homeCurrency: string; + + /** TODO: Doesn't exist in the app */ isMultiCurrencyEnabled: boolean; + /** Collection of journal entry accounts */ journalEntryAccounts: Account[]; + + /** Collection of bank accounts */ bankAccounts: Account[]; + + /** Collection of credit cards */ creditCards: Account[]; + + /** Collection of export destination accounts */ accountsReceivable: Account[]; + + /** TODO: Not enough context */ accountPayable: Account[]; + + /** TODO: Not enough context */ otherCurrentAssetAccounts: Account[]; + /** TODO: Doesn't exist in the app */ taxCodes: TaxCode[]; + + /** TODO: Doesn't exist in the app */ employees: Employee[]; + + /** Collections of vendors */ vendors: Vendor[]; }; +/** Sync entity names */ type IntegrationEntityMap = (typeof CONST.INTEGRATION_ENTITY_MAP_TYPES)[keyof typeof CONST.INTEGRATION_ENTITY_MAP_TYPES]; +/** Non reimbursable account types exported from QuickBooks Online */ type QBONonReimbursableExportAccountType = (typeof CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE)[keyof typeof CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE]; + +/** Reimbursable account types exported from QuickBooks Online */ type QBOReimbursableExportAccountType = (typeof CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE)[keyof typeof CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE]; /** * User configuration for the QuickBooks Online accounting integration. */ type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** TODO: Doesn't exist in the app */ + /** ID of the QuickBooks Online realm */ realmId: string; + + /** TODO: Doesn't exist in the app */ + /** Company name */ companyName: string; + + /** Configuration of automatic synchronization from QuickBooks Online to the app */ autoSync: { + /** TODO: Doesn't exist in the app */ jobID: string; + + /** Whether changes made in QuickBooks Online should be reflected into the app automatically */ enabled: boolean; }; + + /** Whether employees can be invited */ syncPeople: boolean; + + /** TODO: Doesn't exist in the app */ syncItems: boolean; + + /** TODO: Doesn't exist in the app */ markChecksToBePrinted: boolean; + reimbursableExpensesExportDestination: QBOReimbursableExportAccountType; + nonReimbursableExpensesExportDestination: QBONonReimbursableExportAccountType; + nonReimbursableBillDefaultVendor: string; + collectionAccountID?: string; + reimbursementAccountID?: string; + reimbursableExpensesAccount?: Account; + nonReimbursableExpensesAccount?: Account; + + /** Account that receives the exported invoices */ receivableAccount?: Account; + + /** + * Whether a default vendor will be created and applied to all credit card + * transactions upon import + */ autoCreateVendor: boolean; + + /** TODO: Doesn't exist in the app */ hasChosenAutoSyncOption: boolean; + + /** Whether Quickbooks Online classes should be imported */ syncClasses: IntegrationEntityMap; + + /** Whether Quickbooks Online customers should be imported */ syncCustomers: IntegrationEntityMap; + + /** Whether Quickbooks Online locations should be imported */ syncLocations: IntegrationEntityMap; + + /** TODO: Doesn't exist in the app */ lastConfigurationTime: number; + + /** Whether the taxes should be synchronized */ syncTax: boolean; + + /** Whether new categories are enabled in chart of accounts */ enableNewCategories: boolean; + + /** TODO: Doesn't exist in the app */ errors?: OnyxCommon.Errors; + + /** TODO: Doesn't exist in the app */ exportDate: ValueOf; + + /** Configuration of the export */ export: { + /** E-mail of the exporter */ exporter: string; }; + + /** Collections of form field errors */ errorFields?: OnyxCommon.ErrorFields; }>; +/** Xero bill status values */ type BillStatusValues = 'DRAFT' | 'AWT_APPROVAL' | 'AWT_PAYMENT'; +/** Xero expense status values */ type ExpenseTypesValues = 'BILL' | 'BANK_TRANSACTION' | 'SALES_INVOICE' | 'NOTHING'; +/** Xero bill date values */ type BillDateValues = 'REPORT_SUBMITTED' | 'REPORT_EXPORTED' | 'LAST_EXPENSE'; +/** Model of an organization in Xero */ type Tenant = { + /** ID of the organization */ id: string; + + /** Name of the organization */ name: string; + + /** TODO: Doesn't exist in the app */ value: string; }; +/** + * Data imported from Xero + */ type XeroConnectionData = { + /** Collection of bank accounts */ bankAccounts: Account[]; + + /** TODO: Doesn't exist in the app */ countryCode: string; + + /** TODO: Doesn't exist in the app */ organisationID: string; + + /** TODO: Doesn't exist in the app */ revenueAccounts: Array<{ id: string; name: string; }>; + + /** Collection of organizations */ tenants: Tenant[]; + + /** TODO: Doesn't exist in the app */ trackingCategories: unknown[]; }; @@ -221,68 +420,143 @@ type XeroConnectionData = { * User configuration for the Xero accounting integration. */ type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Xero auto synchronization configs */ autoSync: { + /** Whether data should be automatically synched between the app and Xero */ enabled: boolean; + + /** TODO: Doesn't exist in the app */ jobID: string; }; + + /** TODO: Doesn't exist in the app */ enableNewCategories: boolean; + + /** Xero export configs */ export: { + /** Current bill status */ billDate: BillDateValues; + billStatus: { + /** Current status of the purchase bill */ purchase: BillStatusValues; + + /** Current status of the sales bill */ sales: BillStatusValues; }; + + /** TODO: Doesn't exist in the app */ billable: ExpenseTypesValues; + + /** The e-mail of the exporter */ exporter: string; + + /** TODO: Doesn't exist in the app */ nonReimbursable: ExpenseTypesValues; + + /** TODO: Doesn't exist in the app */ nonReimbursableAccount: string; + + /** TODO: Doesn't exist in the app */ reimbursable: ExpenseTypesValues; }; + + /** Whether customers should be imported from Xero */ importCustomers: boolean; + + /** Whether tax rates should be imported from Xero */ importTaxRates: boolean; + + /** Whether tracking categories should be imported from Xero */ importTrackingCategories: boolean; + + /** TODO: Doesn't exist in the app */ isConfigured: boolean; + + /** TODO: Doesn't exist in the app */ mappings: { customer: string; }; sync: { + /** TODO: Doesn't exist in the app */ hasChosenAutoSyncOption: boolean; + + /** TODO: Doesn't exist in the app */ hasChosenSyncReimbursedReportsOption: boolean; + + /** ID of the bank account for Xero invoice collections */ invoiceCollectionsAccountID: string; + + /** TODO: Doesn't exist in the app */ reimbursementAccountID: string; + + /** TODO: Doesn't exist in the app */ syncReimbursedReports: boolean; }; + + /** ID of Xero organization */ tenantID: string; + + /** TODO: Doesn't exist in the app */ errors?: OnyxCommon.Errors; + + /** Collection of form field errors */ errorFields?: OnyxCommon.ErrorFields; }>; +/** State of integration connection */ type Connection = { + /** TODO: Doesn't exist in the app */ + /** State of the last synchronization */ lastSync?: ConnectionLastSync; + + /** Data imported from integration */ data: ConnectionData; + + /** Configuration of the connection */ config: ConnectionConfig; }; +/** Available integration connections */ type Connections = { + /** QuickBooks integration connection */ quickbooksOnline: Connection; + + /** Xero integration connection */ xero: Connection; }; +/** Names of integration connections */ type ConnectionName = keyof Connections; +/** Model of verified reimbursement bank account linked to policy */ type ACHAccount = { + /** ID of the bank account */ bankAccountID: number; + + /** Bank account number */ accountNumber: string; + + /** Routing number of bank account */ routingNumber: string; + + /** Address name of the bank account */ addressName: string; + + /** Name of the bank */ bankName: string; + + /** E-mail of the reimburser */ reimburser: string; }; +/** Day of the month to schedule submission */ type AutoReportingOffset = number | ValueOf; +/** Types of policy report fields */ type PolicyReportFieldType = 'text' | 'date' | 'dropdown' | 'formula'; +/** Model of policy report field */ type PolicyReportField = { /** Name of the field */ name: string; @@ -308,6 +582,7 @@ type PolicyReportField = { /** Options to select from if field is of type dropdown */ values: string[]; + /** TODO: Doesn't seem to be used in app */ target: string; /** Tax UDFs have keys holding the names of taxes (eg, VAT), values holding percentages (eg, 15%) and a value indicating the currently selected tax value (eg, 15%). */ @@ -316,6 +591,7 @@ type PolicyReportField = { /** list of externalIDs, this are either imported from the integrations or auto generated by us, each externalID */ externalIDs: string[]; + /** Collection of flags that state whether droplist field options are disabled */ disabledOptions: boolean[]; /** Is this a tax user defined report field */ @@ -331,22 +607,37 @@ type PolicyReportField = { defaultExternalID?: string | null; }; +/** Names of policy features */ type PolicyFeatureName = ValueOf; +/** Current user policy join request state */ type PendingJoinRequestPolicy = { + /** Whether the current user requested to join the policy */ isJoinRequestPending: boolean; + + /** Record of public policy details, indexed by policy ID */ policyDetailsForNonMembers: Record< string, OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Name of the policy */ name: string; + + /** Policy owner account ID */ ownerAccountID: number; + + /** Policy owner e-mail */ ownerEmail: string; + + /** Policy type */ type: ValueOf; + + /** Policy avatar */ avatar?: string; }> >; }; +/** Model of policy data */ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< { /** The ID of the policy */ @@ -528,10 +819,18 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< 'generalSettings' | 'addWorkspaceRoom' | keyof ACHAccount >; +/** Stages of policy connection sync */ type PolicyConnectionSyncStage = ValueOf; + +/** Names of policy connection services */ type PolicyConnectionName = ValueOf; + +/** Policy connection sync progress state */ type PolicyConnectionSyncProgress = { + /** Current sync stage */ stageInProgress: PolicyConnectionSyncStage; + + /** Name of the connected service */ connectionName: PolicyConnectionName; }; diff --git a/src/types/onyx/PolicyCategory.ts b/src/types/onyx/PolicyCategory.ts index b42bceec0468..c0e80de364d3 100644 --- a/src/types/onyx/PolicyCategory.ts +++ b/src/types/onyx/PolicyCategory.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of policy category */ type PolicyCategory = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Name of a category */ name: string; @@ -27,6 +28,7 @@ type PolicyCategory = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors | null; }>; +/** Record of policy categories, indexed by their name */ type PolicyCategories = Record; export type {PolicyCategory, PolicyCategories}; diff --git a/src/types/onyx/PolicyEmployee.ts b/src/types/onyx/PolicyEmployee.ts index 4a5f374de44a..741e1e01ec05 100644 --- a/src/types/onyx/PolicyEmployee.ts +++ b/src/types/onyx/PolicyEmployee.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of policy employee */ type PolicyEmployee = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Role of the user in the policy */ role?: string; @@ -20,6 +21,7 @@ type PolicyEmployee = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors; }>; +/** Record of policy employees, indexed by their email */ type PolicyEmployeeList = Record; export default PolicyEmployee; diff --git a/src/types/onyx/PolicyJoinMember.ts b/src/types/onyx/PolicyJoinMember.ts index 7c540b334b4a..bb7b346e6ded 100644 --- a/src/types/onyx/PolicyJoinMember.ts +++ b/src/types/onyx/PolicyJoinMember.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of policy join member */ type PolicyJoinMember = { /** The ID of the policy */ policyID?: string; diff --git a/src/types/onyx/PolicyOwnershipChangeChecks.ts b/src/types/onyx/PolicyOwnershipChangeChecks.ts index 8033cffdee3c..9950122b069a 100644 --- a/src/types/onyx/PolicyOwnershipChangeChecks.ts +++ b/src/types/onyx/PolicyOwnershipChangeChecks.ts @@ -1,7 +1,15 @@ +/** Model of policy ownership change checks */ type PolicyOwnershipChangeChecks = { + /** Whether the outstanding balance should be cleared after changing workspace owner */ shouldClearOutstandingBalance?: boolean; + + /** Whether the amount owed should be transferred after changing workspace owner */ shouldTransferAmountOwed?: boolean; + + /** Whether the subscription should be transferred after changing workspace owner */ shouldTransferSubscription?: boolean; + + /** Whether the single subscription should be transferred after changing workspace owner */ shouldTransferSingleSubscription?: boolean; }; diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 37e979fb58f6..a9d5377e1432 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of policy tag */ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Name of a Tag */ name: string; @@ -15,8 +16,10 @@ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors | null; }>; +/** Record of policy tags, indexed by their name */ type PolicyTags = Record; +/** Record of policy tag lists, index by the name of the tag list */ type PolicyTagList = Record< T, OnyxCommon.OnyxValueWithOfflineFeedback<{ diff --git a/src/types/onyx/PreferredTheme.ts b/src/types/onyx/PreferredTheme.ts index 408748ad06ea..98c001df018c 100644 --- a/src/types/onyx/PreferredTheme.ts +++ b/src/types/onyx/PreferredTheme.ts @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** Possible user preferred themes to be used in the whole app */ type PreferredTheme = OnyxEntry>; export default PreferredTheme; diff --git a/src/types/onyx/PriorityMode.ts b/src/types/onyx/PriorityMode.ts index 224c86867f35..404c678945ad 100644 --- a/src/types/onyx/PriorityMode.ts +++ b/src/types/onyx/PriorityMode.ts @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** Modes that define how the user's chats are displayed in his chat list */ type PriorityMode = OnyxEntry>; export default PriorityMode; diff --git a/src/types/onyx/PrivatePersonalDetails.ts b/src/types/onyx/PrivatePersonalDetails.ts index 3099cb0095e9..1f81108bf538 100644 --- a/src/types/onyx/PrivatePersonalDetails.ts +++ b/src/types/onyx/PrivatePersonalDetails.ts @@ -1,25 +1,59 @@ import type {Country} from '@src/CONST'; +/** User address data */ type Address = { + /** Street line 1 */ street: string; + + /** Street line 2 */ street2?: string; + + /** City */ city?: string; + + /** State */ state?: string; + + /** Zip post code */ zip?: string; + + /** Country code */ country?: Country | ''; + + /** Zip post code */ zipPostCode?: string; + + /** Street line 1 */ addressLine1?: string; + + /** Street line 2 */ addressLine2?: string; + + /** Latitude */ lat?: string; + + /** Longitude */ lng?: string; + + /** Zip post code */ zipCode?: string; + + /** Google place description */ address?: string; }; +/** Model of user private personal details */ type PrivatePersonalDetails = { + /** User's legal first name */ legalFirstName?: string; + + /** User's legal last name */ legalLastName?: string; + + /** User's date of birth */ dob?: string; + + /** User's phone number */ phoneNumber?: string; /** User's home address */ diff --git a/src/types/onyx/QuickAction.ts b/src/types/onyx/QuickAction.ts index 325893637911..c10ec8356546 100644 --- a/src/types/onyx/QuickAction.ts +++ b/src/types/onyx/QuickAction.ts @@ -1,8 +1,10 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** Names of quick actions that the user can execute */ type QuickActionName = ValueOf; +/** Model of user quick action */ type QuickAction = { /** The action to take */ action?: QuickActionName; diff --git a/src/types/onyx/RecentWaypoint.ts b/src/types/onyx/RecentWaypoint.ts index 55232f7ef71d..8653366898cb 100644 --- a/src/types/onyx/RecentWaypoint.ts +++ b/src/types/onyx/RecentWaypoint.ts @@ -1,3 +1,4 @@ +/** Model of recent endpoint used in distance expense */ type RecentWaypoint = { /** The name associated with the address of the waypoint */ name?: string; diff --git a/src/types/onyx/RecentlyUsedCategories.ts b/src/types/onyx/RecentlyUsedCategories.ts index d251b16f8667..c3d7c187c710 100644 --- a/src/types/onyx/RecentlyUsedCategories.ts +++ b/src/types/onyx/RecentlyUsedCategories.ts @@ -1,3 +1,4 @@ +/** Workspace categories that have been recently used by members */ type RecentlyUsedCategories = string[]; export default RecentlyUsedCategories; diff --git a/src/types/onyx/RecentlyUsedReportFields.ts b/src/types/onyx/RecentlyUsedReportFields.ts index 2b3d046c8316..95af207eaa31 100644 --- a/src/types/onyx/RecentlyUsedReportFields.ts +++ b/src/types/onyx/RecentlyUsedReportFields.ts @@ -1,3 +1,4 @@ +/** Record of policy recently used report fields, indexed by expensify_${reportFieldId} */ type RecentlyUsedReportFields = Record; export default RecentlyUsedReportFields; diff --git a/src/types/onyx/RecentlyUsedTags.ts b/src/types/onyx/RecentlyUsedTags.ts index 1d6112514609..650a0554ef37 100644 --- a/src/types/onyx/RecentlyUsedTags.ts +++ b/src/types/onyx/RecentlyUsedTags.ts @@ -1,3 +1,4 @@ +/** Record of policy recently used tags, indexed by nvp_recentlyUsedTags_{policyID} */ type RecentlyUsedTags = Record; export default RecentlyUsedTags; diff --git a/src/types/onyx/ReimbursementAccount.ts b/src/types/onyx/ReimbursementAccount.ts index f2565b29eaf3..2322f6faab17 100644 --- a/src/types/onyx/ReimbursementAccount.ts +++ b/src/types/onyx/ReimbursementAccount.ts @@ -4,10 +4,13 @@ import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, import type {BankName} from './Bank'; import type * as OnyxCommon from './OnyxCommon'; +/** Steps to setup a reimbursement bank account */ type BankAccountStep = ValueOf; +/** Substeps to setup a reimbursement bank account */ type BankAccountSubStep = ValueOf; +/** Model of ACH data */ type ACHData = Partial & { /** Step of the setup flow that we are on. Determines which view is presented. */ currentStep?: BankAccountStep; @@ -46,6 +49,7 @@ type ACHData = Partial; +/** Defines who's able to write messages in the chat */ type WriteCapability = ValueOf; +/** Defines which users have access to the chat */ type RoomVisibility = ValueOf; +/** Model of report private note */ type Note = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Content of the note */ note: string; + + /** Collection of errors to show to the user */ errors?: OnyxCommon.Errors; }>; /** The pending member of report */ type PendingChatMember = { + /** Account ID of the pending member */ accountID: string; + + /** Action to be applied to the pending member of report */ pendingAction: OnyxCommon.PendingAction; }; +/** Report participant properties */ type Participant = OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Whether the participant is visible in the report */ hidden?: boolean; + + /** What is the role of the participant in the report */ role?: 'admin' | 'member'; }>; +/** Types of invoice receivers in a report */ type InvoiceReceiver = | { + /** An individual */ type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; + + /** Account ID of the user */ accountID: number; } | { + /** A business */ type: typeof CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS; + + /** ID of the policy */ policyID: string; }; +/** Record of report participants, indexed by their accountID */ type Participants = Record; +/** Model of report data */ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< { /** The URL of the Group Chat report custom avatar */ @@ -141,29 +164,70 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Invoice room receiver data */ invoiceReceiver?: InvoiceReceiver; + /** Translation key of the last message in the report */ lastMessageTranslationKey?: string; + + /** ID of the parent report of the current report, if it exists */ parentReportID?: string; + + /** ID of the parent report action of the current report, if it exists */ parentReportActionID?: string; + + /** Whether the current report is optimistic */ isOptimisticReport?: boolean; + + /** Account ID of the report manager */ managerID?: number; + + /** When was the last visible action last modified */ lastVisibleActionLastModified?: string; + + /** Display name of the report, shown in options and mentions */ displayName?: string; + + /** HTML content of the last message in the report */ lastMessageHtml?: string; + + /** Account ID of the user that sent the last message */ lastActorAccountID?: number; - // The type of the last action + /** The type of the last action */ lastActionType?: ValueOf; + + /** Account ID of the report owner */ ownerAccountID?: number; + + /** E-mail of the report owner */ ownerEmail?: string; + + /** Collection of report participants, indexed by their accountID */ participants?: Participants; + + /** Collection of report participants account IDs */ participantAccountIDs?: number[]; + + /** Collection of visible chat members account IDs */ visibleChatMemberAccountIDs?: number[]; + + /** For expense reports, this is the total amount approved */ total?: number; + + /** For expense reports, this is the total amount requested */ unheldTotal?: number; + + /** For expense reports, this is the currency of the expense */ currency?: string; + + /** Collection of errors to be shown to the user */ errors?: OnyxCommon.Errors; + + /** TODO: Doesn't exist in the app */ managerEmail?: string; + + /** TODO: Doesn't exist in the app */ parentReportActionIDs?: number[]; + + /** Collection of errors that exist in report fields */ errorFields?: OnyxCommon.ErrorFields; /** Whether the report is waiting on a bank account */ @@ -186,13 +250,29 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** If the report contains nonreimbursable expenses, send the nonreimbursable total */ nonReimbursableTotal?: number; + + /** Whether the report is hidden from options list */ isHidden?: boolean; + + /** Whether the report is a chat room */ isChatRoom?: boolean; + + /** Collection of participants personal details */ participantsList?: PersonalDetails[]; + + /** Text to be displayed in options list, which matches reportName by default */ text?: string; + + /** TODO: Doesn't exist in the app */ updateReportInLHN?: boolean; + + /** Collection of participant private notes, indexed by their accountID */ privateNotes?: Record; + + /** Whether participants private notes are being currently loaded */ isLoadingPrivateNotes?: boolean; + + /** Whether the report is currently selected in the options list */ selected?: boolean; /** Pending members of the report */ @@ -201,13 +281,16 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** The ID of the single transaction thread report associated with this report, if one exists */ transactionThreadReportID?: string; + /** Collection of policy report fields, indexed by their fieldID */ fieldList?: Record; + /** Collection of report permissions granted to the current user */ permissions?: Array>; }, PolicyReportField['fieldID'] >; +/** Collection of reports, indexed by report_{reportID} */ type ReportCollectionDataSet = CollectionDataSet; export default Report; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 8c4b3435771c..b8783cfe2d9f 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -11,6 +11,7 @@ import type OriginalMessage from './OriginalMessage'; import type {NotificationPreference} from './Report'; import type {Receipt} from './Transaction'; +/** Model of report action message */ type Message = { /** The type of the action item fragment. Used to render a corresponding component */ type: string; @@ -49,10 +50,17 @@ type Message = { /** Whether the pending transaction was reversed and didn't post to the card */ isReversedTransaction?: boolean; + + /** TODO: Only used in tests */ whisperedTo?: number[]; + + /** TODO: Only used in tests */ reactions?: Reaction[]; + /** In situations where moderation is required, this is the moderator decision data */ moderationDecision?: Decision; + + /** Key to translate the message */ translationKey?: string; /** ID of a task report */ @@ -77,6 +85,7 @@ type Message = { deleted?: string; }; +/** Model of image */ type ImageMetadata = { /** The height of the image. */ height?: number; @@ -91,6 +100,7 @@ type ImageMetadata = { type?: string; }; +/** Model of link */ type LinkMetadata = { /** The URL of the link. */ url?: string; @@ -111,12 +121,19 @@ type LinkMetadata = { logo?: ImageMetadata; }; +/** Model of report action person */ type Person = { + /** Type of the message to display */ type?: string; + + /** Style applied to the message */ style?: string; + + /** Content of the message to display which corresponds to the user display name */ text?: string; }; +/** Main properties of report action */ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The ID of the reportAction. It is the string representation of the a 64-bit integer. */ reportActionID: string; @@ -127,6 +144,7 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The ID of the previous reportAction on the report. It is a string represenation of a 64-bit integer (or null for CREATED actions). */ previousReportActionID?: string; + /** Account ID of the actor that created the action */ actorAccountID?: number; /** The account of the last message's actor */ @@ -150,10 +168,13 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** accountIDs of the people to which the whisper was sent to (if any). Returns empty array if it is not a whisper */ whisperedToAccountIDs?: number[]; + /** Avatar data to display on the report action */ avatar?: AvatarSource; + /** TODO: not enough context, seems to be used in tests only */ automatic?: boolean; + /** TODO: Not enough context, seems to be used in tests only */ shouldShow?: boolean; /** The ID of childReport */ @@ -168,12 +189,25 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The user's ID */ accountID?: number; + /** TODO: Doesn't exist in the app */ childOldestFourEmails?: string; + + /** Account IDs of the oldest four participants, useful to determine which avatars to display in threads */ childOldestFourAccountIDs?: string; + + /** TODO: Not enough context, but I think this represents how many participants are in the thread */ childCommenterCount?: number; + + /** Timestamp of the most recent reply */ childLastVisibleActionCreated?: string; + + /** Number of thread replies */ childVisibleActionCount?: number; + + /** Report ID of the parent report, if there's one */ parentReportID?: string; + + /** In task reports this is account ID of the user assigned to the task */ childManagerAccountID?: number; /** The status of the child report */ @@ -181,12 +215,26 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Report action child status name */ childStateNum?: ValueOf; + + /** TODO: Doesn't exist in the app */ childLastReceiptTransactionIDs?: string; + + /** Content of the last money request comment, used in report preview */ childLastMoneyRequestComment?: string; + + /** Account ID of the last actor */ childLastActorAccountID?: number; + + /** TODO: Only used in tests */ timestamp?: number; + + /** TODO: Only used in tests */ reportActionTimestamp?: number; + + /** Amount of money requests */ childMoneyRequestCount?: number; + + /** TODO: Seems to be used only on tests */ isFirstItem?: boolean; /** Informations about attachments of report action */ @@ -198,6 +246,7 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** ISO-formatted datetime */ lastModified?: string; + /** The accountID of the copilot who took this action on behalf of the user */ delegateAccountID?: number; /** Server side errors keyed by microtime */ @@ -231,10 +280,13 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ adminAccountID?: number; }>; +/** Model of report action */ type ReportAction = ReportActionBase & OriginalMessage; +/** Record of report actions, indexed by report action ID */ type ReportActions = Record; +/** Collection of mock report actions, indexed by reportActions_${reportID} */ type ReportActionsCollectionDataSet = CollectionDataSet; export default ReportAction; diff --git a/src/types/onyx/ReportActionReactions.ts b/src/types/onyx/ReportActionReactions.ts index 983598e0b420..fd14b681d6e9 100644 --- a/src/types/onyx/ReportActionReactions.ts +++ b/src/types/onyx/ReportActionReactions.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of report user reaction */ type UserReaction = { /** ID of user reaction */ id: string; @@ -11,8 +12,10 @@ type UserReaction = { oldestTimestamp: string; }; +/** Record of report user reactions, indexed by their login name or account id */ type UsersReactions = Record; +/** Model of report action reaction */ type ReportActionReaction = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The time the emoji was added */ createdAt: string; @@ -24,6 +27,7 @@ type ReportActionReaction = OnyxCommon.OnyxValueWithOfflineFeedback<{ users: UsersReactions; }>; +/** Record of report action reactions, indexed by emoji name */ type ReportActionReactions = Record; export default ReportActionReactions; diff --git a/src/types/onyx/ReportActionsDraft.ts b/src/types/onyx/ReportActionsDraft.ts index 41a701f16e71..762e4f690853 100644 --- a/src/types/onyx/ReportActionsDraft.ts +++ b/src/types/onyx/ReportActionsDraft.ts @@ -1,5 +1,7 @@ +/** Model of a report action draft */ type ReportActionsDraft = | { + /** Chat message content */ message: string; } | string; diff --git a/src/types/onyx/ReportActionsDrafts.ts b/src/types/onyx/ReportActionsDrafts.ts index e4c51c61ed25..56be910d53de 100644 --- a/src/types/onyx/ReportActionsDrafts.ts +++ b/src/types/onyx/ReportActionsDrafts.ts @@ -2,8 +2,10 @@ import type ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type ReportActionsDraft from './ReportActionsDraft'; +/** Record of report actions drafts, indexed by their ID */ type ReportActionsDrafts = Record; +/** Record of report actions drafts grouped by report, indexed by reportActionsDrafts_\ */ type ReportActionsDraftCollectionDataSet = CollectionDataSet; export default ReportActionsDrafts; diff --git a/src/types/onyx/ReportMetadata.ts b/src/types/onyx/ReportMetadata.ts index c6484705553c..8a6c004bc1c5 100644 --- a/src/types/onyx/ReportMetadata.ts +++ b/src/types/onyx/ReportMetadata.ts @@ -1,3 +1,4 @@ +/** Model of report metadata */ type ReportMetadata = { /** Are we loading newer report actions? */ isLoadingNewerReportActions?: boolean; diff --git a/src/types/onyx/ReportNextStep.ts b/src/types/onyx/ReportNextStep.ts index b619319d8f91..67c0852febc1 100644 --- a/src/types/onyx/ReportNextStep.ts +++ b/src/types/onyx/ReportNextStep.ts @@ -1,9 +1,16 @@ +/** Model of report next step message */ type Message = { + /** Message content */ text: string; + + /** HTML tag name */ type?: string; + + // TODO: Doesn't seem to be used in app action?: string; }; +// TODO: Doesn't seem to be used in app type DataOptions = { canSeeACHOption?: boolean; isManualReimbursementEnabled?: boolean; @@ -11,6 +18,7 @@ type DataOptions = { preferredWithdrawalDeleted?: boolean; }; +// TODO: Doesn't seem to be used in app type Button = { text?: string; tooltip?: string; @@ -19,6 +27,7 @@ type Button = { data?: DataOptions; }; +/** Model for next step of a report */ type ReportNextStep = { /** The message parts of the next step */ message?: Message[]; @@ -26,27 +35,35 @@ type ReportNextStep = { /** The title for the next step */ title?: string; + // TODO: Doesn't seem to be used in app /** Whether the user should take some sort of action in order to unblock the report */ requiresUserAction?: boolean; + // TODO: Doesn't seem to be used in app /** The type of next step */ type: 'alert' | 'neutral' | null; + // TODO: Doesn't seem to be used in app /** If the "Undo submit" button should be visible */ showUndoSubmit?: boolean; + // TODO: Doesn't seem to be used in app /** Deprecated - If the next step should be displayed on mobile, related to OldApp */ showForMobile?: boolean; + // TODO: Doesn't seem to be used in app /** If the next step should be displayed at the expense level */ showForExpense?: boolean; + // TODO: Doesn't seem to be used in app /** An optional alternate message to display on expenses instead of what is provided in the "message" field */ expenseMessage?: Message[]; + // TODO: Doesn't seem to be used in app /** The next person in the approval chain of the report */ nextReceiver?: string; + // TODO: Doesn't seem to be used in app /** An array of buttons to be displayed next to the next step */ buttons?: Record; }; diff --git a/src/types/onyx/ReportUserIsTyping.ts b/src/types/onyx/ReportUserIsTyping.ts index 1e6f482ffa7a..b599da4974fe 100644 --- a/src/types/onyx/ReportUserIsTyping.ts +++ b/src/types/onyx/ReportUserIsTyping.ts @@ -1,3 +1,4 @@ +/** Record of report users typing state, indexed by their login name or account id */ type ReportUserIsTyping = Record; export default ReportUserIsTyping; diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 233519f010fc..ab45fcf84c42 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -1,29 +1,61 @@ import type {OnyxUpdate} from 'react-native-onyx'; import type Response from './Response'; +/** Model of onyx requests sent to the API */ type OnyxData = { + /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ successData?: OnyxUpdate[]; + + /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ failureData?: OnyxUpdate[]; + + /** Onyx instructions that are executed after getting any response from server */ finallyData?: OnyxUpdate[]; + + /** Onyx instructions that are executed before request is made to the server */ optimisticData?: OnyxUpdate[]; }; +/** HTTP request method names */ type RequestType = 'get' | 'post'; +/** Model of overall requests sent to the API */ type RequestData = { + /** Name of the API command */ command: string; + + /** Command name for logging purposes */ commandName?: string; + + /** Additional parameters that can be sent with the request */ data?: Record; + + /** The HTTP request method name */ type?: RequestType; + + /** Whether the app should connect to the secure API endpoints */ shouldUseSecure?: boolean; + + /** Onyx instructions that are executed after getting response from server with jsonCode === 200 */ successData?: OnyxUpdate[]; + + /** Onyx instructions that are executed after getting response from server with jsonCode !== 200 */ failureData?: OnyxUpdate[]; + + /** Onyx instructions that are executed after getting any response from server */ finallyData?: OnyxUpdate[]; + + /** Promise resolve handler */ resolve?: (value: Response) => void; + + /** Promise reject handler */ reject?: (value?: unknown) => void; + + /** Whether the app should skip the web proxy to connect to API endpoints */ shouldSkipWebProxy?: boolean; }; +/** Model of requests sent to the API */ type Request = RequestData & OnyxData; export default Request; diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index 5e606738c56d..02f0ca62063e 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -1,26 +1,65 @@ import type {OnyxUpdate} from 'react-native-onyx'; +/** Model of commands data */ type Data = { + /** Name of the API call */ phpCommandName: string; + + /** Collection of auth write requests */ authWriteCommands: string[]; }; +/** Model of server response */ type Response = { + /** ID of the next update that needs to be fetched from the server */ previousUpdateID?: number | string; + + /** ID of the last update that needs to be fetched from the server */ lastUpdateID?: number | string; + + /** HTTP response status code */ jsonCode?: number | string; + + /** Collection of onyx updates (SET/MERGE/...) */ onyxData?: OnyxUpdate[]; + + /** ID of the request that triggered this response */ requestID?: string; + + /** Report ID of the updated report */ reportID?: string; + + /** + * Whether the sequential queue should not send any requests to the server. + * Used when there's a gap between client and server updates. + */ shouldPauseQueue?: boolean; + + /** User session auth token */ authToken?: string; + + /** Used to load resources like attachment videos and images */ encryptedAuthToken?: string; + + /** Used to pass error messages for error handling purposes */ message?: string; + + /** Used to pass error title for error handling purposes */ title?: string; + + /** Commands data */ data?: Data; + + /** Used to pass error type for error handling purposes */ type?: string; + + /** Short lived auth token generated by API */ shortLivedAuthToken?: string; + + // TODO: This doesn't seem to be used in app auth?: string; + + // TODO: This doesn't seem to be used in app // eslint-disable-next-line @typescript-eslint/naming-convention shared_secret?: string; }; diff --git a/src/types/onyx/ScreenShareRequest.ts b/src/types/onyx/ScreenShareRequest.ts index 564ba99c22ac..920921e8c9b2 100644 --- a/src/types/onyx/ScreenShareRequest.ts +++ b/src/types/onyx/ScreenShareRequest.ts @@ -1,3 +1,4 @@ +/** Model of screen share request */ type ScreenShareRequest = { /** Access token required to join a screen share room, generated by the backend */ accessToken: string; diff --git a/src/types/onyx/SecurityGroup.ts b/src/types/onyx/SecurityGroup.ts index b2362c1eb628..7b213c2e88c8 100644 --- a/src/types/onyx/SecurityGroup.ts +++ b/src/types/onyx/SecurityGroup.ts @@ -1,4 +1,6 @@ +/** Model of security group */ type SecurityGroup = { + /** Whether the security group restricts primary login switching */ hasRestrictedPrimaryLogin: boolean; }; diff --git a/src/types/onyx/SelectedTabRequest.ts b/src/types/onyx/SelectedTabRequest.ts index 8a87db6eee82..db79b948dcde 100644 --- a/src/types/onyx/SelectedTabRequest.ts +++ b/src/types/onyx/SelectedTabRequest.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +/** Selectable IOU request tabs */ type SelectedTabRequest = ValueOf; export default SelectedTabRequest; diff --git a/src/types/onyx/Session.ts b/src/types/onyx/Session.ts index d181114d02d3..7cb47a380717 100644 --- a/src/types/onyx/Session.ts +++ b/src/types/onyx/Session.ts @@ -2,8 +2,10 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; +/** Possible states of the automatic authentication after user clicks on a magic link */ type AutoAuthState = ValueOf; +/** Model of user session data */ type Session = { /** The user's email for the current session */ email?: string; @@ -26,6 +28,7 @@ type Session = { /** Currently logged in user accountID */ accountID?: number; + /** Current state of the automatic authentication after user clicks on a magic link */ autoAuthState?: AutoAuthState; /** Server side errors keyed by microtime */ diff --git a/src/types/onyx/Task.ts b/src/types/onyx/Task.ts index ee3b8e8abd87..4878802135c2 100644 --- a/src/types/onyx/Task.ts +++ b/src/types/onyx/Task.ts @@ -1,5 +1,6 @@ import type Report from './Report'; +/** Model of task data */ type Task = { /** Title of the Task */ title?: string; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 5ed318b21ce5..a7f61a6f07b4 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -8,6 +8,7 @@ import type * as OnyxCommon from './OnyxCommon'; import type RecentWaypoint from './RecentWaypoint'; import type ReportAction from './ReportAction'; +/** Model of waypoint */ type Waypoint = { /** The name associated with the address of the waypoint */ name?: string; @@ -40,80 +41,174 @@ type Waypoint = { street2?: string; }; +/** + * Collection of waypoints, indexed by `waypoint${index}` + * where `index` corresponds to the position of the waypoint in the list + */ type WaypointCollection = Record; +/** Model of transaction comment */ type Comment = { + /** Content of the transaction comment */ comment?: string; + + /** Whether the transaction is on hold */ hold?: string; + + /** Collection of waypoints associated with the transaction */ waypoints?: WaypointCollection; + + /** Whether the transaction comment is loading */ isLoading?: boolean; + + /** TODO: I think this type can be changed to `ValueOf` */ + /** Type of the transaction */ type?: string; + + /** In custom unit transactions this holds the information of the custom unit */ customUnit?: TransactionCustomUnit; + + /** Source of the transaction which when specified matches `split` */ source?: string; + + /** ID of the original transaction */ originalTransactionID?: string; + + /** In split transactions this is a collection of participant split data */ splits?: Split[]; }; +/** Model of transaction custom unit */ type TransactionCustomUnit = { + /** ID of the custom unit */ customUnitID?: string; + + /** ID of the custom unit rate */ customUnitRateID?: string; + + /** Custom unit amount */ quantity?: number; + + /** TODO: I think this value can be changed to `ValueOf` */ + /** Name of the custom unit */ name?: string; + + /** Default rate for custom unit */ defaultP2PRate?: number; }; +/** Types of geometry */ type GeometryType = 'LineString'; +/** Geometry data */ type Geometry = { + /** Matrix of points, indexed by their coordinates */ coordinates: number[][] | null; + + /** Type of connections between coordinates */ type?: GeometryType; }; +/** Accepted receipt paths */ type ReceiptSource = string; +/** Model of receipt */ type Receipt = { + /** TODO: This doesn't exist in the app */ receiptID?: number; + + /** TODO: This doesn't exist in the app */ path?: string; + + /** TODO: This doesn't exist in the app */ name?: string; + + /** Path of the receipt file */ source?: ReceiptSource; + + /** Name of receipt file */ filename?: string; + + /** Current receipt scan state */ state?: ValueOf; + + /** Type of the receipt file */ type?: string; }; +/** Model of route */ type Route = { + /** Distance amount of the route */ distance: number | null; + + /** Route geometry data */ geometry: Geometry; }; +/** Collection of routes, indexed by `route${index}` where `index` is the position of the route in the list */ type Routes = Record; -type ReceiptError = {error?: string; source: string; filename: string}; +/** Model of receipt error */ +type ReceiptError = { + /** TODO: This doesn't exist in the app */ + error?: string; + /** Path of the receipt file */ + source: string; + + /** Name of the receipt file */ + filename: string; +}; + +/** Collection of receipt errors, indexed by a UNIX timestamp of when the error occurred */ type ReceiptErrors = Record; +/** Tax rate data */ type TaxRateData = { + /** TODO: This doesn't exist in the app */ name: string; + + /** Tax rate percentage */ value: string; + + /** Tax rate code */ code?: string; }; +/** Model of tax rate */ type TaxRate = { + /** Default name of the tax rate */ text: string; + + /** Key of the tax rate to index it on options list */ keyForList: string; + + /** TODO: This doesn't exist in the app */ searchText: string; + + /** TODO: This doesn't exist in the app */ tooltipText: string; + + /** TODO: This doesn't exist in the app */ isDisabled?: boolean; + + /** Data of the tax rate */ data?: TaxRateData; }; +/** Participant split data */ type SplitShare = { + /** Amount to be split with participant */ amount: number; + + /** Whether the split was modified */ isModified?: boolean; }; +/** Record of participant split data, indexed by their `accountID` */ type SplitShares = Record; +/** Model of transaction */ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< { /** The original transaction amount */ @@ -250,17 +345,28 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< keyof Comment >; +/** Keys of pending transaction fields */ type TransactionPendingFieldsKey = KeysOfUnion; +/** Additional transaction changes data */ type AdditionalTransactionChanges = { + /** Content of modified comment */ comment?: string; + + /** Collection of modified waypoints */ waypoints?: WaypointCollection; + + /** Previous amount before changes */ oldAmount?: number; + + /** Previous currency before changes */ oldCurrency?: string; }; +/** Model of transaction changes */ type TransactionChanges = Partial & AdditionalTransactionChanges; +/** Collection of mock transactions, indexed by `transactions_${transactionID}` */ type TransactionCollectionDataSet = CollectionDataSet; export default Transaction; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 28de4582bd5e..666a92898cbc 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -6,32 +6,76 @@ import type CONST from '@src/CONST'; */ type ViolationName = (typeof CONST.VIOLATIONS)[keyof typeof CONST.VIOLATIONS]; +/** Model of a transaction violation */ type TransactionViolation = { + /** Type of transaction violation ('violation', 'notice', 'warning', ...) */ type: string; + + /** Name of the transaction violation */ name: ViolationName; + + /** Additional violation information to provide the user */ data?: { + /** Who rejected the transaction */ rejectedBy?: string; + + /** Why the transaction was rejected */ rejectReason?: string; + + /** Limit that the transaction violated */ formattedLimit?: string; + + /** Percentage amount of conversion surcharge applied to the transaction */ surcharge?: number; + + /** Percentage amount of invoice markup applied to the transaction */ invoiceMarkup?: number; + + /** Amount of days which the transaction date overpasses the date limit */ maxAge?: number; + + /** Name of the tag that triggered this violation */ tagName?: string; + + // TODO: Doesn't seem to be used in app categoryLimit?: string; + + // TODO: Doesn't seem to be used in app limit?: string; + + /** Name of the category that triggered this violation */ category?: string; + + /** Whether the transaction failed due to a broken bank connection */ brokenBankConnection?: boolean; + + /** Whether the workspace admin needs to resolve this violation */ isAdmin?: boolean; + + /** Workspace admin email */ email?: string; + + /** Whether the transaction is older than 7 days */ isTransactionOlderThan7Days?: boolean; + + /** Workspace admin name */ member?: string; + + /** Name of the tax that triggered this violation */ taxName?: string; + + /** Index of the tag form field that triggered this violation */ tagListIndex?: number; + + /** Name of the tag form field that triggered this violation */ tagListName?: string; + + /** Collection of form fields that triggered this violation */ errorIndexes?: number[]; }; }; +/** Collection of transaction violations */ type TransactionViolations = TransactionViolation[]; export type {TransactionViolation, ViolationName}; diff --git a/src/types/onyx/User.ts b/src/types/onyx/User.ts index 973f09e16b82..f30ca846ef43 100644 --- a/src/types/onyx/User.ts +++ b/src/types/onyx/User.ts @@ -1,3 +1,4 @@ +/** Model of user data */ type User = { /** Whether or not the user is subscribed to news updates */ isSubscribedToNewsletter: boolean; diff --git a/src/types/onyx/UserLocation.ts b/src/types/onyx/UserLocation.ts index b22802bfefb1..8ff0b8f8f101 100644 --- a/src/types/onyx/UserLocation.ts +++ b/src/types/onyx/UserLocation.ts @@ -1,3 +1,4 @@ +/** Location coordinates for user */ type UserLocation = Pick; export default UserLocation; diff --git a/src/types/onyx/UserWallet.ts b/src/types/onyx/UserWallet.ts index 55a1b31a9084..eb1b11c7adb0 100644 --- a/src/types/onyx/UserWallet.ts +++ b/src/types/onyx/UserWallet.ts @@ -2,12 +2,16 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; +/** Type of account linked to user's wallet */ type WalletLinkedAccountType = 'debitCard' | 'bankAccount'; +/** Error code sent from the server after updating user's wallet */ type ErrorCode = 'ssnError' | 'kbaNeeded' | 'kycFailed'; +/** Type of setup that the user follows to link an account to his wallet */ type SetupType = ValueOf; +/** Model of user wallet */ type UserWallet = { /** The user's available wallet balance */ availableBalance: number; diff --git a/src/types/onyx/WalletAdditionalDetails.ts b/src/types/onyx/WalletAdditionalDetails.ts index c574006e9f66..bd2ba89d181d 100644 --- a/src/types/onyx/WalletAdditionalDetails.ts +++ b/src/types/onyx/WalletAdditionalDetails.ts @@ -1,8 +1,14 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of user wallet Idology question */ type WalletAdditionalQuestionDetails = { + /** Question prompt */ prompt: string; + + /** Question type */ type: string; + + /** Possible answers */ answer: string[]; }; @@ -18,6 +24,7 @@ type WalletPersonalDetails = { phoneNumber: string; }; +/** Model of user wallet additional details */ type WalletAdditionalDetails = { /** Questions returned by Idology */ questions?: WalletAdditionalQuestionDetails[]; @@ -30,8 +37,14 @@ type WalletAdditionalDetails = { /** Which field needs attention? */ errorFields?: OnyxCommon.ErrorFields; + + // TODO: this property is not used in app additionalErrorMessage?: string; + + /** Whether the details are being loaded */ isLoading?: boolean; + + /** Error messages to display to the user */ errors?: OnyxCommon.Errors; }; diff --git a/src/types/onyx/WalletOnfido.ts b/src/types/onyx/WalletOnfido.ts index de7c80b34037..d144431eff9f 100644 --- a/src/types/onyx/WalletOnfido.ts +++ b/src/types/onyx/WalletOnfido.ts @@ -1,5 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; +/** Model of wallet onfido */ type WalletOnfido = { /** Unique identifier returned from openOnfidoFlow then re-sent to ActivateWallet with Onfido response data */ applicantID?: string; diff --git a/src/types/onyx/WalletStatement.ts b/src/types/onyx/WalletStatement.ts index 62b8266c8e43..c9cc72662c1c 100644 --- a/src/types/onyx/WalletStatement.ts +++ b/src/types/onyx/WalletStatement.ts @@ -1,3 +1,4 @@ +/** Model of wallet statement */ type WalletStatement = { /** Whether we are currently generating a PDF version of the statement */ isGenerating: boolean; diff --git a/src/types/onyx/WalletTerms.ts b/src/types/onyx/WalletTerms.ts index c2653cae0f97..be66b779b91f 100644 --- a/src/types/onyx/WalletTerms.ts +++ b/src/types/onyx/WalletTerms.ts @@ -1,6 +1,7 @@ import type {Source} from '@components/KYCWall/types'; import type * as OnyxCommon from './OnyxCommon'; +/** Model of wallet terms */ type WalletTerms = { /** Any error message to show */ errors?: OnyxCommon.Errors; diff --git a/src/types/onyx/WalletTransfer.ts b/src/types/onyx/WalletTransfer.ts index 961a7c9752a5..8bcf6e7217a3 100644 --- a/src/types/onyx/WalletTransfer.ts +++ b/src/types/onyx/WalletTransfer.ts @@ -3,6 +3,7 @@ import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; import type PaymentMethod from './PaymentMethod'; +/** Model of user wallet transfer */ type WalletTransfer = { /** Selected accountID for transfer */ selectedAccountID?: string | number; @@ -22,9 +23,11 @@ type WalletTransfer = { /** Whether or not data is loading */ loading?: boolean; + /** Payment method used for transfer */ paymentMethodType?: ValueOf>; }; +/** Available payment methods */ type FilterMethodPaymentType = typeof CONST.PAYMENT_METHODS.DEBIT_CARD | typeof CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT | ''; export default WalletTransfer; diff --git a/src/types/onyx/WorkspaceRateAndUnit.ts b/src/types/onyx/WorkspaceRateAndUnit.ts index a374239c93f8..ebef0902d972 100644 --- a/src/types/onyx/WorkspaceRateAndUnit.ts +++ b/src/types/onyx/WorkspaceRateAndUnit.ts @@ -1,5 +1,7 @@ +/** Units of distance */ type Unit = 'mi' | 'km'; +/** Model of workspace distance rate */ type WorkspaceRateAndUnit = { /** policyID of the Workspace */ policyID: string; @@ -7,7 +9,7 @@ type WorkspaceRateAndUnit = { /** Unit of the Workspace */ unit?: Unit; - /** Unit of the Workspace */ + /** Distance rate of the Workspace */ rate?: string; }; From 5590c17316a5ed71394d2dddad65b1ab13708a03 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 12:20:17 +0200 Subject: [PATCH 029/419] Refactor LHNOptionsList to use useOnyx --- .../LHNOptionsList/LHNOptionsList.tsx | 58 +++++-------------- src/components/LHNOptionsList/types.ts | 30 +--------- 2 files changed, 15 insertions(+), 73 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 8c43ae542932..b0ef2a4edaef 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -4,7 +4,7 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {memo, useCallback, useContext, useEffect, useMemo, useRef} from 'react'; import {StyleSheet, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import BlockingView from '@components/BlockingViews/BlockingView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -23,31 +23,24 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListProps, RenderItemProps} from './types'; const keyExtractor = (item: string) => `report_${item}`; -function LHNOptionsList({ - style, - contentContainerStyles, - data, - onSelectRow, - optionMode, - shouldDisableFocusOptions = false, - reports = {}, - reportActions = {}, - policy = {}, - preferredLocale = CONST.LOCALES.DEFAULT, - personalDetails = {}, - transactions = {}, - draftComments = {}, - transactionViolations = {}, - onFirstItemRendered = () => {}, -}: LHNOptionsListProps) { +function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optionMode, shouldDisableFocusOptions = false, onFirstItemRendered = () => {}}: LHNOptionsListProps) { const {saveScrollOffset, getScrollOffset} = useContext(ScrollOffsetContext); const flashListRef = useRef>(null); const route = useRoute(); + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); + const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); + const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {initialValue: CONST.LOCALES.DEFAULT}); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const theme = useTheme(); const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); @@ -246,31 +239,6 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policy: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - draftComments: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - }, -})(memo(LHNOptionsList)); +export default memo(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 0f0c921747b4..7248742654d2 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -10,32 +10,6 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; type OptionMode = ValueOf; -type LHNOptionsListOnyxProps = { - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxCollection; - - /** All reports shared with the user */ - reports: OnyxCollection; - - /** Array of report actions for this report */ - reportActions: OnyxCollection; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry; - - /** List of users' personal details */ - personalDetails: OnyxEntry; - - /** The transaction from the parent report action */ - transactions: OnyxCollection; - - /** List of draft comments */ - draftComments: OnyxCollection; - - /** The list of transaction violations */ - transactionViolations: OnyxCollection; -}; - type CustomLHNOptionsListProps = { /** Wrapper style for the section list */ style?: StyleProp; @@ -59,7 +33,7 @@ type CustomLHNOptionsListProps = { onFirstItemRendered: () => void; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & LHNOptionsListOnyxProps; +type LHNOptionsListProps = CustomLHNOptionsListProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ @@ -141,4 +115,4 @@ type OptionRowLHNProps = { type RenderItemProps = {item: string}; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, RenderItemProps}; From 575b0be19bdd958a0de0d4d73443418bf90fb885 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 16:51:38 +0200 Subject: [PATCH 030/419] fix SidebarTest --- tests/unit/SidebarTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index c037c1ced3f0..10f9b4afd4dd 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -53,9 +53,9 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // When Onyx is updated with the data and the sidebar re-renders .then(() => { const reportCollection: ReportCollectionDataSet = { @@ -105,9 +105,9 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // When Onyx is updated with the data and the sidebar re-renders .then(() => { const reportCollection: ReportCollectionDataSet = { From e903c1df50dc13098e32a28e03c12d17ac569c56 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 17:02:02 +0200 Subject: [PATCH 031/419] fix SidebarOrderTest --- tests/unit/SidebarOrderTest.ts | 109 ++++++++++++++++----------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 644bba5a589b..a5285fe186a2 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -62,33 +62,29 @@ describe('Sidebar', () => { expect(screen.toJSON()).toBe(null); }); - it('is rendered with an empty list when personal details exist', () => { - // Given the sidebar is rendered with default props - LHNTestUtils.getDefaultRenderedSidebarLinks(); - - return ( - waitForBatchedUpdates() - // When Onyx is updated with some personal details - .then(() => - Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_APP]: false, - }), - ) - - // Then the component should be rendered with an empty list since it will get past the early return - .then(() => { - expect(screen.toJSON()).not.toBe(null); - const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); - expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(0); - }) - ); - }); + it('is rendered with an empty list when personal details exist', () => + waitForBatchedUpdates() + // Given the sidebar is rendered with default props + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + + // When Onyx is updated with some personal details + .then(() => + Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.IS_LOADING_APP]: false, + }), + ) + + // Then the component should be rendered with an empty list since it will get past the early return + .then(() => { + expect(screen.toJSON()).not.toBe(null); + const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(0); + })); it('contains one report when a report is in Onyx', () => { // Given a single report const report = LHNTestUtils.getFakeReport([1, 2]); - LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID); const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -96,6 +92,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -114,8 +112,6 @@ describe('Sidebar', () => { }); it('orders items with most recently updated on top', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given three unread reports in the recently updated order of 3, 2, 1 const report1 = LHNTestUtils.getFakeReport([1, 2], 3); const report2 = LHNTestUtils.getFakeReport([3, 4], 2); @@ -134,6 +130,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -173,8 +171,6 @@ describe('Sidebar', () => { Report.addComment(report3.reportID, 'Hi, this is a comment'); const currentReportId = report1.reportID; - LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -183,6 +179,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -211,8 +209,6 @@ describe('Sidebar', () => { }); it('reorders the reports to always have the most recently updated one on top', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given three reports in the recently updated order of 3, 2, 1 const report1 = LHNTestUtils.getFakeReport([1, 2], 3); const report2 = LHNTestUtils.getFakeReport([3, 4], 2); @@ -231,6 +227,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -282,8 +280,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks(taskReport.reportID); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -293,6 +289,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(taskReport.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -346,8 +344,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -357,6 +353,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -413,8 +411,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -424,6 +420,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -464,7 +462,6 @@ describe('Sidebar', () => { Report.addComment(report3.reportID, 'Hi, this is a comment'); const currentReportId = report2.reportID; - LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, @@ -474,6 +471,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -507,8 +506,6 @@ describe('Sidebar', () => { }); it('removes the pencil icon when draft is removed', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given a single report // And the report has a draft const report: OnyxTypes.Report = { @@ -521,6 +518,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -548,8 +547,6 @@ describe('Sidebar', () => { }); it('removes the pin icon when chat is unpinned', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given a single report // And the report is pinned const report: OnyxTypes.Report = { @@ -563,6 +560,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -624,8 +623,6 @@ describe('Sidebar', () => { const currentReportId = report2.reportID; const currentlyLoggedInUserAccountID = 9; - LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -635,6 +632,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -683,8 +682,6 @@ describe('Sidebar', () => { isPinned: true, }; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -693,6 +690,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -745,8 +744,6 @@ describe('Sidebar', () => { ...LHNTestUtils.getFakeReport([7, 8], 0), }; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -761,6 +758,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -824,8 +823,6 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -834,6 +831,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -860,8 +859,6 @@ describe('Sidebar', () => { describe('in #focus mode', () => { it('alphabetizes chats', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - const report1 = LHNTestUtils.getFakeReport([1, 2], 3, true); const report2 = LHNTestUtils.getFakeReport([3, 4], 2, true); const report3 = LHNTestUtils.getFakeReport([5, 6], 1, true); @@ -875,6 +872,7 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // Given the sidebar is rendered in #focus mode (hides read chats) // with all reports having unread comments .then(() => @@ -926,8 +924,6 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -936,6 +932,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -1060,8 +1058,6 @@ describe('Sidebar', () => { const currentlyLoggedInUserAccountID = 13; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -1077,6 +1073,7 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -1123,8 +1120,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -1133,6 +1128,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ From 9f0585e6f576bd3df8441b92c34671793b315df2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 11 May 2024 19:08:30 +0530 Subject: [PATCH 032/419] fix: Status - Emoji in custom status holder is not centered. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 060fb1c5ba90..b1b92869c4b7 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4333,7 +4333,20 @@ const styles = (theme: ThemeColors) => }, emojiStatusLHN: { - fontSize: 9, + ...(Browser.getBrowser() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15}), + ...(Browser.getBrowser() && + Browser.isSafari() && + !Browser.isMobileSafari() && { + transform: 'scale(0.65)', + fontSize: 13, + lineHeight: 15, + }), + // transform: 'scale(.65)', + // lineHeight: 15, + // transform: 'scale(.65)', + // lineHeight: 18, + // fontSize: 15, + // overflow: 'visible', }, onboardingVideoPlayer: { From be5bcfa3b7cd047c48e1b542bb5c2abe25126f9c Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 13 May 2024 10:01:05 +0530 Subject: [PATCH 033/419] feat: [Held requests] option does not show in the preview overflow menu. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 86 +++++++++++++++++++ .../report/ContextMenu/ContextMenuActions.tsx | 36 ++++++++ 2 files changed, 122 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 51c797937b1d..cb729b3f7dfd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -14,6 +14,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; +import * as ReportUtils from '@libs/ReportUtils'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; @@ -2689,6 +2690,89 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } +function getParentReportAction(parentReportActions: ReportActions | null | undefined, parentReportActionID: string | undefined): ReportAction | null { + if (!parentReportActions || !parentReportActionID) { + return null; + } + return parentReportActions[parentReportActionID ?? '0']; +} + +function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + + if (!moneyRequestReportID) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + + const moneyRequestReport = getReport(String(moneyRequestReportID)); + + if (!moneyRequestReport) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + + const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isApproved = ReportUtils.isReportApproved(moneyRequestReport); + const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; + const parentReportActions = allReportActions?.[parentReportActionsKey]; + const parentReport = getReport(String(moneyRequestReport.parentReportID)); + const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); + + const isRequestIOU = parentReport?.type === 'iou'; + const isHoldCreator = ReportUtils.isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; + const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(moneyRequestReport); + const isActionOwner = + typeof parentReportAction?.actorAccountID === 'number' && + typeof currentUserPersonalDetails?.accountID === 'number' && + parentReportAction.actorAccountID === currentUserPersonalDetails?.accountID; + const isApprover = + ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; + const isOnHold = TransactionUtils.isOnHold(transaction); + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + + const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover); + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + + const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction; + const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; + const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))); + + return {canHoldRequest, canUnholdRequest}; +} + +const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): void => { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return; + } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + + const moneyRequestReport = getReport(String(moneyRequestReportID)); + if (!moneyRequestReportID || !moneyRequestReport) { + return; + } + + const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; + const parentReportActions = allReportActions?.[parentReportActionsKey]; + const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); + const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + const isOnHold = TransactionUtils.isOnHold(transaction); + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport.policyID}`] ?? null; + + // const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + + if (isOnHold) { + IOU.unholdRequest(transactionID, reportAction.childReportID); + } else { + const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID, activeRoute)); + } +}; + /** * Gets all transactions on an IOU report with a receipt */ @@ -6607,6 +6691,7 @@ export { canCreateTaskInReport, canCurrentUserOpenReport, canDeleteReportAction, + canHoldUnholdReportAction, canEditFieldOfMoneyRequest, canEditMoneyRequest, canEditPolicyDescription, @@ -6825,6 +6910,7 @@ export { shouldShowMerchantColumn, isCurrentUserInvoiceReceiver, isDraftReport, + changeMoneyRequestHoldStatus, }; export type { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 105eadffd436..5d5b6c070eb0 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -255,6 +255,42 @@ const ContextMenuActions: ContextMenuAction[] = [ }, getDescription: () => {}, }, + { + isAnonymousAction: false, + textTranslateKey: 'iou.unholdExpense', + icon: Expensicons.Stopwatch, + shouldShow: (type, reportAction) => + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canUnholdRequest, + onPress: (closePopover, {reportAction}) => { + // const hold=ReportUtils.changeMoneyRequestHoldStatus(). + if (closePopover) { + hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); + return; + } + + // No popover to hide, call changeMoneyRequestHoldStatus immediately + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + getDescription: () => {}, + }, + { + isAnonymousAction: false, + textTranslateKey: 'iou.hold', + icon: Expensicons.Stopwatch, + shouldShow: (type, reportAction) => + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canHoldRequest, + onPress: (closePopover, {reportAction}) => { + // const hold=ReportUtils.changeMoneyRequestHoldStatus(). + if (closePopover) { + hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); + return; + } + + // No popover to hide, call changeMoneyRequestHoldStatus immediately + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + getDescription: () => {}, + }, { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.joinThread', From f06518ef56d9af58342820ad4eb1a4c01792999f Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 13 May 2024 11:55:51 +0530 Subject: [PATCH 034/419] remove redundant code. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index b1b92869c4b7..2d90d89d2406 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4333,20 +4333,16 @@ const styles = (theme: ThemeColors) => }, emojiStatusLHN: { - ...(Browser.getBrowser() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15}), + fontSize: 9, + ...(Browser.getBrowser() && !Browser.isMobile() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15, overflow: 'visible'}), ...(Browser.getBrowser() && Browser.isSafari() && - !Browser.isMobileSafari() && { + !Browser.isMobile() && { transform: 'scale(0.65)', fontSize: 13, lineHeight: 15, + overflow: 'visible', }), - // transform: 'scale(.65)', - // lineHeight: 15, - // transform: 'scale(.65)', - // lineHeight: 18, - // fontSize: 15, - // overflow: 'visible', }, onboardingVideoPlayer: { From 4dac52f425e48af2396760a7c9747a0c9ec32bb8 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 14 May 2024 00:23:07 +0100 Subject: [PATCH 035/419] docs: add missing onyx types descriptions --- src/libs/ReportUtils.ts | 1 + src/types/onyx/OriginalMessage.ts | 224 +++++++++++++++++++++++++++++- src/types/onyx/Policy.ts | 7 + 3 files changed, 231 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 641d3ddaa268..871eff75cdea 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -86,6 +86,7 @@ type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | type WelcomeMessage = {showReportName: boolean; phrase1?: string; phrase2?: string}; +/** TODO: I'd move this to `OriginalMessage.ts` and add it to `OriginalMessageModifiedExpense` type */ type ExpenseOriginalMessage = { oldComment?: string; newComment?: string; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index dabfdc9aa485..5d1f0f290b04 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -2,9 +2,13 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +/** Types of payments methods */ type PaymentMethodType = DeepValueOf; +/** Names of report actions */ type ActionName = DeepValueOf; + +/** Names of task report actions */ type OriginalMessageActionName = | 'ADDCOMMENT' | 'APPROVED' @@ -28,235 +32,425 @@ type OriginalMessageActionName = | 'ACTIONABLEREPORTMENTIONWHISPER' | 'ACTIONABLETRACKEXPENSEWHISPER' | ValueOf; + +/** Model of `approved` report action */ type OriginalMessageApproved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; + + /** TODO: I think the type should match the scructure of `originalMessage` in `buildOptimisticApprovedReportAction` */ originalMessage: unknown; }; + +/** Types of sources of original message */ type OriginalMessageSource = 'Chronos' | 'email' | 'ios' | 'android' | 'web' | ''; +/** Model of `hold` report action */ type OriginalMessageHold = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.HOLD; originalMessage: unknown; }; +/** Model of `hold comment` report action */ type OriginalMessageHoldComment = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.HOLD_COMMENT; originalMessage: unknown; }; +/** Model of `unhold` report action */ type OriginalMessageUnHold = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.UNHOLD; originalMessage: unknown; }; +/** Details provided when sending money */ type IOUDetails = { + /** How much was sent */ amount: number; + + /** Optional comment */ comment?: string; + + /** Currency of the money sent */ currency: string; }; +/** Model of original message of `IOU` report action */ type IOUMessage = { - /** The ID of the iou transaction */ + /** The ID of the `IOU` transaction */ IOUTransactionID?: string; + + /** ID of the `IOU` report */ IOUReportID?: string; + + /** ID of the expense report */ expenseReportID?: string; + + /** How much was transactioned */ amount: number; + + /** Optional comment */ comment?: string; + + /** Currency of the transactioned money */ currency: string; + + /** When was the `IOU` last modified */ lastModified?: string; + + /** Who participated in the transaction, by accountID */ participantAccountIDs?: number[]; + + /** Type of `IOU` report action */ type: ValueOf; + + /** If the action was cancelled, this is the reason for the cancellation */ cancellationReason?: string; + + /** Type of payment method used in transaction */ paymentType?: PaymentMethodType; + + /** Timestamp of when the `IOU` report action was deleted */ deleted?: string; + /** Only exists when we are sending money */ IOUDetails?: IOUDetails; }; +/** Model of original message of `reimbursed dequeued` report action */ type ReimbursementDeQueuedMessage = { + /** TODO: I'd replace this type with `ValueOf` */ + /** Why the reimbursement was cancelled */ cancellationReason: string; + + /** ID of the `expense` report */ expenseReportID?: string; + + /** Amount that wasn't reimbursed */ amount: number; + + /** Currency of the money that wasn't reimbursed */ currency: string; }; +/** Model of `IOU` report action */ type OriginalMessageIOU = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.IOU; originalMessage: IOUMessage; }; +/** Names of severity flags */ type FlagSeverityName = ValueOf< Pick< typeof CONST.MODERATION, 'FLAG_SEVERITY_SPAM' | 'FLAG_SEVERITY_INCONSIDERATE' | 'FLAG_SEVERITY_INTIMIDATION' | 'FLAG_SEVERITY_BULLYING' | 'FLAG_SEVERITY_HARASSMENT' | 'FLAG_SEVERITY_ASSAULT' > >; + +/** Model of severity flag */ type FlagSeverity = { + /** Account ID of the user that flagged the comment */ accountID: number; + + /** When was the comment flagged */ timestamp: string; }; +/** Names of moderation decisions */ type DecisionName = ValueOf< Pick< typeof CONST.MODERATION, 'MODERATOR_DECISION_PENDING' | 'MODERATOR_DECISION_PENDING_HIDE' | 'MODERATOR_DECISION_PENDING_REMOVE' | 'MODERATOR_DECISION_APPROVED' | 'MODERATOR_DECISION_HIDDEN' > >; + +/** Model of moderator decision */ type Decision = { + /** Name of the decision */ decision: DecisionName; + + /** When was the decision name */ timestamp?: string; }; +/** Model of user reaction */ type User = { + /** Account ID of the user that reacted to the comment */ accountID: number; + + /** What's the skin tone of the user reaction */ skinTone: number; }; +/** Model of comment reaction */ type Reaction = { + /** Which emoji was used to react to the comment */ emoji: string; + + /** Which users reacted with this emoji */ users: User[]; }; +/** Model of original message of `closed` report action */ type Closed = { + /** Name of the policy */ policyName: string; + + /** What was the reason to close the report */ reason: ValueOf; + + /** When was the message last modified */ lastModified?: string; + + /** If the report was closed because accounts got merged, then this is the new account ID */ newAccountID?: number; + + /** If the report was closed because accounts got merged, then this is the old account ID */ oldAccountID?: number; }; +/** Model of `add comment` report action */ type OriginalMessageAddComment = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; originalMessage: { + /** HTML content of the comment */ html: string; + + /** Origin of the comment */ source?: OriginalMessageSource; + + /** When was the comment last modified */ lastModified?: string; + + /** ID of the task report */ taskReportID?: string; + + /** TODO: Doesn't exist in the app */ edits?: string[]; + + /** TODO: Doesn't exist in the app */ childReportID?: string; + + /** TODO: Doesn't exist in the app */ isDeletedParentAction?: boolean; + + /** TODO: Doesn't exist in the app */ flags?: Record; + + /** TODO: Doesn't exist in the app */ moderationDecisions?: Decision[]; + + /** TODO: Only used in tests */ whisperedTo: number[]; + + /** TODO: Doesn't exist in the app */ reactions?: Reaction[]; }; }; +/** Model of `actionable mention whisper` report action */ type OriginalMessageActionableMentionWhisper = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER; originalMessage: { + /** Account IDs of users that aren't members of the room */ inviteeAccountIDs: number[]; + + /** TODO: Doesn't exist in the app */ inviteeEmails: string; + + /** TODO: Only used in tests */ lastModified: string; + + /** TODO: Doesn't exist in the app */ reportID: number; + + /** Decision on whether to invite users that were mentioned but aren't members or do nothing */ resolution?: ValueOf | null; + + /** TODO: Doesn't exist in the app */ whisperedTo?: number[]; }; }; +/** Model of `actionable report mention whisper` report action */ type OriginalMessageActionableReportMentionWhisper = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER; originalMessage: { + /** TODO: Doesn't exist in the app */ reportNames: string[]; + + /** TODO: Doesn't exist in the app */ mentionedAccountIDs: number[]; + + /** TODO: Doesn't exist in the app */ reportActionID: number; + + /** TODO: Doesn't exist in the app */ reportID: number; + + /** TODO: Only used in tests */ lastModified: string; + + /** Decision on whether to create a report that were mentioned but doesn't exist or do nothing */ resolution?: ValueOf | null; + + /** TODO: Doesn't exist in the app */ whisperedTo?: number[]; }; }; +/** Model of `submitted` report action */ type OriginalMessageSubmitted = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.SUBMITTED; + + /** TODO: I think the type should match the scructure of `originalMessage` in `buildOptimisticSubmittedReportAction` */ originalMessage: unknown; }; +/** Model of `closed` report action */ type OriginalMessageClosed = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.CLOSED; originalMessage: Closed; }; +/** Model of `created` report action */ type OriginalMessageCreated = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.CREATED; originalMessage?: unknown; }; +/** Model of `marked reimbursed` report action */ type OriginalMessageMarkedReimbursed = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED; originalMessage?: unknown; }; +/** Model of `renamed` report action, created when chat rooms get renamed */ type OriginalMessageRenamed = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.RENAMED; originalMessage: { + /** Renamed room comment */ html: string; + + /** When was report action last modified */ lastModified: string; + + /** Old room name */ oldName: string; + + /** New room name */ newName: string; }; }; +/** Model of Chronos OOO Timestamp */ type ChronosOOOTimestamp = { + /** Date timestamp */ date: string; + + /** TODO: Doesn't exist in the app */ + /** Timezone code */ timezone: string; + + /** TODO: Doesn't exist in the app */ // eslint-disable-next-line @typescript-eslint/naming-convention timezone_type: number; }; +/** Model of change log */ type ChangeLog = { + /** Account IDs of users that either got invited or removed from the room */ targetAccountIDs?: number[]; + + /** Name of the chat room */ roomName?: string; + + /** ID of the report */ reportID?: number; }; +/** Model of Chronos OOO Event */ type ChronosOOOEvent = { + /** ID of the OOO event */ id: string; + + /** How many days will the user be OOO */ lengthInDays: number; + + /** Description of the OOO state */ summary: string; + + /** When will the OOO state start */ start: ChronosOOOTimestamp; + + /** When will the OOO state end */ end: ChronosOOOTimestamp; }; +/** Model of `Chronos OOO List` report action */ type OriginalMessageChronosOOOList = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST; originalMessage: { + /** TODO: Doesn't exist in the app */ edits: string[]; + + /** Collection of OOO events to show in report action */ events: ChronosOOOEvent[]; + + /** TODO: Only used in tests */ html: string; + + /** TODO: Only used in tests */ lastModified: string; }; }; +/** Model of `report preview` report action */ type OriginalMessageReportPreview = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW; originalMessage: { + /** ID of the report to be previewed */ linkedReportID: string; + + /** TODO: Only used in tests */ lastModified?: string; }; }; +/** Model of `policy change log` report action */ type OriginalMessagePolicyChangeLog = { actionName: ValueOf; originalMessage: ChangeLog; }; +/** Model of `join policy change log` report action */ type OriginalMessageJoinPolicyChangeLog = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST; originalMessage: { + /** TODO: I think this type could be changed to `ValueOf` */ + /** What was the invited user decision */ choice: string; + + /** TODO: Doesn't exist in the app */ email: string; + + /** TODO: Doesn't exist in the app */ inviterEmail: string; + + /** TODO: Only used in tests */ lastModified: string; + + /** TODO: Doesn't exist in the app */ policyID: string; }; }; +/** Model of `room change log` report action */ type OriginalMessageRoomChangeLog = { actionName: ValueOf; originalMessage: ChangeLog; }; +/** Model of `policy task` report action */ type OriginalMessagePolicyTask = { actionName: | typeof CONST.REPORT.ACTIONS.TYPE.TASK_EDITED @@ -267,8 +461,10 @@ type OriginalMessagePolicyTask = { originalMessage: unknown; }; +/** Model of `modified expense` report action */ type OriginalMessageModifiedExpense = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE; + /** TODO: I think this type could be replaced by `ExpenseOriginalMessage` from `ReportUtils.ts` */ originalMessage: { oldMerchant?: string; merchant?: string; @@ -293,52 +489,78 @@ type OriginalMessageModifiedExpense = { }; }; +/** Model of `reimbursement queued` report action */ type OriginalMessageReimbursementQueued = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED; originalMessage: { + /** How is the payment getting reimbursed */ paymentType: DeepValueOf; }; }; +/** Model of `actionable tracked expense whisper` report action */ type OriginalMessageActionableTrackedExpenseWhisper = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER; originalMessage: { + /** ID of the transaction */ transactionID: string; + + /** TODO: Only used in tests */ lastModified: string; + + /** What was the decision of the user */ resolution?: ValueOf; }; }; +/** Model of `reimbursement dequeued` report action */ type OriginalMessageReimbursementDequeued = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED; + + /** TODO: I think this type should be `ReimbursementDeQueuedMessage` */ originalMessage: { + /** ID of the expense report */ expenseReportID: string; }; }; +/** Model of `moved` report action */ type OriginalMessageMoved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.MOVED; originalMessage: { + /** ID of the old policy */ fromPolicyID: string; + + /** ID of the new policy */ toPolicyID: string; + + /** ID of the new parent report */ newParentReportID: string; + + /** ID of the moved report */ movedReportID: string; }; }; +/** Model of `merged with cash transaction` report action */ type OriginalMessageMergedWithCashTransaction = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION; originalMessage: Record; // No data is sent with this action }; +/** Model of `dismissed violation` report action */ type OriginalMessageDismissedViolation = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION; originalMessage: { + /** Why the violation was dismissed */ reason: string; + + /** Name of the violation */ violationName: string; }; }; +/** Model of report action */ type OriginalMessage = | OriginalMessageApproved | OriginalMessageIOU diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index c0f74747d5b0..170104eab0e5 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -309,18 +309,25 @@ type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** TODO: Doesn't exist in the app */ markChecksToBePrinted: boolean; + /** Defines how reimbursable expenses are exported */ reimbursableExpensesExportDestination: QBOReimbursableExportAccountType; + /** Defines how non reimbursable expenses are exported */ nonReimbursableExpensesExportDestination: QBONonReimbursableExportAccountType; + /** Default vendor of non reimbursable bill */ nonReimbursableBillDefaultVendor: string; + /** ID of the invoice collection account */ collectionAccountID?: string; + /** ID of the bill payment account */ reimbursementAccountID?: string; + /** Account that receives the reimbursable expenses */ reimbursableExpensesAccount?: Account; + /** Account that receives the non reimbursable expenses */ nonReimbursableExpensesAccount?: Account; /** Account that receives the exported invoices */ From 2263ee97af35ca6ddd2dfdab2e32ddd67190581f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 May 2024 22:16:29 +0800 Subject: [PATCH 036/419] fix wrong selection after replacing all values --- src/components/MoneyRequestAmountInput.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index a59b50e5bdb7..6201df3e8e6f 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -123,6 +123,8 @@ function MoneyRequestAmountInput( }); const forwardDeletePressedRef = useRef(false); + // The ref is used to ignore any onSelectionChange event that happens while we are updating the selection manually in setNewAmount + const willSelectionBeUpdatedManually = useRef(false); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -145,6 +147,7 @@ function MoneyRequestAmountInput( // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. + willSelectionBeUpdatedManually.current = true; let hasSelectionBeenSet = false; setCurrentAmount((prevAmount) => { const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(finalAmount); @@ -152,6 +155,7 @@ function MoneyRequestAmountInput( if (!hasSelectionBeenSet) { hasSelectionBeenSet = true; setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); + willSelectionBeUpdatedManually.current = false; } onAmountChange?.(strippedAmount); return strippedAmount; @@ -266,6 +270,10 @@ function MoneyRequestAmountInput( selectedCurrencyCode={currency} selection={selection} onSelectionChange={(e: NativeSyntheticEvent) => { + if (willSelectionBeUpdatedManually.current) { + willSelectionBeUpdatedManually.current = false; + return; + } if (!shouldUpdateSelection) { return; } From 772d1b2ca03fe1f713a9c88ae5c7ecb13274bbaf Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Wed, 15 May 2024 02:30:17 +0530 Subject: [PATCH 037/419] Disable write capability for invoice rooms --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b8c306b549a7..966db769d070 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5785,7 +5785,7 @@ function shouldDisableRename(report: OnyxEntry, policy: OnyxEntry, policy: OnyxEntry): boolean { - return PolicyUtils.isPolicyAdmin(policy) && !isAdminRoom(report) && !isArchivedRoom(report) && !isThread(report); + return PolicyUtils.isPolicyAdmin(policy) && !isAdminRoom(report) && !isArchivedRoom(report) && !isThread(report) && !isInvoiceRoom(report); } /** From 5c427c1d9fc66c8bce90ad22e9492d24ee2dfe21 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 15 May 2024 14:40:43 +0200 Subject: [PATCH 038/419] feat: add verify identity intro page --- src/languages/en.ts | 2 + src/languages/es.ts | 2 + .../VerifyIdentity/VerifyIdentity.tsx | 50 ++++++++++++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c174c2182ef2..b079a7ecc624 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1571,6 +1571,8 @@ export default { facialScan: 'Onfido’s Facial Scan Policy and Release', tryAgain: 'Try again', verifyIdentity: 'Verify identity', + letsVerifyIdentity: "Let's verify your identity.", + butFirst: `But first, the boring stuff. Read up on the legalese in the next step and click "Accept" when you're ready.`, genericError: 'There was an error while processing this step. Please try again.', cameraPermissionsNotGranted: 'Enable camera access', cameraRequestMessage: 'We need access to your camera to complete bank account verification. Please enable via Settings > New Expensify.', diff --git a/src/languages/es.ts b/src/languages/es.ts index e9e1ed6eb815..eca0e3ac16ad 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1590,6 +1590,8 @@ export default { facialScan: 'Política y lanzamiento de la exploración facial de Onfido', tryAgain: 'Intentar otra vez', verifyIdentity: 'Verificar identidad', + letsVerifyIdentity: '¡Vamos a verificar tu identidad!', + butFirst: 'Pero primero, lo aburrido. Lee la jerga legal en el siguiente paso y haz clic en "Aceptar" cuando estés listo.', genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.', cameraPermissionsNotGranted: 'Permiso para acceder a la cámara', cameraRequestMessage: 'Necesitamos acceso a tu cámara para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > Nuevo Expensify.', diff --git a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx index 40aab5ba8b35..0bcb0beb675f 100644 --- a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx @@ -3,14 +3,20 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import FixedFooter from '@components/FixedFooter'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Icon from '@components/Icon'; +import * as Illustrations from '@components/Icon/Illustrations'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import Onfido from '@components/Onfido'; import type {OnfidoData} from '@components/Onfido/types'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; import Growl from '@libs/Growl'; import * as BankAccounts from '@userActions/BankAccounts'; import * as Wallet from '@userActions/Wallet'; @@ -45,6 +51,8 @@ function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, wa const styles = useThemeStyles(); const {translate} = useLocalize(); + console.log({personalBankAccount, onfidoApplicantID, onfidoToken, walletOnfidoData}); + const handleOnfidoSuccess = useCallback( (onfidoData: OnfidoData) => { BankAccounts.verifyIdentity({ @@ -58,6 +66,8 @@ function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, wa [personalBankAccount, onfidoApplicantID], ); + const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) ?? ''; + const handleOnfidoError = () => { // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. Growl.error(translate('onfidoStep.genericError'), ONFIDO_ERROR_DISPLAY_DURATION); @@ -74,6 +84,8 @@ function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, wa Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS); }; + const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData; + return ( - + {hasAcceptedPrivacyPolicy ? ( + + ) : ( + <> + + + {translate('onfidoStep.letsVerifyIdentity')} + {translate('onfidoStep.butFirst')} + + + {}} + onFixTheErrorsLinkPressed={() => {}} + message={onfidoError} + isLoading={isLoading} + buttonText={onfidoError ? translate('onfidoStep.tryAgain') : translate('common.continue')} + containerStyles={[styles.mh0, styles.mv0, styles.mb0]} + /> + + + )} From 13029ae924c52345317b5865f576d5241e428230 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 15 May 2024 15:21:01 +0100 Subject: [PATCH 039/419] docs: add missing type descriptions --- src/types/onyx/IOU.ts | 8 +++++++- src/types/onyx/OriginalMessage.ts | 4 ++++ src/types/onyx/ReportAction.ts | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 04c1bb49ab52..20f9a0ea4677 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -15,7 +15,8 @@ type Participant = { /** Is IOU participant associated with policy expense chat */ isPolicyExpenseChat?: boolean; - + + /** Whether the IOU participant is an invoice receiver */ isInvoiceRoom?: boolean; /** Is IOU participant associated with is own policy expense chat */ @@ -62,7 +63,12 @@ type Participant = { /** Is IOU participant the current user */ isSelfDM?: boolean; + + /** Whether the IOU participant is an invoice sender */ isSender?: boolean; + + /** TODO: I think this type could be changes to `IOUType` */ + /** The type of IOU report, i.e. split, request, send, track */ iouType?: string; }; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 7f7c9001d7ff..c54602503bae 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -114,6 +114,8 @@ type IOUMessage = { /** Only exists when we are sending money */ IOUDetails?: IOUDetails; + + /** Collection of accountIDs of users mentioned in message */ whisperedTo?: number[]; }; @@ -414,6 +416,8 @@ type OriginalMessageReportPreview = { /** TODO: Only used in tests */ lastModified?: string; + + /** Collection of accountIDs of users mentioned in report */ whisperedTo?: number[]; }; }; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 1e973d9a7a86..ae319c120f20 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -11,7 +11,9 @@ import type OriginalMessage from './OriginalMessage'; import type {NotificationPreference} from './Report'; import type {Receipt} from './Transaction'; +/** Partial content of report action message */ type ReportActionMessageJSON = { + /** Collection of accountIDs from users that were mentioned in report */ whisperedTo?: number[]; }; @@ -283,7 +285,11 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Model of report action */ type ReportAction = ReportActionBase & OriginalMessage; + +/** Model of report preview action */ type ReportPreviewAction = ReportActionBase & OriginalMessageReportPreview; + +/** Model of modifies expense action */ type ModifiedExpenseAction = ReportActionBase & OriginalMessageModifiedExpense; /** Record of report actions, indexed by report action ID */ From 2ec341172ba1daa9bd17d29659b9bfbbf62270f4 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 16 May 2024 20:43:38 +0200 Subject: [PATCH 040/419] fix: get token and applicantID from walletOnfidoData --- .../VerifyIdentity/VerifyIdentity.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx index 0bcb0beb675f..c2023077af50 100644 --- a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx @@ -47,11 +47,15 @@ type VerifyIdentityProps = VerifyIdentityOnyxProps; const ONFIDO_ERROR_DISPLAY_DURATION = 10000; -function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { +function VerifyIdentity({personalBankAccount, walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - console.log({personalBankAccount, onfidoApplicantID, onfidoToken, walletOnfidoData}); + const openOnfidoFlow = () => { + BankAccounts.openOnfidoFlow(); + }; + + const {isLoading = false, hasAcceptedPrivacyPolicy, sdkToken, applicantID} = walletOnfidoData; const handleOnfidoSuccess = useCallback( (onfidoData: OnfidoData) => { @@ -63,7 +67,7 @@ function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, wa }); BankAccounts.updateAddPersonalBankAccountDraft({isOnfidoSetupComplete: true}); }, - [personalBankAccount, onfidoApplicantID], + [personalBankAccount, applicantID], ); const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) ?? ''; @@ -84,8 +88,6 @@ function VerifyIdentity({personalBankAccount, onfidoApplicantID, onfidoToken, wa Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS); }; - const {isLoading = false, hasAcceptedPrivacyPolicy} = walletOnfidoData; - return ( {hasAcceptedPrivacyPolicy ? ( {}} + onSubmit={openOnfidoFlow} onFixTheErrorsLinkPressed={() => {}} message={onfidoError} isLoading={isLoading} @@ -145,12 +147,6 @@ export default withOnyx({ personalBankAccount: { key: ONYXKEYS.PERSONAL_BANK_ACCOUNT, }, - onfidoApplicantID: { - key: ONYXKEYS.ONFIDO_APPLICANT_ID, - }, - onfidoToken: { - key: ONYXKEYS.ONFIDO_TOKEN, - }, walletOnfidoData: { key: ONYXKEYS.WALLET_ONFIDO, From d8fc20b3c8056f80f0ff498780b8e2b4cd975881 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 17 May 2024 14:30:07 +0530 Subject: [PATCH 041/419] minor fixes. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 47 ++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e563bbc14500..8454ea5b918c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -14,7 +14,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; -import * as ReportUtils from '@libs/ReportUtils'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; @@ -2392,6 +2391,14 @@ function isReportFieldOfTypeTitle(reportField: OnyxEntry): bo return reportField?.type === 'formula' && reportField?.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID; } +/** + * Check if Report has any held expenses + */ +function isHoldCreator(transaction: OnyxEntry, reportID: string): boolean { + const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); + return isActionCreator(holdReportAction); +} + /** * Check if report fields are available to use in a report */ @@ -2731,8 +2738,8 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH return {canHoldRequest: false, canUnholdRequest: false}; } - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); - const isApproved = ReportUtils.isReportApproved(moneyRequestReport); + const isRequestSettled = isSettled(moneyRequestReport?.reportID); + const isApproved = isReportApproved(moneyRequestReport); const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; @@ -2741,23 +2748,22 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); const isRequestIOU = parentReport?.type === 'iou'; - const isHoldCreator = ReportUtils.isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; - const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(moneyRequestReport); + const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; + const isTrackExpenseMoneyReport = isTrackExpenseReport(moneyRequestReport); const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof currentUserPersonalDetails?.accountID === 'number' && parentReportAction.actorAccountID === currentUserPersonalDetails?.accountID; - const isApprover = - ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; + const isApprover = isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; const isOnHold = TransactionUtils.isOnHold(transaction); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); - const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover); + const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); - const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction; - const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; - const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))); + const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; + const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; + const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus))); return {canHoldRequest, canUnholdRequest}; } @@ -2773,21 +2779,16 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): vo return; } - const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; - const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); - const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transactionID = reportAction?.originalMessage?.IOUTransactionID ?? ''; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const isOnHold = TransactionUtils.isOnHold(transaction); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport.policyID}`] ?? null; - // const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; - if (isOnHold) { - IOU.unholdRequest(transactionID, reportAction.childReportID); + IOU.unholdRequest(transactionID, reportAction.childReportID ?? ''); } else { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); - Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID, activeRoute)); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID ?? '', activeRoute)); } }; @@ -6294,14 +6295,6 @@ function navigateToPrivateNotes(report: OnyxEntry, session: OnyxEntry, reportID: string): boolean { - const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); - return isActionCreator(holdReportAction); -} - /** * Get all held transactions of a iouReport */ From 6b33432a88f50a2c7501ebaaade4bafa74dc9a96 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 17 May 2024 19:23:06 +0800 Subject: [PATCH 042/419] don't set pointer events to none for deleted parent action --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 95bc4f5231d2..2bb5d59f674b 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -933,7 +933,7 @@ function ReportActionItem({ isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onSecondaryInteraction={showPopover} From 4e0968d9a4385d1e9c30c008fce19e11067bcea3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 18 May 2024 17:13:00 +0700 Subject: [PATCH 043/419] fix Inconsistency while pasting highlighted mention in room and expense description --- src/pages/RoomDescriptionPage.tsx | 1 + src/pages/workspace/WorkspaceNewRoomPage.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx index 3992dff188e2..2cab7a4ed40b 100644 --- a/src/pages/RoomDescriptionPage.tsx +++ b/src/pages/RoomDescriptionPage.tsx @@ -97,6 +97,7 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) { value={description} onChangeText={handleReportDescriptionChange} autoCapitalize="none" + isMarkdownEnabled />
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 5716812ced16..5004bac942cd 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -295,6 +295,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli maxLength={CONST.REPORT_DESCRIPTION.MAX_LENGTH} autoCapitalize="none" shouldInterceptSwipe + isMarkdownEnabled />
From 4706357c1d51789d9f747f5f0b90ac472e04952b Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 20 May 2024 18:09:32 +0200 Subject: [PATCH 044/419] feat: change wallet agreement url for bancorp --- src/CONST.ts | 1 + .../FeesAndTerms/substeps/TermsStep.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6517ece4276d..af5f32808034 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -574,6 +574,7 @@ const CONST = { LICENSES_URL: `${USE_EXPENSIFY_URL}/licenses`, ACH_TERMS_URL: `${USE_EXPENSIFY_URL}/achterms`, WALLET_AGREEMENT_URL: `${USE_EXPENSIFY_URL}/walletagreement`, + BANCORP_WALLET_AGREEMENT_URL: `${USE_EXPENSIFY_URL}/bancorp-bank-wallet-terms-of-service`, HELP_LINK_URL: `${USE_EXPENSIFY_URL}/usa-patriot-act`, ELECTRONIC_DISCLOSURES_URL: `${USE_EXPENSIFY_URL}/esignagreement`, GITHUB_RELEASE_URL: 'https://api.github.com/repos/expensify/app/releases/latest', diff --git a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx index fe17ea7e1afb..270f0c1f1699 100644 --- a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx @@ -9,8 +9,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@navigation/Navigation'; -// TODO: uncomment at the end of the refactor https://github.com/Expensify/App/issues/36648 -// import * as BankAccounts from '@userActions/BankAccounts'; +import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -28,13 +27,15 @@ function HaveReadAndAgreeLabel() { function AgreeToTheLabel() { const {translate} = useLocalize(); + const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + const walletAgreementUrl = userWallet?.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID ? CONST.WALLET_AGREEMENT_URL : CONST.BANCORP_WALLET_AGREEMENT_URL; return ( {`${translate('termsStep.agreeToThe')} `} {`${translate('common.privacy')} `} {`${translate('common.and')} `} - {`${translate('termsStep.walletAgreement')}.`} + {`${translate('termsStep.walletAgreement')}.`} ); } @@ -93,11 +94,10 @@ function TermsStep() { } setError(false); - // TODO: uncomment at the end of the refactor https://github.com/Expensify/App/issues/36648 - // BankAccounts.acceptWalletTerms({ - // hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, - // reportID: walletTerms?.chatReportID ?? '', - // }); + BankAccounts.acceptWalletTerms({ + hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, + reportID: walletTerms?.chatReportID ?? '', + }); Navigation.navigate(ROUTES.SETTINGS_WALLET); }} message={errorMessage} From 96f6955166498e0b4a08f377fd057d9f8b7149dc Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 20 May 2024 23:47:50 +0100 Subject: [PATCH 045/419] docs: apply pull request suggestions --- src/types/onyx/BankAccount.ts | 4 ++-- src/types/onyx/BlockedFromConcierge.ts | 2 +- src/types/onyx/Card.ts | 8 ++++---- src/types/onyx/Console.ts | 2 +- src/types/onyx/Fund.ts | 2 +- src/types/onyx/IOU.ts | 26 +++++++++++++------------- src/types/onyx/WalletOnfido.ts | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index 6ca40f37093d..33d35d547935 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -39,10 +39,10 @@ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ /* Determines if the bank account is a savings account */ isSavings?: boolean; - /** Date when the 3 micro amounts for validation were supposed to reach the bank account. */ + /** Date when the 3 micro amounts for validation were supposed to reach the bank account */ validateCodeExpectedDate?: string; - /** string like 'bankAccount-{\}' where is the bankAccountID */ + /** string like `bankAccount-` */ key?: string; /** Alias for bankAccountID */ diff --git a/src/types/onyx/BlockedFromConcierge.ts b/src/types/onyx/BlockedFromConcierge.ts index 7274602bee38..ed4381076555 100644 --- a/src/types/onyx/BlockedFromConcierge.ts +++ b/src/types/onyx/BlockedFromConcierge.ts @@ -1,4 +1,4 @@ -/** Model of blocked from concierge */ +/** Model of blocked user from concierge */ type BlockedFromConcierge = { /** The date that the user will be unblocked */ expiresAt: string; diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 4ede25c34be6..7c958a4e8bd1 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -2,7 +2,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; -/** Model of expensify card */ +/** Model of Expensify card */ type Card = { /** Card ID number */ cardID: number; @@ -22,7 +22,7 @@ type Card = { /** Last four Primary Account Number digits */ lastFourPAN?: string; - /** Determines if the current card was flagged as fraud */ + /** Current fraud state of the card */ fraud: ValueOf; /** Card related error messages */ @@ -64,7 +64,7 @@ type Card = { }; }; -/** Model of expensify card details */ +/** Model of Expensify card details */ type TCardDetails = { /** Card Primary Account Number */ pan: string; @@ -98,7 +98,7 @@ type TCardDetails = { }; }; -/** Record of expensify cards, indexed by cardID */ +/** Record of Expensify cards, indexed by cardID */ type CardList = Record; export default Card; diff --git a/src/types/onyx/Console.ts b/src/types/onyx/Console.ts index 371782fe9156..c8d2b714ae2b 100644 --- a/src/types/onyx/Console.ts +++ b/src/types/onyx/Console.ts @@ -12,7 +12,7 @@ type Log = { message: string; }; -/** Record of logs */ +/** Record of captured logs */ type CapturedLogs = Record; export type {Log, CapturedLogs}; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index 3a527a45db2c..3cb7255e4bfe 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -61,7 +61,7 @@ type Fund = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Debit card description */ description?: string; - /** String like 'fund-{}' where is the fundID */ + /** String like `fund-` */ key?: string; /** Alias for fundID */ diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 20f9a0ea4677..9c04ad59ef3a 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -116,46 +116,46 @@ type Split = { /** Model of IOU request */ type IOU = { - /** IOU ID */ + /** ID of the IOU request */ id: string; - /** IOU amount */ + /** Amount requested in IOU */ amount?: number; /** Selected Currency Code of the current IOU */ currency?: string; - /** IOU comment */ + /** Comment of the IOU request creator */ comment?: string; - /** IOU category */ + /** Category assigned to the IOU request */ category?: string; - /** IOU merchant */ + /** Merchant where the amount was spent */ merchant?: string; - /** IOU creation date */ + /** Date timestamp when the IOU request was created */ created?: string; - /** IOU receipt file path */ + /** Local file path of the expense receipt */ receiptPath?: string; - /** IOU comment */ + /** File name of the expense receipt */ receiptFilename?: string; - /** IOU transaction ID */ + /** Transaction ID assigned to the IOU request */ transactionID?: string; - /** IOU participants */ + /** Users involved in the IOU request */ participants?: Participant[]; - /** IOU tag */ + /** Tag assigned to the IOU request */ tag?: string; - /** Is IOU billable */ + /** Whether the IOU request is billable */ billable?: boolean; - /** Is an IOU split request */ + /** Whether the IOU request is to be split with multiple users */ isSplitRequest?: boolean; }; diff --git a/src/types/onyx/WalletOnfido.ts b/src/types/onyx/WalletOnfido.ts index d144431eff9f..344ffdef170c 100644 --- a/src/types/onyx/WalletOnfido.ts +++ b/src/types/onyx/WalletOnfido.ts @@ -1,6 +1,6 @@ import type * as OnyxCommon from './OnyxCommon'; -/** Model of wallet onfido */ +/** Model of wallet Onfido flow */ type WalletOnfido = { /** Unique identifier returned from openOnfidoFlow then re-sent to ActivateWallet with Onfido response data */ applicantID?: string; From ce10020ffdc791278c1f1a83fcfca44f170d2c30 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 21 May 2024 12:32:37 +0200 Subject: [PATCH 046/419] Fixing the chat icon navigation --- src/ONYXKEYS.ts | 4 ++++ .../ActiveWorkspaceProvider.tsx | 15 +++++++++--- .../BottomTabBar.tsx | 23 +++++++++++-------- src/libs/actions/Policy.ts | 5 ++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ddf37fba2354..5d91ddcfa04a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -75,6 +75,9 @@ const ONYXKEYS = { * rates and units for different workspaces at the same time. */ WORKSPACE_RATE_AND_UNIT: 'workspaceRateAndUnit', + /** Contains the last active workspace ID */ + ACTIVE_WORKSPACE_ID: 'activeWorkspaceID', + /** Contains a list of all currencies available to the user - user can * select a currency based on the list */ CURRENCY_LIST: 'currencyList', @@ -601,6 +604,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PERSONAL_DETAILS_METADATA]: Record; [ONYXKEYS.TASK]: OnyxTypes.Task; [ONYXKEYS.WORKSPACE_RATE_AND_UNIT]: OnyxTypes.WorkspaceRateAndUnit; + [ONYXKEYS.ACTIVE_WORKSPACE_ID]: string | undefined; [ONYXKEYS.CURRENCY_LIST]: OnyxTypes.CurrencyList; [ONYXKEYS.UPDATE_AVAILABLE]: boolean; [ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest; diff --git a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx index 884b9a2a2d95..2d1b62ba9a65 100644 --- a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx +++ b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx @@ -1,16 +1,25 @@ -import React, {useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; +import * as Policy from '@libs/actions/Policy'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import ActiveWorkspaceContext from './ActiveWorkspaceContext'; function ActiveWorkspaceContextProvider({children}: ChildrenProps) { - const [activeWorkspaceID, setActiveWorkspaceID] = useState(undefined); + const [activeWorkspaceID, updateActiveWorkspaceID] = useState(undefined); + + const setActiveWorkspaceID = useCallback( + (workspaceID: string | undefined) => { + Policy.setActiveWorkspaceID(workspaceID); + updateActiveWorkspaceID(workspaceID); + }, + [updateActiveWorkspaceID], + ); const value = useMemo( () => ({ activeWorkspaceID, setActiveWorkspaceID, }), - [activeWorkspaceID], + [activeWorkspaceID, setActiveWorkspaceID], ); return {children}; diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 7f26177eeb0f..71da79a2f7cd 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -1,5 +1,5 @@ import {useNavigation, useNavigationState} from '@react-navigation/native'; -import React, {useEffect} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -24,20 +24,19 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; +import ROUTES, {Route} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; type PurposeForUsingExpensifyModalOnyxProps = { isLoadingApp: OnyxEntry; + activeWorkspaceID: OnyxEntry; }; type PurposeForUsingExpensifyModalProps = PurposeForUsingExpensifyModalOnyxProps; -function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps) { +function BottomTabBar({isLoadingApp = false, activeWorkspaceID}: PurposeForUsingExpensifyModalProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {activeWorkspaceID} = useActiveWorkspace(); - const navigation = useNavigation(); useEffect(() => { @@ -66,15 +65,18 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps return topmostBottomTabRoute?.name ?? SCREENS.HOME; }); - const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); + const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID as string | undefined); + + const navigateToChats = useCallback(() => { + const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/r` as Route) : ROUTES.HOME; + Navigation.navigate(route); + }, [activeWorkspaceID]); return ( { - Navigation.navigate(ROUTES.HOME); - }} + onPress={navigateToChats} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.chats')} wrapperStyle={styles.flex1} @@ -105,4 +107,7 @@ export default withOnyx Date: Tue, 21 May 2024 16:24:24 +0530 Subject: [PATCH 047/419] aligm status emoji to center. Signed-off-by: Krishna Gupta --- .../home/sidebar/AvatarWithOptionalStatus.tsx | 14 ++++++++------ src/styles/index.ts | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index 609e4044002e..5d4af7ea4961 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -19,12 +19,14 @@ function AvatarWithOptionalStatus({emojiStatus = '', isSelected = false}: Avatar - - {emojiStatus} - + + + {emojiStatus} + + ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 2b3207eee4a8..5f160009d923 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4335,11 +4335,11 @@ const styles = (theme: ThemeColors) => emojiStatusLHN: { fontSize: 9, - ...(Browser.getBrowser() && !Browser.isMobile() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15, overflow: 'visible'}), + ...(Browser.getBrowser() && !Browser.isMobile() && {transform: 'scale(.5)', fontSize: 22, overflow: 'visible'}), ...(Browser.getBrowser() && Browser.isSafari() && !Browser.isMobile() && { - transform: 'scale(0.65)', + transform: 'scale(0.7)', fontSize: 13, lineHeight: 15, overflow: 'visible', From 9f330e4d6f101b5cb6c6bffc3c28b847bed73af3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 21 May 2024 18:38:16 +0530 Subject: [PATCH 048/419] canHoldUnholdReportAction util function refactoring. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 21 ++++--------------- .../report/ContextMenu/ContextMenuActions.tsx | 2 -- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8454ea5b918c..23874dc2da13 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2715,26 +2715,15 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } -function getParentReportAction(parentReportActions: ReportActions | null | undefined, parentReportActionID: string | undefined): ReportAction | null { - if (!parentReportActions || !parentReportActionID) { - return null; - } - return parentReportActions[parentReportActionID ?? '0']; -} - function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return {canHoldRequest: false, canUnholdRequest: false}; } - const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; - - if (!moneyRequestReportID) { - return {canHoldRequest: false, canUnholdRequest: false}; - } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; const moneyRequestReport = getReport(String(moneyRequestReportID)); - if (!moneyRequestReport) { + if (!moneyRequestReportID || !moneyRequestReport) { return {canHoldRequest: false, canUnholdRequest: false}; } @@ -2742,10 +2731,8 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isApproved = isReportApproved(moneyRequestReport); const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); - const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReport = getReport(String(moneyRequestReport.parentReportID)); - const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); + const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); const isRequestIOU = parentReport?.type === 'iou'; const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; @@ -2759,7 +2746,7 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + const isDeletedParentAction = !isEmpty(parentReportAction) ? ReportActionsUtils.isDeletedAction(parentReportAction as ReportAction) : true; const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 5d5b6c070eb0..5ccdefce0646 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -262,7 +262,6 @@ const ContextMenuActions: ContextMenuAction[] = [ shouldShow: (type, reportAction) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canUnholdRequest, onPress: (closePopover, {reportAction}) => { - // const hold=ReportUtils.changeMoneyRequestHoldStatus(). if (closePopover) { hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); return; @@ -280,7 +279,6 @@ const ContextMenuActions: ContextMenuAction[] = [ shouldShow: (type, reportAction) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canHoldRequest, onPress: (closePopover, {reportAction}) => { - // const hold=ReportUtils.changeMoneyRequestHoldStatus(). if (closePopover) { hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); return; From 0fac245e625ef26324c3b32c52f259698906dfa9 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 22 May 2024 02:21:04 +0530 Subject: [PATCH 049/419] add getParentReportAction in ReportUtils. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 15 +++++++++++++-- src/pages/home/ReportScreen.tsx | 13 +++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 23874dc2da13..d5f0cc8a3158 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2715,6 +2715,13 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } +function getParentReportAction(parentReportActions: OnyxEntry, parentReportActionID: string | undefined): OnyxEntry { + if (!parentReportActions || !parentReportActionID) { + return null; + } + return parentReportActions[parentReportActionID ?? '0']; +} + function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return {canHoldRequest: false, canUnholdRequest: false}; @@ -2731,8 +2738,11 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isApproved = isReportApproved(moneyRequestReport); const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + + const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; + const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReport = getReport(String(moneyRequestReport.parentReportID)); - const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); + const parentReportAction = getParentReportAction(parentReportActions ?? {}, moneyRequestReport?.parentReportActionID); const isRequestIOU = parentReport?.type === 'iou'; const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; @@ -2746,7 +2756,7 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); - const isDeletedParentAction = !isEmpty(parentReportAction) ? ReportActionsUtils.isDeletedAction(parentReportAction as ReportAction) : true; + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; @@ -6847,6 +6857,7 @@ export { getOriginalReportID, getOutstandingChildRequest, getParentNavigationSubtitle, + getParentReportAction, getParsedComment, getParticipantAccountIDs, getParticipants, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 8620d8d4866e..d55f5f42b34d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -124,13 +124,6 @@ function isEmpty(report: OnyxTypes.Report): boolean { return !Object.values(report).some((value) => value !== undefined && value !== ''); } -function getParentReportAction(parentReportActions: OnyxEntry, parentReportActionID: string | undefined): OnyxEntry { - if (!parentReportActions || !parentReportActionID) { - return null; - } - return parentReportActions[parentReportActionID ?? '0']; -} - function ReportScreen({ betas = [], route, @@ -261,7 +254,7 @@ function ReportScreen({ ], ); - const parentReportAction = useMemo(() => getParentReportAction(parentReportActions, report?.parentReportActionID), [parentReportActions, report.parentReportActionID]); + const parentReportAction = useMemo(() => ReportUtils.getParentReportAction(parentReportActions, report?.parentReportActionID), [parentReportActions, report.parentReportActionID]); const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); @@ -813,8 +806,8 @@ export default withCurrentReportID( }, })( memo(ReportScreen, (prevProps, nextProps) => { - const prevParentReportAction = getParentReportAction(prevProps.parentReportActions, prevProps.report?.parentReportActionID); - const nextParentReportAction = getParentReportAction(nextProps.parentReportActions, nextProps.report?.parentReportActionID); + const prevParentReportAction = ReportUtils.getParentReportAction(prevProps.parentReportActions, prevProps.report?.parentReportActionID); + const nextParentReportAction = ReportUtils.getParentReportAction(nextProps.parentReportActions, nextProps.report?.parentReportActionID); return ( prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && lodashIsEqual(prevProps.sortedAllReportActions, nextProps.sortedAllReportActions) && From 65022fb6ef5ed7962007f5877eea6766eee2180f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 22 May 2024 10:14:59 +0100 Subject: [PATCH 050/419] refactor: apply pull request suggestions --- src/libs/ModifiedExpenseMessage.ts | 4 +- src/libs/ReportUtils.ts | 31 +----- src/libs/actions/Card.ts | 6 +- src/libs/models/BankAccount.ts | 4 +- .../settings/Wallet/ExpensifyCardPage.tsx | 8 +- src/types/onyx/AccountData.ts | 26 +---- src/types/onyx/BankAccount.ts | 5 +- src/types/onyx/Card.ts | 4 +- src/types/onyx/Fund.ts | 12 +-- src/types/onyx/OriginalMessage.ts | 51 ++++----- src/types/onyx/Transaction.ts | 6 +- src/types/onyx/TransactionViolation.ts | 102 +++++++++--------- 12 files changed, 103 insertions(+), 156 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 2df75030ac19..77a77281a95e 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -3,12 +3,12 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyTagList, ReportAction} from '@src/types/onyx'; +import type {ModifiedExpense} from '@src/types/onyx/OriginalMessage'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import getReportPolicyID from './getReportPolicyID'; import * as Localize from './Localize'; import * as PolicyUtils from './PolicyUtils'; -import type {ExpenseOriginalMessage} from './ReportUtils'; import * as TransactionUtils from './TransactionUtils'; let allPolicyTags: OnyxCollection = {}; @@ -109,7 +109,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { return ''; } - const reportActionOriginalMessage = reportAction?.originalMessage as ExpenseOriginalMessage | undefined; + const reportActionOriginalMessage = reportAction?.originalMessage as ModifiedExpense | undefined; const policyID = getReportPolicyID(reportID) ?? ''; const removalFragments: string[] = []; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 99e21c243c73..47c9b12dec6f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -41,6 +41,7 @@ import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import type { ChangeLog, IOUMessage, + ModifiedExpense, OriginalMessageActionName, OriginalMessageCreated, OriginalMessageDismissedViolation, @@ -86,31 +87,6 @@ type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | type WelcomeMessage = {showReportName: boolean; phrase1?: string; phrase2?: string}; -/** TODO: I'd move this to `OriginalMessage.ts` and add it to `OriginalMessageModifiedExpense` type */ -type ExpenseOriginalMessage = { - oldComment?: string; - newComment?: string; - comment?: string; - merchant?: string; - oldCreated?: string; - created?: string; - oldMerchant?: string; - oldAmount?: number; - amount?: number; - oldCurrency?: string; - currency?: string; - category?: string; - oldCategory?: string; - tag?: string; - oldTag?: string; - billable?: string; - oldBillable?: string; - oldTaxAmount?: number; - taxAmount?: number; - taxRate?: string; - oldTaxRate?: string; -}; - type SpendBreakdown = { nonReimbursableSpend: number; reimbursableSpend: number; @@ -2970,8 +2946,8 @@ function getModifiedExpenseOriginalMessage( transactionChanges: TransactionChanges, isFromExpenseReport: boolean, policy: OnyxEntry, -): ExpenseOriginalMessage { - const originalMessage: ExpenseOriginalMessage = {}; +): ModifiedExpense { + const originalMessage: ModifiedExpense = {}; // Remark: Comment field is the only one which has new/old prefixes for the keys (newComment/ oldComment), // all others have old/- pattern such as oldCreated/created if ('comment' in transactionChanges) { @@ -6947,7 +6923,6 @@ export { export type { Ancestor, DisplayNameWithTooltips, - ExpenseOriginalMessage, OptimisticAddCommentReportAction, OptimisticChatReport, OptimisticClosedReportAction, diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 756ef902d913..9a011d88e582 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -5,7 +5,7 @@ import type {ActivatePhysicalExpensifyCardParams, ReportVirtualExpensifyCardFrau import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Response} from '@src/types/onyx'; +import type {ExpensifyCardDetails} from '@src/types/onyx/Card'; type ReplacementReason = 'damaged' | 'stolen'; @@ -158,7 +158,7 @@ function clearCardListErrors(cardID: number) { * * @returns promise with card details object */ -function revealVirtualCardDetails(cardID: number): Promise { +function revealVirtualCardDetails(cardID: number): Promise { return new Promise((resolve, reject) => { const parameters: RevealExpensifyCardDetailsParams = {cardID}; @@ -170,7 +170,7 @@ function revealVirtualCardDetails(cardID: number): Promise { reject('cardPage.cardDetailsLoadingFailure'); return; } - resolve(response); + resolve(response as ExpensifyCardDetails); }) // eslint-disable-next-line prefer-promise-reject-errors .catch(() => reject('cardPage.cardDetailsLoadingFailure')); diff --git a/src/libs/models/BankAccount.ts b/src/libs/models/BankAccount.ts index 611d77c99927..198b8f25334b 100644 --- a/src/libs/models/BankAccount.ts +++ b/src/libs/models/BankAccount.ts @@ -1,7 +1,7 @@ import Str from 'expensify-common/lib/str'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; -import type {AdditionalData} from '@src/types/onyx/BankAccount'; +import type {BankAccountAdditionalData} from '@src/types/onyx/BankAccount'; import type BankAccountJSON from '@src/types/onyx/BankAccount'; type State = ValueOf; @@ -194,7 +194,7 @@ class BankAccount { /** * Get the additional data of a bankAccount */ - getAdditionalData(): Partial { + getAdditionalData(): Partial { return this.json.accountData?.additionalData ?? {}; } diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index 2f4135820b08..6ab2554c2cda 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -32,7 +32,7 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {GetPhysicalCardForm} from '@src/types/form'; import type {LoginList, Card as OnyxCard, PrivatePersonalDetails} from '@src/types/onyx'; -import type {TCardDetails} from '@src/types/onyx/Card'; +import type {ExpensifyCardDetails} from '@src/types/onyx/Card'; import RedDotCardSection from './RedDotCardSection'; import CardDetails from './WalletPage/CardDetails'; @@ -101,7 +101,7 @@ function ExpensifyCardPage({ const virtualCards = useMemo(() => cardsToShow?.filter((card) => card?.nameValuePairs?.isVirtual), [cardsToShow]); const physicalCards = useMemo(() => cardsToShow?.filter((card) => !card?.nameValuePairs?.isVirtual), [cardsToShow]); - const [cardsDetails, setCardsDetails] = useState>({}); + const [cardsDetails, setCardsDetails] = useState>({}); const [isCardDetailsLoading, setIsCardDetailsLoading] = useState>({}); const [cardsDetailsErrors, setCardsDetailsErrors] = useState>({}); @@ -116,9 +116,7 @@ function ExpensifyCardPage({ // eslint-disable-next-line rulesdir/no-thenable-actions-in-views Card.revealVirtualCardDetails(revealedCardID) .then((value) => { - // TODO: Card.revealVirtualCardDetails return type doesn't include TCardDetails, forcing us to type cast it here. - // The return type could be rewritten like Promise - setCardsDetails((prevState: Record) => ({...prevState, [revealedCardID]: value as TCardDetails})); + setCardsDetails((prevState: Record) => ({...prevState, [revealedCardID]: value})); setCardsDetailsErrors((prevState) => ({ ...prevState, [revealedCardID]: '', diff --git a/src/types/onyx/AccountData.ts b/src/types/onyx/AccountData.ts index 78c8391ac8ae..010715f15f85 100644 --- a/src/types/onyx/AccountData.ts +++ b/src/types/onyx/AccountData.ts @@ -1,28 +1,6 @@ -import type {BankName} from './Bank'; +import type {BankAccountAdditionalData} from './BankAccount'; import type * as OnyxCommon from './OnyxCommon'; -/** Model of additional bank account data */ -type AdditionalData = { - /** Is a Peer-To-Peer debit card */ - isP2PDebitCard?: boolean; - - /** Owners that can benefit from this bank account */ - beneficialOwners?: string[]; - - /** In which currency is the bank account */ - currency?: string; - - /** In which bank is the bank account */ - bankName?: BankName; - - // TODO: Confirm this - /** Whether the bank account is local or international */ - fieldsType?: string; - - /** In which country is the bank account */ - country?: string; -}; - /** Model of bank account data */ type AccountData = { /** The masked bank account number */ @@ -59,7 +37,7 @@ type AccountData = { bankAccountID?: number; /** All data related to the bank account */ - additionalData?: AdditionalData; + additionalData?: BankAccountAdditionalData; /** The bank account type */ type?: string; diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index 33d35d547935..1a2505a83592 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -2,9 +2,8 @@ import type CONST from '@src/CONST'; import type AccountData from './AccountData'; import type * as OnyxCommon from './OnyxCommon'; -// TODO: This type is a duplicate of the one present in AccountData.ts /** Model of additional bank account data */ -type AdditionalData = { +type BankAccountAdditionalData = { /** Is a Peer-To-Peer Debit Card */ isP2PDebitCard?: boolean; @@ -62,4 +61,4 @@ type BankAccount = OnyxCommon.OnyxValueWithOfflineFeedback<{ type BankAccountList = Record; export default BankAccount; -export type {AccountData, AdditionalData, BankAccountList}; +export type {AccountData, BankAccountAdditionalData, BankAccountList}; diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 7c958a4e8bd1..9e683a29ee06 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -65,7 +65,7 @@ type Card = { }; /** Model of Expensify card details */ -type TCardDetails = { +type ExpensifyCardDetails = { /** Card Primary Account Number */ pan: string; @@ -102,4 +102,4 @@ type TCardDetails = { type CardList = Record; export default Card; -export type {TCardDetails, CardList}; +export type {ExpensifyCardDetails, CardList}; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index 3cb7255e4bfe..8ee8fe42e734 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -1,20 +1,12 @@ import type CONST from '@src/CONST'; import type {BankName} from './Bank'; +import type {BankAccountAdditionalData} from './BankAccount'; import type * as OnyxCommon from './OnyxCommon'; -/** Mode of additional debit card account data */ -type AdditionalData = { - // TODO: Not used in app explicitly - isBillingCard?: boolean; - - /** Is Peer-To-Peer debit card */ - isP2PDebitCard?: boolean; -}; - /** Model of debit card account data */ type AccountData = { /** Additional account data */ - additionalData?: AdditionalData; + additionalData?: BankAccountAdditionalData; /** Address name */ addressName?: string; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index c54602503bae..734ffc3cf064 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -389,6 +389,31 @@ type ChronosOOOEvent = { end: ChronosOOOTimestamp; }; +/** Model of modified expense */ +type ModifiedExpense = { + oldComment?: string; + newComment?: string; + comment?: string; + merchant?: string; + oldCreated?: string; + created?: string; + oldMerchant?: string; + oldAmount?: number; + amount?: number; + oldCurrency?: string; + currency?: string; + category?: string; + oldCategory?: string; + tag?: string; + oldTag?: string; + billable?: string; + oldBillable?: string; + oldTaxAmount?: number; + taxAmount?: number; + taxRate?: string; + oldTaxRate?: string; +}; + /** Model of `Chronos OOO List` report action */ type OriginalMessageChronosOOOList = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST; @@ -470,30 +495,7 @@ type OriginalMessagePolicyTask = { /** Model of `modified expense` report action */ type OriginalMessageModifiedExpense = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE; - /** TODO: I think this type could be replaced by `ExpenseOriginalMessage` from `ReportUtils.ts` */ - originalMessage: { - oldMerchant?: string; - merchant?: string; - oldCurrency?: string; - currency?: string; - oldAmount?: number; - amount?: number; - oldComment?: string; - newComment?: string; - oldCreated?: string; - created?: string; - oldCategory?: string; - category?: string; - oldTag?: string; - tag?: string; - oldTaxAmount?: number; - taxAmount?: number; - oldTaxRate?: string; - taxRate?: string; - oldBillable?: string; - billable?: string; - whisperedTo?: number[]; - }; + originalMessage: ModifiedExpense; }; /** Model of `reimbursement queued` report action */ @@ -607,6 +609,7 @@ export type { Closed, OriginalMessageActionName, ChangeLog, + ModifiedExpense, OriginalMessageIOU, OriginalMessageCreated, OriginalMessageRenamed, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index a7f61a6f07b4..844bacc9c66c 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -61,9 +61,8 @@ type Comment = { /** Whether the transaction comment is loading */ isLoading?: boolean; - /** TODO: I think this type can be changed to `ValueOf` */ /** Type of the transaction */ - type?: string; + type?: ValueOf; /** In custom unit transactions this holds the information of the custom unit */ customUnit?: TransactionCustomUnit; @@ -89,9 +88,8 @@ type TransactionCustomUnit = { /** Custom unit amount */ quantity?: number; - /** TODO: I think this value can be changed to `ValueOf` */ /** Name of the custom unit */ - name?: string; + name?: ValueOf; /** Default rate for custom unit */ defaultP2PRate?: number; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 49cdf51c1eb5..821bbdcb1fa4 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -1,79 +1,83 @@ +import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; /** * Names of violations. * Derived from `CONST.VIOLATIONS` to maintain a single source of truth. */ -type ViolationName = (typeof CONST.VIOLATIONS)[keyof typeof CONST.VIOLATIONS]; +type ViolationName = ValueOf; -/** Model of a transaction violation */ -type TransactionViolation = { - /** Type of transaction violation ('violation', 'notice', 'warning', ...) */ - type: string; +/** Model of transaction violation data */ +type TransactionViolationData = { + /** Who rejected the transaction */ + rejectedBy?: string; - /** Name of the transaction violation */ - name: ViolationName; + /** Why the transaction was rejected */ + rejectReason?: string; - /** Additional violation information to provide the user */ - data?: { - /** Who rejected the transaction */ - rejectedBy?: string; + /** Limit that the transaction violated */ + formattedLimit?: string; + + /** Percentage amount of conversion surcharge applied to the transaction */ + surcharge?: number; - /** Why the transaction was rejected */ - rejectReason?: string; + /** Percentage amount of invoice markup applied to the transaction */ + invoiceMarkup?: number; - /** Limit that the transaction violated */ - formattedLimit?: string; + /** Amount of days which the transaction date overpasses the date limit */ + maxAge?: number; - /** Percentage amount of conversion surcharge applied to the transaction */ - surcharge?: number; + /** Name of the tag that triggered this violation */ + tagName?: string; - /** Percentage amount of invoice markup applied to the transaction */ - invoiceMarkup?: number; + // TODO: Doesn't seem to be used in app + categoryLimit?: string; - /** Amount of days which the transaction date overpasses the date limit */ - maxAge?: number; + // TODO: Doesn't seem to be used in app + limit?: string; - /** Name of the tag that triggered this violation */ - tagName?: string; + /** Name of the category that triggered this violation */ + category?: string; - // TODO: Doesn't seem to be used in app - categoryLimit?: string; + /** Whether the transaction failed due to a broken bank connection */ + brokenBankConnection?: boolean; - // TODO: Doesn't seem to be used in app - limit?: string; + /** Whether the workspace admin needs to resolve this violation */ + isAdmin?: boolean; - /** Name of the category that triggered this violation */ - category?: string; + /** Workspace admin email */ + email?: string; - /** Whether the transaction failed due to a broken bank connection */ - brokenBankConnection?: boolean; + /** Whether the transaction is older than 7 days */ + isTransactionOlderThan7Days?: boolean; - /** Whether the workspace admin needs to resolve this violation */ - isAdmin?: boolean; + /** Workspace admin name */ + member?: string; - /** Workspace admin email */ - email?: string; + /** Name of the tax that triggered this violation */ + taxName?: string; - /** Whether the transaction is older than 7 days */ - isTransactionOlderThan7Days?: boolean; + /** Index of the tag form field that triggered this violation */ + tagListIndex?: number; - /** Workspace admin name */ - member?: string; + /** Name of the tag form field that triggered this violation */ + tagListName?: string; - /** Name of the tax that triggered this violation */ - taxName?: string; + /** Collection of form fields that triggered this violation */ + errorIndexes?: number[]; + pendingPattern?: boolean; +}; - /** Index of the tag form field that triggered this violation */ - tagListIndex?: number; +/** Model of a transaction violation */ +type TransactionViolation = { + /** Type of transaction violation ('violation', 'notice', 'warning', ...) */ + type: string; - /** Name of the tag form field that triggered this violation */ - tagListName?: string; + /** Name of the transaction violation */ + name: ViolationName; - /** Collection of form fields that triggered this violation */ - errorIndexes?: number[]; - pendingPattern?: boolean; - }; + /** Additional violation information to provide the user */ + data?: TransactionViolationData; }; /** Collection of transaction violations */ From 8f019fd2507569145f0fcf179c767d46d249e3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Wed, 22 May 2024 15:11:28 +0200 Subject: [PATCH 051/419] enable no-unsafe-call rule --- .eslintrc.js | 1 - .../javascript/getGraphiteString/getGraphiteString.ts | 3 ++- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 6 +++++- tests/unit/markPullRequestsAsDeployedTest.ts | 2 +- workflow_tests/utils/ExtendedAct.ts | 3 ++- workflow_tests/utils/preGenerateTest.ts | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index eb50d1fcc5f3..2cf5ba68aa9a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -100,7 +100,6 @@ module.exports = { __DEV__: 'readonly', }, rules: { - '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', diff --git a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts index c486fdbd39f3..15b5e885c921 100644 --- a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts +++ b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts @@ -32,7 +32,8 @@ const run = () => { } if (current.name && current.meanDuration && current.meanCount && timestamp) { - const formattedName = current.name.split(' ').join('-'); + const currentName = current.name as string; + const formattedName = currentName.split(' ').join('-'); const renderDurationString = `${GRAPHITE_PATH}.${formattedName}.renderDuration ${current.meanDuration} ${timestamp}`; const renderCountString = `${GRAPHITE_PATH}.${formattedName}.renderCount ${current.meanCount} ${timestamp}`; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0b585de8f059..1a490a26867a 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -182,7 +182,11 @@ type SwitchToCurrentReportProps = { callback: () => void; }; -const {RNTextInputReset} = NativeModules; +type RNTextInputResetProps = { + resetKeyboardInput: (nodeHandle: number | null) => void; +}; + +const RNTextInputReset: RNTextInputResetProps = NativeModules.RNTextInputReset; const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; diff --git a/tests/unit/markPullRequestsAsDeployedTest.ts b/tests/unit/markPullRequestsAsDeployedTest.ts index 24a6733d1244..8d8b25968a28 100644 --- a/tests/unit/markPullRequestsAsDeployedTest.ts +++ b/tests/unit/markPullRequestsAsDeployedTest.ts @@ -35,7 +35,7 @@ type CommitData = { }; }; -let run; +let run: () => Promise; const mockGetInput = jest.fn(); const mockGetPullRequest = jest.fn(); diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts index e2bb12ec8e01..7b35eb260ba3 100644 --- a/workflow_tests/utils/ExtendedAct.ts +++ b/workflow_tests/utils/ExtendedAct.ts @@ -17,7 +17,8 @@ type ActOptions = { // @ts-expect-error Override shouldn't be done on private methods wait until https://github.com/kiegroup/act-js/issues/77 is resolved or try to create a params workaround class ExtendedAct extends Act { async parseRunOpts(opts?: ExtendedActOpts): Promise { - const {cwd, actArguments, proxy} = await super['parseRunOpts'](opts); + const parseSuperRunOpts: (opts?: ExtendedActOpts) => Promise = super['parseRunOpts']; + const {cwd, actArguments, proxy} = await parseSuperRunOpts(opts); if (opts?.actor) { actArguments.push('--actor', opts.actor); diff --git a/workflow_tests/utils/preGenerateTest.ts b/workflow_tests/utils/preGenerateTest.ts index 1e7e7bb04184..eb1d2e8f9b69 100644 --- a/workflow_tests/utils/preGenerateTest.ts +++ b/workflow_tests/utils/preGenerateTest.ts @@ -219,7 +219,8 @@ const getMockFileContent = (workflowName: string, jobs: Record { - const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${step.name + const stepName = step.name as string; + const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${stepName .replaceAll(' ', '_') .replaceAll('-', '_') .replaceAll(',', '') From f3e1c8f75fb6ffff6ee4153a06810c58d8cdb591 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 22 May 2024 15:17:48 +0100 Subject: [PATCH 052/419] refactor: apply pull request suggestions --- src/types/onyx/Response.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index 02f0ca62063e..aa060223637b 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -56,10 +56,10 @@ type Response = { /** Short lived auth token generated by API */ shortLivedAuthToken?: string; - // TODO: This doesn't seem to be used in app + /** User authorization token to authorize Pusher connections */ auth?: string; - // TODO: This doesn't seem to be used in app + /** Base64 key to decrypt messages from Pusher encrypted channels */ // eslint-disable-next-line @typescript-eslint/naming-convention shared_secret?: string; }; From 530d6d681c806fab09862755f6c003846cc57556 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 22 May 2024 15:55:25 +0100 Subject: [PATCH 053/419] refactor: remove unused property from wallet additional details --- src/libs/actions/Wallet.ts | 5 ----- src/types/onyx/WalletAdditionalDetails.ts | 3 --- 2 files changed, 8 deletions(-) diff --git a/src/libs/actions/Wallet.ts b/src/libs/actions/Wallet.ts index 045cc34f39ef..3dd3f01c0703 100644 --- a/src/libs/actions/Wallet.ts +++ b/src/libs/actions/Wallet.ts @@ -61,10 +61,6 @@ function setAdditionalDetailsErrors(errorFields: OnyxCommon.ErrorFields) { Onyx.merge(ONYXKEYS.WALLET_ADDITIONAL_DETAILS, {errorFields}); } -function setAdditionalDetailsErrorMessage(additionalErrorMessage: string) { - Onyx.merge(ONYXKEYS.WALLET_ADDITIONAL_DETAILS, {additionalErrorMessage}); -} - /** * Save the source that triggered the KYC wall and optionally the chat report ID associated with the IOU */ @@ -304,7 +300,6 @@ export { openInitialSettingsPage, openEnablePaymentsPage, setAdditionalDetailsErrors, - setAdditionalDetailsErrorMessage, setAdditionalDetailsQuestions, updateCurrentStep, answerQuestionsForWallet, diff --git a/src/types/onyx/WalletAdditionalDetails.ts b/src/types/onyx/WalletAdditionalDetails.ts index bd2ba89d181d..c92fd14390b5 100644 --- a/src/types/onyx/WalletAdditionalDetails.ts +++ b/src/types/onyx/WalletAdditionalDetails.ts @@ -38,9 +38,6 @@ type WalletAdditionalDetails = { /** Which field needs attention? */ errorFields?: OnyxCommon.ErrorFields; - // TODO: this property is not used in app - additionalErrorMessage?: string; - /** Whether the details are being loaded */ isLoading?: boolean; From 9a50d43625b9aa680ea42650ed97fdd4e851497d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 22 May 2024 15:55:48 +0100 Subject: [PATCH 054/419] docs: add missing type description --- src/types/onyx/TransactionViolation.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 821bbdcb1fa4..ffbbb23037d0 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -65,6 +65,8 @@ type TransactionViolationData = { /** Collection of form fields that triggered this violation */ errorIndexes?: number[]; + + /** Whether the current violation is `pending RTER` */ pendingPattern?: boolean; }; From c6028f762608b9dc68b863b24e7e32006b50c1e2 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 22 May 2024 20:39:27 +0200 Subject: [PATCH 055/419] refactor: cleanup --- src/pages/EnablePayments/EnablePayments.tsx | 20 ++------- .../FeesAndTerms/FeesAndTerms.tsx | 16 ++++++- .../FeesAndTerms/substeps/TermsStep.tsx | 6 +-- .../VerifyIdentity/VerifyIdentity.tsx | 42 ++++++------------- src/types/form/PersonalBankAccountForm.ts | 6 ++- 5 files changed, 36 insertions(+), 54 deletions(-) diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx index 6df56afced57..fdee18e8147d 100644 --- a/src/pages/EnablePayments/EnablePayments.tsx +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -7,17 +7,16 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import Navigation from '@libs/Navigation/Navigation'; -import FeesAndTerms from '@pages/EnablePayments/FeesAndTerms/FeesAndTerms'; -import PersonalInfo from '@pages/EnablePayments/PersonalInfo/PersonalInfo'; -import VerifyIdentity from '@pages/EnablePayments/VerifyIdentity/VerifyIdentity'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {UserWallet} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import ActivateStep from './ActivateStep'; import FailedKYC from './FailedKYC'; +import FeesAndTerms from './FeesAndTerms/FeesAndTerms'; +import PersonalInfo from './PersonalInfo/PersonalInfo'; +import VerifyIdentity from './VerifyIdentity/VerifyIdentity'; type EnablePaymentsPageOnyxProps = { /** The user's wallet */ @@ -30,21 +29,13 @@ function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const {isPendingOnfidoResult, hasFailedOnfido} = userWallet ?? {}; - useEffect(() => { if (isOffline) { return; } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (isPendingOnfidoResult || hasFailedOnfido) { - Navigation.navigate(ROUTES.SETTINGS_WALLET, CONST.NAVIGATION.TYPE.UP); - return; - } - Wallet.openEnablePaymentsPage(); - }, [isOffline, isPendingOnfidoResult, hasFailedOnfido]); + }, [isOffline]); if (isEmptyObject(userWallet)) { return ; @@ -73,14 +64,11 @@ function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { switch (currentStep) { case CONST.WALLET.STEP.ADDITIONAL_DETAILS: - case CONST.WALLET.STEP.ADDITIONAL_DETAILS_KBA: return ; case CONST.WALLET.STEP.ONFIDO: return ; case CONST.WALLET.STEP.TERMS: return ; - case CONST.WALLET.STEP.ACTIVATE: - return ; default: return null; } diff --git a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx index b54564eff254..64e99bd9be05 100644 --- a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -8,7 +9,10 @@ import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; +import * as BankAccounts from '@userActions/BankAccounts'; +import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import FeesStep from './substeps/FeesStep'; import TermsStep from './substeps/TermsStep'; @@ -18,13 +22,21 @@ const termsAndFeesSubsteps: Array> = [FeesStep function FeesAndTerms() { const {translate} = useLocalize(); const styles = useThemeStyles(); + const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); - const submit = () => {}; + const submit = () => { + BankAccounts.acceptWalletTerms({ + hasAcceptedTerms: true, + reportID: walletTerms?.chatReportID ?? '', + }); + BankAccounts.clearPersonalBankAccount(); + // TODO: clear wallet draft + Navigation.navigate(ROUTES.SETTINGS_WALLET); + }; const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent: termsAndFeesSubsteps, startFrom: 0, onFinished: submit}); const handleBackButtonPress = () => { if (screenIndex === 0) { - // TODO: temporary for refactor https://github.com/Expensify/App/issues/36648 Navigation.navigate(ROUTES.SETTINGS_WALLET); return; } diff --git a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx index 270f0c1f1699..afbd6d594274 100644 --- a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx @@ -28,6 +28,7 @@ function HaveReadAndAgreeLabel() { function AgreeToTheLabel() { const {translate} = useLocalize(); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + const walletAgreementUrl = userWallet?.walletProgramID === CONST.WALLET.MTL_WALLET_PROGRAM_ID ? CONST.WALLET_AGREEMENT_URL : CONST.BANCORP_WALLET_AGREEMENT_URL; return ( @@ -94,11 +95,6 @@ function TermsStep() { } setError(false); - BankAccounts.acceptWalletTerms({ - hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, - reportID: walletTerms?.chatReportID ?? '', - }); - Navigation.navigate(ROUTES.SETTINGS_WALLET); }} message={errorMessage} isAlertVisible={error || Boolean(errorMessage)} diff --git a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx index c2023077af50..0c27fb5980af 100644 --- a/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx +++ b/src/pages/EnablePayments/VerifyIdentity/VerifyIdentity.tsx @@ -22,23 +22,16 @@ import * as BankAccounts from '@userActions/BankAccounts'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalBankAccount, WalletOnfido} from '@src/types/onyx'; +import type {WalletOnfido} from '@src/types/onyx'; const DEFAULT_WALLET_ONFIDO_DATA = { - loading: false, + isLoading: false, hasAcceptedPrivacyPolicy: false, + sdkToken: '', + applicantID: '', }; type VerifyIdentityOnyxProps = { - /** Reimbursement account from ONYX */ - personalBankAccount: OnyxEntry; - - /** Onfido applicant ID from ONYX */ - onfidoApplicantID: OnyxEntry; - - /** The token required to initialize the Onfido SDK */ - onfidoToken: OnyxEntry; - /** The wallet onfido data */ walletOnfidoData: OnyxEntry; }; @@ -47,7 +40,7 @@ type VerifyIdentityProps = VerifyIdentityOnyxProps; const ONFIDO_ERROR_DISPLAY_DURATION = 10000; -function VerifyIdentity({personalBankAccount, walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { +function VerifyIdentity({walletOnfidoData = DEFAULT_WALLET_ONFIDO_DATA}: VerifyIdentityProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -55,8 +48,6 @@ function VerifyIdentity({personalBankAccount, walletOnfidoData = DEFAULT_WALLET_ BankAccounts.openOnfidoFlow(); }; - const {isLoading = false, hasAcceptedPrivacyPolicy, sdkToken, applicantID} = walletOnfidoData; - const handleOnfidoSuccess = useCallback( (onfidoData: OnfidoData) => { BankAccounts.verifyIdentity({ @@ -67,24 +58,16 @@ function VerifyIdentity({personalBankAccount, walletOnfidoData = DEFAULT_WALLET_ }); BankAccounts.updateAddPersonalBankAccountDraft({isOnfidoSetupComplete: true}); }, - [personalBankAccount, applicantID], + [walletOnfidoData?.applicantID], ); const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) ?? ''; const handleOnfidoError = () => { - // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. Growl.error(translate('onfidoStep.genericError'), ONFIDO_ERROR_DISPLAY_DURATION); - BankAccounts.clearOnfidoToken(); - // BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }; - - const handleOnfidoUserExit = () => { - BankAccounts.clearOnfidoToken(); - // BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); }; - const handleBackButtonPress = () => { + const goBack = () => { Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS); }; @@ -92,7 +75,7 @@ function VerifyIdentity({personalBankAccount, walletOnfidoData = DEFAULT_WALLET_ - {hasAcceptedPrivacyPolicy ? ( + {walletOnfidoData?.hasAcceptedPrivacyPolicy ? ( @@ -125,9 +108,8 @@ function VerifyIdentity({personalBankAccount, walletOnfidoData = DEFAULT_WALLET_ {}} message={onfidoError} - isLoading={isLoading} + isLoading={walletOnfidoData?.isLoading} buttonText={onfidoError ? translate('onfidoStep.tryAgain') : translate('common.continue')} containerStyles={[styles.mh0, styles.mv0, styles.mb0]} /> diff --git a/src/types/form/PersonalBankAccountForm.ts b/src/types/form/PersonalBankAccountForm.ts index 1a1922bf8013..a5b95b20e684 100644 --- a/src/types/form/PersonalBankAccountForm.ts +++ b/src/types/form/PersonalBankAccountForm.ts @@ -32,7 +32,11 @@ type PlaidAccountProps = { [INPUT_IDS.BANK_INFO_STEP.SELECTED_PLAID_ACCOUNT_ID]: string; }; -type PersonalBankAccountForm = Form; +type OnfidoStepProps = { + isOnfidoSetupComplete: boolean; +}; + +type PersonalBankAccountForm = Form & OnfidoStepProps; export type {BankAccountStepProps, PlaidAccountProps, PersonalBankAccountForm}; From ec1846c2568cd745facfbbb103c9f402443c83c1 Mon Sep 17 00:00:00 2001 From: dragnoir Date: Thu, 23 May 2024 12:35:47 +0100 Subject: [PATCH 056/419] Fix; add spacing between comment and Uploading attachment... --- .../HTMLEngineProvider/BaseHTMLEngineProvider.tsx | 7 ++++++- src/libs/ReportUtils.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index 51f9981f1524..406e5e1cbc8b 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -77,8 +77,13 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim mixedUAStyles: {...styles.textSupporting, ...styles.textLineThrough}, contentModel: HTMLContentModel.textual, }), + 'uploading-attachment': HTMLElementModel.fromCustomModel({ + tagName: 'uploading-attachment', + mixedUAStyles: {...styles.mt3}, + contentModel: HTMLContentModel.block, + }), }), - [styles.formError, styles.mb0, styles.colorMuted, styles.textLabelSupporting, styles.lh16, styles.textSupporting, styles.textLineThrough], + [styles.formError, styles.mb0, styles.colorMuted, styles.textLabelSupporting, styles.lh16, styles.textSupporting, styles.textLineThrough, styles.mt3], ); /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b8e4c448fdc2..ecd0a94230c0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3450,7 +3450,7 @@ function buildOptimisticAddCommentReportAction( htmlForNewComment = commentText; textForNewComment = parser.htmlToText(htmlForNewComment); } else { - htmlForNewComment = `${commentText}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`; + htmlForNewComment = `${commentText}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`; textForNewComment = `${parser.htmlToText(commentText)}\n${CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML}`; } From 9764c602fbf58b74790457f1b4836bf6c4c32da4 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 23 May 2024 16:56:27 +0200 Subject: [PATCH 057/419] chore: clean wallet draft after finishing the flow --- src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx index 64e99bd9be05..5b462c278509 100644 --- a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx @@ -30,7 +30,7 @@ function FeesAndTerms() { reportID: walletTerms?.chatReportID ?? '', }); BankAccounts.clearPersonalBankAccount(); - // TODO: clear wallet draft + Wallet.resetWalletAdditionalDetailsDraft(); Navigation.navigate(ROUTES.SETTINGS_WALLET); }; const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent: termsAndFeesSubsteps, startFrom: 0, onFinished: submit}); From aa7d744a1736bf3cde203caacc2dd2eabb7ba307 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 23 May 2024 18:50:24 +0200 Subject: [PATCH 058/419] fix: remove old add plaid bank account layout --- src/components/AddPlaidBankAccount.tsx | 68 +++++-------------- src/pages/AddPersonalBankAccountPage.tsx | 1 + .../BankInfo/substeps/Plaid.tsx | 1 - 3 files changed, 18 insertions(+), 52 deletions(-) diff --git a/src/components/AddPlaidBankAccount.tsx b/src/components/AddPlaidBankAccount.tsx index 366f14ec9780..fddfe5b8e8d9 100644 --- a/src/components/AddPlaidBankAccount.tsx +++ b/src/components/AddPlaidBankAccount.tsx @@ -59,9 +59,6 @@ type AddPlaidBankAccountProps = AddPlaidBankAccountOnyxProps & { /** Are we adding a withdrawal account? */ allowDebit?: boolean; - /** Is displayed in new VBBA */ - isDisplayedInNewVBBA?: boolean; - /** Is displayed in new enable wallet flow */ isDisplayedInWalletFlow?: boolean; @@ -84,7 +81,6 @@ function AddPlaidBankAccount({ bankAccountID = 0, allowDebit = false, isPlaidDisabled, - isDisplayedInNewVBBA = false, errorText = '', onInputChange = () => {}, isDisplayedInWalletFlow = false, @@ -259,62 +255,32 @@ function AddPlaidBankAccount({ return {renderPlaidLink()}; } - if (isDisplayedInNewVBBA || isDisplayedInWalletFlow) { - return ( - - {translate(isDisplayedInWalletFlow ? 'walletPage.chooseYourBankAccount' : 'bankAccount.chooseAnAccount')} - {!!text && {text}} - - - - {bankName} - {selectedPlaidAccountMask.length > 0 && ( - {`${translate('bankAccount.accountEnding')} ${selectedPlaidAccountMask}`} - )} - - - {`${translate('bankAccount.chooseAnAccountBelow')}:`} - - - - ); - } - - // Plaid bank accounts view return ( - {!!text && {text}} - + {translate(isDisplayedInWalletFlow ? 'walletPage.chooseYourBankAccount' : 'bankAccount.chooseAnAccount')} + {!!text && {text}} + - {bankName} - - - + + {bankName} + {selectedPlaidAccountMask.length > 0 && ( + {`${translate('bankAccount.accountEnding')} ${selectedPlaidAccountMask}`} + )} + + {`${translate('bankAccount.chooseAnAccountBelow')}:`} + + ); } diff --git a/src/pages/AddPersonalBankAccountPage.tsx b/src/pages/AddPersonalBankAccountPage.tsx index 5cd0f3ef8026..98506477eaf9 100644 --- a/src/pages/AddPersonalBankAccountPage.tsx +++ b/src/pages/AddPersonalBankAccountPage.tsx @@ -90,6 +90,7 @@ function AddPersonalBankAccountPage({personalBankAccount, plaidData}: AddPersona Navigation.goBack()} receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()} selectedPlaidAccountID={selectedPlaidAccountId} diff --git a/src/pages/ReimbursementAccount/BankInfo/substeps/Plaid.tsx b/src/pages/ReimbursementAccount/BankInfo/substeps/Plaid.tsx index c58c2f40aa07..03409e4adddf 100644 --- a/src/pages/ReimbursementAccount/BankInfo/substeps/Plaid.tsx +++ b/src/pages/ReimbursementAccount/BankInfo/substeps/Plaid.tsx @@ -88,7 +88,6 @@ function Plaid({reimbursementAccount, reimbursementAccountDraft, onNext, plaidDa allowDebit bankAccountID={bankAccountID} selectedPlaidAccountID={selectedPlaidAccountID} - isDisplayedInNewVBBA inputID={BANK_INFO_STEP_KEYS.SELECTED_PLAID_ACCOUNT_ID} defaultValue={selectedPlaidAccountID} /> From f189a8cc3cbf50a5c7758d004a46011454cb5853 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 23 May 2024 21:23:02 +0200 Subject: [PATCH 059/419] fix: include add bank acount to the flow --- src/CONST.ts | 1 + src/pages/AddPersonalBankAccountPage.tsx | 1 + src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx | 4 ++-- src/pages/EnablePayments/EnablePayments.tsx | 5 ++++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index af5f32808034..6ef98d656e14 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1423,6 +1423,7 @@ const CONST = { }, STEP: { // In the order they appear in the Wallet flow + ADD_BANK_ACCOUNT: 'AddBankAccountStep', ADDITIONAL_DETAILS: 'AdditionalDetailsStep', ADDITIONAL_DETAILS_KBA: 'AdditionalDetailsKBAStep', ONFIDO: 'OnfidoStep', diff --git a/src/pages/AddPersonalBankAccountPage.tsx b/src/pages/AddPersonalBankAccountPage.tsx index 98506477eaf9..59046e103c6b 100644 --- a/src/pages/AddPersonalBankAccountPage.tsx +++ b/src/pages/AddPersonalBankAccountPage.tsx @@ -89,6 +89,7 @@ function AddPersonalBankAccountPage({personalBankAccount, plaidData}: AddPersona > Navigation.goBack()} diff --git a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx index 9fde5456226b..149d6a41aac7 100644 --- a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx +++ b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx @@ -12,9 +12,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; import * as BankAccounts from '@userActions/BankAccounts'; import * as PaymentMethods from '@userActions/PaymentMethods'; +import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type {PersonalBankAccountForm} from '@src/types/form'; import type {PersonalBankAccount, PlaidData} from '@src/types/onyx'; import SetupMethod from './SetupMethod'; @@ -44,7 +44,7 @@ function AddBankAccount({personalBankAccount, plaidData, personalBankAccountDraf if (selectedPlaidBankAccount) { BankAccounts.addPersonalBankAccount(selectedPlaidBankAccount); - Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS_REFACTOR); + Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS); } }, [personalBankAccountDraft?.plaidAccountID, plaidData?.bankAccounts]); diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx index fdee18e8147d..752705cd6d27 100644 --- a/src/pages/EnablePayments/EnablePayments.tsx +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -7,6 +7,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import Navigation from '@libs/Navigation/Navigation'; +import AddBankAccount from '@pages/EnablePayments/AddBankAccount/AddBankAccount'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -60,9 +61,11 @@ function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { ); } - const currentStep = userWallet?.currentStep || CONST.WALLET.STEP.ADDITIONAL_DETAILS; + const currentStep = userWallet?.currentStep || (userWallet?.bankAccountID ? CONST.WALLET.STEP.ADDITIONAL_DETAILS : CONST.WALLET.STEP.ADD_BANK_ACCOUNT); switch (currentStep) { + case CONST.WALLET.STEP.ADD_BANK_ACCOUNT: + return ; case CONST.WALLET.STEP.ADDITIONAL_DETAILS: return ; case CONST.WALLET.STEP.ONFIDO: From 034b82df570a5822c65be0d8a1f475ef4104cf8a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 24 May 2024 11:55:44 +0200 Subject: [PATCH 060/419] fix: show loading when confirming personal info, fetch userWallet only when missing data --- src/pages/EnablePayments/EnablePayments.tsx | 10 ++++------ .../PersonalInfo/substeps/ConfirmationStep.tsx | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx index 752705cd6d27..5d7dd52c1e89 100644 --- a/src/pages/EnablePayments/EnablePayments.tsx +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -35,8 +35,10 @@ function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { return; } - Wallet.openEnablePaymentsPage(); - }, [isOffline]); + if (isEmptyObject(userWallet)) { + Wallet.openEnablePaymentsPage(); + } + }, [isOffline, userWallet]); if (isEmptyObject(userWallet)) { return ; @@ -85,9 +87,5 @@ EnablePaymentsPage.displayName = 'EnablePaymentsPage'; export default withOnyx({ userWallet: { key: ONYXKEYS.USER_WALLET, - - // We want to refresh the wallet each time the user attempts to activate the wallet so we won't use the - // stored values here. - initWithStoredValues: false, }, })(EnablePaymentsPage); diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx index b24400face07..ea1b70cd6074 100644 --- a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx +++ b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx @@ -29,7 +29,7 @@ function ConfirmationStep({onNext, onMove}: SubStepProps) { const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS); const [walletAdditionalDetailsDraft] = useOnyx(ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS_DRAFT); - const isLoading = walletAdditionalDetailsDraft?.isLoading ?? false; + const isLoading = walletAdditionalDetails?.isLoading ?? false; const error = ErrorUtils.getLatestErrorMessage(walletAdditionalDetails ?? {}); const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, walletAdditionalDetailsDraft, walletAdditionalDetails), [walletAdditionalDetails, walletAdditionalDetailsDraft]); From 549e647063a14d8435aaf5dd2df13e930e9b929d Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:32:38 +0200 Subject: [PATCH 061/419] Show all policies in the workspace list --- src/libs/PolicyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 6a26b1a6cfc2..37dafafb3e5e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -116,7 +116,7 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf, isOffline: boolean): boolean { return ( !!policy && - (policy?.isPolicyExpenseChatEnabled || Boolean(policy?.isJoinRequestPending)) && + Boolean(policy?.isJoinRequestPending) && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } From ee6a0129eb5c6ff06d8e547a81787711b84e5214 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:32:52 +0200 Subject: [PATCH 062/419] Do not show personal policies in the list --- src/libs/PolicyUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 37dafafb3e5e..a1dc5534df8c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -116,6 +116,7 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf, isOffline: boolean): boolean { return ( !!policy && + policy?.type !== CONST.POLICY.TYPE.PERSONAL && Boolean(policy?.isJoinRequestPending) && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); From 8b2b02e411703c5e43633baea5994935787a4d39 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:50:10 +0200 Subject: [PATCH 063/419] Remove the isPolicyExpenseChatEnabled check from the AccessOrNotFoundPage --- src/libs/PolicyUtils.ts | 4 ++-- src/pages/workspace/AccessOrNotFoundWrapper.tsx | 2 +- src/pages/workspace/WorkspacesListPage.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 021dbb5abcff..b5092f8244eb 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -116,8 +116,8 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf, isOffline: boolean): boolean { return ( !!policy && - policy?.type !== CONST.POLICY.TYPE.PERSONAL && - Boolean(policy?.isJoinRequestPending) && + (policy?.type !== CONST.POLICY.TYPE.PERSONAL || + Boolean(policy?.isJoinRequestPending)) && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index b90a9cb38151..da33238793df 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -18,7 +18,7 @@ import callOrReturn from '@src/types/utils/callOrReturn'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; const POLICY_ACCESS_VARIANTS = { - [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => PolicyUtils.isPaidGroupPolicy(policy) && !!policy?.isPolicyExpenseChatEnabled, + [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => PolicyUtils.isPaidGroupPolicy(policy), [CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry) => PolicyUtils.isPolicyAdmin(policy), } as const satisfies Record boolean>; diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 60e471deb328..4028f2f18341 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -309,7 +309,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}: if (isEmptyObject(policies)) { return []; } - + console.log("policies: ", policies); return Object.values(policies) .filter((policy): policy is PolicyType => PolicyUtils.shouldShowPolicy(policy, !!isOffline)) .map((policy): WorkspaceItem => { From c5291d7abd9521f9ea0b6d8afb7853b8f049c1fd Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 24 May 2024 13:25:22 +0200 Subject: [PATCH 064/419] fix: proper submit/go back handling for the terms step --- .../FeesAndTerms/FeesAndTerms.tsx | 2 +- .../FeesAndTerms/substeps/TermsStep.tsx | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx index 5b462c278509..512abc57a990 100644 --- a/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx @@ -37,7 +37,7 @@ function FeesAndTerms() { const handleBackButtonPress = () => { if (screenIndex === 0) { - Navigation.navigate(ROUTES.SETTINGS_WALLET); + Wallet.updateCurrentStep(CONST.WALLET.STEP.ONFIDO); return; } prevScreen(); diff --git a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx index afbd6d594274..12fce0b2ec50 100644 --- a/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx @@ -6,13 +6,11 @@ import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; +import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; -import Navigation from '@navigation/Navigation'; -import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; function HaveReadAndAgreeLabel() { const {translate} = useLocalize(); @@ -41,7 +39,7 @@ function AgreeToTheLabel() { ); } -function TermsStep() { +function TermsStep({onNext}: SubStepProps) { const styles = useThemeStyles(); const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false); const [hasAcceptedPrivacyPolicyAndWalletAgreement, setHasAcceptedPrivacyPolicyAndWalletAgreement] = useState(false); @@ -60,6 +58,15 @@ function TermsStep() { setHasAcceptedPrivacyPolicyAndWalletAgreement(!hasAcceptedPrivacyPolicyAndWalletAgreement); }; + const submit = () => { + if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { + setError(true); + return; + } + setError(false); + onNext(); + }; + /** clear error */ useEffect(() => { if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { @@ -88,14 +95,7 @@ function TermsStep() { { - if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { - setError(true); - return; - } - - setError(false); - }} + onSubmit={submit} message={errorMessage} isAlertVisible={error || Boolean(errorMessage)} isLoading={!!walletTerms?.isLoading} From a4d41028ea452559e4748257f8be86ac8321fb2d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 24 May 2024 13:15:30 +0100 Subject: [PATCH 065/419] chore: resolve pending descriptions --- src/types/onyx/Card.ts | 24 ++++++++++++++---------- src/types/onyx/ReportNextStep.ts | 24 ++++++++++-------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 9e683a29ee06..9ab60a06f7e6 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -33,32 +33,36 @@ type Card = { /** Additional card data */ nameValuePairs?: { - // TODO: Doesn't seem to be used in app - /** Type of spending limits */ + /** Type of card spending limits */ limitType?: ValueOf; - // TODO: Doesn't seem to be used in app - cardTitle?: string; // Used only for admin-issued virtual cards + /** User-defined nickname for a virtual card */ + cardTitle?: string; - // TODO: Doesn't seem to be used in app + /** Account ID of user that issued the card */ issuedBy?: number; - // TODO: Doesn't seem to be used in app + /** + * Whether the card has a custom unapproved expense limit. + * When not set, the domain unapproved expense limit is used + */ hasCustomUnapprovedExpenseLimit?: boolean; - // TODO: Doesn't seem to be used in app + /** + * The maximum unapproved spend allowed on the card. + * If it's $100 and you spend $100, you need to get the expenses approved for the card to continue working + */ unapprovedExpenseLimit?: number; - // TODO: Doesn't seem to be used in app + /** Card product under which the card is provisioned */ feedCountry?: string; /** Is a virtual card */ isVirtual?: boolean; - // TODO: Doesn't seem to be used in app + /** Previous card state */ previousState?: number; - // TODO: Doesn't seem to be used in app /** Card expiration date */ expirationDate?: string; }; diff --git a/src/types/onyx/ReportNextStep.ts b/src/types/onyx/ReportNextStep.ts index 67c0852febc1..d2c1f2477c56 100644 --- a/src/types/onyx/ReportNextStep.ts +++ b/src/types/onyx/ReportNextStep.ts @@ -35,36 +35,32 @@ type ReportNextStep = { /** The title for the next step */ title?: string; - // TODO: Doesn't seem to be used in app /** Whether the user should take some sort of action in order to unblock the report */ requiresUserAction?: boolean; - // TODO: Doesn't seem to be used in app - /** The type of next step */ + /** + * The type of next step + * + * "neutral" for normal next steps, "alert" for more urgent/actionable + */ type: 'alert' | 'neutral' | null; - // TODO: Doesn't seem to be used in app - /** If the "Undo submit" button should be visible */ + /** Whether the "Undo submit" button should be visible */ showUndoSubmit?: boolean; - // TODO: Doesn't seem to be used in app - /** Deprecated - If the next step should be displayed on mobile, related to OldApp */ + /** Whether the next step should be displayed on mobile, related to OldApp */ showForMobile?: boolean; - // TODO: Doesn't seem to be used in app - /** If the next step should be displayed at the expense level */ + /** Whether the next step should be displayed at the expense level */ showForExpense?: boolean; - // TODO: Doesn't seem to be used in app /** An optional alternate message to display on expenses instead of what is provided in the "message" field */ expenseMessage?: Message[]; - // TODO: Doesn't seem to be used in app - /** The next person in the approval chain of the report */ + /** Email of the next person in the approval chain that needs to approve the report */ nextReceiver?: string; - // TODO: Doesn't seem to be used in app - /** An array of buttons to be displayed next to the next step */ + /** An array listing the buttons to be displayed alongside the next step copy */ buttons?: Record; }; From 3538c6951b2b98a88ed5430d60f657061b1d9220 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 24 May 2024 14:43:47 +0200 Subject: [PATCH 066/419] fix: properly show the first step --- src/pages/EnablePayments/EnablePayments.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx index 5d7dd52c1e89..ff0aa7b353bb 100644 --- a/src/pages/EnablePayments/EnablePayments.tsx +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -7,13 +7,13 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import Navigation from '@libs/Navigation/Navigation'; -import AddBankAccount from '@pages/EnablePayments/AddBankAccount/AddBankAccount'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {UserWallet} from '@src/types/onyx'; +import type {BankAccountList, UserWallet} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import AddBankAccount from './AddBankAccount/AddBankAccount'; import FailedKYC from './FailedKYC'; import FeesAndTerms from './FeesAndTerms/FeesAndTerms'; import PersonalInfo from './PersonalInfo/PersonalInfo'; @@ -22,11 +22,14 @@ import VerifyIdentity from './VerifyIdentity/VerifyIdentity'; type EnablePaymentsPageOnyxProps = { /** The user's wallet */ userWallet: OnyxEntry; + + /** The list of bank accounts */ + bankAccountList: OnyxEntry; }; type EnablePaymentsPageProps = EnablePaymentsPageOnyxProps; -function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { +function EnablePaymentsPage({userWallet, bankAccountList}: EnablePaymentsPageProps) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -63,7 +66,7 @@ function EnablePaymentsPage({userWallet}: EnablePaymentsPageProps) { ); } - const currentStep = userWallet?.currentStep || (userWallet?.bankAccountID ? CONST.WALLET.STEP.ADDITIONAL_DETAILS : CONST.WALLET.STEP.ADD_BANK_ACCOUNT); + const currentStep = userWallet?.currentStep || (isEmptyObject(bankAccountList) ? CONST.WALLET.STEP.ADD_BANK_ACCOUNT : CONST.WALLET.STEP.ADDITIONAL_DETAILS); switch (currentStep) { case CONST.WALLET.STEP.ADD_BANK_ACCOUNT: @@ -88,4 +91,7 @@ export default withOnyx({ userWallet: { key: ONYXKEYS.USER_WALLET, }, + bankAccountList: { + key: ONYXKEYS.BANK_ACCOUNT_LIST, + }, })(EnablePaymentsPage); From c1bea82678c9a16796fdef26b914c48a2795691f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 24 May 2024 15:02:55 +0200 Subject: [PATCH 067/419] fix: remove unnecessary comment --- src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx index 149d6a41aac7..574034159e06 100644 --- a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx +++ b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx @@ -54,7 +54,6 @@ function AddBankAccount({personalBankAccount, plaidData, personalBankAccountDraf const exitFlow = (shouldContinue = false) => { const exitReportID = personalBankAccount?.exitReportID; - // TODO: https://github.com/Expensify/App/issues/36648 This should be updated to the correct route once the refactor is complete const onSuccessFallbackRoute = personalBankAccount?.onSuccessFallbackRoute ?? ''; if (exitReportID) { From f3287b8e9acfc57977821d6ccd918fb3a46a5d9f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Fri, 24 May 2024 15:27:06 +0200 Subject: [PATCH 068/419] feat: routes cleanup --- src/ROUTES.ts | 5 ----- src/SCREENS.ts | 3 --- .../AppNavigator/ModalStackNavigators/index.tsx | 5 +---- src/libs/Navigation/linkingConfig/config.ts | 14 -------------- .../EnablePayments/AddBankAccount/SetupMethod.tsx | 4 ++-- src/pages/EnablePayments/EnablePayments.tsx | 2 +- .../settings/Wallet/WalletPage/WalletPage.tsx | 2 +- 7 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2bc04c4a99ea..20520a2eb9ce 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -123,12 +123,7 @@ const ROUTES = { }, SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card', SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', - SETTINGS_ADD_BANK_ACCOUNT_REFACTOR: 'settings/wallet/add-bank-account-refactor', SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - SETTINGS_ENABLE_PAYMENTS_REFACTOR: 'settings/wallet/enable-payments-refactor', - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - SETTINGS_ENABLE_PAYMENTS_TEMPORARY_TERMS: 'settings/wallet/enable-payments-temporary-terms', SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: { route: 'settings/wallet/card/:domain/digital-details/update-address', getRoute: (domain: string) => `settings/wallet/card/${domain}/digital-details/update-address` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index f74002312623..fc57724614b5 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -42,7 +42,6 @@ const SCREENS = { APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links', ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', - ADD_BANK_ACCOUNT_REFACTOR: 'Settings_Add_Bank_Account_Refactor', CLOSE: 'Settings_Close', TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged', @@ -91,8 +90,6 @@ const SCREENS = { ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments', // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 ENABLE_PAYMENTS_REFACTOR: 'Settings_Wallet_EnablePayments_Refactor', - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - ENABLE_PAYMENTS_TEMPORARY_TERMS: 'Settings_Wallet_EnablePayments_Temporary_Terms', CARD_ACTIVATE: 'Settings_Wallet_Card_Activate', REPORT_VIRTUAL_CARD_FRAUD: 'Settings_Wallet_ReportVirtualCardFraud', CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index ca46a61787b9..f19a9f5be1ad 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -203,12 +203,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Wallet/Card/GetPhysicalCardConfirm').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.TRANSFER_BALANCE]: () => require('../../../../pages/settings/Wallet/TransferBalancePage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.CHOOSE_TRANSFER_ACCOUNT]: () => require('../../../../pages/settings/Wallet/ChooseTransferAccountPage').default as React.ComponentType, - [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: () => require('../../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_REFACTOR]: () => require('../../../../pages/EnablePayments/EnablePayments').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: () => require('../../../../pages/EnablePayments/EnablePayments').default as React.ComponentType, [SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType, [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, - [SCREENS.SETTINGS.ADD_BANK_ACCOUNT_REFACTOR]: () => require('../../../../pages/EnablePayments/AddBankAccount/AddBankAccount').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS]: () => require('../../../../pages/settings/Profile/CustomStatus/StatusPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER]: () => require('../../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index a093b778360e..d9982a0bf00c 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -161,16 +161,6 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_ENABLE_PAYMENTS, exact: true, }, - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_REFACTOR]: { - path: ROUTES.SETTINGS_ENABLE_PAYMENTS_REFACTOR, - exact: true, - }, - // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 - [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_TEMPORARY_TERMS]: { - path: ROUTES.SETTINGS_ENABLE_PAYMENTS_TEMPORARY_TERMS, - exact: true, - }, [SCREENS.SETTINGS.WALLET.TRANSFER_BALANCE]: { path: ROUTES.SETTINGS_WALLET_TRANSFER_BALANCE, exact: true, @@ -199,10 +189,6 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT, exact: true, }, - [SCREENS.SETTINGS.ADD_BANK_ACCOUNT_REFACTOR]: { - path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT_REFACTOR, - exact: true, - }, [SCREENS.SETTINGS.PROFILE.PRONOUNS]: { path: ROUTES.SETTINGS_PRONOUNS, exact: true, diff --git a/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx b/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx index e70d5979e92e..f58c158ba448 100644 --- a/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx +++ b/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx @@ -28,7 +28,7 @@ type SetupMethodOnyxProps = { type SetupMethodProps = SetupMethodOnyxProps; const plaidDesktopMessage = getPlaidDesktopMessage(); -const bankAccountRoute = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.SETTINGS_ADD_BANK_ACCOUNT_REFACTOR}`; +const enablePayments = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.SETTINGS_ENABLE_PAYMENTS}`; function SetupMethod({isPlaidDisabled, user}: SetupMethodProps) { const styles = useThemeStyles(); @@ -46,7 +46,7 @@ function SetupMethod({isPlaidDisabled, user}: SetupMethodProps) { {!!plaidDesktopMessage && ( - {translate(plaidDesktopMessage)} + {translate(plaidDesktopMessage)} )}