From cdcdcc579b5d69370f805b9d4a40d67aed8a777b Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 28 Oct 2024 14:12:02 +0700 Subject: [PATCH 1/9] fix: hide "saved" text when deleting saved search --- src/pages/Search/SearchTypeMenu.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index babc0fea14a5..0267180c217a 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -14,6 +14,7 @@ import type {SearchQueryJSON} from '@components/Search/types'; import Text from '@components/Text'; import useDeleteSavedSearch from '@hooks/useDeleteSavedSearch'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -67,6 +68,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const taxRates = getAllTaxRates(); const [cardList = {}] = useOnyx(ONYXKEYS.CARD_LIST); + const {isOffline} = useNetwork(); const typeMenuItems: SearchTypeMenuItem[] = [ { @@ -234,6 +236,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { /> ); } + const shouldShowSavedSearchesMenuItemTitle = Object.values(savedSearches ?? {}).filter((s) => s.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length > 0; return ( <> @@ -261,7 +264,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { ); })} - {savedSearches && Object.keys(savedSearches).length > 0 && ( + {shouldShowSavedSearchesMenuItemTitle && ( <> {translate('search.savedSearchesMenuItemTitle')} Date: Mon, 28 Oct 2024 11:06:42 +0000 Subject: [PATCH 2/9] Use %bank% cards --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f4501f758557..4362c02a827e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3122,6 +3122,7 @@ const translations = { assignCard: 'Assign card', cardNumber: 'Card number', customFeed: 'Custom feed', + feedName: ({feedName}: CompanyCardFeedNameParams) => `${feedName} cards`, directFeed: 'Direct feed', whoNeedsCardAssigned: 'Who needs a card assigned?', chooseCard: 'Choose a card', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6614afbc483d..7d2d3d643a4e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3161,6 +3161,7 @@ const translations = { assignCard: 'Asignar tarjeta', cardNumber: 'Número de la tarjeta', customFeed: 'Fuente personalizada', + feedName: ({feedName}: CompanyCardFeedNameParams) => `Tarjetas ${feedName}`, directFeed: 'Fuente directa', whoNeedsCardAssigned: '¿Quién necesita una tarjeta?', chooseCard: 'Elige una tarjeta', diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx index efd95ecb8980..4b8f34897076 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListHeaderButtons.tsx @@ -38,6 +38,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed}: Worksp const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const shouldChangeLayout = isMediumScreenWidth || shouldUseNarrowLayout; const feedName = cardFeeds?.settings?.companyCardNicknames?.[selectedFeed] ?? CardUtils.getCardFeedName(selectedFeed); + const formattedFeedName = translate('workspace.companyCards.feedName', {feedName}); const isCustomFeed = CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD === selectedFeed || CONST.COMPANY_CARD.FEED_BANK_NAME.VISA === selectedFeed || CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX === selectedFeed; @@ -51,7 +52,7 @@ function WorkspaceCompanyCardsListHeaderButtons({policyID, selectedFeed}: Worksp Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS_SELECT_FEED.getRoute(policyID))} style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, shouldChangeLayout && styles.mb3]} - accessibilityLabel={feedName} + accessibilityLabel={formattedFeedName} > - {feedName} + {formattedFeedName} {PolicyUtils.hasPolicyFeedsError(cardFeeds?.settings?.companyCards ?? {}, selectedFeed) && ( Date: Mon, 28 Oct 2024 14:21:20 +0100 Subject: [PATCH 3/9] Add fields limit --- src/CONST.ts | 1 + src/pages/workspace/companyCards/addNew/CardNameStep.tsx | 1 + src/pages/workspace/companyCards/addNew/DetailsStep.tsx | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 2ba002d47534..4ae3975cfc4d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2929,6 +2929,7 @@ const CONST = { // Character Limits FORM_CHARACTER_LIMIT: 50, + STANDARD_LENGTH_LIMIT: 100, LEGAL_NAMES_CHARACTER_LIMIT: 150, LOGIN_CHARACTER_LIMIT: 254, CATEGORY_NAME_LIMIT: 256, diff --git a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx index 2b910515ba64..c1fd28accef6 100644 --- a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx @@ -67,6 +67,7 @@ function CardNameStep() { role={CONST.ROLE.PRESENTATION} defaultValue={addNewCard?.data?.bankName} containerStyles={[styles.mb6]} + maxLength={CONST.STANDARD_LENGTH_LIMIT} ref={inputCallbackRef} /> diff --git a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx index 2c719acd47d8..c4b427578a52 100644 --- a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx +++ b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx @@ -104,6 +104,7 @@ function DetailsStep({policyID}: DetailsStepProps) { inputID={INPUT_IDS.PROCESSOR_ID} label={translate('workspace.companyCards.addNewCard.feedDetails.vcf.processorLabel')} role={CONST.ROLE.PRESENTATION} + maxLength={CONST.STANDARD_LENGTH_LIMIT} containerStyles={[styles.mb6]} ref={inputCallbackRef} /> @@ -112,6 +113,7 @@ function DetailsStep({policyID}: DetailsStepProps) { inputID={INPUT_IDS.BANK_ID} label={translate('workspace.companyCards.addNewCard.feedDetails.vcf.bankLabel')} role={CONST.ROLE.PRESENTATION} + maxLength={CONST.STANDARD_LENGTH_LIMIT} containerStyles={[styles.mb6]} /> @@ -130,6 +133,7 @@ function DetailsStep({policyID}: DetailsStepProps) { inputID={INPUT_IDS.DISTRIBUTION_ID} label={translate('workspace.companyCards.addNewCard.feedDetails.cdf.distributionLabel')} role={CONST.ROLE.PRESENTATION} + maxLength={CONST.STANDARD_LENGTH_LIMIT} containerStyles={[styles.mb6]} ref={inputCallbackRef} /> @@ -141,6 +145,7 @@ function DetailsStep({policyID}: DetailsStepProps) { inputID={INPUT_IDS.DELIVERY_FILE_NAME} label={translate('workspace.companyCards.addNewCard.feedDetails.gl1025.fileNameLabel')} role={CONST.ROLE.PRESENTATION} + maxLength={CONST.STANDARD_LENGTH_LIMIT} containerStyles={[styles.mb6]} ref={inputCallbackRef} /> From 7fc50badbca13add0d467d4337e69e70c765ca20 Mon Sep 17 00:00:00 2001 From: Getabalew Date: Tue, 29 Oct 2024 13:40:49 +0300 Subject: [PATCH 4/9] feat: add timer on validate code action modal --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 76 +++++++++++++------ .../Contacts/ContactMethodDetailsPage.tsx | 10 +-- .../Profile/Contacts/NewContactMethodPage.tsx | 13 +--- .../AddDelegate/DelegateMagicCodeModal.tsx | 11 +-- .../settings/Wallet/ExpensifyCardPage.tsx | 17 ++--- .../settings/Wallet/VerifyAccountPage.tsx | 1 + 6 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index 02121ce26906..611a07faa758 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -90,6 +90,10 @@ function BaseValidateCodeForm({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case const shouldDisableResendValidateCode = !!isOffline || account?.isLoading; const focusTimeoutRef = useRef(null); + const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number); + const [isResend, setIsResend] = useState(false); + + const timerRef = useRef(); useImperativeHandle(innerRef, () => ({ focus() { @@ -135,12 +139,28 @@ function BaseValidateCodeForm({ inputValidateCodeRef.current?.clear(); }, [hasMagicCodeBeenSent]); + useEffect(() => { + if (timeRemaining > 0) { + timerRef.current = setTimeout(() => { + setTimeRemaining(timeRemaining - 1); + }, 1000); + } + return () => { + clearTimeout(timerRef.current); + }; + }, [timeRemaining]); + /** * Request a validate code / magic code be sent to verify this contact method */ const resendValidateCode = () => { + if (hasMagicCodeBeenSent && !isResend) { + return; + } + sendValidateCode(); inputValidateCodeRef.current?.clear(); + setTimeRemaining(CONST.REQUEST_CODE_DELAY); }; /** @@ -177,6 +197,7 @@ function BaseValidateCodeForm({ handleSubmitForm(validateCode); }, [validateCode, handleSubmitForm]); + const shouldShowTimer = timeRemaining > 0 && !isOffline; return ( <> + {shouldShowTimer && ( + + {translate('validateCodeForm.requestNewCode')} + 00:{String(timeRemaining).padStart(2, '0')} + + )} User.clearValidateCodeActionError('actionVerified')} > - - - {translate('validateCodeForm.magicCodeNotReceived')} - - {hasMagicCodeBeenSent && ( - - )} - + {!shouldShowTimer && ( + + { + resendValidateCode(); + setIsResend(true); + }} + underlayColor={theme.componentBG} + hoverDimmingValue={1} + pressDimmingValue={0.2} + role={CONST.ROLE.BUTTON} + accessibilityLabel={translate('validateCodeForm.magicCodeNotReceived')} + > + {translate('validateCodeForm.magicCodeNotReceived')} + + + )} + {hasMagicCodeBeenSent && ( + + )} { - if (loginData?.validateCodeSent) { - return; - } - - User.requestContactMethodValidateCode(contactMethod); - }; - const prevValidatedDate = usePrevious(loginData?.validatedDate); useEffect(() => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -276,7 +268,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo)); setIsValidateCodeActionModalVisible(false); }} - sendValidateCode={sendValidateCode} + sendValidateCode={() => User.requestContactMethodValidateCode(contactMethod)} description={translate('contacts.enterMagicCode', {contactMethod})} footer={() => getMenuItems()} /> diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx index 124d6525113b..c2a7e1b6712c 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx @@ -109,14 +109,6 @@ function NewContactMethodPage({route}: NewContactMethodPageProps) { Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(navigateBackTo)); }, [navigateBackTo]); - const sendValidateCode = () => { - if (loginData?.validateCodeSent) { - return; - } - - User.requestValidateCodeAction(); - }; - return ( User.requestValidateCodeAction()} description={translate('contacts.enterMagicCode', {contactMethod})} /> diff --git a/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx b/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx index dd54aa5b9404..0393382415fc 100644 --- a/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx +++ b/src/pages/settings/Security/AddDelegate/DelegateMagicCodeModal.tsx @@ -46,14 +46,6 @@ function DelegateMagicCodeModal({login, role, onClose}: DelegateMagicCodeModalPr Delegate.clearAddDelegateErrors(currentDelegate?.email ?? '', 'addDelegate'); }; - const sendValidateCode = () => { - if (currentDelegate?.validateCodeSent) { - return; - } - - User.requestValidateCodeAction(); - }; - return ( User.requestValidateCodeAction()} + hasMagicCodeBeenSent={!!currentDelegate?.validateCodeSent} handleSubmitForm={(validateCode) => Delegate.addDelegate(login, role, validateCode)} description={translate('delegate.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})} /> diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index 9aac1db023e1..ff6af4e08ccb 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -134,6 +134,9 @@ function ExpensifyCardPage({ const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(cardsToShow?.at(0)?.availableSpend); const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(cardsToShow?.at(0)?.nameValuePairs?.limitType); + const primaryLogin = account?.primaryLogin ?? ''; + const loginData = loginList?.[primaryLogin]; + const goToGetPhysicalCardFlow = () => { let updatedDraftValues = draftValues; if (!draftValues) { @@ -146,17 +149,6 @@ function ExpensifyCardPage({ GetPhysicalCardUtils.goToNextPhysicalCardRoute(domain, GetPhysicalCardUtils.getUpdatedPrivatePersonalDetails(updatedDraftValues, privatePersonalDetails)); }; - const sendValidateCode = () => { - const primaryLogin = account?.primaryLogin ?? ''; - const loginData = loginList?.[primaryLogin]; - - if (loginData?.validateCodeSent) { - return; - } - - requestValidateCodeAction(); - }; - if (isNotFound) { return Navigation.goBack(ROUTES.SETTINGS_WALLET)} />; } @@ -310,9 +302,10 @@ function ExpensifyCardPage({ {}} - sendValidateCode={sendValidateCode} + sendValidateCode={() => requestValidateCodeAction()} onClose={() => setIsValidateCodeActionModalVisible(false)} isVisible={isValidateCodeActionModalVisible} + hasMagicCodeBeenSent={!!loginData?.validateCodeSent} title={translate('cardPage.validateCardTitle')} description={translate('cardPage.enterMagicCode', {contactMethod: account?.primaryLogin ?? ''})} /> diff --git a/src/pages/settings/Wallet/VerifyAccountPage.tsx b/src/pages/settings/Wallet/VerifyAccountPage.tsx index a3b51ef0de17..200b6b55363a 100644 --- a/src/pages/settings/Wallet/VerifyAccountPage.tsx +++ b/src/pages/settings/Wallet/VerifyAccountPage.tsx @@ -69,6 +69,7 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) { sendValidateCode={() => User.requestValidateCodeAction()} handleSubmitForm={handleSubmitForm} validateError={validateLoginError} + hasMagicCodeBeenSent={!!loginData?.validateCodeSent} isVisible={isValidateCodeActionModalVisible} title={translate('contacts.validateAccount')} description={translate('contacts.featureRequiresValidate')} From 948b600ad55db2c760c36065728c09bebebafc40 Mon Sep 17 00:00:00 2001 From: Getabalew Date: Tue, 29 Oct 2024 13:46:30 +0300 Subject: [PATCH 5/9] fix: the forgotten doc --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index 611a07faa758..3d8c8a370e26 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -63,6 +63,7 @@ type ValidateCodeFormProps = { /** Function to clear error of the form */ clearError: () => void; + /** Function is called when validate code modal is mounted and on magic code resend */ sendValidateCode: () => void; }; From 3efc05415ce1ee0531b187b538d43ea8a980c66b Mon Sep 17 00:00:00 2001 From: Getabalew Date: Tue, 29 Oct 2024 14:12:45 +0300 Subject: [PATCH 6/9] fix: prevent multiple api requests --- src/components/ValidateCodeActionModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ValidateCodeActionModal/index.tsx b/src/components/ValidateCodeActionModal/index.tsx index ff84198002d8..bdeeca67e0f9 100644 --- a/src/components/ValidateCodeActionModal/index.tsx +++ b/src/components/ValidateCodeActionModal/index.tsx @@ -38,7 +38,7 @@ function ValidateCodeActionModal({ }, [onClose, clearError]); useEffect(() => { - if (!firstRenderRef.current || !isVisible) { + if (!firstRenderRef.current || !isVisible || hasMagicCodeBeenSent) { return; } firstRenderRef.current = false; From 0954b4d7224f99807a91deb915283950aa83a26d Mon Sep 17 00:00:00 2001 From: Getabalew Date: Tue, 29 Oct 2024 21:54:41 +0300 Subject: [PATCH 7/9] fix: lint --- src/components/ValidateCodeActionModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ValidateCodeActionModal/index.tsx b/src/components/ValidateCodeActionModal/index.tsx index bdeeca67e0f9..8c09d8caad62 100644 --- a/src/components/ValidateCodeActionModal/index.tsx +++ b/src/components/ValidateCodeActionModal/index.tsx @@ -44,7 +44,7 @@ function ValidateCodeActionModal({ firstRenderRef.current = false; sendValidateCode(); - }, [isVisible, sendValidateCode]); + }, [isVisible, sendValidateCode, hasMagicCodeBeenSent]); return ( Date: Wed, 30 Oct 2024 10:07:47 +0300 Subject: [PATCH 8/9] Update src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx Co-authored-by: Youssef Lourayad --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index 3d8c8a370e26..bc212755dc33 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -92,7 +92,7 @@ function BaseValidateCodeForm({ const shouldDisableResendValidateCode = !!isOffline || account?.isLoading; const focusTimeoutRef = useRef(null); const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number); - const [isResend, setIsResend] = useState(false); + const [isResent, setIsResent] = useState(false); const timerRef = useRef(); From 3c7ae6dee1196362fa8057d96d42309aac7565b8 Mon Sep 17 00:00:00 2001 From: Getabalew Date: Wed, 30 Oct 2024 10:09:52 +0300 Subject: [PATCH 9/9] refactor: rename variable name to more descriptive name --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index bc212755dc33..a4df2925f18e 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -155,7 +155,7 @@ function BaseValidateCodeForm({ * Request a validate code / magic code be sent to verify this contact method */ const resendValidateCode = () => { - if (hasMagicCodeBeenSent && !isResend) { + if (hasMagicCodeBeenSent && !isResent) { return; } @@ -231,7 +231,7 @@ function BaseValidateCodeForm({ style={[styles.mr1]} onPress={() => { resendValidateCode(); - setIsResend(true); + setIsResent(true); }} underlayColor={theme.componentBG} hoverDimmingValue={1}