From 43e030b5d42bf5f19bc7dcb93a6cc3d44890ba3c Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 23 Apr 2024 03:32:30 +0100 Subject: [PATCH 001/246] 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 002/246] 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 0a0cac1e1baa810315b5686c0cfb6332e0c9573e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 24 Apr 2024 22:04:08 +0100 Subject: [PATCH 003/246] 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 004/246] 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 72f85755fab249ec89a8e8b674094baec90d261e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 21:41:03 +0100 Subject: [PATCH 005/246] 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 006/246] 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 007/246] 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 e29768615c1e43b9708e4048aa0feb64041e6c7f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 3 May 2024 09:29:44 +0100 Subject: [PATCH 008/246] 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 009/246] 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 5b9433ce8e8570dca2692606aab76b4a2f34b53e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 9 May 2024 22:18:26 +0100 Subject: [PATCH 010/246] 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 9f0585e6f576bd3df8441b92c34671793b315df2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 11 May 2024 19:08:30 +0530 Subject: [PATCH 011/246] 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 012/246] 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 013/246] 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 014/246] 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 015/246] 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 016/246] 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 13029ae924c52345317b5865f576d5241e428230 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 15 May 2024 15:21:01 +0100 Subject: [PATCH 017/246] 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 d8fc20b3c8056f80f0ff498780b8e2b4cd975881 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 17 May 2024 14:30:07 +0530 Subject: [PATCH 018/246] 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 4e0968d9a4385d1e9c30c008fce19e11067bcea3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 18 May 2024 17:13:00 +0700 Subject: [PATCH 019/246] 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 96f6955166498e0b4a08f377fd057d9f8b7149dc Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 20 May 2024 23:47:50 +0100 Subject: [PATCH 020/246] 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 021/246] 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 022/246] 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 023/246] 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 024/246] 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 025/246] 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 f3e1c8f75fb6ffff6ee4153a06810c58d8cdb591 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 22 May 2024 15:17:48 +0100 Subject: [PATCH 026/246] 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 027/246] 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 028/246] 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 a4d41028ea452559e4748257f8be86ac8321fb2d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 24 May 2024 13:15:30 +0100 Subject: [PATCH 029/246] 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 6454fe21c9a9c3d06bcd5a66b5d33f38cc238648 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 24 May 2024 21:51:08 +0530 Subject: [PATCH 030/246] hide overflowing emoji from emoji status container. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/index.ts b/src/styles/index.ts index 192b2779e05d..e567026a2e7b 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4380,6 +4380,7 @@ const styles = (theme: ThemeColors) => bottom: -4, borderColor: theme.highlightBG, borderWidth: 2, + overflow: 'hidden', }, moneyRequestViewImage: { ...spacing.mh5, From 5599455b32aa406fbe4f7714a432e925dd7acd78 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 27 May 2024 21:20:52 +0100 Subject: [PATCH 031/246] docs: apply suggestions --- src/types/onyx/BankAccount.ts | 3 +-- src/types/onyx/Card.ts | 22 ---------------------- src/types/onyx/Task.ts | 7 +++++-- src/types/onyx/TransactionViolation.ts | 6 ------ 4 files changed, 6 insertions(+), 32 deletions(-) diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index 1a2505a83592..34eb15646b2f 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -16,8 +16,7 @@ type BankAccountAdditionalData = { /** In which bank is the bank account */ bankName?: string; - // TODO: Confirm this - /** Whether the bank account is local or international */ + /** Whether the bank account details were obtained for local transfer or international wire */ fieldsType?: string; /** In which country is the bank account */ diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 9ab60a06f7e6..595104d4aed3 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -78,28 +78,6 @@ type ExpensifyCardDetails = { /** 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 */ diff --git a/src/types/onyx/Task.ts b/src/types/onyx/Task.ts index 4878802135c2..ac11e5f3755f 100644 --- a/src/types/onyx/Task.ts +++ b/src/types/onyx/Task.ts @@ -8,8 +8,11 @@ type Task = { /** Description of the Task */ description?: string; - // TODO: Make sure this field exists in the API - /** Share destination of the Task */ + /** + * Report ID of the report where the task will be shared + * + * (Note: This variable doesn't exist in the API. It's only used locally for UI purposes) + */ shareDestination?: string; /** The task report if it's currently being edited */ diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index ffbbb23037d0..745c3c260a1f 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -30,12 +30,6 @@ type TransactionViolationData = { /** 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; From 2d91475808f042b2c47401f398566d4436e03296 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 27 May 2024 23:34:31 +0100 Subject: [PATCH 032/246] docs: apply suggestions --- src/types/onyx/ReportNextStep.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/types/onyx/ReportNextStep.ts b/src/types/onyx/ReportNextStep.ts index d2c1f2477c56..00943dc1a1f8 100644 --- a/src/types/onyx/ReportNextStep.ts +++ b/src/types/onyx/ReportNextStep.ts @@ -6,24 +6,44 @@ type Message = { /** HTML tag name */ type?: string; - // TODO: Doesn't seem to be used in app + /** Action for the user to take */ action?: string; }; -// TODO: Doesn't seem to be used in app +/** Model of report next step button data */ type DataOptions = { + /** Whether the user should see the option to pay via Expensify (ACH) */ canSeeACHOption?: boolean; + + /** Whether workspace reimbursements is set to Indirect reimbursements */ isManualReimbursementEnabled?: boolean; + + /** + * If there is a masked bank account number from the server, the account needs to be unlocked + * + * (Note: Copied directly from a comment in Old Dot JS) + */ maskedLockedAccountNumber?: string; + + /** Whether the preferred business bank account of the policy is deleted or no longer accessible to the policy reimburser */ preferredWithdrawalDeleted?: boolean; }; -// TODO: Doesn't seem to be used in app +/** Model of report next step button */ type Button = { + /** Text/label shown on the button */ text?: string; + + /** Text to show on a tooltip */ tooltip?: string; + + /** Whether the button should be disabled */ disabled?: boolean; + + /** Whether the button should be hidden */ hidden?: boolean; + + /** Data needed to render the button and handle its click events */ data?: DataOptions; }; From 1a6199d7cf10a531ab60ce236a44e27e2cd836ae Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 28 May 2024 10:50:29 +0200 Subject: [PATCH 033/246] Use session storage to save the current active workspace --- src/CONST.ts | 1 + src/ONYXKEYS.ts | 4 ---- .../ActiveWorkspace/ActiveWorkspaceProvider.tsx | 17 +++++++++-------- .../BottomTabBar.tsx | 8 ++------ 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d2be946cdb2b..8c27a61e9776 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4740,6 +4740,7 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', + ACTIVE_WORKSPACE_ID: 'ACTIVE_WORKSPACE_ID', }, DOT_SEPARATOR: '•', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 4e858e32495c..e669d4740f98 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -75,9 +75,6 @@ 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', @@ -607,7 +604,6 @@ 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 2d1b62ba9a65..419dbef5d171 100644 --- a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx +++ b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx @@ -1,18 +1,19 @@ import React, {useCallback, useMemo, useState} from 'react'; -import * as Policy from '@libs/actions/Policy'; +import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import ActiveWorkspaceContext from './ActiveWorkspaceContext'; function ActiveWorkspaceContextProvider({children}: ChildrenProps) { const [activeWorkspaceID, updateActiveWorkspaceID] = useState(undefined); - const setActiveWorkspaceID = useCallback( - (workspaceID: string | undefined) => { - Policy.setActiveWorkspaceID(workspaceID); - updateActiveWorkspaceID(workspaceID); - }, - [updateActiveWorkspaceID], - ); + const setActiveWorkspaceID = useCallback((workspaceID: string | undefined) => { + updateActiveWorkspaceID(workspaceID); + if (workspaceID) { + sessionStorage.setItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID, workspaceID); + } else { + sessionStorage.removeItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); + } + }, []); const value = useMemo( () => ({ diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 71da79a2f7cd..3e48b89ad5c0 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -7,7 +7,6 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; -import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -29,15 +28,15 @@ import SCREENS from '@src/SCREENS'; type PurposeForUsingExpensifyModalOnyxProps = { isLoadingApp: OnyxEntry; - activeWorkspaceID: OnyxEntry; }; type PurposeForUsingExpensifyModalProps = PurposeForUsingExpensifyModalOnyxProps; -function BottomTabBar({isLoadingApp = false, activeWorkspaceID}: PurposeForUsingExpensifyModalProps) { +function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); const navigation = useNavigation(); + const activeWorkspaceID = sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); useEffect(() => { const navigationState = navigation.getState() as State | undefined; @@ -107,7 +106,4 @@ export default withOnyx Date: Tue, 28 May 2024 14:26:16 +0200 Subject: [PATCH 034/246] include invoice room for participants --- src/components/MoneyRequestConfirmationList.tsx | 11 +++++++++-- src/libs/OptionsListUtils.ts | 16 ++++++++++++++-- .../request/MoneyRequestParticipantsSelector.tsx | 1 + 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 65a83090a059..ce5a18c6c3fd 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -281,11 +281,18 @@ function MoneyRequestConfirmationList({ const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); const senderWorkspace = useMemo(() => { + const invoiceRoomParticipant = selectedParticipantsProp.find((participant) => participant.isInvoiceRoom); const senderWorkspaceParticipant = selectedParticipantsProp.find((participant) => participant.isSender); - return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; + const senderPolicyID = invoiceRoomParticipant?.policyID ?? senderWorkspaceParticipant?.policyID; + + return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderPolicyID}`]; }, [allPolicies, selectedParticipantsProp]); - const canUpdateSenderWorkspace = useMemo(() => PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate, [allPolicies, transaction?.isFromGlobalCreate]); + const canUpdateSenderWorkspace = useMemo(() => { + const isInvoiceRoomParticipant = selectedParticipantsProp.some((participant) => participant.isInvoiceRoom); + + return PolicyUtils.canSendInvoice(allPolicies) && !!transaction?.isFromGlobalCreate && !isInvoiceRoomParticipant; + }, [allPolicies, selectedParticipantsProp, transaction?.isFromGlobalCreate]); // A flag for showing the tags field // TODO: remove the !isTypeInvoice from this condition after BE supports tags for invoices: https://github.com/Expensify/App/issues/41281 diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 0d5854764946..d59bea1f1141 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -170,6 +170,7 @@ type GetOptionsConfig = { policyReportFieldOptions?: string[]; recentlyUsedPolicyReportFieldOptions?: string[]; transactionViolations?: OnyxCollection; + includeInvoiceRooms?: boolean; }; type GetUserToInviteConfig = { @@ -1691,6 +1692,7 @@ function getOptions( includePolicyReportFieldOptions = false, policyReportFieldOptions = [], recentlyUsedPolicyReportFieldOptions = [], + includeInvoiceRooms = false, }: GetOptionsConfig, ): Options { if (includeCategories) { @@ -1890,8 +1892,16 @@ function getOptions( const isCurrentUserOwnedPolicyExpenseChatThatCouldShow = reportOption.isPolicyExpenseChat && reportOption.ownerAccountID === currentUserAccountID && includeOwnedWorkspaceChats && !reportOption.isArchivedRoom; - // Skip if we aren't including multiple participant reports and this report has multiple participants - if (!isCurrentUserOwnedPolicyExpenseChatThatCouldShow && !includeMultipleParticipantReports && !reportOption.login) { + const shouldShowInvoiceRoom = includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item); + + /** + Exclude the report option if it doesn't meet any of the following conditions: + - It is not an owned policy expense chat that could be shown + - Multiple participant reports are not included + - It doesn't have a login + - It is not an invoice room that should be shown + */ + if (!isCurrentUserOwnedPolicyExpenseChatThatCouldShow && !includeMultipleParticipantReports && !reportOption.login && !shouldShowInvoiceRoom) { continue; } @@ -2084,6 +2094,7 @@ function getFilteredOptions( recentlyUsedPolicyReportFieldOptions: string[] = [], includePersonalDetails = true, maxRecentReportsToShow = 5, + includeInvoiceRooms = false, ) { return getOptions( {reports, personalDetails}, @@ -2111,6 +2122,7 @@ function getFilteredOptions( includePolicyReportFieldOptions, policyReportFieldOptions, recentlyUsedPolicyReportFieldOptions, + includeInvoiceRooms, }, ); } diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index b525a2c1e3dd..34cfb2eb186f 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -117,6 +117,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic undefined, !isCategorizeOrShareAction, isCategorizeOrShareAction ? 0 : undefined, + iouType === CONST.IOU.TYPE.INVOICE, ); const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( From 72eae40af92d571caa770cdfb937d016b833bbec Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 28 May 2024 18:39:40 +0200 Subject: [PATCH 035/246] Add Report type to Participant item --- src/types/onyx/IOU.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 68e8836e149c..7b439467f7b4 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type {Icon} from './OnyxCommon'; +import type Report from './Report'; type Participant = { accountID?: number; @@ -26,6 +27,7 @@ type Participant = { isSender?: boolean; iouType?: string; ownerAccountID?: number; + item?: Report; }; type Split = { From a7aa13a084e53a142e3739b64d1d5a3ec5cb06f2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 28 May 2024 18:40:10 +0200 Subject: [PATCH 036/246] integrate participant handling --- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 6 +++--- src/pages/iou/request/step/IOURequestStepParticipants.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 34cfb2eb186f..2caed9f19a0d 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -22,6 +22,7 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import type {MaybePhraseKey} from '@libs/Localize'; import type {Options} from '@libs/OptionsListUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import * as Policy from '@userActions/Policy'; import * as Report from '@userActions/Report'; import type {IOUAction, IOURequestType, IOUType} from '@src/CONST'; @@ -191,10 +192,9 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic ]; if (iouType === CONST.IOU.TYPE.INVOICE) { - const primaryPolicy = Policy.getPrimaryPolicy(activePolicyID); - + const policyID = ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getPrimaryPolicy(activePolicyID)?.id; newParticipants.push({ - policyID: primaryPolicy?.id, + policyID, isSender: true, selected: false, iouType, diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 89c6a4043b28..9c6601a84c06 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -94,7 +94,7 @@ function IOURequestStepParticipants({ // When multiple participants are selected, the reportID is generated at the end of the confirmation step. // So we are resetting selectedReportID ref to the reportID coming from params. - if (val.length !== 1) { + if (val.length !== 1 && iouType !== CONST.IOU.TYPE.INVOICE) { selectedReportID.current = reportID; return; } @@ -102,7 +102,7 @@ function IOURequestStepParticipants({ // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. selectedReportID.current = val[0]?.reportID ?? reportID; }, - [reportID, transactionID], + [iouType, reportID, transactionID], ); const goToNextStep = useCallback(() => { From f99563cc70db7797c8637ebc799ad2033713c2ce Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 28 May 2024 18:44:13 +0200 Subject: [PATCH 037/246] simplify --- src/components/MoneyRequestConfirmationList.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index ce5a18c6c3fd..faa5175f6633 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -281,11 +281,8 @@ function MoneyRequestConfirmationList({ const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); const senderWorkspace = useMemo(() => { - const invoiceRoomParticipant = selectedParticipantsProp.find((participant) => participant.isInvoiceRoom); const senderWorkspaceParticipant = selectedParticipantsProp.find((participant) => participant.isSender); - const senderPolicyID = invoiceRoomParticipant?.policyID ?? senderWorkspaceParticipant?.policyID; - - return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderPolicyID}`]; + return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${senderWorkspaceParticipant?.policyID}`]; }, [allPolicies, selectedParticipantsProp]); const canUpdateSenderWorkspace = useMemo(() => { From d3e3e9234ba58c90c1e595c68b3c560ab43caa73 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 29 May 2024 01:35:15 +0700 Subject: [PATCH 038/246] fix cannot add new line using enter --- src/CONST.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index d2be946cdb2b..02de81e61aa4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3386,6 +3386,8 @@ const CONST = { * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. */ IMAGE: 'image', + + TEXTBOX: 'textbox', }, /** * Acceptable values for the `role` attribute on react native components. From 8dfbaa796a39a6661ececaf5054a8cad587518ee Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 29 May 2024 11:53:28 +0530 Subject: [PATCH 039/246] Immediately show file size message for large attachments --- src/components/AttachmentPicker/index.native.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 218a255d34d3..e9bd2e5e2e02 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -5,7 +5,7 @@ import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker'; import {launchImageLibrary} from 'react-native-image-picker'; -import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; +import type {Asset, Callback, CameraOptions, ImageLibraryOptions, ImagePickerResponse} from 'react-native-image-picker'; import ImageSize from 'react-native-image-size'; import type {FileObject, ImagePickerResponse as FileResponse} from '@components/AttachmentModal'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -41,11 +41,12 @@ type Item = { * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options */ -const imagePickerOptions = { +const imagePickerOptions: Partial = { includeBase64: false, saveToPhotos: false, selectionLimit: 1, includeExtra: false, + assetRepresentationMode: 'current', }; /** From 5e206959fb7c2fe723c892ff7e30f840ed7f1d58 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 29 May 2024 09:38:05 +0100 Subject: [PATCH 040/246] fix: allTagLevels required violation showing on tags that are already filled --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 ++ src/hooks/useViolations.ts | 11 ++++++++--- src/types/onyx/TransactionViolation.ts | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index d2b92b9a0c41..73844630e2cc 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -479,6 +479,7 @@ function MoneyRequestView({ getErrorForField('tag', { tagListIndex: index, tagListName: name, + tagListValue: TransactionUtils.getTagForDisplay(transaction, index), policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), }) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR @@ -487,6 +488,7 @@ function MoneyRequestView({ errorText={getErrorForField('tag', { tagListIndex: index, tagListName: name, + tagListValue: TransactionUtils.getTagForDisplay(transaction, index), policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), })} /> diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 582d997f436a..7dad3be026fe 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -67,7 +67,7 @@ function useViolations(violations: TransactionViolation[]) { // 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 (currentViolations[0]?.name === CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { return currentViolations .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) .map((violation) => ({ @@ -81,7 +81,7 @@ function useViolations(violations: TransactionViolation[]) { // 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 && currentViolations[0]?.name === 'missingTag' && data?.tagListName) { + if (data?.policyHasDependentTags && currentViolations[0]?.name === CONST.VIOLATIONS.MISSING_TAG && data?.tagListName) { return [ { ...currentViolations[0], @@ -94,7 +94,12 @@ 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 (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + if (currentViolations[0]?.name === CONST.VIOLATIONS.TAG_OUT_OF_POLICY && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); + } + + // allTagLevelsRequired has special logic because we have to account for tags that are already filled + if (currentViolations[0]?.name === CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED && data?.tagListValue) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 3dfa5fc8ba42..99ce3d86dfe1 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -29,6 +29,7 @@ type TransactionViolation = { taxName?: string; tagListIndex?: number; tagListName?: string; + tagListValue?: string; errorIndexes?: number[]; pendingPattern?: boolean; }; From 3c7886b0d6f74a46a7ed93e87e4d8c6c9cd7e3d7 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 29 May 2024 10:56:50 +0200 Subject: [PATCH 041/246] fix --- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index b23a66662d40..9abf0ab14595 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -192,7 +192,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic ]; if (iouType === CONST.IOU.TYPE.INVOICE) { - const policyID = ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getPrimaryPolicy(activePolicyID)?.id; + const policyID = option.item && ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getPrimaryPolicy(activePolicyID)?.id; newParticipants.push({ policyID, isSender: true, From 0364669b7527d72109b762f988ac8618de62c8f6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 29 May 2024 16:09:51 +0200 Subject: [PATCH 042/246] show invoice room only for admins --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 03cf34e33549..d3cb8eeec2e1 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1889,7 +1889,7 @@ function getOptions( const isCurrentUserOwnedPolicyExpenseChatThatCouldShow = reportOption.isPolicyExpenseChat && reportOption.ownerAccountID === currentUserAccountID && includeOwnedWorkspaceChats && !reportOption.isArchivedRoom; - const shouldShowInvoiceRoom = includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item); + const shouldShowInvoiceRoom = includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies); /** Exclude the report option if it doesn't meet any of the following conditions: From 1455e033300f8e913a4c51052d69cd5cea7d629c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 29 May 2024 16:23:32 +0200 Subject: [PATCH 043/246] use participant report id for invoice room only --- .../iou/request/step/IOURequestStepParticipants.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 8131f3b2667d..5a9d7cfd233d 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -84,21 +84,23 @@ function IOURequestStepParticipants({ const addParticipant = useCallback( (val: Participant[]) => { + const firstParticipantReportID = val[0]?.reportID ?? ''; + const rateID = DistanceRequestUtils.getCustomUnitRateID(firstParticipantReportID); + const isInvoice = iouType === CONST.IOU.TYPE.INVOICE && ReportUtils.isInvoiceRoom(ReportUtils.getReport(firstParticipantReportID)); + numberOfParticipants.current = val.length; + IOU.setMoneyRequestParticipants(transactionID, val); - const rateID = DistanceRequestUtils.getCustomUnitRateID(val[0]?.reportID ?? ''); IOU.setCustomUnitRateID(transactionID, rateID); - numberOfParticipants.current = val.length; - // When multiple participants are selected, the reportID is generated at the end of the confirmation step. // So we are resetting selectedReportID ref to the reportID coming from params. - if (val.length !== 1 && iouType !== CONST.IOU.TYPE.INVOICE) { + if (val.length !== 1 && !isInvoice) { selectedReportID.current = reportID; return; } // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. - selectedReportID.current = val[0]?.reportID ?? reportID; + selectedReportID.current = firstParticipantReportID || reportID; }, [iouType, reportID, transactionID], ); From 6ae01c121e425f40d221567bac76e7a242750621 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 30 May 2024 00:04:24 +0200 Subject: [PATCH 044/246] Removing the openWalletPage call from the SettlementButton component --- src/components/SettlementButton.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index f56c4dd1a863..945f8be1f327 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -143,10 +143,6 @@ function SettlementButton({ const {translate} = useLocalize(); const {isOffline} = useNetwork(); - useEffect(() => { - PaymentMethods.openWalletPage(); - }, []); - const session = useSession(); const chatReport = ReportUtils.getReport(chatReportID); const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport); From e7196bba4a224e6e5fc39eebc901e56ef7de1009 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 30 May 2024 00:34:10 +0200 Subject: [PATCH 045/246] cleaning --- src/components/SettlementButton.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 945f8be1f327..a994c4df8ab5 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -8,7 +8,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as BankAccounts from '@userActions/BankAccounts'; import * as IOU from '@userActions/IOU'; -import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; From aa4be9e61984816836cbf388a17329186c5f0259 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 30 May 2024 11:11:27 +0800 Subject: [PATCH 046/246] always convert the amount to cent --- src/libs/MoneyRequestUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 1d55c0f49356..44c9bf7a2161 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -40,10 +40,10 @@ function addLeadingZero(amount: string): string { /** * Calculate the length of the amount with leading zeroes */ -function calculateAmountLength(amount: string, decimals: number): number { +function calculateAmountLength(amount: string): number { const leadingZeroes = amount.match(/^0+/); const leadingZeroesLength = leadingZeroes?.[0]?.length ?? 0; - const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 10 ** decimals).toFixed(2)).toString(); + const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 100).toFixed(2)).toString(); if (/\D/.test(absAmount)) { return CONST.IOU.AMOUNT_MAX_LENGTH + 1; @@ -61,7 +61,7 @@ function validateAmount(amount: string, decimals: number, amountMaxLength: numbe ? `^\\d+(,\\d*)*$` // Don't allow decimal point if decimals === 0 : `^\\d+(,\\d*)*(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point const decimalNumberRegex = new RegExp(regexString, 'i'); - return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount, decimals) <= amountMaxLength); + return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount) <= amountMaxLength); } /** From 08e85dc299a94e8fe71fc3448a91bc8dc7509040 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 30 May 2024 11:12:04 +0800 Subject: [PATCH 047/246] don't allow NaN, Infinity, and number with scientific notation --- src/libs/MoneyRequestUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 44c9bf7a2161..db11b7db9adb 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -45,7 +45,8 @@ function calculateAmountLength(amount: string): number { const leadingZeroesLength = leadingZeroes?.[0]?.length ?? 0; const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 100).toFixed(2)).toString(); - if (/\D/.test(absAmount)) { + // Don't allow the amount if the parsing to number results in NaN, Infinity, or with a scientific notation (e.g., 1e+26) + if (/NaN|Infinity|e\+/.test(absAmount)) { return CONST.IOU.AMOUNT_MAX_LENGTH + 1; } From 6319c887347c742aa92b612b736974cbb15e4de9 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 30 May 2024 11:29:24 +0800 Subject: [PATCH 048/246] remove dot when calculating the length --- src/libs/MoneyRequestUtils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index db11b7db9adb..e476f02e3075 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -10,6 +10,13 @@ function stripCommaFromAmount(amount: string): string { return amount.replace(/,/g, ''); } +/** + * Strip dot from the amount + */ +function stripDotFromAmount(amount: string): string { + return amount.replace(/\./g, ''); +} + /** * Strip spaces from the amount */ @@ -50,7 +57,7 @@ function calculateAmountLength(amount: string): number { return CONST.IOU.AMOUNT_MAX_LENGTH + 1; } - return leadingZeroesLength + (absAmount === '0' ? 2 : absAmount.length); + return leadingZeroesLength + (absAmount === '0' ? 2 : stripDotFromAmount(absAmount).length); } /** From 9e79630ec347a3551b1fc8bf16067be7e3f7f27d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 30 May 2024 17:53:45 +0700 Subject: [PATCH 049/246] fix Login error isn't appearing when you tap away --- .../AppleSignIn/index.android.tsx | 8 ++++-- .../SignInButtons/AppleSignIn/index.ios.tsx | 8 ++++-- .../SignInButtons/AppleSignIn/index.tsx | 2 ++ .../GoogleSignIn/index.native.tsx | 8 ++++-- .../SignInButtons/GoogleSignIn/index.tsx | 2 ++ src/pages/signin/LoginForm/BaseLoginForm.tsx | 27 +++++++++---------- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/components/SignInButtons/AppleSignIn/index.android.tsx b/src/components/SignInButtons/AppleSignIn/index.android.tsx index ec669590d029..a528fe7c5a10 100644 --- a/src/components/SignInButtons/AppleSignIn/index.android.tsx +++ b/src/components/SignInButtons/AppleSignIn/index.android.tsx @@ -5,6 +5,7 @@ import Log from '@libs/Log'; import * as Session from '@userActions/Session'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; +import type {AppleSignInProps} from '.'; /** * Apple Sign In Configuration for Android. @@ -33,7 +34,7 @@ function appleSignInRequest(): Promise { /** * Apple Sign In button for Android. */ -function AppleSignIn() { +function AppleSignIn({onPress = () => {}}: AppleSignInProps) { const handleSignIn = () => { appleSignInRequest() .then((token) => Session.beginAppleSignIn(token)) @@ -46,7 +47,10 @@ function AppleSignIn() { }; return ( { + onPress(); + handleSignIn(); + }} provider={CONST.SIGN_IN_METHOD.APPLE} /> ); diff --git a/src/components/SignInButtons/AppleSignIn/index.ios.tsx b/src/components/SignInButtons/AppleSignIn/index.ios.tsx index 4df8375edad8..57aae97b9c48 100644 --- a/src/components/SignInButtons/AppleSignIn/index.ios.tsx +++ b/src/components/SignInButtons/AppleSignIn/index.ios.tsx @@ -5,6 +5,7 @@ import IconButton from '@components/SignInButtons/IconButton'; import Log from '@libs/Log'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; +import type {AppleSignInProps} from '.'; /** * Apple Sign In method for iOS that returns identityToken. @@ -32,7 +33,7 @@ function appleSignInRequest(): Promise { /** * Apple Sign In button for iOS. */ -function AppleSignIn() { +function AppleSignIn({onPress = () => {}}: AppleSignInProps) { const handleSignIn = () => { appleSignInRequest() .then((token) => Session.beginAppleSignIn(token)) @@ -45,7 +46,10 @@ function AppleSignIn() { }; return ( { + onPress(); + handleSignIn(); + }} provider={CONST.SIGN_IN_METHOD.APPLE} /> ); diff --git a/src/components/SignInButtons/AppleSignIn/index.tsx b/src/components/SignInButtons/AppleSignIn/index.tsx index 9d7322878c98..4f45f00066e6 100644 --- a/src/components/SignInButtons/AppleSignIn/index.tsx +++ b/src/components/SignInButtons/AppleSignIn/index.tsx @@ -24,6 +24,7 @@ type SingletonAppleSignInButtonProps = AppleSignInDivProps & { type AppleSignInProps = WithNavigationFocusProps & { isDesktopFlow?: boolean; + onPress?: () => void; }; /** @@ -139,3 +140,4 @@ function AppleSignIn({isDesktopFlow = false}: AppleSignInProps) { AppleSignIn.displayName = 'AppleSignIn'; export default withNavigationFocus(AppleSignIn); +export type {AppleSignInProps}; diff --git a/src/components/SignInButtons/GoogleSignIn/index.native.tsx b/src/components/SignInButtons/GoogleSignIn/index.native.tsx index 2744d8958080..3fac942c1279 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.native.tsx +++ b/src/components/SignInButtons/GoogleSignIn/index.native.tsx @@ -5,6 +5,7 @@ import Log from '@libs/Log'; import * as Session from '@userActions/Session'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; +import type {GoogleSignInProps} from '.'; /** * Google Sign In method for iOS and android that returns identityToken. @@ -44,10 +45,13 @@ function googleSignInRequest() { /** * Google Sign In button for iOS. */ -function GoogleSignIn() { +function GoogleSignIn({onPress = () => {}}: GoogleSignInProps) { return ( { + onPress(); + googleSignInRequest(); + }} provider={CONST.SIGN_IN_METHOD.GOOGLE} /> ); diff --git a/src/components/SignInButtons/GoogleSignIn/index.tsx b/src/components/SignInButtons/GoogleSignIn/index.tsx index 3cc4cdebffa6..5bb317a3aa08 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.tsx +++ b/src/components/SignInButtons/GoogleSignIn/index.tsx @@ -9,6 +9,7 @@ import type Response from '@src/types/modules/google'; type GoogleSignInProps = { isDesktopFlow?: boolean; + onPress?: () => void; }; /** Div IDs for styling the two different Google Sign-In buttons. */ @@ -90,3 +91,4 @@ function GoogleSignIn({isDesktopFlow = false}: GoogleSignInProps) { GoogleSignIn.displayName = 'GoogleSignIn'; export default GoogleSignIn; +export type {GoogleSignInProps}; diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 4286a2603341..a7cc9be3779d 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -215,6 +215,8 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false const serverErrorText = useMemo(() => (account ? ErrorUtils.getLatestErrorMessage(account) : ''), [account]); const shouldShowServerError = !!serverErrorText && !formError; + const didPressGoogleOrIOSButton = useRef(false); + const setDidPressGoogleOrIOSButton = useCallback((pressed: boolean) => (didPressGoogleOrIOSButton.current = pressed), []); return ( <> @@ -237,18 +239,15 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false // As we have only two signin buttons (Apple/Google) other than the text input, // for natives onBlur is called only when the buttons are pressed and we don't need // to validate in those case as the user has opted for other signin flow. - willBlurTextInputOnTapOutside - ? () => - // This delay is to avoid the validate being called before google iframe is rendered to - // avoid error message appearing after pressing google signin button. - setTimeout(() => { - if (firstBlurred.current || !Visibility.isVisible() || !Visibility.hasFocus()) { - return; - } - firstBlurred.current = true; - validate(login); - }, 500) - : undefined + () => + setTimeout(() => { + if (didPressGoogleOrIOSButton.current || firstBlurred.current || !Visibility.isVisible() || !Visibility.hasFocus()) { + setDidPressGoogleOrIOSButton(false); + return; + } + firstBlurred.current = true; + validate(login); + }, 500) } onChangeText={onTextInput} onSubmitEditing={validateAndSubmitForm} @@ -300,10 +299,10 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false - + setDidPressGoogleOrIOSButton(true)} /> - + setDidPressGoogleOrIOSButton(true)} /> From 89c79ece66f5e57895caca7b5db6c901b7827456 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 30 May 2024 18:26:46 +0700 Subject: [PATCH 050/246] fix lint --- src/components/SignInButtons/AppleSignIn/index.tsx | 1 + src/components/SignInButtons/GoogleSignIn/index.tsx | 1 + src/pages/signin/LoginForm/BaseLoginForm.tsx | 3 --- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/SignInButtons/AppleSignIn/index.tsx b/src/components/SignInButtons/AppleSignIn/index.tsx index 4f45f00066e6..7ca0261d6c72 100644 --- a/src/components/SignInButtons/AppleSignIn/index.tsx +++ b/src/components/SignInButtons/AppleSignIn/index.tsx @@ -24,6 +24,7 @@ type SingletonAppleSignInButtonProps = AppleSignInDivProps & { type AppleSignInProps = WithNavigationFocusProps & { isDesktopFlow?: boolean; + // eslint-disable-next-line react/no-unused-prop-types onPress?: () => void; }; diff --git a/src/components/SignInButtons/GoogleSignIn/index.tsx b/src/components/SignInButtons/GoogleSignIn/index.tsx index 5bb317a3aa08..f12d039209f5 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.tsx +++ b/src/components/SignInButtons/GoogleSignIn/index.tsx @@ -9,6 +9,7 @@ import type Response from '@src/types/modules/google'; type GoogleSignInProps = { isDesktopFlow?: boolean; + // eslint-disable-next-line react/no-unused-prop-types onPress?: () => void; }; diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index a7cc9be3779d..d01d061aa855 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -26,7 +26,6 @@ import * as LoginUtils from '@libs/LoginUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as ValidationUtils from '@libs/ValidationUtils'; import Visibility from '@libs/Visibility'; -import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; import * as CloseAccount from '@userActions/CloseAccount'; import * as Session from '@userActions/Session'; import CONFIG from '@src/CONFIG'; @@ -51,8 +50,6 @@ type BaseLoginFormOnyxProps = { type BaseLoginFormProps = WithToggleVisibilityViewProps & BaseLoginFormOnyxProps & LoginFormProps; -const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc(); - function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false, isVisible}: BaseLoginFormProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); From 2974defa78ac22160fa41fc92252c981a1ae58c5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 12:26:51 +0700 Subject: [PATCH 051/246] Add confirmation prompt when approving held request via report preview --- .../ReportActionItem/ReportPreview.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index be3b104018db..933381b9e116 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -1,5 +1,5 @@ import truncate from 'lodash/truncate'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -9,6 +9,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import ProcessMoneyReportHoldMenu from '@components/ProcessMoneyReportHoldMenu'; import SettlementButton from '@components/SettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; @@ -16,6 +17,7 @@ import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -119,6 +121,12 @@ function ReportPreview({ [transactions, iouReportID, action], ); + const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); + const [requestType, setRequestType] = useState<'pay' | 'approve'>(); + const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); + const {isSmallScreenWidth} = useWindowDimensions(); + const [paymentType, setPaymentType] = useState(); + const managerID = iouReport?.managerID ?? 0; const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); @@ -162,6 +170,28 @@ function ReportPreview({ [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled], ); + const confirmPayment = (type?: PaymentMethodType | undefined) => { + if (!type) { + return; + } + setPaymentType(type); + setRequestType('pay'); + if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + setIsHoldMenuVisible(true); + } else if (chatReport) { + IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); + } + }; + + const confirmApproval = () => { + setRequestType('approve'); + if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + setIsHoldMenuVisible(true); + } else { + IOU.approveMoneyRequest(iouReport as Report, true); + } + }; + const getDisplayAmount = (): string => { if (totalDisplaySpend) { return CurrencyUtils.convertToDisplayString(totalDisplaySpend, iouReport?.currency); @@ -368,7 +398,8 @@ function ReportPreview({ policyID={policyID} chatReportID={chatReportID} iouReport={iouReport} - onPress={(paymentType?: PaymentMethodType) => chatReport && iouReport && paymentType && IOU.payMoneyRequest(paymentType, chatReport, iouReport)} + onPress={confirmPayment} + confirmApproval={confirmApproval} enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} addBankAccountRoute={bankAccountRoute} shouldHidePaymentOptions={!shouldShowPayButton} @@ -399,6 +430,19 @@ function ReportPreview({ + {isHoldMenuVisible && requestType !== undefined && ( + setIsHoldMenuVisible(false)} + isVisible={isHoldMenuVisible} + paymentType={paymentType} + chatReport={chatReport} + moneyRequestReport={iouReport as Report} + /> + )} ); } From 68aa796fddcffcc8f4f1b128d9ad0af46c5ace9e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 13:19:00 +0700 Subject: [PATCH 052/246] fix lint --- src/components/ReportActionItem/ReportPreview.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 933381b9e116..755ca06ecb4d 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -179,6 +179,7 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); } }; @@ -188,6 +189,7 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style IOU.approveMoneyRequest(iouReport as Report, true); } }; @@ -440,6 +442,7 @@ function ReportPreview({ isVisible={isHoldMenuVisible} paymentType={paymentType} chatReport={chatReport} + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style moneyRequestReport={iouReport as Report} /> )} From 4c6771566bc96f37dac77dcbe386b2b1e64a7822 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 13:34:12 +0700 Subject: [PATCH 053/246] safely check iouReport --- src/components/ReportActionItem/ReportPreview.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 755ca06ecb4d..ef34c6ba2982 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -178,9 +178,8 @@ function ReportPreview({ setRequestType('pay'); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); - } else if (chatReport) { - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); + } else if (chatReport && iouReport) { + IOU.payMoneyRequest(type, chatReport, iouReport, false); } }; @@ -189,8 +188,7 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - IOU.approveMoneyRequest(iouReport as Report, true); + IOU.approveMoneyRequest(iouReport ?? {}, true); } }; @@ -432,7 +430,7 @@ function ReportPreview({ - {isHoldMenuVisible && requestType !== undefined && ( + {isHoldMenuVisible && requestType !== undefined && !!iouReport && ( )} From 21b35f7eb2f6ce6abad4c431c1876d0bdb0745e0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 15:15:23 +0700 Subject: [PATCH 054/246] fix test --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index ef34c6ba2982..15e33b2d4d89 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -430,7 +430,7 @@ function ReportPreview({ - {isHoldMenuVisible && requestType !== undefined && !!iouReport && ( + {isHoldMenuVisible && !!iouReport && requestType !== undefined && ( Date: Fri, 31 May 2024 16:25:11 +0700 Subject: [PATCH 055/246] fix perf test --- src/components/ReportActionItem/ReportPreview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 15e33b2d4d89..e1db781c5d16 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -178,8 +178,8 @@ function ReportPreview({ setRequestType('pay'); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); - } else if (chatReport && iouReport) { - IOU.payMoneyRequest(type, chatReport, iouReport, false); + } else if (chatReport) { + IOU.payMoneyRequest(type, chatReport, iouReport!, false); } }; From fca320f7dc7a7b44e9c63d5a15012e92416ce8e9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 16:34:56 +0700 Subject: [PATCH 056/246] fix lint --- src/components/ReportActionItem/ReportPreview.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index e1db781c5d16..5fcf1c53a337 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -179,7 +179,8 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { - IOU.payMoneyRequest(type, chatReport, iouReport!, false); + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style + IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); } }; From 68ce115af543eed22d87242a2c0dc086da2424e8 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Sat, 1 Jun 2024 15:28:26 +0100 Subject: [PATCH 057/246] refactor: apply pull request suggestions --- src/libs/ReportActionsUtils.ts | 2 +- src/libs/ReportUtils.ts | 9 +- src/pages/home/report/ReportActionItem.tsx | 6 +- src/types/onyx/BankAccount.ts | 3 +- src/types/onyx/IOU.ts | 7 +- src/types/onyx/OriginalMessage.ts | 136 +++++---------------- src/types/onyx/Policy.ts | 37 ++++-- src/types/onyx/Report.ts | 9 -- src/types/onyx/ReportAction.ts | 26 +--- src/types/onyx/Transaction.ts | 23 +--- tests/ui/UnreadIndicatorsTest.tsx | 1 - tests/utils/LHNTestUtils.tsx | 24 ---- tests/utils/ReportTestUtils.ts | 5 +- tests/utils/collections/reportActions.ts | 11 -- 14 files changed, 81 insertions(+), 218 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 75d8e22ac975..724412230a57 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1205,7 +1205,7 @@ function isActionableJoinRequest(reportAction: OnyxEntry): reportA */ function isActionableJoinRequestPending(reportID: string): boolean { const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(reportID))); - const findPendingRequest = sortedReportActions.find((reportActionItem) => isActionableJoinRequest(reportActionItem) && reportActionItem.originalMessage.choice === ''); + const findPendingRequest = sortedReportActions.find((reportActionItem) => isActionableJoinRequest(reportActionItem) && reportActionItem.originalMessage.choice === '' as ValueOf); return !!findPendingRequest; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f9068cd6cf45..4ad7db0f6a72 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -43,12 +43,13 @@ import type { IOUMessage, ModifiedExpense, OriginalMessageActionName, + OriginalMessageApproved, OriginalMessageCreated, OriginalMessageDismissedViolation, OriginalMessageReimbursementDequeued, OriginalMessageRenamed, + OriginalMessageSubmitted, PaymentMethodType, - ReimbursementDeQueuedMessage, } from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference, Participants, PendingChatMember, Participant as ReportParticipant} from '@src/types/onyx/Report'; @@ -189,12 +190,12 @@ type ReportOfflinePendingActionAndErrors = { }; type OptimisticApprovedReportAction = Pick< - ReportAction, + ReportAction & OriginalMessageApproved, 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' >; type OptimisticSubmittedReportAction = Pick< - ReportAction, + ReportAction & OriginalMessageSubmitted, | 'actionName' | 'actorAccountID' | 'adminAccountID' @@ -2159,7 +2160,7 @@ function getReimbursementDeQueuedActionMessage( report: OnyxEntry | EmptyObject, isLHNPreview = false, ): string { - const originalMessage = reportAction?.originalMessage as ReimbursementDeQueuedMessage | undefined; + const originalMessage = reportAction?.originalMessage; const amount = originalMessage?.amount; const currency = originalMessage?.currency; const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 4b248bf14131..b45c25d26d53 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -4,6 +4,7 @@ import type {GestureResponderEvent, TextInput} from 'react-native'; import {InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; @@ -393,7 +394,10 @@ function ReportActionItem({ const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || action.originalMessage.choice !== '')) { + if ( + !isActionableWhisper && + (!ReportActionsUtils.isActionableJoinRequest(action) || action.originalMessage.choice !== ('' as ValueOf)) + ) { return []; } diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index 34eb15646b2f..a5b96941e172 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -1,5 +1,6 @@ import type CONST from '@src/CONST'; import type AccountData from './AccountData'; +import type {BankName} from './Bank'; import type * as OnyxCommon from './OnyxCommon'; /** Model of additional bank account data */ @@ -14,7 +15,7 @@ type BankAccountAdditionalData = { currency?: string; /** In which bank is the bank account */ - bankName?: string; + bankName?: BankName; /** Whether the bank account details were obtained for local transfer or international wire */ fieldsType?: string; diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts index 9c04ad59ef3a..068cd6dc5c68 100644 --- a/src/types/onyx/IOU.ts +++ b/src/types/onyx/IOU.ts @@ -1,5 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type {IOUType} from '@src/CONST'; import type {Icon} from './OnyxCommon'; /** Model of IOU participant */ @@ -67,9 +68,11 @@ type Participant = { /** 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; + iouType?: IOUType; + + /** When the participant is associated with a policy expense chat, this is the account ID of the policy owner */ + ownerAccountID?: number; }; /** Model of IOU split */ diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 734ffc3cf064..1e7c3cb3e0df 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -36,9 +36,16 @@ type OriginalMessageActionName = /** Model of `approved` report action */ type OriginalMessageApproved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; + originalMessage: { + /** Approved expense amount */ + amount: number; - /** TODO: I think the type should match the scructure of `originalMessage` in `buildOptimisticApprovedReportAction` */ - originalMessage: unknown; + /** Currency of the approved expense amount */ + currency: string; + + /** Report ID of the expense */ + expenseReportID: string; + }; }; /** Types of sources of original message */ @@ -121,9 +128,8 @@ type IOUMessage = { /** 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; + cancellationReason: ValueOf; /** ID of the `expense` report */ expenseReportID?: string; @@ -141,23 +147,6 @@ type OriginalMessageIOU = { 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< @@ -227,26 +216,8 @@ type OriginalMessageAddComment = { /** 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 */ + /** Collection of accountIDs of users mentioned in message */ whisperedTo: number[]; - - /** TODO: Doesn't exist in the app */ - reactions?: Reaction[]; }; }; @@ -257,19 +228,10 @@ type OriginalMessageActionableMentionWhisper = { /** 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 */ + /** Collection of accountIDs of users mentioned in message */ whisperedTo?: number[]; }; }; @@ -278,25 +240,10 @@ type OriginalMessageActionableMentionWhisper = { 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 */ + /** Collection of accountIDs of users mentioned in message */ whisperedTo?: number[]; }; }; @@ -304,9 +251,16 @@ type OriginalMessageActionableReportMentionWhisper = { /** Model of `submitted` report action */ type OriginalMessageSubmitted = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.SUBMITTED; + originalMessage: { + /** Approved expense amount */ + amount: number; - /** TODO: I think the type should match the scructure of `originalMessage` in `buildOptimisticSubmittedReportAction` */ - originalMessage: unknown; + /** Currency of the approved expense amount */ + currency: string; + + /** Report ID of the expense */ + expenseReportID: string; + }; }; /** Model of `closed` report action */ @@ -349,14 +303,6 @@ type OriginalMessageRenamed = { 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 */ @@ -412,23 +358,15 @@ type ModifiedExpense = { taxAmount?: number; taxRate?: string; oldTaxRate?: string; + whisperedTo?: number[]; }; /** 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; }; }; @@ -439,9 +377,6 @@ type OriginalMessageReportPreview = { /** ID of the report to be previewed */ linkedReportID: string; - /** TODO: Only used in tests */ - lastModified?: string; - /** Collection of accountIDs of users mentioned in report */ whisperedTo?: number[]; }; @@ -457,20 +392,10 @@ type OriginalMessagePolicyChangeLog = { 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; + choice: ValueOf; - /** TODO: Only used in tests */ - lastModified: string; - - /** TODO: Doesn't exist in the app */ + /** ID of the affected policy */ policyID: string; }; }; @@ -514,7 +439,7 @@ type OriginalMessageActionableTrackedExpenseWhisper = { /** ID of the transaction */ transactionID: string; - /** TODO: Only used in tests */ + /** When was the tracked expense whisper last modified */ lastModified: string; /** What was the decision of the user */ @@ -525,12 +450,7 @@ type OriginalMessageActionableTrackedExpenseWhisper = { /** 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; - }; + originalMessage: ReimbursementDeQueuedMessage; }; /** Model of `moved` report action */ @@ -610,6 +530,8 @@ export type { OriginalMessageActionName, ChangeLog, ModifiedExpense, + OriginalMessageApproved, + OriginalMessageSubmitted, OriginalMessageIOU, OriginalMessageCreated, OriginalMessageRenamed, diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 8659d0d474f5..ebacad3482b7 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -8,8 +8,12 @@ import type {WorkspaceTravelSettings} from './TravelSettings'; /** Distance units */ type Unit = 'mi' | 'km'; +/** TODO: Not enough context */ type TaxRateAttributes = { + /** TODO: Not enough context */ taxClaimablePercentage?: number; + + /** TODO: Not enough context */ taxRateExternalID?: string; }; @@ -35,6 +39,8 @@ type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Form fields that triggered the errors */ errorFields?: OnyxCommon.ErrorFields; + + /** TODO: Not enough context */ attributes?: TaxRateAttributes; }>; @@ -42,6 +48,8 @@ type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ type Attributes = { /** Distance unit name */ unit: Unit; + + /** TODO: Not enough context */ taxEnabled?: boolean; }; @@ -239,9 +247,8 @@ type TaxCode = { * Data imported from QuickBooks Online. */ type QBOConnectionData = { - /** TODO: I think this value can be changed to `ValueOf` */ /** Country code */ - country: string; + country: ValueOf; /** TODO: Doesn't exist in the app */ edition: string; @@ -373,7 +380,7 @@ type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** TODO: Doesn't exist in the app */ errors?: OnyxCommon.Errors; - /** TODO: Doesn't exist in the app */ + /** TODO: Not enough context */ exportDate: ValueOf; /** Configuration of the export */ @@ -407,8 +414,12 @@ type Tenant = { value: string; }; +/** TODO: Not enough context */ type XeroTrackingCategory = { + /** TODO: Not enough context */ id: string; + + /** TODO: Not enough context */ name: string; }; @@ -427,12 +438,17 @@ type XeroConnectionData = { /** TODO: Doesn't exist in the app */ revenueAccounts: Array<{ + /** TODO: Not enough context */ id: string; + + /** TODO: Not enough context */ name: string; }>; /** Collection of organizations */ tenants: Tenant[]; + + /** TODO: Not enough context */ trackingCategories: XeroTrackingCategory[]; }; @@ -455,7 +471,7 @@ type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ jobID: string; }; - /** TODO: Doesn't exist in the app */ + /** TODO: Not enough context */ enableNewCategories: boolean; /** Xero export configs */ @@ -480,7 +496,7 @@ type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** TODO: Doesn't exist in the app */ nonReimbursable: ExpenseTypesValues; - /** TODO: Doesn't exist in the app */ + /** TODO: Not enough context */ nonReimbursableAccount: string; /** TODO: Doesn't exist in the app */ @@ -498,7 +514,10 @@ type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** TODO: Doesn't exist in the app */ isConfigured: boolean; + + /** TODO: Not enough context */ mappings: XeroMappingType; + sync: { /** TODO: Doesn't exist in the app */ hasChosenAutoSyncOption: boolean; @@ -509,10 +528,10 @@ type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** ID of the bank account for Xero invoice collections */ invoiceCollectionsAccountID: string; - /** TODO: Doesn't exist in the app */ + /** ID of the bank account for Xero bill payment account */ reimbursementAccountID: string; - /** TODO: Doesn't exist in the app */ + /** Whether the reimbursed reports should be synched */ syncReimbursedReports: boolean; }; @@ -528,7 +547,6 @@ type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** State of integration connection */ type Connection = { - /** TODO: Doesn't exist in the app */ /** State of the last synchronization */ lastSync?: ConnectionLastSync; @@ -604,9 +622,6 @@ 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%). */ keys: string[]; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 82b8bb0260c5..3818a6dd6ad1 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -216,12 +216,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** 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; @@ -258,9 +252,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** 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; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index ae319c120f20..dc9bd2f94410 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -6,7 +6,7 @@ import type ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type * as OnyxCommon from './OnyxCommon'; -import type {Decision, OriginalMessageModifiedExpense, OriginalMessageReportPreview, Reaction} from './OriginalMessage'; +import type {Decision, OriginalMessageModifiedExpense, OriginalMessageReportPreview} from './OriginalMessage'; import type OriginalMessage from './OriginalMessage'; import type {NotificationPreference} from './Report'; import type {Receipt} from './Transaction'; @@ -57,12 +57,9 @@ type Message = { /** Whether the pending transaction was reversed and didn't post to the card */ isReversedTransaction?: boolean; - /** TODO: Only used in tests */ + /** Collection of accountIDs of users mentioned in message */ whisperedTo?: number[]; - /** TODO: Only used in tests */ - reactions?: Reaction[]; - /** In situations where moderation is required, this is the moderator decision data */ moderationDecision?: Decision; @@ -174,10 +171,10 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Avatar data to display on the report action */ avatar?: AvatarSource; - /** TODO: not enough context, seems to be used in tests only */ + /** TODO: not enough context */ automatic?: boolean; - /** TODO: Not enough context, seems to be used in tests only */ + /** TODO: Not enough context */ shouldShow?: boolean; /** The ID of childReport */ @@ -192,13 +189,10 @@ 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 */ + /** How many participants commented in the report */ childCommenterCount?: number; /** Timestamp of the most recent reply */ @@ -219,25 +213,15 @@ 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 */ diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index b3177b3d5d32..bda3f780bc37 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -115,13 +115,7 @@ 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 of receipt file */ name?: string; /** Path of the receipt file */ @@ -151,9 +145,6 @@ type Routes = Record; /** Model of receipt error */ type ReceiptError = { - /** TODO: This doesn't exist in the app */ - error?: string; - /** Path of the receipt file */ source: string; @@ -166,9 +157,6 @@ type ReceiptErrors = Record; /** Tax rate data */ type TaxRateData = { - /** TODO: This doesn't exist in the app */ - name: string; - /** Tax rate percentage */ value: string; @@ -184,15 +172,6 @@ type TaxRate = { /** 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; }; diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 0f13062b2e94..7b820281a5a6 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -607,7 +607,6 @@ describe('Unread Indicators', () => { lastReportAction = reportActions ? CollectionUtils.lastItem(reportActions) : undefined; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { lastMessageText: lastReportAction?.message?.[0]?.text, - lastVisibleActionCreated: DateUtils.getDBTime(lastReportAction?.timestamp), lastActorAccountID: lastReportAction?.actorAccountID, reportID: REPORT_ID, }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index d35eb61feb35..89c31d92843e 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -150,8 +150,6 @@ function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, shouldShow: true, created, - timestamp, - reportActionTimestamp: timestamp, person: [ { type: 'TEXT', @@ -168,17 +166,6 @@ function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = isEdited: false, whisperedTo: [], isDeletedParentAction: false, - reactions: [ - { - emoji: 'heart', - users: [ - { - accountID: 1, - skinTone: -1, - }, - ], - }, - ], }, ], originalMessage: { @@ -198,17 +185,6 @@ function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = }, html: 'hey', lastModified: '2023-08-28 15:28:12.432', - reactions: [ - { - emoji: 'heart', - users: [ - { - accountID: 1, - skinTone: -1, - }, - ], - }, - ], }, }; } diff --git a/tests/utils/ReportTestUtils.ts b/tests/utils/ReportTestUtils.ts index b9dc50aecec0..88fc6fc1bde1 100644 --- a/tests/utils/ReportTestUtils.ts +++ b/tests/utils/ReportTestUtils.ts @@ -19,7 +19,6 @@ const getFakeReportAction = (index: number, actionName?: ActionName): ReportActi html: 'hey', isDeletedParentAction: false, isEdited: false, - reactions: [], text: 'test', type: 'TEXT', whisperedTo: [], @@ -31,6 +30,8 @@ const getFakeReportAction = (index: number, actionName?: ActionName): ReportActi // IOUReportID: index, linkedReportID: index.toString(), whisperedTo: [], + reason: '', + violationName: '', }, pendingAction: null, person: [ @@ -42,10 +43,8 @@ const getFakeReportAction = (index: number, actionName?: ActionName): ReportActi ], reportActionID: index.toString(), previousReportActionID: (index === 0 ? 0 : index - 1).toString(), - reportActionTimestamp: 1696243169753, sequenceNumber: 0, shouldShow: true, - timestamp: 1696243169, } as ReportAction); const getMockedSortedReportActions = (length = 100): ReportAction[] => diff --git a/tests/utils/collections/reportActions.ts b/tests/utils/collections/reportActions.ts index 6dd82c3134ab..dc3e9d8427b5 100644 --- a/tests/utils/collections/reportActions.ts +++ b/tests/utils/collections/reportActions.ts @@ -58,17 +58,6 @@ export default function createRandomReportAction(index: number): ReportAction { isEdited: randBoolean(), isDeletedParentAction: randBoolean(), whisperedTo: randAggregation(), - reactions: [ - { - emoji: randWord(), - users: [ - { - accountID: index, - skinTone: index, - }, - ], - }, - ], }, ], originalMessage: { From e6ebaf91eae41e2376a3f61034d9000d721bb009 Mon Sep 17 00:00:00 2001 From: dominictb Date: Sat, 1 Jun 2024 23:55:13 +0700 Subject: [PATCH 058/246] chore: remove all selection ranges during navigation --- src/libs/Navigation/NavigationRoot.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 06a3dce8d59a..0584bf22a49f 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -135,6 +135,10 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N // We want to clean saved scroll offsets for screens that aren't anymore in the state. cleanStaleScrollOffsets(state); + + // clear all window selection on navigation + // this is to prevent the selection from persisting when navigating to a new page in web + window?.getSelection()?.removeAllRanges(); }; return ( From 261d3074cfe905131b71ada556a18a9265395b02 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 3 Jun 2024 11:46:55 +0800 Subject: [PATCH 059/246] refactor max amount digit logic --- src/CONST.ts | 2 +- src/libs/MoneyRequestUtils.ts | 22 +++---------------- src/pages/workspace/taxes/ValuePage.tsx | 2 +- .../taxes/WorkspaceCreateTaxPage.tsx | 2 +- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 4f622cc0b3bf..a8101a7c64db 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1571,7 +1571,7 @@ const CONST = { APPROVE: 'approve', TRACK: 'track', }, - AMOUNT_MAX_LENGTH: 10, + AMOUNT_MAX_LENGTH: 8, RECEIPT_STATE: { SCANREADY: 'SCANREADY', OPEN: 'OPEN', diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index e476f02e3075..5999ce2dcbcf 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -44,32 +44,16 @@ function addLeadingZero(amount: string): string { return amount.startsWith('.') ? `0${amount}` : amount; } -/** - * Calculate the length of the amount with leading zeroes - */ -function calculateAmountLength(amount: string): number { - const leadingZeroes = amount.match(/^0+/); - const leadingZeroesLength = leadingZeroes?.[0]?.length ?? 0; - const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 100).toFixed(2)).toString(); - - // Don't allow the amount if the parsing to number results in NaN, Infinity, or with a scientific notation (e.g., 1e+26) - if (/NaN|Infinity|e\+/.test(absAmount)) { - return CONST.IOU.AMOUNT_MAX_LENGTH + 1; - } - - return leadingZeroesLength + (absAmount === '0' ? 2 : stripDotFromAmount(absAmount).length); -} - /** * Check if amount is a decimal up to 3 digits */ function validateAmount(amount: string, decimals: number, amountMaxLength: number = CONST.IOU.AMOUNT_MAX_LENGTH): boolean { const regexString = decimals === 0 - ? `^\\d+(,\\d*)*$` // Don't allow decimal point if decimals === 0 - : `^\\d+(,\\d*)*(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point + ? `^\\d{1,${amountMaxLength}}$` // Don't allow decimal point if decimals === 0 + : `^\\d{1,${amountMaxLength}}(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point const decimalNumberRegex = new RegExp(regexString, 'i'); - return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount) <= amountMaxLength); + return amount === '' || decimalNumberRegex.test(amount); } /** diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx index d73e5997a5ed..654618f299bd 100644 --- a/src/pages/workspace/taxes/ValuePage.tsx +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -89,7 +89,7 @@ function ValuePage({ // The default currency uses 2 decimal places, so we substract it extraDecimals={CONST.MAX_TAX_RATE_DECIMAL_PLACES - 2} // We increase the amount max length to support the extra decimals. - amountMaxLength={CONST.MAX_TAX_RATE_DECIMAL_PLACES + CONST.MAX_TAX_RATE_INTEGER_PLACES} + amountMaxLength={CONST.MAX_TAX_RATE_INTEGER_PLACES} extraSymbol={%} ref={inputCallbackRef} /> diff --git a/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx index 1301ad100d77..2045ffe3e79b 100644 --- a/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx @@ -105,7 +105,7 @@ function WorkspaceCreateTaxPage({ // The default currency uses 2 decimal places, so we substract it extraDecimals={CONST.MAX_TAX_RATE_DECIMAL_PLACES - 2} // We increase the amount max length to support the extra decimals. - amountMaxLength={CONST.MAX_TAX_RATE_DECIMAL_PLACES + CONST.MAX_TAX_RATE_INTEGER_PLACES} + amountMaxLength={CONST.MAX_TAX_RATE_DECIMAL_PLACES} extraSymbol={%} /> From 2c9e8cc50cff3dadcdd514cfab5cf88556ee953d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 3 Jun 2024 11:53:00 +0800 Subject: [PATCH 060/246] add unit test --- tests/unit/MoneyRequestUtilsTest.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/unit/MoneyRequestUtilsTest.ts diff --git a/tests/unit/MoneyRequestUtilsTest.ts b/tests/unit/MoneyRequestUtilsTest.ts new file mode 100644 index 000000000000..f8e2c75c360b --- /dev/null +++ b/tests/unit/MoneyRequestUtilsTest.ts @@ -0,0 +1,22 @@ +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; + +describe('ReportActionsUtils', () => { + describe('validateAmount', () => { + it('should pass the validation when amount is within the max digit and decimal', () => { + expect(MoneyRequestUtils.validateAmount('12345678', 2, 8)).toBe(true); + expect(MoneyRequestUtils.validateAmount('12345678', 0, 8)).toBe(true); + expect(MoneyRequestUtils.validateAmount('12345678.12', 2, 8)).toBe(true); + expect(MoneyRequestUtils.validateAmount('1234567.1', 2, 8)).toBe(true); + expect(MoneyRequestUtils.validateAmount('12345678.123', 3, 8)).toBe(true); + expect(MoneyRequestUtils.validateAmount('1234.1234', 4, 4)).toBe(true); + }); + + it("shouldn't pass the validation when amount is bigger than the max digit and decimal", () => { + expect(MoneyRequestUtils.validateAmount('12345678.123', 2, 8)).toBe(false); + expect(MoneyRequestUtils.validateAmount('12345678.1', 0, 8)).toBe(false); + expect(MoneyRequestUtils.validateAmount('123456789.12', 2, 8)).toBe(false); + expect(MoneyRequestUtils.validateAmount('123456789.1234', 3, 8)).toBe(false); + expect(MoneyRequestUtils.validateAmount('1234.12345', 4, 4)).toBe(false); + }); + }); +}); \ No newline at end of file From cc87e5280a47eabdc9925ff5ed20c134c7639dc3 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 3 Jun 2024 11:53:33 +0800 Subject: [PATCH 061/246] remove unused function --- src/libs/MoneyRequestUtils.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 5999ce2dcbcf..67ba9f62421d 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -10,13 +10,6 @@ function stripCommaFromAmount(amount: string): string { return amount.replace(/,/g, ''); } -/** - * Strip dot from the amount - */ -function stripDotFromAmount(amount: string): string { - return amount.replace(/\./g, ''); -} - /** * Strip spaces from the amount */ From 63819bfc672d154090648c93ed33ef688e937197 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 3 Jun 2024 11:53:38 +0800 Subject: [PATCH 062/246] prettier --- tests/unit/MoneyRequestUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/MoneyRequestUtilsTest.ts b/tests/unit/MoneyRequestUtilsTest.ts index f8e2c75c360b..dfd339049d37 100644 --- a/tests/unit/MoneyRequestUtilsTest.ts +++ b/tests/unit/MoneyRequestUtilsTest.ts @@ -19,4 +19,4 @@ describe('ReportActionsUtils', () => { expect(MoneyRequestUtils.validateAmount('1234.12345', 4, 4)).toBe(false); }); }); -}); \ No newline at end of file +}); From 8d75abfffddab16157bb4b41336c5c27e8461c03 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 3 Jun 2024 12:02:16 +0800 Subject: [PATCH 063/246] use the correct const --- src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx index 2045ffe3e79b..5a8f6df383d6 100644 --- a/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx @@ -105,7 +105,7 @@ function WorkspaceCreateTaxPage({ // The default currency uses 2 decimal places, so we substract it extraDecimals={CONST.MAX_TAX_RATE_DECIMAL_PLACES - 2} // We increase the amount max length to support the extra decimals. - amountMaxLength={CONST.MAX_TAX_RATE_DECIMAL_PLACES} + amountMaxLength={CONST.MAX_TAX_RATE_INTEGER_PLACES} extraSymbol={%} /> From 0c7c71d46fecc8cde46f009d8ccb472316786ef8 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 12:19:55 +0700 Subject: [PATCH 064/246] fix chat doesn't scroll to bottom --- src/libs/actions/IOU.ts | 7 ++++++- src/libs/actions/Report.ts | 2 +- src/pages/home/report/ReportActionsList.tsx | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 128ee5b85b7b..2702dd4b7b0c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1983,12 +1983,17 @@ function getMoneyRequestInformation( reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction as ReportPreviewAction, false, comment, optimisticTransaction); } else { reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); + chatReport.lastVisibleActionCreated = reportPreviewAction.created; // Generated ReportPreview action is a parent report action of the iou report. // We are setting the iou report's parentReportActionID to display subtitle correctly in IOU page when offline. iouReport.parentReportActionID = reportPreviewAction.reportActionID; } + if (iouAction) { + iouReport.lastVisibleActionCreated = iouAction.created; + } + const shouldCreateOptimisticPersonalDetails = isNewChatReport && !allPersonalDetails[payerAccountID]; // Add optimistic personal details for participant const optimisticPersonalDetailListAction = shouldCreateOptimisticPersonalDetails @@ -4625,7 +4630,7 @@ function startSplitBill({ API.write(WRITE_COMMANDS.START_SPLIT_BILL, parameters, {optimisticData, successData, failureData}); Navigation.dismissModalWithReport(splitChatReport); - Report.notifyNewAction(splitChatReport.chatReportID ?? '', currentUserAccountID); + Report.notifyNewAction(splitChatReport.reportID ?? '', currentUserAccountID); } /** Used for editing a split expense while it's still scanning or when SmartScan fails, it completes a split expense started by startSplitBill above. diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7c73ef4a1eac..1dd67332cb99 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -478,7 +478,7 @@ function addActions(reportID: string, text = '', file?: FileObject) { const lastCommentText = ReportUtils.formatReportLastMessageText(lastComment?.text ?? ''); const optimisticReport: Partial = { - lastVisibleActionCreated: currentTime, + lastVisibleActionCreated: lastAction?.created, lastMessageTranslationKey: lastComment?.translationKey ?? '', lastMessageText: lastCommentText, lastMessageHtml: lastCommentText, diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index c700fea4fb85..6ad01455b543 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -211,15 +211,17 @@ function ReportActionsList({ ); // whisper action doesn't affect lastVisibleActionCreated, so we should not take it into account while checking if there is the newest report action - const newestVisibleReportAction = useMemo(() => sortedVisibleReportActions.find((item) => !ReportActionsUtils.isWhisperAction(item)) ?? null, [sortedVisibleReportActions]); + // const newestVisibleReportAction = useMemo(() => sortedVisibleReportActions.find((item) => !ReportActionsUtils.isWhisperAction(item)) ?? null, [sortedVisibleReportActions]); const lastActionIndex = sortedVisibleReportActions[0]?.reportActionID; const reportActionSize = useRef(sortedVisibleReportActions.length); - const hasNewestReportAction = newestVisibleReportAction?.created === report.lastVisibleActionCreated; + const hasNewestReportAction = sortedVisibleReportActions[0]?.created === report.lastVisibleActionCreated; const hasNewestReportActionRef = useRef(hasNewestReportAction); hasNewestReportActionRef.current = hasNewestReportAction; const previousLastIndex = useRef(lastActionIndex); + console.log(sortedVisibleReportActions[0].created, report.lastVisibleActionCreated) + const isLastPendingActionIsDelete = sortedReportActions?.[0]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const linkedReportActionID = route.params?.reportActionID ?? ''; @@ -346,6 +348,7 @@ function ReportActionsList({ (isFromCurrentUser: boolean) => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where // they are now in the list. + console.log(hasNewestReportActionRef.current); if (!isFromCurrentUser || !hasNewestReportActionRef.current) { return; } From 5ae57d9e45c7dca9f5e6c78232895f3e1b7b29de Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 3 Jun 2024 09:49:49 +0200 Subject: [PATCH 065/246] feat: subscription settings ui --- src/CONST.ts | 1 + src/components/FeedbackSurvey.tsx | 75 +++++++++++++++ src/languages/en.ts | 21 ++++ src/languages/es.ts | 21 ++++ .../DisableAutoRenewSurveyPage.tsx | 35 +++++++ .../Subscription/SubscriptionSettingsPage.tsx | 2 + .../SubscriptionSettingsSection.tsx | 96 +++++++++++++++++++ .../workflows/ToggleSettingsOptionRow.tsx | 15 ++- 8 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 src/components/FeedbackSurvey.tsx create mode 100644 src/pages/settings/Subscription/DisableAutoRenewSurveyPage.tsx create mode 100644 src/pages/settings/Subscription/SubscriptionSettingsSection.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 6517ece4276d..000ad3798d72 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -594,6 +594,7 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', LIST_OF_RESTRICTED_BUSINESSES: 'https://community.expensify.com/discussion/6191/list-of-restricted-businesses', TRAVEL_TERMS_URL: `${USE_EXPENSIFY_URL}/travelterms`, + CORPORATE_KARMA_URL: `${USE_EXPENSIFY_URL}/corporate-karma`, // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', diff --git a/src/components/FeedbackSurvey.tsx b/src/components/FeedbackSurvey.tsx new file mode 100644 index 000000000000..92743c1f3f0e --- /dev/null +++ b/src/components/FeedbackSurvey.tsx @@ -0,0 +1,75 @@ +import React, {useState} from 'react'; +import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import type {TranslationPaths} from '@src/languages/types'; +import Button from './Button'; +import FixedFooter from './FixedFooter'; +import FormAlertWithSubmitButton from './FormAlertWithSubmitButton'; +import SingleOptionSelector from './SingleOptionSelector'; +import Text from './Text'; + +type FeedbackSurveyProps = { + /** Title of the survey */ + title: string; + /** Description of the survey */ + description: string; + /** Callback to be called when the survey is submitted */ + onSubmit: () => void; +}; + +const OPTION_KEYS = { + IMPROVEMENT: 'improvement', + EXPENSIVE: 'expensive', + SUPPORT: 'support', + CLOSING: 'closing', +}; + +type Option = { + key: string; + label: TranslationPaths; +}; + +const OPTIONS: Option[] = [ + {key: OPTION_KEYS.IMPROVEMENT, label: 'subscription.subscriptionSettings.functionalityNeeds'}, + {key: OPTION_KEYS.EXPENSIVE, label: 'subscription.subscriptionSettings.tooExpensive'}, + {key: OPTION_KEYS.SUPPORT, label: 'subscription.subscriptionSettings.inadequateCustomerSupport'}, + {key: OPTION_KEYS.CLOSING, label: 'subscription.subscriptionSettings.companyClosing'}, +]; + +function FeedbackSurvey({title, description, onSubmit}: FeedbackSurveyProps) { + const {translate} = useLocalize(); + const [reason, setReason] = useState