From c66dd944f8f637d2238622253fb40425d35ab987 Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 19 Aug 2024 12:07:55 +0700 Subject: [PATCH 001/231] feat: blur the composer focus when open context menu --- src/pages/home/report/ReportActionItem.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 68ced63d12d0..126dcfa6509a 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -80,6 +80,7 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; const getDraftMessage = (drafts: OnyxCollection, reportID: string, action: OnyxTypes.ReportAction): string | undefined => { const originalReportID = ReportUtils.getOriginalReportID(reportID, action); @@ -342,6 +343,8 @@ function ReportActionItem({ return; } + ReportActionComposeFocusManager.composerRef?.current?.blur(); + ReportActionComposeFocusManager.editComposerRef?.current?.blur(); setIsContextMenuActive(true); const selection = SelectionScraper.getCurrentSelection(); ReportActionContextMenu.showContextMenu( From 9dd234290052160c2ac7083475b3873ec11d98ee Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 20 Aug 2024 22:28:09 +0700 Subject: [PATCH 002/231] fix: assign editComposerRef when focus --- src/libs/ReportActionComposeFocusManager.ts | 2 +- src/pages/home/report/ReportActionItem.tsx | 6 +++--- src/pages/home/report/ReportActionItemMessageEdit.tsx | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 2dfa81d89a20..9344b58a2cb4 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -9,7 +9,7 @@ import navigationRef from './Navigation/navigationRef'; type FocusCallback = (shouldFocusForNonBlurInputOnTapOutside?: boolean) => void; const composerRef: MutableRefObject = React.createRef(); -const editComposerRef = React.createRef(); +const editComposerRef: MutableRefObject = React.createRef(); // There are two types of composer: general composer (edit composer) and main composer. // The general composer callback will take priority if it exists. let focusCallback: FocusCallback | null = null; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 126dcfa6509a..201048faf915 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -47,6 +47,7 @@ import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; @@ -80,7 +81,6 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; const getDraftMessage = (drafts: OnyxCollection, reportID: string, action: OnyxTypes.ReportAction): string | undefined => { const originalReportID = ReportUtils.getOriginalReportID(reportID, action); @@ -343,8 +343,8 @@ function ReportActionItem({ return; } - ReportActionComposeFocusManager.composerRef?.current?.blur(); - ReportActionComposeFocusManager.editComposerRef?.current?.blur(); + ReportActionComposeFocusManager.composerRef.current?.blur(); + ReportActionComposeFocusManager.editComposerRef.current?.blur(); setIsContextMenuActive(true); const selection = SelectionScraper.getCurrentSelection(); ReportActionContextMenu.showContextMenu( diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 15b901689ddc..0ab7eea84a2e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -494,6 +494,9 @@ function ReportActionItemMessageEdit( style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} onFocus={() => { setIsFocused(true); + if (textInputRef.current) { + ReportActionComposeFocusManager.editComposerRef.current = textInputRef.current; + } InteractionManager.runAfterInteractions(() => { requestAnimationFrame(() => { reportScrollManager.scrollToIndex(index, true); From fcb1c1b3e6fd9106d325e0a72865cc184e58d6bf Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:33:48 +0300 Subject: [PATCH 003/231] rm copilot lang --- src/languages/en.ts | 4 +++- src/languages/es.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index a6ab1329dd10..0b57d436f61d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1,5 +1,5 @@ import {CONST as COMMON_CONST, Str} from 'expensify-common'; -import {startCase} from 'lodash'; +import {remove, startCase} from 'lodash'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; @@ -4496,5 +4496,7 @@ export default { return ''; } }, + removeCopilot: 'Remove copilot', + removeCopilotConfirmation: 'Are you sure you want to remove this copilot?', }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index bf7d2212d490..f33813be537c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5012,5 +5012,7 @@ export default { return ''; } }, + removeCopilot: 'Remove copilot', + removeCopilotConfirmation: 'Are you sure you want to remove this copilot?', }, } satisfies EnglishTranslation; From 21521faed0a0eeb2559cdfbbe642d72373e74771 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:34:01 +0300 Subject: [PATCH 004/231] add popover and confirm modal --- .../Security/SecuritySettingsPage.tsx | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index b91af4f29c80..4d42ebec0872 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -1,6 +1,8 @@ -import React, {useMemo} from 'react'; +import React, {RefObject, useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; @@ -8,6 +10,7 @@ import LottieAnimations from '@components/LottieAnimations'; import MenuItem from '@components/MenuItem'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; +import Popover from '@components/Popover'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; @@ -18,8 +21,11 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -32,7 +38,35 @@ function SecuritySettingsPage() { const {shouldUseNarrowLayout} = useResponsiveLayout(); const theme = useTheme(); const {canUseNewDotCopilot} = usePermissions(); + const {windowWidth} = useWindowDimensions(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const delegateButtonRef = useRef(null); + + const [shouldShowDelegateMenu, setShouldShowDelegateMenu] = useState(false); + const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); + + const [anchorPosition, setAnchorPosition] = useState({ + anchorPositionHorizontal: 0, + anchorPositionVertical: 0, + anchorPositionTop: 0, + anchorPositionRight: 0, + }); + + const setMenuPosition = useCallback(() => { + if (!delegateButtonRef.current) { + return; + } + + const position = getClickedTargetLocation(delegateButtonRef.current); + + setAnchorPosition({ + anchorPositionTop: position.top + position.height - variables.bankAccountActionPopoverTopSpacing, + // We want the position to be 23px to the right of the left border + anchorPositionRight: windowWidth - position.right + variables.bankAccountActionPopoverRightSpacing, + anchorPositionHorizontal: position.x + variables.addBankAccountLeftSpacing, + anchorPositionVertical: position.y, + }); + }, [windowWidth]); const delegates = account?.delegatedAccess?.delegates ?? []; const delegators = account?.delegatedAccess?.delegators ?? []; @@ -40,6 +74,11 @@ function SecuritySettingsPage() { const hasDelegates = delegates.length > 0; const hasDelegators = delegators.length > 0; + const showPopoverMenu = (nativeEvent?: GestureResponderEvent | KeyboardEvent) => { + console.log('showPopoverMenu', nativeEvent); + delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; + }; + const securityMenuItems = useMemo(() => { const baseMenuItems = [ { @@ -79,6 +118,7 @@ function SecuritySettingsPage() { wrapperStyle: [styles.sectionMenuItemTopDescription], iconRight: Expensicons.ThreeDots, shouldShowRightIcon: true, + onPress: showPopoverMenu, }; }); @@ -156,6 +196,22 @@ function SecuritySettingsPage() { )} + } + onClose={() => setShouldShowDelegateMenu(false)} + > + {}} + onCancel={() => {}} + confirmText={translate('delegate.removeCopilot')} + cancelText={translate('common.cancel')} + shouldShowCancelButton + /> + )} From 8cae34abf45ab347f9c14b6821caa1f2c8eba034 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:34:33 +0300 Subject: [PATCH 005/231] import as type --- src/pages/settings/Security/SecuritySettingsPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 4d42ebec0872..254f9f3a8118 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -1,4 +1,5 @@ -import React, {RefObject, useCallback, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; +import type {RefObject} from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; From 8393ac6ab42c702d088182bedbccac9978f86718 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:36:42 +0300 Subject: [PATCH 006/231] add change access level --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 0b57d436f61d..453238e3b5be 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4498,5 +4498,6 @@ export default { }, removeCopilot: 'Remove copilot', removeCopilotConfirmation: 'Are you sure you want to remove this copilot?', + changeAccessLevel: 'Change access level', }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index f33813be537c..7e72c90906e1 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5014,5 +5014,6 @@ export default { }, removeCopilot: 'Remove copilot', removeCopilotConfirmation: 'Are you sure you want to remove this copilot?', + changeAccessLevel: 'Change access level', }, } satisfies EnglishTranslation; From af9ce30275ef5e48c9c02ab4575f0f5628e1ad2e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:36:55 +0300 Subject: [PATCH 007/231] add change access level menu item --- .../settings/Security/SecuritySettingsPage.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 254f9f3a8118..d0eac7f84a0b 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -200,8 +200,24 @@ function SecuritySettingsPage() { } + anchorPosition={{ + top: anchorPosition.anchorPositionTop, + right: anchorPosition.anchorPositionRight, + }} onClose={() => setShouldShowDelegateMenu(false)} > + setShouldShowRemoveDelegateModal(true)} + wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} + /> + setShouldShowRemoveDelegateModal(true)} + wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} + /> Date: Mon, 26 Aug 2024 06:48:56 +0300 Subject: [PATCH 008/231] show confirm modal on remove delegate --- .../Security/SecuritySettingsPage.tsx | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index d0eac7f84a0b..d302c5dee052 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -43,7 +43,7 @@ function SecuritySettingsPage() { const [account] = useOnyx(ONYXKEYS.ACCOUNT); const delegateButtonRef = useRef(null); - const [shouldShowDelegateMenu, setShouldShowDelegateMenu] = useState(false); + const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); const [anchorPosition, setAnchorPosition] = useState({ @@ -76,8 +76,9 @@ function SecuritySettingsPage() { const hasDelegators = delegators.length > 0; const showPopoverMenu = (nativeEvent?: GestureResponderEvent | KeyboardEvent) => { - console.log('showPopoverMenu', nativeEvent); delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; + setMenuPosition(); + setShouldShowDelegatePopoverMenu(true); }; const securityMenuItems = useMemo(() => { @@ -198,37 +199,45 @@ function SecuritySettingsPage() { )} } anchorPosition={{ top: anchorPosition.anchorPositionTop, right: anchorPosition.anchorPositionRight, }} - onClose={() => setShouldShowDelegateMenu(false)} + onClose={() => setShouldShowDelegatePopoverMenu(false)} > - setShouldShowRemoveDelegateModal(true)} - wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} - /> - setShouldShowRemoveDelegateModal(true)} - wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} - /> - {}} - onCancel={() => {}} - confirmText={translate('delegate.removeCopilot')} - cancelText={translate('common.cancel')} - shouldShowCancelButton - /> + + { + setShouldShowDelegatePopoverMenu(false); + }} + wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} + /> + { + setShouldShowDelegatePopoverMenu(false); + setShouldShowRemoveDelegateModal(true); + }} + wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} + /> + + {}} + onCancel={() => setShouldShowRemoveDelegateModal(false)} + confirmText={translate('delegate.removeCopilot')} + cancelText={translate('common.cancel')} + shouldShowCancelButton + /> )} From 73ba8304bf546da42833857e8c9a5e1da467be9f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:51:50 +0300 Subject: [PATCH 009/231] add remove delegate func --- .../API/parameters/RemoveDelegateParams.ts | 5 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Delegate.ts | 40 +++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 src/libs/API/parameters/RemoveDelegateParams.ts diff --git a/src/libs/API/parameters/RemoveDelegateParams.ts b/src/libs/API/parameters/RemoveDelegateParams.ts new file mode 100644 index 000000000000..b27f91f183f6 --- /dev/null +++ b/src/libs/API/parameters/RemoveDelegateParams.ts @@ -0,0 +1,5 @@ +type RemoveDelegateParams = { + email: string; +}; + +export default RemoveDelegateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 124fc9376173..a5ac856600c1 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -282,3 +282,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 RemoveDelegateParams} from './RemoveDelegateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index afeb4561a24b..b9b4ee2b0404 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -336,6 +336,7 @@ const WRITE_COMMANDS = { CREATE_EXPENSIFY_CARD: 'CreateExpensifyCard', CREATE_ADMIN_ISSUED_VIRTUAL_CARD: 'CreateAdminIssuedVirtualCard', ADD_DELEGATE: 'AddDelegate', + REMOVE_DELEGATE: 'RemoveDelegate', TOGGLE_CARD_CONTINUOUS_RECONCILIATION: 'ToggleCardContinuousReconciliation', } as const; @@ -678,6 +679,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CREATE_EXPENSIFY_CARD]: Parameters.CreateExpensifyCardParams; [WRITE_COMMANDS.CREATE_ADMIN_ISSUED_VIRTUAL_CARD]: Omit; [WRITE_COMMANDS.ADD_DELEGATE]: Parameters.AddDelegateParams; + [WRITE_COMMANDS.REMOVE_DELEGATE]: Parameters.RemoveDelegateParams; [WRITE_COMMANDS.TOGGLE_CARD_CONTINUOUS_RECONCILIATION]: Parameters.ToggleCardContinuousReconciliationParams; }; diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 8187b99675ea..5c63f8a7328c 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -201,4 +201,44 @@ function addDelegate(email: string, role: DelegateRole) { API.write(WRITE_COMMANDS.ADD_DELEGATE, parameters, {optimisticData, successData, failureData}); } +function removeDelegate(email: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.ACCOUNT, + value: { + delegatedAccess: { + delegates: delegatedAccess.delegates?.map((delegate) => (delegate.email === email ? {...delegate, pendingAction: 'remove'} : delegate)), + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.ACCOUNT, + value: { + delegatedAccess: { + delegates: delegatedAccess.delegates?.filter((delegate) => delegate.email !== email), + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.ACCOUNT, + value: { + delegatedAccess: { + delegates: delegatedAccess.delegates?.map((delegate) => (delegate.email === email ? {...delegate, error: 'delegate.genericError'} : delegate)), + }, + }, + }, + ]; + + API.write(WRITE_COMMANDS.REMOVE_DELEGATE, {email}, {optimisticData, successData, failureData}); +} + export {connect, disconnect, clearDelegatorErrors, addDelegate}; From 2ced9045f2b8cc3ec2e8183b175f6d3b3f89afaf Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:59:35 +0300 Subject: [PATCH 010/231] set menu positon on resize --- .../Security/SecuritySettingsPage.tsx | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index d302c5dee052..f691c21aee17 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -1,6 +1,7 @@ -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import _ from 'lodash'; +import React, {useCallback, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {RefObject} from 'react'; -import {View} from 'react-native'; +import {Dimensions, View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; @@ -23,6 +24,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {removeDelegate} from '@libs/actions/Delegate'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; @@ -81,6 +83,19 @@ function SecuritySettingsPage() { setShouldShowDelegatePopoverMenu(true); }; + useLayoutEffect(() => { + const popoverPositionListener = Dimensions.addEventListener('change', () => { + _.debounce(setMenuPosition, CONST.TIMING.RESIZE_DEBOUNCE_TIME)(); + }); + + return () => { + if (!popoverPositionListener) { + return; + } + popoverPositionListener.remove(); + }; + }, [setMenuPosition]); + const securityMenuItems = useMemo(() => { const baseMenuItems = [ { @@ -232,7 +247,10 @@ function SecuritySettingsPage() { title={translate('delegate.removeCopilot')} prompt={translate('delegate.removeCopilotConfirmation')} danger - onConfirm={() => {}} + onConfirm={() => { + removeDelegate(); + setShouldShowRemoveDelegateModal(false); + }} onCancel={() => setShouldShowRemoveDelegateModal(false)} confirmText={translate('delegate.removeCopilot')} cancelText={translate('common.cancel')} From acc9ad1e60a22d0b822669516e37d263cbbdfa1b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 06:59:46 +0300 Subject: [PATCH 011/231] fix rm delegate --- src/libs/actions/Delegate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 5c63f8a7328c..64ddff64869a 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -208,7 +208,7 @@ function removeDelegate(email: string) { key: ONYXKEYS.ACCOUNT, value: { delegatedAccess: { - delegates: delegatedAccess.delegates?.map((delegate) => (delegate.email === email ? {...delegate, pendingAction: 'remove'} : delegate)), + delegates: delegatedAccess.delegates?.filter((delegate) => delegate.email !== email), }, }, }, @@ -241,4 +241,4 @@ function removeDelegate(email: string) { API.write(WRITE_COMMANDS.REMOVE_DELEGATE, {email}, {optimisticData, successData, failureData}); } -export {connect, disconnect, clearDelegatorErrors, addDelegate}; +export {connect, disconnect, clearDelegatorErrors, addDelegate, removeDelegate}; From eee5dafc0b95013d3ab65e51dc1d1eee4fffe66b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 07:15:55 +0300 Subject: [PATCH 012/231] add null check --- src/libs/actions/Delegate.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 64ddff64869a..89607b2c2eff 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -202,6 +202,10 @@ function addDelegate(email: string, role: DelegateRole) { } function removeDelegate(email: string) { + if (!email) { + return; + } + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, From 95fb806edb1f561105d13053da28b6d0c926ce13 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 07:16:13 +0300 Subject: [PATCH 013/231] rm delegate and navigate to change access level --- src/pages/settings/Security/SecuritySettingsPage.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index f691c21aee17..cc6c627ef402 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -33,6 +33,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {PersonalDetails} from '@src/types/onyx'; function SecuritySettingsPage() { const styles = useThemeStyles(); @@ -47,6 +48,7 @@ function SecuritySettingsPage() { const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); + const [selectedDelegate, setSelectedDelegate] = useState(); const [anchorPosition, setAnchorPosition] = useState({ anchorPositionHorizontal: 0, @@ -77,10 +79,11 @@ function SecuritySettingsPage() { const hasDelegates = delegates.length > 0; const hasDelegators = delegators.length > 0; - const showPopoverMenu = (nativeEvent?: GestureResponderEvent | KeyboardEvent) => { + const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, personalDetail?: PersonalDetails) => { delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; setMenuPosition(); setShouldShowDelegatePopoverMenu(true); + setSelectedDelegate(personalDetail); }; useLayoutEffect(() => { @@ -135,7 +138,7 @@ function SecuritySettingsPage() { wrapperStyle: [styles.sectionMenuItemTopDescription], iconRight: Expensicons.ThreeDots, shouldShowRightIcon: true, - onPress: showPopoverMenu, + onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, personalDetail), }; }); @@ -227,6 +230,7 @@ function SecuritySettingsPage() { title={translate('delegate.changeAccessLevel')} icon={Expensicons.Pencil} onPress={() => { + Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(selectedDelegate?.accountID ?? -1)); setShouldShowDelegatePopoverMenu(false); }} wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} @@ -248,7 +252,7 @@ function SecuritySettingsPage() { prompt={translate('delegate.removeCopilotConfirmation')} danger onConfirm={() => { - removeDelegate(); + removeDelegate(selectedDelegate?.login ?? ''); setShouldShowRemoveDelegateModal(false); }} onCancel={() => setShouldShowRemoveDelegateModal(false)} From 442c83f59bb9327f00bb65f4848e4554a0b73dbb Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 07:30:27 +0300 Subject: [PATCH 014/231] add type delegate with acc id --- .../settings/Security/SecuritySettingsPage.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index cc6c627ef402..7f483d24955c 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -33,7 +33,11 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails} from '@src/types/onyx'; +import type {Delegate} from '@src/types/onyx/Account'; + +type DelegateWithAccountID = { + accountID: number; +} & Delegate; function SecuritySettingsPage() { const styles = useThemeStyles(); @@ -48,7 +52,7 @@ function SecuritySettingsPage() { const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); - const [selectedDelegate, setSelectedDelegate] = useState(); + const [selectedDelegate, setSelectedDelegate] = useState(); const [anchorPosition, setAnchorPosition] = useState({ anchorPositionHorizontal: 0, @@ -79,11 +83,11 @@ function SecuritySettingsPage() { const hasDelegates = delegates.length > 0; const hasDelegators = delegators.length > 0; - const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, personalDetail?: PersonalDetails) => { + const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, delegateWithAccountID: DelegateWithAccountID) => { delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; setMenuPosition(); setShouldShowDelegatePopoverMenu(true); - setSelectedDelegate(personalDetail); + setSelectedDelegate(delegateWithAccountID); }; useLayoutEffect(() => { @@ -138,7 +142,7 @@ function SecuritySettingsPage() { wrapperStyle: [styles.sectionMenuItemTopDescription], iconRight: Expensicons.ThreeDots, shouldShowRightIcon: true, - onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, personalDetail), + onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, {email, role, accountID: personalDetail?.accountID ?? -1}), }; }); @@ -230,7 +234,7 @@ function SecuritySettingsPage() { title={translate('delegate.changeAccessLevel')} icon={Expensicons.Pencil} onPress={() => { - Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(selectedDelegate?.accountID ?? -1)); + Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(selectedDelegate?.accountID ?? -1, selectedDelegate?.role)); setShouldShowDelegatePopoverMenu(false); }} wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} @@ -252,7 +256,7 @@ function SecuritySettingsPage() { prompt={translate('delegate.removeCopilotConfirmation')} danger onConfirm={() => { - removeDelegate(selectedDelegate?.login ?? ''); + removeDelegate(selectedDelegate?.email ?? ''); setShouldShowRemoveDelegateModal(false); }} onCancel={() => setShouldShowRemoveDelegateModal(false)} From fb02b79f411d4cb6bcd61bc024d8d7a8d51f8326 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 16:18:13 +0300 Subject: [PATCH 015/231] use delegate as param --- src/libs/API/parameters/RemoveDelegateParams.ts | 2 +- src/libs/actions/Delegate.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libs/API/parameters/RemoveDelegateParams.ts b/src/libs/API/parameters/RemoveDelegateParams.ts index b27f91f183f6..e19b7680a9b8 100644 --- a/src/libs/API/parameters/RemoveDelegateParams.ts +++ b/src/libs/API/parameters/RemoveDelegateParams.ts @@ -1,5 +1,5 @@ type RemoveDelegateParams = { - email: string; + delegate: string; }; export default RemoveDelegateParams; diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index 89607b2c2eff..f6a98e900c7a 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; import * as API from '@libs/API'; -import type {AddDelegateParams} from '@libs/API/parameters'; +import type {AddDelegateParams, RemoveDelegateParams} from '@libs/API/parameters'; import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import Log from '@libs/Log'; import * as NetworkStore from '@libs/Network/NetworkStore'; @@ -242,7 +242,9 @@ function removeDelegate(email: string) { }, ]; - API.write(WRITE_COMMANDS.REMOVE_DELEGATE, {email}, {optimisticData, successData, failureData}); + const parameters: RemoveDelegateParams = {delegate: email}; + + API.write(WRITE_COMMANDS.REMOVE_DELEGATE, parameters, {optimisticData, successData, failureData}); } export {connect, disconnect, clearDelegatorErrors, addDelegate, removeDelegate}; From 823fd57f73f15522621983ac390a3e3ac45d7fbb Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 16:32:03 +0300 Subject: [PATCH 016/231] add offline behaviour for menu item list --- src/components/MenuItemList.tsx | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index c03bd712d74c..9332bbfc96ab 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -3,8 +3,10 @@ import type {GestureResponderEvent, View} from 'react-native'; import useSingleExecution from '@hooks/useSingleExecution'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {MenuItemProps} from './MenuItem'; import MenuItem from './MenuItem'; +import OfflineWithFeedback from './OfflineWithFeedback'; type MenuItemLink = string | (() => Promise); @@ -14,6 +16,9 @@ type MenuItemWithLink = MenuItemProps & { /** A unique key for the menu item */ key?: string; + + /** The pending action for the menu item */ + pendingAction?: OnyxCommon.PendingAction | null; }; type MenuItemListProps = { @@ -45,16 +50,18 @@ function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuIt return ( <> {menuItems.map((menuItemProps) => ( - secondaryInteraction(menuItemProps.link, e) : undefined} - ref={popoverAnchor} - shouldBlockSelection={!!menuItemProps.link} - // eslint-disable-next-line react/jsx-props-no-spreading - {...menuItemProps} - disabled={!!menuItemProps.disabled || isExecuting} - onPress={shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} - /> + + secondaryInteraction(menuItemProps.link, e) : undefined} + ref={popoverAnchor} + shouldBlockSelection={!!menuItemProps.link} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + disabled={!!menuItemProps.disabled || isExecuting} + onPress={shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress} + /> + ))} ); From 53cef6bf4d4864479eeb6124e0e70bdfa40a0a6a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 16:32:25 +0300 Subject: [PATCH 017/231] add pending actions and pending fields for remove delegate --- src/libs/actions/Delegate.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts index f6a98e900c7a..6e3f4c320088 100644 --- a/src/libs/actions/Delegate.ts +++ b/src/libs/actions/Delegate.ts @@ -212,7 +212,15 @@ function removeDelegate(email: string) { key: ONYXKEYS.ACCOUNT, value: { delegatedAccess: { - delegates: delegatedAccess.delegates?.filter((delegate) => delegate.email !== email), + delegates: delegatedAccess.delegates?.map((delegate) => + delegate.email === email + ? { + ...delegate, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingFields: {email: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, role: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}, + } + : delegate, + ), }, }, }, @@ -236,7 +244,9 @@ function removeDelegate(email: string) { key: ONYXKEYS.ACCOUNT, value: { delegatedAccess: { - delegates: delegatedAccess.delegates?.map((delegate) => (delegate.email === email ? {...delegate, error: 'delegate.genericError'} : delegate)), + delegates: delegatedAccess.delegates?.map((delegate) => + delegate.email === email ? {...delegate, error: 'delegate.genericError', pendingAction: null, pendingFields: undefined} : delegate, + ), }, }, }, From dcb5cd53474dd7b0c7a89f2d2032b16071b6ad23 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 26 Aug 2024 16:32:51 +0300 Subject: [PATCH 018/231] add pending action for delegate --- src/pages/settings/Security/SecuritySettingsPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 7f483d24955c..68b4e9b404af 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -128,7 +128,7 @@ function SecuritySettingsPage() { })); }, [translate, waitForNavigate, styles]); - const delegateMenuItems: MenuItemProps[] = delegates.map(({email, role}) => { + const delegateMenuItems: MenuItemProps[] = delegates.map(({email, role, pendingAction}) => { const personalDetail = getPersonalDetailByEmail(email); return { @@ -143,6 +143,7 @@ function SecuritySettingsPage() { iconRight: Expensicons.ThreeDots, shouldShowRightIcon: true, onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, {email, role, accountID: personalDetail?.accountID ?? -1}), + pendingAction, }; }); From 80bd82e44ff76b1c78d682e1a296d82d99027616 Mon Sep 17 00:00:00 2001 From: dominictb Date: Thu, 5 Sep 2024 18:39:32 +0700 Subject: [PATCH 019/231] chore: add blurAll utility to ReportActionComposeFocusManager --- src/libs/ReportActionComposeFocusManager.ts | 9 +++++++++ src/pages/home/report/ReportActionItem.tsx | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 9344b58a2cb4..6c657b199549 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -76,6 +76,14 @@ function isEditFocused(): boolean { return !!editComposerRef.current?.isFocused(); } +/** + * Utility function to blur both main composer and edit composer. + */ +function blurAll(): void { + composerRef.current?.blur(); + editComposerRef.current?.blur(); +} + export default { composerRef, onComposerFocus, @@ -84,4 +92,5 @@ export default { isFocused, editComposerRef, isEditFocused, + blurAll, }; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 201048faf915..f024eb0824d6 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -343,8 +343,7 @@ function ReportActionItem({ return; } - ReportActionComposeFocusManager.composerRef.current?.blur(); - ReportActionComposeFocusManager.editComposerRef.current?.blur(); + ReportActionComposeFocusManager.blurAll(); setIsContextMenuActive(true); const selection = SelectionScraper.getCurrentSelection(); ReportActionContextMenu.showContextMenu( From dec716c6a6c730fe91dd1fe3ebeaa95c0052443a Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 9 Sep 2024 15:41:43 +0700 Subject: [PATCH 020/231] fix: reset editComposerRef when clear focus manager --- src/libs/ReportActionComposeFocusManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 6c657b199549..9b9022f0efb4 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -58,6 +58,7 @@ function clear(isMainComposer = false) { if (isMainComposer) { mainComposerFocusCallback = null; } else { + editComposerRef.current = null; focusCallback = null; } } From a28252409e4b49eaa91971680dcf5fef5be21ca0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 00:52:14 +0300 Subject: [PATCH 021/231] fix conflicts --- .../ModalStackNavigators/index.tsx | 3 -- .../Security/SecuritySettingsPage.tsx | 45 ++----------------- 2 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index fd3c59aedd3a..a51e0a475bdd 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -465,10 +465,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Security/AddDelegate/AddDelegatePage').default, [SCREENS.SETTINGS.DELEGATE.DELEGATE_ROLE]: () => require('../../../../pages/settings/Security/AddDelegate/SelectDelegateRolePage').default, [SCREENS.SETTINGS.DELEGATE.DELEGATE_CONFIRM]: () => require('../../../../pages/settings/Security/AddDelegate/ConfirmDelegatePage').default, -<<<<<<< HEAD -======= [SCREENS.SETTINGS.DELEGATE.DELEGATE_MAGIC_CODE]: () => require('../../../../pages/settings/Security/AddDelegate/DelegateMagicCodePage').default, ->>>>>>> show-copilot [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: () => require('../../../../pages/workspace/rules/RulesCustomNamePage').default, [SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER]: () => require('../../../../pages/workspace/rules/RulesAutoApproveReportsUnderPage').default, [SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT]: () => require('../../../../pages/workspace/rules/RulesRandomReportAuditPage').default, diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 2829cd492877..b6f15ae42160 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -25,15 +25,11 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; -<<<<<<< HEAD import useWindowDimensions from '@hooks/useWindowDimensions'; -import {removeDelegate} from '@libs/actions/Delegate'; -import getClickedTargetLocation from '@libs/getClickedTargetLocation'; -======= -import {clearAddDelegateErrors} from '@libs/actions/Delegate'; +import {clearAddDelegateErrors, removeDelegate} from '@libs/actions/Delegate'; import * as ErrorUtils from '@libs/ErrorUtils'; +import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; ->>>>>>> show-copilot import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import variables from '@styles/variables'; @@ -41,15 +37,12 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -<<<<<<< HEAD import type {Delegate} from '@src/types/onyx/Account'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; type DelegateWithAccountID = { accountID: number; } & Delegate; -======= -import {isEmptyObject} from '@src/types/utils/EmptyObject'; ->>>>>>> show-copilot function SecuritySettingsPage() { const styles = useThemeStyles(); @@ -60,7 +53,6 @@ function SecuritySettingsPage() { const {canUseNewDotCopilot} = usePermissions(); const {windowWidth} = useWindowDimensions(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); -<<<<<<< HEAD const delegateButtonRef = useRef(null); const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); @@ -89,9 +81,7 @@ function SecuritySettingsPage() { anchorPositionVertical: position.y, }); }, [windowWidth]); -======= const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false; ->>>>>>> show-copilot const delegates = account?.delegatedAccess?.delegates ?? []; const delegators = account?.delegatedAccess?.delegators ?? []; @@ -144,26 +134,6 @@ function SecuritySettingsPage() { })); }, [translate, waitForNavigate, styles]); -<<<<<<< HEAD - const delegateMenuItems: MenuItemProps[] = delegates.map(({email, role, pendingAction}) => { - const personalDetail = getPersonalDetailByEmail(email); - - return { - title: personalDetail?.displayName ?? email, - description: personalDetail?.displayName ? email : '', - badgeText: translate('delegate.role', role), - avatarID: personalDetail?.accountID ?? -1, - icon: personalDetail?.avatar ?? '', - iconType: CONST.ICON_TYPE_AVATAR, - numberOfLinesDescription: 1, - wrapperStyle: [styles.sectionMenuItemTopDescription], - iconRight: Expensicons.ThreeDots, - shouldShowRightIcon: true, - onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, {email, role, accountID: personalDetail?.accountID ?? -1}), - pendingAction, - }; - }); -======= const delegateMenuItems: MenuItemProps[] = delegates .filter((d) => !d.optimisticAccountID) .map(({email, role, pendingAction, errorFields}) => { @@ -172,6 +142,7 @@ function SecuritySettingsPage() { const error = ErrorUtils.getLatestErrorField({errorFields}, 'addDelegate'); const onPress = () => { + // onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, {email, role, accountID: personalDetail?.accountID ?? -1}), if (isEmptyObject(pendingAction)) { return; } @@ -201,7 +172,6 @@ function SecuritySettingsPage() { onPress, }; }); ->>>>>>> show-copilot const delegatorMenuItems: MenuItemProps[] = delegators.map(({email, role}) => { const personalDetail = getPersonalDetailByEmail(email); @@ -296,7 +266,6 @@ function SecuritySettingsPage() { )} -<<<<<<< HEAD - - )} - - -======= )} ->>>>>>> show-copilot ); } From 0bb76f8769c4be8a4ce11816e5bee441bc177824 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:01:35 +0300 Subject: [PATCH 022/231] fix errs --- .../settings/Security/SecuritySettingsPage.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index b6f15ae42160..c25955478ec0 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -40,10 +40,6 @@ import ROUTES from '@src/ROUTES'; import type {Delegate} from '@src/types/onyx/Account'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type DelegateWithAccountID = { - accountID: number; -} & Delegate; - function SecuritySettingsPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -57,7 +53,7 @@ function SecuritySettingsPage() { const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); - const [selectedDelegate, setSelectedDelegate] = useState(); + const [selectedDelegate, setSelectedDelegate] = useState(); const [anchorPosition, setAnchorPosition] = useState({ anchorPositionHorizontal: 0, @@ -89,11 +85,11 @@ function SecuritySettingsPage() { const hasDelegates = delegates.length > 0; const hasDelegators = delegators.length > 0; - const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, delegateWithAccountID: DelegateWithAccountID) => { + const showPopoverMenu = (nativeEvent: GestureResponderEvent | KeyboardEvent, delegate: Delegate) => { delegateButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement; setMenuPosition(); setShouldShowDelegatePopoverMenu(true); - setSelectedDelegate(delegateWithAccountID); + setSelectedDelegate(delegate); }; useLayoutEffect(() => { @@ -141,10 +137,9 @@ function SecuritySettingsPage() { const error = ErrorUtils.getLatestErrorField({errorFields}, 'addDelegate'); - const onPress = () => { - // onPress: (e: GestureResponderEvent | KeyboardEvent) => showPopoverMenu(e, {email, role, accountID: personalDetail?.accountID ?? -1}), + const onPress = (e: GestureResponderEvent | KeyboardEvent) => { if (isEmptyObject(pendingAction)) { - return; + showPopoverMenu(e, {email, role}); } if (!role) { Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(email)); @@ -294,7 +289,7 @@ function SecuritySettingsPage() { title={translate('delegate.changeAccessLevel')} icon={Expensicons.Pencil} onPress={() => { - Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(selectedDelegate?.accountID ?? -1, selectedDelegate?.role)); + Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? '')); setShouldShowDelegatePopoverMenu(false); }} wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} From 50861b43d0b83820861dd7b534e558715eb400e0 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:04:35 +0300 Subject: [PATCH 023/231] rm dupe --- .../settings/Security/SecuritySettingsPage.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index c25955478ec0..9ecfd0344dba 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -261,20 +261,6 @@ function SecuritySettingsPage() { )} - Navigation.navigate(ROUTES.SETTINGS_ADD_DELEGATE)} - shouldShowRightIcon - wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mb6]} - /> - {hasDelegators && ( - <> - {translate('delegate.youCanAccessTheseAccounts')} - - - )} } From e1095de967e0d6354115fc9c6d678a42b99ea869 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:06:01 +0300 Subject: [PATCH 024/231] return if no action --- src/pages/settings/Security/SecuritySettingsPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 9ecfd0344dba..738cfc69b03b 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -140,6 +140,7 @@ function SecuritySettingsPage() { const onPress = (e: GestureResponderEvent | KeyboardEvent) => { if (isEmptyObject(pendingAction)) { showPopoverMenu(e, {email, role}); + return; } if (!role) { Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(email)); From 9f2ae305e361aad458b7a0dc7dbbe113333df09e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:23:40 +0300 Subject: [PATCH 025/231] add update delegate role page --- src/ROUTES.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 32f1ef2f3de5..ba699c033ecf 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -139,6 +139,10 @@ const ROUTES = { route: 'settings/security/delegate/:login/role/:role', getRoute: (login: string, role?: string) => `settings/security/delegate/${encodeURIComponent(login)}/role/${role}` as const, }, + SETTINGS_UPDATE_DELEGATE_ROLE: { + route: 'settings/security/delegate/:login/update-role/:role', + getRoute: (login: string, currentRole: string) => `settings/security/delegate/${encodeURIComponent(login)}/update-role/${currentRole}` as const, + }, SETTINGS_DELEGATE_CONFIRM: { route: 'settings/security/delegate/:login/role/:role/confirm', getRoute: (login: string, role: string) => `settings/security/delegate/${encodeURIComponent(login)}/role/${role}/confirm` as const, From 93c3b24c2792a300a22fd5f1cc8c7e3def1e3b33 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:23:46 +0300 Subject: [PATCH 026/231] add update delegate role page --- src/SCREENS.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index b8f4b0f98c0a..8f52a75c2010 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -135,6 +135,7 @@ const SCREENS = { DELEGATE_ROLE: 'Settings_Delegate_Role', DELEGATE_CONFIRM: 'Settings_Delegate_Confirm', DELEGATE_MAGIC_CODE: 'Settings_Delegate_Magic_Code', + UPDATE_DELEGATE_ROLE: 'Settings_Delegate_Update_Role', }, }, SAVE_THE_WORLD: { From ae88543b8e4795d36b7f2a49c2115d7a90f8f59c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:23:53 +0300 Subject: [PATCH 027/231] add update delegate role page --- src/libs/Navigation/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ccb074d3e4f8..902e70e2089a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -657,6 +657,10 @@ type SettingsNavigatorParamList = { login: string; role?: string; }; + [SCREENS.SETTINGS.DELEGATE.UPDATE_DELEGATE_ROLE]: { + login: string; + currentRole: string; + }; [SCREENS.SETTINGS.DELEGATE.DELEGATE_CONFIRM]: { login: string; role: string; From 4102e5410a4023ea70e1f6520ed21706de2bcdce Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:24:02 +0300 Subject: [PATCH 028/231] add update delegate role page --- src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index a51e0a475bdd..a743cfb910a2 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -464,6 +464,7 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage').default, [SCREENS.SETTINGS.DELEGATE.ADD_DELEGATE]: () => require('../../../../pages/settings/Security/AddDelegate/AddDelegatePage').default, [SCREENS.SETTINGS.DELEGATE.DELEGATE_ROLE]: () => require('../../../../pages/settings/Security/AddDelegate/SelectDelegateRolePage').default, + [SCREENS.SETTINGS.DELEGATE.UPDATE_DELEGATE_ROLE]: () => require('../../../../pages/settings/Security/AddDelegate/UpdateDelegateRolePage').default, [SCREENS.SETTINGS.DELEGATE.DELEGATE_CONFIRM]: () => require('../../../../pages/settings/Security/AddDelegate/ConfirmDelegatePage').default, [SCREENS.SETTINGS.DELEGATE.DELEGATE_MAGIC_CODE]: () => require('../../../../pages/settings/Security/AddDelegate/DelegateMagicCodePage').default, [SCREENS.WORKSPACE.RULES_CUSTOM_NAME]: () => require('../../../../pages/workspace/rules/RulesCustomNamePage').default, From 3ceb3051a37ae648da34ba074a15c683444383e8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:24:09 +0300 Subject: [PATCH 029/231] add update delegate role page --- src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 2b8abc84f31e..324bf2165261 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -43,6 +43,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = SCREENS.SETTINGS.CLOSE, SCREENS.SETTINGS.DELEGATE.ADD_DELEGATE, SCREENS.SETTINGS.DELEGATE.DELEGATE_ROLE, + SCREENS.SETTINGS.DELEGATE.UPDATE_DELEGATE_ROLE, SCREENS.SETTINGS.DELEGATE.DELEGATE_CONFIRM, SCREENS.SETTINGS.DELEGATE.DELEGATE_MAGIC_CODE, ], From 24c2e398b7ae38ffcd8fc3f15098907ae5016856 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:24:24 +0300 Subject: [PATCH 030/231] add update delegate role page --- src/libs/Navigation/linkingConfig/config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6af935e69751..4fb8916ac34f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -290,6 +290,12 @@ const config: LinkingOptions['config'] = { login: (login: string) => decodeURIComponent(login), }, }, + [SCREENS.SETTINGS.DELEGATE.UPDATE_DELEGATE_ROLE]: { + path: ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.route, + parse: { + login: (login: string) => decodeURIComponent(login), + }, + }, [SCREENS.SETTINGS.DELEGATE.DELEGATE_CONFIRM]: { path: ROUTES.SETTINGS_DELEGATE_CONFIRM.route, parse: { From f6e5d3361d0ac39e8f9db72507de34051f86bc2c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:24:29 +0300 Subject: [PATCH 031/231] add update delegate role page --- .../AddDelegate/UpdateDelegateRolePage.tsx | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/pages/settings/Security/AddDelegate/UpdateDelegateRolePage.tsx diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRolePage.tsx new file mode 100644 index 000000000000..7f10e785c389 --- /dev/null +++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRolePage.tsx @@ -0,0 +1,69 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type UpdateDelegateRolePageProps = StackScreenProps; + +function UpdateDelegateRolePage({route}: UpdateDelegateRolePageProps) { + const {translate} = useLocalize(); + const login = route.params.login; + + const styles = useThemeStyles(); + const roleOptions = Object.values(CONST.DELEGATE_ROLE).map((role) => ({ + value: role, + text: translate('delegate.role', role), + keyForList: role, + alternateText: translate('delegate.roleDescription', role), + isSelected: role === route.params.currentRole, + })); + + return ( + + Navigation.goBack()} + /> + + <> + {translate('delegate.accessLevelDescription')}{' '} + + {translate('common.learnMore')} + + + + } + onSelectRow={(option) => { + Navigation.navigate(ROUTES.SETTINGS_DELEGATE_MAGIC_CODE.getRoute(login, option.value)); + }} + sections={[{data: roleOptions}]} + ListItem={RadioListItem} + /> + + ); +} + +UpdateDelegateRolePage.displayName = 'UpdateDelegateRolePage'; + +export default UpdateDelegateRolePage; From 6ccf1cc1a0e4daa6a35e8511942f7761476cd85c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 11 Sep 2024 01:31:13 +0300 Subject: [PATCH 032/231] fix navigation --- src/ROUTES.ts | 2 +- src/pages/settings/Security/SecuritySettingsPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ba699c033ecf..31a4baa83f20 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -140,7 +140,7 @@ const ROUTES = { getRoute: (login: string, role?: string) => `settings/security/delegate/${encodeURIComponent(login)}/role/${role}` as const, }, SETTINGS_UPDATE_DELEGATE_ROLE: { - route: 'settings/security/delegate/:login/update-role/:role', + route: 'settings/security/delegate/:login/update-role/:currentRole', getRoute: (login: string, currentRole: string) => `settings/security/delegate/${encodeURIComponent(login)}/update-role/${currentRole}` as const, }, SETTINGS_DELEGATE_CONFIRM: { diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 738cfc69b03b..36f4546d551f 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -276,7 +276,7 @@ function SecuritySettingsPage() { title={translate('delegate.changeAccessLevel')} icon={Expensicons.Pencil} onPress={() => { - Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? '')); + Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE.getRoute(selectedDelegate?.email ?? '', selectedDelegate?.role ?? '')); setShouldShowDelegatePopoverMenu(false); }} wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]} From 30bd3529daa416053de6db31e3d9a7eb30348fa3 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 12 Sep 2024 11:40:21 +0700 Subject: [PATCH 033/231] fix: LHN shows Hidden report with RBR --- src/libs/actions/ReportActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index ed8d4886659c..9338527eaccc 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -36,6 +36,7 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID || '-1'); if (linkedTransactionID) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null); + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportAction.childReportID}`, null); } // Delete the failed task report too From accc8a138100172748051974350c18deeae406bf Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 13 Sep 2024 17:44:06 +0700 Subject: [PATCH 034/231] feat: add employee and accounting page to onboarding flow --- src/CONST.ts | 25 ++- src/ONYXKEYS.ts | 6 +- src/ROUTES.ts | 8 + src/SCREENS.ts | 2 + src/languages/en.ts | 13 ++ src/languages/es.ts | 14 ++ .../Navigators/OnboardingModalNavigator.tsx | 10 ++ src/libs/Navigation/linkingConfig/config.ts | 8 + src/libs/Navigation/types.ts | 6 + src/libs/NavigationUtils.ts | 9 +- src/libs/actions/Welcome/OnboardingFlow.ts | 4 + src/libs/actions/Welcome/index.ts | 7 +- .../BaseOnboardingAccounting.tsx | 156 ++++++++++++++++++ .../OnboardingAccounting/index.native.tsx | 17 ++ src/pages/OnboardingAccounting/index.tsx | 25 +++ src/pages/OnboardingAccounting/types.ts | 12 ++ .../BaseOnboardingEmployees.tsx | 103 ++++++++++++ .../OnboardingEmployees/index.native.tsx | 17 ++ src/pages/OnboardingEmployees/index.tsx | 25 +++ src/pages/OnboardingEmployees/types.ts | 12 ++ .../BaseOnboardingPurpose.tsx | 2 +- 21 files changed, 476 insertions(+), 5 deletions(-) create mode 100644 src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx create mode 100644 src/pages/OnboardingAccounting/index.native.tsx create mode 100644 src/pages/OnboardingAccounting/index.tsx create mode 100644 src/pages/OnboardingAccounting/types.ts create mode 100644 src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx create mode 100644 src/pages/OnboardingEmployees/index.native.tsx create mode 100644 src/pages/OnboardingEmployees/index.tsx create mode 100644 src/pages/OnboardingEmployees/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index cd4d2b24e97a..1462fc7f7af1 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -130,12 +130,22 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessageType = { type OnboardingPurposeType = ValueOf; +type OnboardingCompanySizeType = ValueOf; + const onboardingInviteTypes = { IOU: 'iou', INVOICE: 'invoice', CHAT: 'chat', } as const; +const onboardingCompanySize = { + MICRO: 'micro', + SMALL: 'small', + MEDIUM_SMALL: 'mediumSmall', + MEDIUM: 'medium', + LARGE: 'large', +}; + type OnboardingInviteType = ValueOf; type OnboardingTaskType = { @@ -4431,6 +4441,7 @@ const CONST = { ONBOARDING_CHOICES: {...onboardingChoices}, SELECTABLE_ONBOARDING_CHOICES: {...selectableOnboardingChoices}, ONBOARDING_INVITE_TYPES: {...onboardingInviteTypes}, + ONBOARDING_COMPANY_SIZE: {...onboardingCompanySize}, ACTIONABLE_TRACK_EXPENSE_WHISPER_MESSAGE: 'What would you like to do with this expense?', ONBOARDING_CONCIERGE: { [onboardingChoices.EMPLOYER]: @@ -5731,6 +5742,18 @@ type FeedbackSurveyOptionID = ValueOf; type CancellationType = ValueOf; -export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType, OnboardingInviteType}; +export type { + Country, + IOUAction, + IOUType, + RateAndUnit, + OnboardingPurposeType, + OnboardingCompanySizeType, + IOURequestType, + SubscriptionType, + FeedbackSurveyOptionID, + CancellationType, + OnboardingInviteType, +}; export default CONST; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1d33b6892d51..940d6bff46e1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,6 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from './CONST'; -import type {OnboardingPurposeType} from './CONST'; +import type {OnboardingCompanySizeType, OnboardingPurposeType} from './CONST'; import type * as FormTypes from './types/form'; import type * as OnyxTypes from './types/onyx'; import type Onboarding from './types/onyx/Onboarding'; @@ -340,6 +340,9 @@ const ONYXKEYS = { /** Onboarding policyID selected by the user during Onboarding flow */ ONBOARDING_POLICY_ID: 'onboardingPolicyID', + /** Onboarding company size selected by the user during Onboarding flow */ + ONBOARDING_COMPANY_SIZE: 'onboardingCompanySize', + /** Onboarding Purpose selected by the user during Onboarding flow */ ONBOARDING_ADMINS_CHAT_REPORT_ID: 'onboardingAdminsChatReportID', @@ -934,6 +937,7 @@ type OnyxValuesMapping = { [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; [ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: OnboardingPurposeType; + [ONYXKEYS.ONBOARDING_COMPANY_SIZE]: OnboardingCompanySizeType; [ONYXKEYS.ONBOARDING_ERROR_MESSAGE]: string; [ONYXKEYS.ONBOARDING_POLICY_ID]: string; [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2b6268c05b3a..bf3cd00cd87b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1102,6 +1102,14 @@ const ROUTES = { route: 'onboarding/work', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/work`, backTo), }, + ONBOARDING_EMPLOYEES: { + route: 'onboarding/employees', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/employees`, backTo), + }, + ONBOARDING_ACCOUNTING: { + route: 'onboarding/accounting', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/accounting`, backTo), + }, ONBOARDING_PURPOSE: { route: 'onboarding/purpose', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/purpose`, backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e8a3698e9a95..343818ad50c0 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -502,6 +502,8 @@ const SCREENS = { PERSONAL_DETAILS: 'Onboarding_Personal_Details', PURPOSE: 'Onboarding_Purpose', WORK: 'Onboarding_Work', + EMPLOYEES: 'Onboarding_Employees', + ACCOUNTING: 'Onboarding_accounting', }, WELCOME_VIDEO: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 5a1e242519c2..a34dddb3ecc6 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1626,6 +1626,19 @@ export default { [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chat and split expenses with friends', [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: 'Something else', }, + employees: { + title: 'How many employees do you have?', + [CONST.ONBOARDING_COMPANY_SIZE.MICRO]: '1-10 employees', + [CONST.ONBOARDING_COMPANY_SIZE.SMALL]: '11-50 employees', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL]: '51-100 employees', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM]: '101-1,000 employees', + [CONST.ONBOARDING_COMPANY_SIZE.LARGE]: 'More than 1,000 employees', + }, + accounting: { + title: 'Do you use any accounting software?', + description: 'Connect your accounting software directly to Expensify', + noneOfAbove: 'None of the above', + }, error: { requiredFirstName: 'Please input your first name to continue.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 0ce9c70a9b56..625d25c8ba4e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1635,6 +1635,20 @@ export default { [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'Chatea y divide gastos con tus amigos', [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: 'Algo más', }, + employees: { + title: 'How many empleados do you have?', + description: `Don't worry, you can change this later.`, + [CONST.ONBOARDING_COMPANY_SIZE.MICRO]: '1-10 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.SMALL]: '11-50 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL]: '51-100 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.MEDIUM]: '101-1,000 empleados', + [CONST.ONBOARDING_COMPANY_SIZE.LARGE]: 'More than 1,000 empleados', + }, + accounting: { + title: 'Do you use any accounting software?', + description: 'Connect your accounting software directly to Expensify', + noneOfAbove: 'None of the above', + }, error: { requiredFirstName: 'Introduce tu nombre para continuar.', }, diff --git a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx index 4eb1409f192d..eb852eb6a108 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx @@ -12,6 +12,8 @@ import OnboardingModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator import Navigation from '@libs/Navigation/Navigation'; import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types'; import OnboardingRefManager from '@libs/OnboardingRefManager'; +import OnboardingAccounting from '@pages/OnboardingAccounting'; +import OnboardingEmployees from '@pages/OnboardingEmployees'; import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails'; import OnboardingPurpose from '@pages/OnboardingPurpose'; import OnboardingWork from '@pages/OnboardingWork'; @@ -87,6 +89,14 @@ function OnboardingModalNavigator() { name={SCREENS.ONBOARDING.WORK} component={OnboardingWork} /> + + diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 87656c5997db..e23edad21ec9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -123,6 +123,14 @@ const config: LinkingOptions['config'] = { path: ROUTES.ONBOARDING_WORK.route, exact: true, }, + [SCREENS.ONBOARDING.EMPLOYEES]: { + path: ROUTES.ONBOARDING_EMPLOYEES.route, + exact: true, + }, + [SCREENS.ONBOARDING.ACCOUNTING]: { + path: ROUTES.ONBOARDING_ACCOUNTING.route, + exact: true, + }, }, }, [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3dc7c708f0b6..6ee26850f4e8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1289,6 +1289,12 @@ type OnboardingModalNavigatorParamList = { [SCREENS.ONBOARDING.WORK]: { backTo?: string; }; + [SCREENS.ONBOARDING.EMPLOYEES]: { + backTo?: string; + }; + [SCREENS.ONBOARDING.ACCOUNTING]: { + backTo?: string; + }; }; type WelcomeVideoModalNavigatorParamList = { diff --git a/src/libs/NavigationUtils.ts b/src/libs/NavigationUtils.ts index ea1710b9931c..f4ce80ac3a4e 100644 --- a/src/libs/NavigationUtils.ts +++ b/src/libs/NavigationUtils.ts @@ -17,7 +17,14 @@ const CENTRAL_PANE_SCREEN_NAMES = new Set([ SCREENS.REPORT, ]); -const ONBOARDING_SCREEN_NAMES = new Set([SCREENS.ONBOARDING.PERSONAL_DETAILS, SCREENS.ONBOARDING.PURPOSE, SCREENS.ONBOARDING.WORK, SCREENS.ONBOARDING_MODAL.ONBOARDING]); +const ONBOARDING_SCREEN_NAMES = new Set([ + SCREENS.ONBOARDING.PERSONAL_DETAILS, + SCREENS.ONBOARDING.PURPOSE, + SCREENS.ONBOARDING.WORK, + SCREENS.ONBOARDING_MODAL.ONBOARDING, + SCREENS.ONBOARDING.EMPLOYEES, + SCREENS.ONBOARDING.ACCOUNTING, +]); const removePolicyIDParamFromState = (state: State) => { const stateCopy = cloneDeep(state); diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index 4e780090299d..3e16c36415bc 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -64,6 +64,10 @@ function adaptOnboardingRouteState() { name: SCREENS.ONBOARDING.WORK, params: currentRoute?.params, }, + { + name: SCREENS.ONBOARDING.EMPLOYEES, + params: currentRoute?.params, + }, {...currentRoute}, ], } as Readonly>; diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index f5995aa1e2a9..22a087264c59 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -5,7 +5,7 @@ import * as API from '@libs/API'; import {WRITE_COMMANDS} from '@libs/API/types'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; -import type {OnboardingPurposeType} from '@src/CONST'; +import type {OnboardingCompanySizeType, OnboardingPurposeType} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type Onboarding from '@src/types/onyx/Onboarding'; @@ -144,6 +144,10 @@ function setOnboardingPurposeSelected(value: OnboardingPurposeType) { Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, value ?? null); } +function setOnboardingCompanySize(value: OnboardingCompanySizeType) { + Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, value); +} + function setOnboardingErrorMessage(value: string) { Onyx.set(ONYXKEYS.ONBOARDING_ERROR_MESSAGE, value ?? null); } @@ -236,4 +240,5 @@ export { completeHybridAppOnboarding, handleHybridAppOnboarding, setOnboardingErrorMessage, + setOnboardingCompanySize, }; diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx new file mode 100644 index 000000000000..bb05a456ba60 --- /dev/null +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -0,0 +1,156 @@ +import React, {useMemo, useState} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import FormHelpMessage from '@components/FormHelpMessage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import type {ListItem} from '@components/SelectionList/types'; +import UserListItem from '@components/SelectionList/UserListItem'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {Icon} from '@src/types/onyx/OnyxCommon'; +import type {BaseOnboardingAccountingProps} from './types'; + +function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboardingAccountingProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); + const [userReportedIntegration, setUserReportedIntegration] = useState(null); + const [error, setError] = useState(''); + + const accountingOptions: ListItem[] = useMemo(() => { + const policyAccountingOptions: ListItem[] = Object.values(CONST.POLICY.CONNECTIONS.NAME).map((connectionName): ListItem => { + let text; + let icons: Icon[]; + switch (connectionName) { + case CONST.POLICY.CONNECTIONS.NAME.QBO: { + text = translate('workspace.accounting.qbo'); + icons = [ + { + source: Expensicons.QBOCircle, + type: 'avatar', + }, + ]; + break; + } + case CONST.POLICY.CONNECTIONS.NAME.XERO: { + text = translate('workspace.accounting.xero'); + icons = [ + { + source: Expensicons.XeroCircle, + type: 'avatar', + }, + ]; + break; + } + case CONST.POLICY.CONNECTIONS.NAME.NETSUITE: { + text = translate('workspace.accounting.netsuite'); + icons = [ + { + source: Expensicons.NetSuiteSquare, + type: 'avatar', + }, + ]; + break; + } + default: { + text = translate('workspace.accounting.intacct'); + icons = [ + { + source: Expensicons.IntacctSquare, + type: 'avatar', + }, + ]; + break; + } + } + return { + keyForList: connectionName, + text, + icons, + }; + }); + const noneAccountingOption: ListItem = { + text: translate('onboarding.accounting.noneOfAbove'), + keyForList: null, + icons: [ + { + source: Expensicons.Close, + type: 'avatar', + }, + ], + }; + return [...policyAccountingOptions, noneAccountingOption]; + }, [translate]); + + const footerContent = ( + <> + {!!error && ( + + )} +