From 53828c7b306c4fa136db7e270ee1890ba248127c Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 28 Aug 2024 14:00:45 +0200 Subject: [PATCH 001/274] Add WorkspaceMembersSelectionList --- .../WorkspaceMembersSelectionList.tsx | 115 ++++++++++++++++++ .../categories/CategoryApproverPage.tsx | 60 +++++++++ 2 files changed, 175 insertions(+) create mode 100644 src/components/WorkspaceMembersSelectionList.tsx create mode 100644 src/pages/workspace/categories/CategoryApproverPage.tsx diff --git a/src/components/WorkspaceMembersSelectionList.tsx b/src/components/WorkspaceMembersSelectionList.tsx new file mode 100644 index 000000000000..f2ce215fc500 --- /dev/null +++ b/src/components/WorkspaceMembersSelectionList.tsx @@ -0,0 +1,115 @@ +import React, {useMemo} from 'react'; +import type {SectionListData} from 'react-native'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; +import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import type {Icon} from '@src/types/onyx/OnyxCommon'; +import Badge from './Badge'; +import {FallbackAvatar} from './Icon/Expensicons'; +import {usePersonalDetails} from './OnyxProvider'; +import SelectionList from './SelectionList'; +import InviteMemberListItem from './SelectionList/InviteMemberListItem'; +import type {Section} from './SelectionList/types'; + +type WorkspaceMembersSelectionListProps = { + policyID: string; + selectedApprover: string; + setApprover: (email: string) => void; +}; + +type SelectionListApprover = { + text: string; + alternateText: string; + keyForList: string; + isSelected: boolean; + login: string; + rightElement?: React.ReactNode; + icons: Icon[]; +}; +type ApproverSection = SectionListData>; + +function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}: WorkspaceMembersSelectionListProps) { + const {translate} = useLocalize(); + const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus(); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const personalDetails = usePersonalDetails(); + const policy = usePolicy(policyID); + + const sections: ApproverSection[] = useMemo(() => { + const approvers: SelectionListApprover[] = []; + + if (policy?.employeeList) { + const availableApprovers = Object.values(policy.employeeList) + .map((employee): SelectionListApprover | null => { + const isAdmin = employee?.role === CONST.REPORT.ROLE.ADMIN; + const email = employee.email; + + if (!email) { + return null; + } + + const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); + const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? ''); + const {avatar, displayName = email} = personalDetails?.[accountID] ?? {}; + + return { + text: displayName, + alternateText: email, + keyForList: email, + isSelected: selectedApprover === email, + login: email, + icons: [{source: avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: displayName, id: accountID}], + rightElement: isAdmin ? : undefined, + }; + }) + .filter((approver): approver is SelectionListApprover => !!approver); + + approvers.push(...availableApprovers); + } + + const filteredApprovers = + debouncedSearchTerm !== '' + ? approvers.filter((option) => { + const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm); + const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue); + return isPartOfSearchTerm; + }) + : approvers; + + return [ + { + title: undefined, + data: OptionsListUtils.sortAlphabetically(filteredApprovers, 'text'), + shouldShow: true, + }, + ]; + }, [debouncedSearchTerm, personalDetails, policy?.employeeList, selectedApprover, translate]); + + const handleOnSelectRow = (approver: SelectionListApprover) => { + setApprover(approver.login); + }; + + const headerMessage = useMemo(() => (searchTerm && !sections[0].data.length ? translate('common.noResultsFound') : ''), [searchTerm, sections, translate]); + + return ( + + ); +} + +export default WorkspaceMembersSelectionList; diff --git a/src/pages/workspace/categories/CategoryApproverPage.tsx b/src/pages/workspace/categories/CategoryApproverPage.tsx new file mode 100644 index 000000000000..ae882ceef149 --- /dev/null +++ b/src/pages/workspace/categories/CategoryApproverPage.tsx @@ -0,0 +1,60 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import WorkspaceMembersSelectionList from '@components/WorkspaceMembersSelectionList'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as Category from '@userActions/Policy/Category'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type EditCategoryPageProps = StackScreenProps; + +function CategoryApproverPage({ + route: { + params: {policyID, categoryName}, + }, +}: EditCategoryPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))} + /> + { + Category.setPolicyCategoryApprover(policyID, categoryName, email); + Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))); + }} + /> + + + ); +} + +CategoryApproverPage.displayName = 'CategoryApproverPage'; + +export default CategoryApproverPage; From 374f29870cdc8c0ca00c6218b8ab8e161925cb07 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 28 Aug 2024 21:52:34 +0200 Subject: [PATCH 002/274] add interface for tag edit --- src/languages/en.ts | 2 + src/languages/es.ts | 130 ++---------------- .../categories/CategoryApproverPage.tsx | 110 +++++++-------- src/pages/workspace/tags/TagSettingsPage.tsx | 17 +++ 4 files changed, 82 insertions(+), 177 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index afa501c0b6bc..fb496e870e4b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2999,6 +2999,8 @@ export default { importedFromAccountingSoftware: 'The tags below are imported from your', glCode: 'GL code', updateGLCodeFailureMessage: 'An error occurred while updating the GL code, please try again.', + tagRules: 'Tag rules', + approverDescription: 'Approver', }, taxes: { subtitle: 'Add tax names, rates, and set defaults.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 941592276dd7..062a57395c4f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,125 +1,8 @@ -import {Str} from 'expensify-common'; +import { Str } from 'expensify-common'; import CONST from '@src/CONST'; -import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; -import type { - AddressLineParams, - AdminCanceledRequestParams, - AlreadySignedInParams, - ApprovalWorkflowErrorParams, - ApprovedAmountParams, - BeginningOfChatHistoryAdminRoomPartOneParams, - BeginningOfChatHistoryAnnounceRoomPartOneParams, - BeginningOfChatHistoryAnnounceRoomPartTwo, - BeginningOfChatHistoryDomainRoomPartOneParams, - CanceledRequestParams, - ChangeFieldParams, - ChangePolicyParams, - ChangeTypeParams, - CharacterLimitParams, - ConfirmHoldExpenseParams, - ConfirmThatParams, - DateShouldBeAfterParams, - DateShouldBeBeforeParams, - DelegateSubmitParams, - DeleteActionParams, - DeleteConfirmationParams, - DeleteExpenseTranslationParams, - DidSplitAmountMessageParams, - DistanceRateOperationsParams, - EditActionParams, - ElectronicFundsParams, - EnglishTranslation, - EnterMagicCodeParams, - ExportedToIntegrationParams, - FormattedMaxLengthParams, - ForwardedAmountParams, - GoBackMessageParams, - GoToRoomParams, - InstantSummaryParams, - IssueVirtualCardParams, - LocalTimeParams, - LoggedInAsParams, - LogSizeParams, - ManagerApprovedAmountParams, - ManagerApprovedParams, - MarkedReimbursedParams, - MarkReimbursedFromIntegrationParams, - NoLongerHaveAccessParams, - NotAllowedExtensionParams, - NotYouParams, - OOOEventSummaryFullDayParams, - OOOEventSummaryPartialDayParams, - OurEmailProviderParams, - PaidElsewhereWithAmountParams, - PaidWithExpensifyWithAmountParams, - ParentNavigationSummaryParams, - PayerOwesAmountParams, - PayerOwesParams, - PayerPaidAmountParams, - PayerPaidParams, - PayerSettledParams, - PaySomeoneParams, - ReimbursementRateParams, - RemovedTheRequestParams, - RemoveMembersWarningPrompt, - RenamedRoomActionParams, - ReportArchiveReasonsClosedParams, - ReportArchiveReasonsMergedParams, - ReportArchiveReasonsPolicyDeletedParams, - ReportArchiveReasonsRemovedFromPolicyParams, - RequestAmountParams, - RequestCountParams, - RequestedAmountMessageParams, - ResolutionConstraintsParams, - RoomNameReservedErrorParams, - RoomRenamedToParams, - SetTheDistanceParams, - SetTheRequestParams, - SettledAfterAddedBankAccountParams, - SettleExpensifyCardParams, - ShareParams, - SignUpNewFaceCodeParams, - SizeExceededParams, - SplitAmountParams, - StepCounterParams, - StripePaidParams, - TaskCreatedActionParams, - TermsParams, - ThreadRequestReportNameParams, - ThreadSentMoneyReportNameParams, - ToValidateLoginParams, - TransferParams, - UnapprovedParams, - UnshareParams, - UntilTimeParams, - UpdatedTheDistanceParams, - UpdatedTheRequestParams, - UsePlusButtonParams, - UserIsAlreadyMemberParams, - UserSplitParams, - ViolationsAutoReportedRejectedExpenseParams, - ViolationsCashExpenseWithNoReceiptParams, - ViolationsConversionSurchargeParams, - ViolationsInvoiceMarkupParams, - ViolationsMaxAgeParams, - ViolationsMissingTagParams, - ViolationsModifiedAmountParams, - ViolationsOverAutoApprovalLimitParams, - ViolationsOverCategoryLimitParams, - ViolationsOverLimitParams, - ViolationsPerDayLimitParams, - ViolationsReceiptRequiredParams, - ViolationsRterParams, - ViolationsTagOutOfPolicyParams, - ViolationsTaxOutOfPolicyParams, - WaitingOnBankAccountParams, - WalletProgramParams, - WelcomeEnterMagicCodeParams, - WelcomeNoteParams, - WelcomeToRoomParams, - WeSentYouMagicSignInLinkParams, - ZipCodeExampleFormatParams, -} from './types'; +import type { ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName } from '@src/types/onyx/Policy'; +import type { AddressLineParams, AdminCanceledRequestParams, AlreadySignedInParams, ApprovalWorkflowErrorParams, ApprovedAmountParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, BeginningOfChatHistoryDomainRoomPartOneParams, CanceledRequestParams, ChangeFieldParams, ChangePolicyParams, ChangeTypeParams, CharacterLimitParams, ConfirmHoldExpenseParams, ConfirmThatParams, DateShouldBeAfterParams, DateShouldBeBeforeParams, DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, DeleteExpenseTranslationParams, DidSplitAmountMessageParams, DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnglishTranslation, EnterMagicCodeParams, ExportedToIntegrationParams, FormattedMaxLengthParams, ForwardedAmountParams, GoBackMessageParams, GoToRoomParams, InstantSummaryParams, IssueVirtualCardParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, ManagerApprovedAmountParams, ManagerApprovedParams, MarkedReimbursedParams, MarkReimbursedFromIntegrationParams, NoLongerHaveAccessParams, NotAllowedExtensionParams, NotYouParams, OOOEventSummaryFullDayParams, OOOEventSummaryPartialDayParams, OurEmailProviderParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, ParentNavigationSummaryParams, PayerOwesAmountParams, PayerOwesParams, PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, PaySomeoneParams, ReimbursementRateParams, RemovedTheRequestParams, RemoveMembersWarningPrompt, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, ReportArchiveReasonsMergedParams, ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, ResolutionConstraintsParams, RoomNameReservedErrorParams, RoomRenamedToParams, SetTheDistanceParams, SetTheRequestParams, SettledAfterAddedBankAccountParams, SettleExpensifyCardParams, ShareParams, SignUpNewFaceCodeParams, SizeExceededParams, SplitAmountParams, StepCounterParams, StripePaidParams, TaskCreatedActionParams, TermsParams, ThreadRequestReportNameParams, ThreadSentMoneyReportNameParams, ToValidateLoginParams, TransferParams, UnapprovedParams, UnshareParams, UntilTimeParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, ViolationsAutoReportedRejectedExpenseParams, ViolationsCashExpenseWithNoReceiptParams, ViolationsConversionSurchargeParams, ViolationsInvoiceMarkupParams, ViolationsMaxAgeParams, ViolationsMissingTagParams, ViolationsModifiedAmountParams, ViolationsOverAutoApprovalLimitParams, ViolationsOverCategoryLimitParams, ViolationsOverLimitParams, ViolationsPerDayLimitParams, ViolationsReceiptRequiredParams, ViolationsRterParams, ViolationsTagOutOfPolicyParams, ViolationsTaxOutOfPolicyParams, WaitingOnBankAccountParams, WalletProgramParams, WelcomeEnterMagicCodeParams, WelcomeNoteParams, WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, ZipCodeExampleFormatParams } from './types'; + /* eslint-disable max-len */ export default { @@ -3047,6 +2930,8 @@ export default { importedFromAccountingSoftware: 'Etiquetas importadas desde', glCode: 'Código de Libro Mayor', updateGLCodeFailureMessage: 'Se produjo un error al actualizar el código de Libro Mayor. Por favor, inténtelo nuevamente.', + tagRules: 'Tag rules', + approverDescription: 'Approver', }, taxes: { subtitle: 'Añade nombres, tasas y establezca valores por defecto para los impuestos.', @@ -3771,6 +3656,7 @@ export default { pleaseEnterTaskName: 'Por favor, introduce un título', pleaseEnterTaskDestination: 'Por favor, selecciona dónde deseas compartir esta tarea.', }, + task: { task: 'Tarea', title: 'Título', @@ -5010,4 +4896,4 @@ export default { updateRoomDescription: 'establece la descripción de la sala a:', clearRoomDescription: 'la descripción de la habitación ha sido borrada', }, -} satisfies EnglishTranslation; +} satisfies EnglishTranslation; \ No newline at end of file diff --git a/src/pages/workspace/categories/CategoryApproverPage.tsx b/src/pages/workspace/categories/CategoryApproverPage.tsx index ae882ceef149..b02abd7f8524 100644 --- a/src/pages/workspace/categories/CategoryApproverPage.tsx +++ b/src/pages/workspace/categories/CategoryApproverPage.tsx @@ -1,60 +1,60 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; -import {useOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import WorkspaceMembersSelectionList from '@components/WorkspaceMembersSelectionList'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import Navigation from '@libs/Navigation/Navigation'; -import type {SettingsNavigatorParamList} from '@navigation/types'; -import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import * as Category from '@userActions/Policy/Category'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; +// import type {StackScreenProps} from '@react-navigation/stack'; +// import React from 'react'; +// import {useOnyx} from 'react-native-onyx'; +// import HeaderWithBackButton from '@components/HeaderWithBackButton'; +// import ScreenWrapper from '@components/ScreenWrapper'; +// import WorkspaceMembersSelectionList from '@components/WorkspaceMembersSelectionList'; +// import useLocalize from '@hooks/useLocalize'; +// import useThemeStyles from '@hooks/useThemeStyles'; +// import Navigation from '@libs/Navigation/Navigation'; +// import type {SettingsNavigatorParamList} from '@navigation/types'; +// import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +// import * as Category from '@userActions/Policy/Category'; +// import CONST from '@src/CONST'; +// import ONYXKEYS from '@src/ONYXKEYS'; +// import ROUTES from '@src/ROUTES'; +// import type SCREENS from '@src/SCREENS'; -type EditCategoryPageProps = StackScreenProps; +// type EditCategoryPageProps = StackScreenProps; -function CategoryApproverPage({ - route: { - params: {policyID, categoryName}, - }, -}: EditCategoryPageProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); +// function CategoryApproverPage({ +// route: { +// params: {policyID, categoryName}, +// }, +// }: EditCategoryPageProps) { +// const styles = useThemeStyles(); +// const {translate} = useLocalize(); +// const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); - return ( - - - Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))} - /> - { - Category.setPolicyCategoryApprover(policyID, categoryName, email); - Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))); - }} - /> - - - ); -} +// return ( +// +// +// Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))} +// /> +// { +// Category.setPolicyCategoryApprover(policyID, categoryName, email); +// Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))); +// }} +// /> +// +// +// ); +// } -CategoryApproverPage.displayName = 'CategoryApproverPage'; +// CategoryApproverPage.displayName = 'CategoryApproverPage'; -export default CategoryApproverPage; +// export default CategoryApproverPage; diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index a0f98ef699ed..c4dbd85a0821 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -85,6 +85,10 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps) Navigation.navigate(ROUTES.WORKSPACE_TAG_GL_CODE.getRoute(route.params.policyID, route.params.orderWeight, currentPolicyTag.name)); }; + const navigateToEditTagApprover = () => { + Navigation.navigate(ROUTES.WORKSPACE_TAG_EDIT.getRoute(route.params.policyID, route.params.orderWeight, currentPolicyTag.name)); + }; + const isThereAnyAccountingConnection = Object.keys(policy?.connections ?? {}).length !== 0; const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); @@ -150,6 +154,19 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps) shouldShowRightIcon /> + + + {translate('workspace.tags.tagRules')} + + + + + {shouldShowDeleteMenuItem && ( Date: Thu, 29 Aug 2024 12:38:52 +0200 Subject: [PATCH 003/274] wire up the tags --- src/ROUTES.ts | 4 ++ src/SCREENS.ts | 1 + .../WorkspaceMembersSelectionList.tsx | 2 +- .../parameters/SetPolicyTagApproverParams.ts | 7 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 11 ++-- .../ModalStackNavigators/index.tsx | 1 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 7 ++ src/libs/Navigation/types.ts | 5 ++ src/libs/actions/Policy/Tag.ts | 12 ++++ src/pages/workspace/tags/TagApproverPage.tsx | 64 +++++++++++++++++++ src/pages/workspace/tags/TagSettingsPage.tsx | 28 ++++---- src/types/onyx/Policy.ts | 52 +++++++++++++++ 14 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyTagApproverParams.ts create mode 100644 src/pages/workspace/tags/TagApproverPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 6f9289e0b283..4fc164d91830 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -786,6 +786,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName', getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${encodeURIComponent(tagName)}` as const, }, + WORKSPACE_TAG_APPROVER: { + route: 'settings/workspaces/:policyID/tag/:orderWeight/:tagName/approver', + getRoute: (policyID: string, orderWeight: number, tagName: string) => `settings/workspaces/${policyID}/tag/${orderWeight}/${tagName}/approver` as const, + }, WORKSPACE_TAG_LIST_VIEW: { route: 'settings/workspaces/:policyID/tag-list/:orderWeight', getRoute: (policyID: string, orderWeight: number) => `settings/workspaces/${policyID}/tag-list/${orderWeight}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e8c1f71b9db0..d2ec2e6b249a 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -408,6 +408,7 @@ const SCREENS = { TAX_CREATE: 'Workspace_Tax_Create', TAG_CREATE: 'Tag_Create', TAG_SETTINGS: 'Tag_Settings', + TAG_APPROVER: 'Tag_Approver', TAG_LIST_VIEW: 'Tag_List_View', TAG_GL_CODE: 'Tag_GL_Code', CURRENCY: 'Workspace_Profile_Currency', diff --git a/src/components/WorkspaceMembersSelectionList.tsx b/src/components/WorkspaceMembersSelectionList.tsx index f2ce215fc500..fb98fe9fb447 100644 --- a/src/components/WorkspaceMembersSelectionList.tsx +++ b/src/components/WorkspaceMembersSelectionList.tsx @@ -84,7 +84,7 @@ function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover} return [ { title: undefined, - data: OptionsListUtils.sortAlphabetically(filteredApprovers, 'text'), + data: filteredApprovers, shouldShow: true, }, ]; diff --git a/src/libs/API/parameters/SetPolicyTagApproverParams.ts b/src/libs/API/parameters/SetPolicyTagApproverParams.ts new file mode 100644 index 000000000000..fbd086dfebed --- /dev/null +++ b/src/libs/API/parameters/SetPolicyTagApproverParams.ts @@ -0,0 +1,7 @@ +type SetPolicyTagApproverParams = { + policyID: string; + tagName: string; + email: string; +}; + +export default SetPolicyTagApproverParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index fe2e89faa7f0..537897514f87 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -289,3 +289,4 @@ export type {default as OpenCardDetailsPageParams} from './OpenCardDetailsPagePa export type {default as EnablePolicyCompanyCardsParams} from './EnablePolicyCompanyCardsParams'; export type {default as ToggleCardContinuousReconciliationParams} from './ToggleCardContinuousReconciliationParams'; export type {default as UpdateExpensifyCardLimitTypeParams} from './UpdateExpensifyCardLimitTypeParams'; +export type {default as SetPolicyTagApproverParams} from './SetPolicyTagApproverParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 5dad9820ea19..f892de8d3e89 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1,11 +1,12 @@ -import type {ValueOf} from 'type-fest'; +import type { ValueOf } from 'type-fest'; import type CONST from '@src/CONST'; -import type {SageIntacctMappingValue} from '@src/types/onyx/Policy'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import type { SageIntacctMappingValue } from '@src/types/onyx/Policy'; +import type { EmptyObject } from '@src/types/utils/EmptyObject'; import type * as Parameters from './parameters'; import type SignInUserParams from './parameters/SignInUserParams'; import type UpdateBeneficialOwnersForBankAccountParams from './parameters/UpdateBeneficialOwnersForBankAccountParams'; + type ApiRequestType = ValueOf; const WRITE_COMMANDS = { @@ -345,6 +346,7 @@ const WRITE_COMMANDS = { CREATE_EXPENSIFY_CARD: 'CreateExpensifyCard', CREATE_ADMIN_ISSUED_VIRTUAL_CARD: 'CreateAdminIssuedVirtualCard', TOGGLE_CARD_CONTINUOUS_RECONCILIATION: 'ToggleCardContinuousReconciliation', + SET_POLICY_TAG_APPROVER: 'SetPolicyTagApprover', } as const; type WriteCommand = ValueOf; @@ -557,6 +559,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.SET_POLICY_TAXES_CURRENCY_DEFAULT]: Parameters.SetPolicyCurrencyDefaultParams; [WRITE_COMMANDS.SET_POLICY_CUSTOM_TAX_NAME]: Parameters.SetPolicyCustomTaxNameParams; + [WRITE_COMMANDS.SET_POLICY_TAG_APPROVER]: Parameters.SetPolicyTagApproverParams; [WRITE_COMMANDS.SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT]: Parameters.SetPolicyForeignCurrencyDefaultParams; [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; @@ -854,4 +857,4 @@ type CommandOfType = TRequestType extends t ? ReadCommand : SideEffectRequestCommand; -export type {ApiCommand, ApiRequestType, ApiRequestCommandParameters, CommandOfType, WriteCommand, ReadCommand, SideEffectRequestCommand}; +export type {ApiCommand, ApiRequestType, ApiRequestCommandParameters, CommandOfType, WriteCommand, ReadCommand, SideEffectRequestCommand}; \ No newline at end of file diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 9c95edd8be9c..3564c6fd6774 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -260,6 +260,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/tags/WorkspaceEditTagsPage').default, [SCREENS.WORKSPACE.TAG_CREATE]: () => require('../../../../pages/workspace/tags/WorkspaceCreateTagPage').default, [SCREENS.WORKSPACE.TAG_EDIT]: () => require('../../../../pages/workspace/tags/EditTagPage').default, + [SCREENS.WORKSPACE.TAG_APPROVER]: () => require('../../../../pages/workspace/tags/TagApproverPage').default, [SCREENS.WORKSPACE.TAG_GL_CODE]: () => require('../../../../pages/workspace/tags/TagGLCodePage').default, [SCREENS.WORKSPACE.TAXES_SETTINGS]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsPage').default, [SCREENS.WORKSPACE.TAXES_SETTINGS_CUSTOM_TAX_NAME]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName').default, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 72ce7d6d1058..156db729fb8b 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -133,6 +133,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.TAG_EDIT, SCREENS.WORKSPACE.TAG_LIST_VIEW, SCREENS.WORKSPACE.TAG_GL_CODE, + SCREENS.WORKSPACE.TAG_APPROVER, ], [SCREENS.WORKSPACE.CATEGORIES]: [ SCREENS.WORKSPACE.CATEGORY_CREATE, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 0db9c5833fd4..67b588c43148 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -626,6 +626,13 @@ const config: LinkingOptions['config'] = { tagName: (tagName: string) => decodeURIComponent(tagName), }, }, + [SCREENS.WORKSPACE.TAG_APPROVER]: { + path: ROUTES.WORKSPACE_TAG_APPROVER.route, + parse: { + orderWeight: Number, + tagName: (tagName: string) => decodeURIComponent(tagName), + }, + }, [SCREENS.WORKSPACE.TAG_GL_CODE]: { path: ROUTES.WORKSPACE_TAG_GL_CODE.route, parse: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c856e7d89425..e55875c89b2d 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -274,6 +274,11 @@ type SettingsNavigatorParamList = { orderWeight: number; tagName: string; }; + [SCREENS.WORKSPACE.TAG_APPROVER]: { + policyID: string; + orderWeight: number; + tagName: string; + }; [SCREENS.WORKSPACE.TAG_GL_CODE]: { policyID: string; orderWeight: number; diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index 49a285c12bbe..f6ab37dd1b98 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -6,6 +6,7 @@ import type { OpenPolicyTagsPageParams, RenamePolicyTaglistParams, RenamePolicyTagsParams, + SetPolicyTagApproverParams, SetPolicyTagsEnabled, SetPolicyTagsRequired, UpdatePolicyTagGLCodeParams, @@ -846,6 +847,16 @@ function setPolicyTagGLCode(policyID: string, tagName: string, tagListIndex: num API.write(WRITE_COMMANDS.UPDATE_POLICY_TAG_GL_CODE, parameters, onyxData); } +function setPolicyTagApprover(policyID: string, tag: string, approver: string) { + const parameters: SetPolicyTagApproverParams = { + policyID, + tagName: tag, + email: approver, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_TAG_APPROVER, parameters); +} + export { buildOptimisticPolicyRecentlyUsedTags, setPolicyRequiresTag, @@ -861,6 +872,7 @@ export { renamePolicyTaglist, setWorkspaceTagEnabled, setPolicyTagGLCode, + setPolicyTagApprover, }; export type {NewCustomUnit}; diff --git a/src/pages/workspace/tags/TagApproverPage.tsx b/src/pages/workspace/tags/TagApproverPage.tsx new file mode 100644 index 000000000000..5f2f0a50fdc9 --- /dev/null +++ b/src/pages/workspace/tags/TagApproverPage.tsx @@ -0,0 +1,64 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import WorkspaceMembersSelectionList from '@components/WorkspaceMembersSelectionList'; +import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as Tag from '@userActions/Policy/Tag'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; + +type TagApproverPageProps = StackScreenProps; + +function TagApproverPage({route}: TagApproverPageProps) { + const {policyID, orderWeight, tagName} = route.params; + + const policy = usePolicy(policyID); + const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`); + + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const policyExpenseRules = policy?.rules?.expenseRules; + // const tagExpenseRule = policyExpenseRules?.find(({applyWhen}) => applyWhen.some(({condition, field, value}) => condition === 'matches' && field === 'tag' && value === tagName)); + + console.log('POLICY EXPENSE RULES ', policyTags); + return ( + + + Navigation.goBack()} + /> + { + Tag.setPolicyTagApprover(policyID, tagName, email); + Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); + }} + /> + + + ); +} + +TagApproverPage.displayName = 'TagApproverPage'; + +export default TagApproverPage; diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index c4dbd85a0821..103b7e659680 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -86,7 +86,7 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps) }; const navigateToEditTagApprover = () => { - Navigation.navigate(ROUTES.WORKSPACE_TAG_EDIT.getRoute(route.params.policyID, route.params.orderWeight, currentPolicyTag.name)); + Navigation.navigate(ROUTES.WORKSPACE_TAG_APPROVER.getRoute(route.params.policyID, route.params.orderWeight, currentPolicyTag.name)); }; const isThereAnyAccountingConnection = Object.keys(policy?.connections ?? {}).length !== 0; @@ -155,17 +155,21 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps) /> - - {translate('workspace.tags.tagRules')} - - - - + {policy?.areRulesEnabled && ( + <> + + {translate('workspace.tags.tagRules')} + + + + + + )} {shouldShowDeleteMenuItem && ( ; }; +/** + * + */ +type ExpenseRule = { + /** Set of conditions under which the expense rule should be applied */ + applyWhen: ApplyRulesWhen[]; + + /** Policy tag approver */ + approver: string; + + /** An id of the rule */ + id: string; +}; + +/** Data informing when a given rule should be applied */ +type ApplyRulesWhen = { + /** The condition for applying the rule to the workspace */ + condition: 'matches'; + + /** The target field to which the rule is applied */ + field: 'category' | 'tag'; + + /** The value of the target field */ + value: string; +}; + +/** Approval rule data model */ +type ApprovalRule = { + /** The approver's email */ + approver: string; + + /** Set of conditions under which the approval rule should be applied */ + applyWhen: ApplyRulesWhen[]; + + /** An id of the rule */ + id: string; +}; + /** Model of policy data */ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< { @@ -1510,6 +1548,20 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Collection of tax rates attached to a policy */ taxRates?: TaxRatesWithDefault; + /** A set of rules related to the workpsace */ + rules?: { + /** A set of rules related to the workpsace approvals */ + approvalRules?: ApprovalRule[]; + + /** A set of rules related to the workpsace expenses */ + expenseRules?: ExpenseRule[]; + }; + + /** + * + */ + expenseRules?: ExpenseRule[]; + /** ReportID of the admins room for this workspace */ chatReportIDAdmins?: number; From 4a3bfcc806c3d3f0bf52e5c59e71ad4a0c323bf2 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Fri, 30 Aug 2024 13:20:16 +0200 Subject: [PATCH 004/274] wire up action --- .../parameters/SetPolicyTagApproverParams.ts | 2 +- src/libs/PolicyUtils.ts | 10 +++ src/libs/actions/Policy/Tag.ts | 65 ++++++++++++++++++- src/pages/workspace/tags/TagApproverPage.tsx | 8 +-- src/pages/workspace/tags/TagSettingsPage.tsx | 5 +- src/types/onyx/Policy.ts | 4 +- 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/libs/API/parameters/SetPolicyTagApproverParams.ts b/src/libs/API/parameters/SetPolicyTagApproverParams.ts index fbd086dfebed..0e9b286ba816 100644 --- a/src/libs/API/parameters/SetPolicyTagApproverParams.ts +++ b/src/libs/API/parameters/SetPolicyTagApproverParams.ts @@ -1,7 +1,7 @@ type SetPolicyTagApproverParams = { policyID: string; tagName: string; - email: string; + approver: string; }; export default SetPolicyTagApproverParams; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 3f3a2a96a1e1..b914f3d7816c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -969,6 +969,15 @@ function getWorkspaceAccountID(policyID: string) { return policy.workspaceAccountID ?? 0; } +function getTagExpenseRule(policyID: string, tagName: string) { + const policy = getPolicy(policyID); + + const expenseRules = policy?.rules?.expenseRules ?? []; + const expenseRule = expenseRules.find((rule) => rule.applyWhen.find(({condition, field, value}) => condition === 'matches' && field === 'tag' && value === tagName)); + + return expenseRule; +} + export { canEditTaxRate, extractPolicyIDFromPath, @@ -1074,6 +1083,7 @@ export { getWorkspaceAccountID, getAllTaxRatesNamesAndKeys as getAllTaxRates, getTagNamesFromTagsLists, + getTagExpenseRule, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index f6ab37dd1b98..d02a42cccd98 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -848,13 +848,74 @@ function setPolicyTagGLCode(policyID: string, tagName: string, tagListIndex: num } function setPolicyTagApprover(policyID: string, tag: string, approver: string) { + const policy = PolicyUtils.getPolicy(policyID); + const prevExpenseRules = policy?.rules?.approvalRules ?? []; + const expenseRuleToUpdate = PolicyUtils.getTagExpenseRule(policyID, tag); + const filteredExpenseRules = expenseRuleToUpdate ? prevExpenseRules.filter((rule) => rule.id === expenseRuleToUpdate.id) : prevExpenseRules; + + const updatedExpenseRule = expenseRuleToUpdate + ? {...expenseRuleToUpdate, approver} + : { + applyWhen: [ + { + condition: 'matches', + field: 'tag', + value: tag, + }, + ], + approver, + id: '-1', + }; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + rules: { + expenseRules: [...filteredExpenseRules, updatedExpenseRule], + }, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + pendingFields: {rules: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + rules: { + expenseRules: [...filteredExpenseRules, updatedExpenseRule], + }, + pendingAction: null, + pendingFields: {rules: null}, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + rules: { + expenseRules: prevExpenseRules, + }, + pendingAction: null, + pendingFields: {rules: null}, + }, + }, + ], + }; + const parameters: SetPolicyTagApproverParams = { policyID, tagName: tag, - email: approver, + approver, }; - API.write(WRITE_COMMANDS.SET_POLICY_TAG_APPROVER, parameters); + API.write(WRITE_COMMANDS.SET_POLICY_TAG_APPROVER, parameters, onyxData); } export { diff --git a/src/pages/workspace/tags/TagApproverPage.tsx b/src/pages/workspace/tags/TagApproverPage.tsx index 5f2f0a50fdc9..ce3fa822e761 100644 --- a/src/pages/workspace/tags/TagApproverPage.tsx +++ b/src/pages/workspace/tags/TagApproverPage.tsx @@ -8,6 +8,7 @@ import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import * as Tag from '@userActions/Policy/Tag'; @@ -26,10 +27,9 @@ function TagApproverPage({route}: TagApproverPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const policyExpenseRules = policy?.rules?.expenseRules; - // const tagExpenseRule = policyExpenseRules?.find(({applyWhen}) => applyWhen.some(({condition, field, value}) => condition === 'matches' && field === 'tag' && value === tagName)); + const tagApprover = PolicyUtils.getTagExpenseRule(policyID, tagName)?.approver; - console.log('POLICY EXPENSE RULES ', policyTags); + console.log('POLICY ', policy); return ( { Tag.setPolicyTagApprover(policyID, tagName, email); Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack); diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index 103b7e659680..92166504dd7d 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -91,6 +91,7 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps) const isThereAnyAccountingConnection = Object.keys(policy?.connections ?? {}).length !== 0; const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); + const tagApprover = PolicyUtils.getTagExpenseRule(route.params.policyID, currentPolicyTag.name)?.approver; const shouldShowDeleteMenuItem = !isThereAnyAccountingConnection && !isMultiLevelTags; @@ -160,9 +161,9 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps) {translate('workspace.tags.tagRules')} - + Date: Fri, 30 Aug 2024 14:28:28 +0200 Subject: [PATCH 005/274] update forceClearInput for web --- src/libs/ComponentUtils/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/ComponentUtils/index.ts b/src/libs/ComponentUtils/index.ts index f7c48f87af5a..55a7252b3510 100644 --- a/src/libs/ComponentUtils/index.ts +++ b/src/libs/ComponentUtils/index.ts @@ -1,4 +1,3 @@ -import type {Component} from 'react'; import type {TextInput} from 'react-native'; import type {AnimatedRef} from 'react-native-reanimated'; import type {AccessibilityRoleForm, NewPasswordAutocompleteType, PasswordAutocompleteType} from './types'; @@ -10,10 +9,10 @@ const PASSWORD_AUTOCOMPLETE_TYPE: PasswordAutocompleteType = 'current-password'; const NEW_PASSWORD_AUTOCOMPLETE_TYPE: NewPasswordAutocompleteType = 'new-password'; const ACCESSIBILITY_ROLE_FORM: AccessibilityRoleForm = 'form'; -function forceClearInput(_: AnimatedRef, textInputRef: React.RefObject) { +function forceClearInput(animatedInputRef: AnimatedRef) { 'worklet'; - textInputRef.current?.clear(); + animatedInputRef.current?.clear(); } export {PASSWORD_AUTOCOMPLETE_TYPE, ACCESSIBILITY_ROLE_FORM, NEW_PASSWORD_AUTOCOMPLETE_TYPE, forceClearInput}; From cda29f7153d3c66618c7875190680d137682b098 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 30 Aug 2024 14:28:48 +0200 Subject: [PATCH 006/274] type --- src/libs/ComponentUtils/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/ComponentUtils/index.ts b/src/libs/ComponentUtils/index.ts index 55a7252b3510..518791b572c9 100644 --- a/src/libs/ComponentUtils/index.ts +++ b/src/libs/ComponentUtils/index.ts @@ -1,4 +1,4 @@ -import type {TextInput} from 'react-native'; +import type {Component} from 'react'; import type {AnimatedRef} from 'react-native-reanimated'; import type {AccessibilityRoleForm, NewPasswordAutocompleteType, PasswordAutocompleteType} from './types'; @@ -9,10 +9,13 @@ const PASSWORD_AUTOCOMPLETE_TYPE: PasswordAutocompleteType = 'current-password'; const NEW_PASSWORD_AUTOCOMPLETE_TYPE: NewPasswordAutocompleteType = 'new-password'; const ACCESSIBILITY_ROLE_FORM: AccessibilityRoleForm = 'form'; -function forceClearInput(animatedInputRef: AnimatedRef) { +function forceClearInput(animatedInputRef: AnimatedRef) { 'worklet'; - animatedInputRef.current?.clear(); + const input = animatedInputRef.current; + if (input && 'clear' in input && typeof input.clear === 'function') { + input.clear(); + } } export {PASSWORD_AUTOCOMPLETE_TYPE, ACCESSIBILITY_ROLE_FORM, NEW_PASSWORD_AUTOCOMPLETE_TYPE, forceClearInput}; From 3792093db7397a2bf9b569544b26211baf768b2b Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 30 Aug 2024 14:29:50 +0200 Subject: [PATCH 007/274] clean invocation forceClearInput --- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 82482e1c0590..fc3765af81ce 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -607,7 +607,7 @@ function ComposerWithSuggestions( const clear = useCallback(() => { 'worklet'; - forceClearInput(animatedRef, textInputRef); + forceClearInput(animatedRef); }, [animatedRef]); const getCurrentText = useCallback(() => { From ac12ad37b928145bdf227ccadea19062901a1652 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 1 Sep 2024 05:40:47 +0200 Subject: [PATCH 008/274] Changing free trial text --- .../LHNOptionsList/LHNOptionsList.tsx | 18 +++++---- .../LHNOptionsList/OptionRowLHN.tsx | 17 +++++++-- src/components/LHNOptionsList/types.ts | 6 +++ src/libs/PolicyUtils.ts | 6 +++ src/libs/ReportUtils.ts | 1 + src/libs/SubscriptionUtils.ts | 37 +++++++++++++++---- src/pages/home/HeaderView.tsx | 8 ++-- src/pages/settings/InitialSettingsPage.tsx | 8 ++-- 8 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index a734890a1f38..d20d9dc1b196 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -19,6 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as DraftCommentUtils from '@libs/DraftCommentUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -148,6 +149,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio } const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`]; + const freeTrialText = SubscriptionUtils.getFreeTrialText(policy); + return ( ); }, [ + reports, + reportActions, + policy, + transactions, draftComments, - onSelectRow, - optionMode, personalDetails, - policy, - preferredLocale, - reportActions, - reports, + optionMode, shouldDisableFocusOptions, - transactions, + onSelectRow, + preferredLocale, transactionViolations, onLayoutItem, ], diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index cee949133eb2..f0b50d3b48fc 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -26,14 +26,23 @@ import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; -import * as SubscriptionUtils from '@libs/SubscriptionUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; -function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { +function OptionRowLHN({ + reportID, + isFocused = false, + onSelectRow = () => {}, + optionItem, + viewMode = 'default', + style, + onLayout = () => {}, + hasDraftComment, + freeTrialText, +}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -229,10 +238,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti ReportUtils.isSystemChat(report) } /> - {ReportUtils.isChatUsedForOnboarding(report) && SubscriptionUtils.isUserOnFreeTrial() && ( + {ReportUtils.isChatUsedForOnboarding(report) && freeTrialText && ( )} diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index f914b001aba6..9dc0c6702482 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -93,6 +93,9 @@ type OptionRowLHNDataProps = { /** Callback to execute when the OptionList lays out */ onLayout?: (event: LayoutChangeEvent) => void; + + /** The free trial banner text */ + freeTrialText?: string; }; type OptionRowLHNProps = { @@ -117,6 +120,9 @@ type OptionRowLHNProps = { /** Whether a report contains a draft */ hasDraftComment: boolean; + /** The free trial banner text */ + freeTrialText?: string; + onLayout?: (event: LayoutChangeEvent) => void; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 07dfe7a89b85..26046fb1321a 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -343,6 +343,11 @@ function isPaidGroupPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE; } +function hasPaidPolicy(policies: OnyxCollection | null, currentUserAccountID: number): boolean { + const ownerPaidPolicy = Object.values(policies ?? {}).some((policy) => isPolicyOwner(policy, currentUserAccountID ?? -1) && isPaidGroupPolicy(policy)); + return ownerPaidPolicy; +} + function isControlPolicy(policy: OnyxEntry): boolean { return policy?.type === CONST.POLICY.TYPE.CORPORATE; } @@ -1010,6 +1015,7 @@ export { hasPolicyCategoriesError, hasPolicyError, hasPolicyErrorFields, + hasPaidPolicy, hasTaxRateError, isControlOnAdvancedApprovalMode, isExpensifyTeam, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a000d64a9adf..9ba354a61464 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -428,6 +428,7 @@ type OptionData = { hasDraftComment?: boolean | null; keyForList?: string; searchText?: string; + freeTrialText?: string; isIOUReportOwner?: boolean | null; isArchivedRoom?: boolean | null; shouldShowSubscript?: boolean | null; diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 6df0b2f2c205..a4054e2e221a 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -1,9 +1,11 @@ import {differenceInSeconds, fromUnixTime, isAfter, isBefore} from 'date-fns'; -import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; +import CardSectionUtils from '@src/pages/settings/Subscription/CardSection/utils'; import type {BillingGraceEndPeriod, BillingStatus, Fund, FundList, Policy, StripeCustomerID} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {translateLocal} from './Localize'; import * as PolicyUtils from './PolicyUtils'; const PAYMENT_STATUS = { @@ -369,6 +371,24 @@ function calculateRemainingFreeTrialDays(): number { return diffInDays < 0 ? 0 : diffInDays; } +function getFreeTrialText(policies: OnyxCollection | null): string | undefined { + if (!PolicyUtils.hasPaidPolicy(policies, currentUserAccountID)) { + return undefined; + } + + if (CardSectionUtils.shouldShowPreTrialBillingBanner()) { + return translateLocal('subscription.billingBanner.preTrial.title'); + } + if (isUserOnFreeTrial()) { + return translateLocal('subscription.billingBanner.trialStarted.title', {numOfDays: calculateRemainingFreeTrialDays()}); + } + if (hasUserFreeTrialEnded()) { + return translateLocal('subscription.billingBanner.trialEnded.title'); + } + + return undefined; +} + /** * Whether the workspace's owner is on its free trial period. */ @@ -449,16 +469,17 @@ function shouldRestrictUserBillableActions(policyID: string): boolean { export { calculateRemainingFreeTrialDays, doesUserHavePaymentCardAdded, - hasUserFreeTrialEnded, - isUserOnFreeTrial, - shouldRestrictUserBillableActions, - getSubscriptionStatus, - hasSubscriptionRedDotError, getAmountOwed, - getOverdueGracePeriodDate, getCardForSubscriptionBilling, + getFreeTrialText, + getOverdueGracePeriodDate, + getSubscriptionStatus, hasCardAuthenticatedError, - hasSubscriptionGreenDotInfo, hasRetryBillingError, + hasSubscriptionGreenDotInfo, + hasSubscriptionRedDotError, + hasUserFreeTrialEnded, + isUserOnFreeTrial, PAYMENT_STATUS, + shouldRestrictUserBillableActions, }; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 2ca54d3a54d7..ecaa6e5225c0 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -1,4 +1,4 @@ -import React, {memo} from 'react'; +import React, {memo, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; @@ -59,6 +59,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || report?.reportID || '-1'}`); const policy = usePolicy(report?.policyID); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [policies] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}`); const {translate} = useLocalize(); const theme = useTheme(); @@ -131,6 +132,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); const shouldUseGroupTitle = isGroupChat && (!!report?.reportName || !isMultipleParticipant); const isLoading = !report.reportID || !title; + const freeTrialText = SubscriptionUtils.getFreeTrialText(policies); return ( - {ReportUtils.isChatUsedForOnboarding(report) && SubscriptionUtils.isUserOnFreeTrial() && ( + {ReportUtils.isChatUsedForOnboarding(report) && freeTrialText && ( )} {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 20e42aba01d8..6548d4857f88 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -113,6 +113,8 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa const [shouldShowSignoutConfirmModal, setShouldShowSignoutConfirmModal] = useState(false); + const freeTrialText = SubscriptionUtils.getFreeTrialText(policies); + useEffect(() => { Wallet.openInitialSettingsPage(); }, []); @@ -211,8 +213,8 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa icon: Expensicons.CreditCard, routeName: ROUTES.SETTINGS_SUBSCRIPTION, brickRoadIndicator: !!privateSubscription?.errors || SubscriptionUtils.hasSubscriptionRedDotError() ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, - badgeText: SubscriptionUtils.isUserOnFreeTrial() ? translate('subscription.badge.freeTrial', {numOfDays: SubscriptionUtils.calculateRemainingFreeTrialDays()}) : undefined, - badgeStyle: SubscriptionUtils.isUserOnFreeTrial() ? styles.badgeSuccess : undefined, + badgeText: freeTrialText, + badgeStyle: freeTrialText ? styles.badgeSuccess : undefined, }); } @@ -221,7 +223,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa sectionTranslationKey: 'common.workspaces', items, }; - }, [policies, privateSubscription?.errors, styles.badgeSuccess, styles.workspaceSettingsSectionContainer, subscriptionPlan, translate]); + }, [freeTrialText, policies, privateSubscription?.errors, styles.badgeSuccess, styles.workspaceSettingsSectionContainer, subscriptionPlan]); /** * Retuns a list of menu items data for general section From eb1a18d1fff2efac4db67ef099a3f0199d23b471 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 3 Sep 2024 17:42:29 +0200 Subject: [PATCH 009/274] new asset --- ...gstate_laptop-with-hourglass-and-cards.svg | 244 ++++++++++++++++++ src/components/Icon/Illustrations.ts | 2 + 2 files changed, 246 insertions(+) create mode 100644 assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg diff --git a/assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg b/assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg new file mode 100644 index 000000000000..0f40859c8839 --- /dev/null +++ b/assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index e30fc8bce7b7..670ab18fbd41 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -1,6 +1,7 @@ import AmexCompanyCards from '@assets/images/companyCards/amex.svg'; import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; import MasterCardCompanyCards from '@assets/images/companyCards/mastercard.svg'; +import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; import VisaCompanyCards from '@assets/images/companyCards/visa.svg'; import EmptyCardState from '@assets/images/emptystate__expensifycard.svg'; import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; @@ -230,4 +231,5 @@ export { AmexCompanyCards, MasterCardCompanyCards, VisaCompanyCards, + CompanyCardsPendingState, }; From d08cfcb97426fbfb9858228bd40075955ec15ec0 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 3 Sep 2024 17:42:48 +0200 Subject: [PATCH 010/274] add support for reactNode for the subtitle --- src/components/EmptyStateComponent/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmptyStateComponent/types.ts b/src/components/EmptyStateComponent/types.ts index 80fac980864f..3c9c24cf6ed2 100644 --- a/src/components/EmptyStateComponent/types.ts +++ b/src/components/EmptyStateComponent/types.ts @@ -13,7 +13,7 @@ type MediaTypes = ValueOf; type SharedProps = { SkeletonComponent: ValidSkeletons; title: string; - subtitle: string; + subtitle: string | React.ReactNode; buttonText?: string; buttonAction?: () => void; headerStyles?: StyleProp; From de98cdc58a5a6279eab71e5d037f6c5cf9455b39 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 3 Sep 2024 17:43:00 +0200 Subject: [PATCH 011/274] new translations --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 30e2dad391d9..996d84dfe1cb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2922,6 +2922,8 @@ export default { setTransactionLiabilityDescription: 'When enabled, cardholders can delete card transactions. New transactions will follow this rule.', emptyAddedFeedTitle: 'Assign company cards', emptyAddedFeedDescription: 'Get started by assigning your first card to a member.', + pendingNoFeedTitle: `We're reviewing your request...`, + pendingNoFeedDescription: `We're currently reviewing your feed details. Once that's done we'll reach out to you via`, }, workflows: { title: 'Workflows', diff --git a/src/languages/es.ts b/src/languages/es.ts index 16b8f6f42ed2..6fa1377f187f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2967,6 +2967,8 @@ export default { 'Cuando está habilitada, los titulares de tarjetas pueden eliminar transacciones con tarjeta. Las transacciones nuevas seguirán esta regla.', emptyAddedFeedTitle: 'Asignar tarjetas de empresa', emptyAddedFeedDescription: 'Comienza asignando tu primera tarjeta a un miembro.', + pendingNoFeedTitle: `Estamos revisando tu solicitud...`, + pendingNoFeedDescription: `Actualmente estamos revisando los detalles de tu feed. Una vez hecho esto, nos pondremos en contacto contigo a través de Concierge.`, }, distanceRates: { title: 'Tasas de distancia', From 99d0bde7bfe2b2aedf80b298a238462c5b323860 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 3 Sep 2024 17:43:26 +0200 Subject: [PATCH 012/274] add openPolicyCompanyCardsPage API --- .../OpenPolicyCompanyCardsPageParams.ts | 6 +++ src/libs/API/types.ts | 2 + src/libs/actions/Policy/Policy.ts | 42 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/libs/API/parameters/OpenPolicyCompanyCardsPageParams.ts diff --git a/src/libs/API/parameters/OpenPolicyCompanyCardsPageParams.ts b/src/libs/API/parameters/OpenPolicyCompanyCardsPageParams.ts new file mode 100644 index 000000000000..0fd101ab03b7 --- /dev/null +++ b/src/libs/API/parameters/OpenPolicyCompanyCardsPageParams.ts @@ -0,0 +1,6 @@ +type OpenPolicyCompanyCardsPageParams = { + policyID: string; + authToken: string | null | undefined; +}; + +export default OpenPolicyCompanyCardsPageParams; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 42cfc8d01aa1..79d8a99d16f5 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -752,6 +752,7 @@ const READ_COMMANDS = { OPEN_POLICY_TAXES_PAGE: 'OpenPolicyTaxesPage', OPEN_POLICY_REPORT_FIELDS_PAGE: 'OpenPolicyReportFieldsPage', OPEN_POLICY_EXPENSIFY_CARDS_PAGE: 'OpenPolicyExpensifyCardsPage', + OPEN_POLICY_COMPANY_CARDS_PAGE: 'OpenPolicyCompanyCardsPage', OPEN_POLICY_EDIT_CARD_LIMIT_TYPE_PAGE: 'OpenPolicyEditCardLimitTypePage', OPEN_WORKSPACE_INVITE_PAGE: 'OpenWorkspaceInvitePage', OPEN_DRAFT_WORKSPACE_REQUEST: 'OpenDraftWorkspaceRequest', @@ -816,6 +817,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE]: Parameters.OpenPolicyMoreFeaturesPageParams; [READ_COMMANDS.OPEN_POLICY_ACCOUNTING_PAGE]: Parameters.OpenPolicyAccountingPageParams; [READ_COMMANDS.OPEN_POLICY_EXPENSIFY_CARDS_PAGE]: Parameters.OpenPolicyExpensifyCardsPageParams; + [READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_PAGE]: Parameters.OpenPolicyExpensifyCardsPageParams; [READ_COMMANDS.OPEN_POLICY_EDIT_CARD_LIMIT_TYPE_PAGE]: Parameters.OpenPolicyEditCardLimitTypePageParams; [READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE]: Parameters.OpenPolicyProfilePageParams; [READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE]: Parameters.OpenPolicyInitialPageParams; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 3ccbb7873cef..fb5e091d99c0 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2073,6 +2073,47 @@ function openPolicyTaxesPage(policyID: string) { API.read(READ_COMMANDS.OPEN_POLICY_TAXES_PAGE, params); } +function openPolicyCompanyCardsPage(policyID: string, workspaceAccountID: number) { + const authToken = NetworkStore.getAuthToken(); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`, + value: { + isLoading: true, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`, + value: { + isLoading: false, + }, + }, + ]; + + const params: OpenPolicyExpensifyCardsPageParams = { + policyID, + authToken, + }; + + API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_PAGE, params, {optimisticData, successData, failureData}); +} + function openPolicyExpensifyCardsPage(policyID: string, workspaceAccountID: number) { const authToken = NetworkStore.getAuthToken(); @@ -3913,6 +3954,7 @@ export { setWorkspaceCompanyCardFeedName, deleteWorkspaceCompanyCardFeed, setWorkspaceCompanyCardTransactionLiability, + openPolicyCompanyCardsPage, }; export type {NewCustomUnit}; From 5920205f3fa0022321fb52620cb01775ad26c6ba Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 3 Sep 2024 17:43:41 +0200 Subject: [PATCH 013/274] new pending page --- ...spaceCompanyCardsFeedNoFeedPendingPage.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedNoFeedPendingPage.tsx diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedNoFeedPendingPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedNoFeedPendingPage.tsx new file mode 100644 index 000000000000..26dd1b6771ea --- /dev/null +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedNoFeedPendingPage.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import EmptyStateComponent from '@components/EmptyStateComponent'; +import * as Illustrations from '@components/Icon/Illustrations'; +import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import colors from '@styles/theme/colors'; +import * as ReportInstance from '@userActions/Report'; +import CONST from '@src/CONST'; + +function WorkspaceCompanyCardsFeedNoFeedPendingPage() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const subtitle = ( + <> + {translate('workspace.moreFeatures.companyCards.pendingNoFeedDescription')} + ReportInstance.navigateToConciergeChat()}> {CONST?.CONCIERGE_CHAT_NAME} + + ); + + return ( + + ); +} + +WorkspaceCompanyCardsFeedNoFeedPendingPage.displayName = 'WorkspaceCompanyCardsFeedNoFeedPendingPage'; + +export default WorkspaceCompanyCardsFeedNoFeedPendingPage; From 498cf13d62eef94e34d2157d92326ef8a0da1db2 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Tue, 3 Sep 2024 17:44:08 +0200 Subject: [PATCH 014/274] update main page --- .../WorkspaceCompanyCardsPage.tsx | 63 ++++++++++--------- src/types/onyx/CardFeeds.ts | 3 + 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index 201f64e7681c..c52bd22c10eb 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -1,59 +1,60 @@ +import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; -// import Onyx from 'react-native-onyx'; +import {ActivityIndicator} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import useLocalize from '@hooks/useLocalize'; -// import usePolicy from '@hooks/usePolicy'; +import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import Navigation from '@navigation/Navigation'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; -// import ONYXKEYS from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import WorkspaceCompanyCardPageEmptyState from './WorkspaceCompanyCardPageEmptyState'; import WorkspaceCompanyCardsFeedAddedEmptyPage from './WorkspaceCompanyCardsFeedAddedEmptyPage'; +import WorkspaceCompanyCardsFeedNoFeedPendingPage from './WorkspaceCompanyCardsFeedNoFeedPendingPage'; type WorkspaceCompanyCardPageProps = StackScreenProps; function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const theme = useTheme(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const policyID = route.params.policyID; - // const policy = usePolicy(policyID); - // const workspaceAccountID = policy?.workspaceAccountID ?? -1; + const policy = usePolicy(policyID); + const workspaceAccountID = policy?.workspaceAccountID ?? -1; + const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID.toString()}`); + const fetchCompanyCards = useCallback(() => { + Policy.openPolicyCompanyCardsPage(policyID, workspaceAccountID); + }, [policyID, workspaceAccountID]); - // useEffect(() => { - // Onyx.set(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`, { - // companyCards: { - // cdfbmo: { - // pending: false, - // asrEnabled: true, - // forceReimbursable: 'force_no', - // liabilityType: 'corporate', - // preferredPolicy: '', - // reportTitleFormat: '{report:card}{report:bank}{report:submit:from}{report:total}{report:enddate:MMMM}', - // statementPeriodEndDay: 'LAST_DAY_OF_MONTH', - // }, - // }, - // companyCardNicknames: { - // cdfbmo: 'BMO MasterCard', - // }, - // }); - // }, [workspaceAccountID]); + useFocusEffect(fetchCompanyCards); + + const companyCards = cardFeeds?.companyCards; + const companyCardsLength = Object.keys(companyCards ?? {}).length; + const isNoFeed = companyCardsLength === 0; + const isFeedAdded = companyCardsLength > 0 && !Object.values(companyCards ?? {})[0].pending; + const isPending = companyCardsLength === 1 && Object.values(companyCards ?? {})[0].pending; + const isLoading = !cardFeeds || cardFeeds.isLoading; const getHeaderButtons = () => (