diff --git a/android/app/build.gradle b/android/app/build.gradle index 08e313c13462..0c3fc7afa2ee 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -96,8 +96,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001041114 - versionName "1.4.11-14" + versionCode 1001041123 + versionName "1.4.11-23" } flavorDimensions "default" diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 0ff280c4b9c6..bfeb58ceec05 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -10,7 +10,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^6.1.4", + "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" } }, @@ -50,9 +50,9 @@ } }, "node_modules/builder-util-runtime": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", - "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz", + "integrity": "sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==", "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -150,11 +150,11 @@ "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" }, "node_modules/electron-updater": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", - "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.6.tgz", + "integrity": "sha512-G2bO72i7kv+bVdBAjq6lQn8zkZ3wMRRjxBD4TGBjB77UiuMDUBeP45YAs4y08udPzttGW2qzpnbOUJY5RYWZfw==", "dependencies": { - "builder-util-runtime": "9.2.1", + "builder-util-runtime": "9.2.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", @@ -461,9 +461,9 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "builder-util-runtime": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", - "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz", + "integrity": "sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==", "requires": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -535,11 +535,11 @@ "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" }, "electron-updater": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", - "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.6.tgz", + "integrity": "sha512-G2bO72i7kv+bVdBAjq6lQn8zkZ3wMRRjxBD4TGBjB77UiuMDUBeP45YAs4y08udPzttGW2qzpnbOUJY5RYWZfw==", "requires": { - "builder-util-runtime": "9.2.1", + "builder-util-runtime": "9.2.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", diff --git a/desktop/package.json b/desktop/package.json index bf49d93f1a7b..a6b92bde81c4 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -7,7 +7,7 @@ "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", "electron-serve": "^1.0.0", - "electron-updater": "^6.1.4", + "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 507543c84d41..e707e4c590b7 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.11.14 + 1.4.11.23 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6cb3c324fdee..f87a232a7454 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.11.14 + 1.4.11.23 diff --git a/package-lock.json b/package-lock.json index c4380a82ec12..f985d9383fba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.11-14", + "version": "1.4.11-23", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.11-14", + "version": "1.4.11-23", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c9c20d641402..817567dcb855 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.11-14", + "version": "1.4.11-23", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/CONST.ts b/src/CONST.ts index d4208d51a78c..072f780b54ae 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1099,11 +1099,6 @@ const CONST = { USER_CANCELLED: 'User canceled flow.', USER_TAPPED_BACK: 'User exited by clicking the back button.', USER_EXITED: 'User exited by manual action.', - USER_CAMERA_DENINED: 'Onfido.OnfidoFlowError', - USER_CAMERA_PERMISSION: 'Encountered an error: cameraPermission', - // eslint-disable-next-line max-len - USER_CAMERA_CONSENT_DENIED: - 'Unexpected result Intent. It might be a result of incorrect integration, make sure you only pass Onfido intent to handleActivityResult. It might be due to unpredictable crash or error. Please report the problem to android-sdk@onfido.com. Intent: null \n resultCode: 0', }, }, @@ -2731,17 +2726,125 @@ const CONST = { EXPENSIFY_LOGO_SIZE_RATIO: 0.22, EXPENSIFY_LOGO_MARGIN_RATIO: 0.03, }, + /** + * Acceptable values for the `accessibilityRole` prop on react native components. + * + * **IMPORTANT:** Do not use with the `role` prop as it can cause errors. + * + * @deprecated ACCESSIBILITY_ROLE is deprecated. Please use CONST.ROLE instead. + */ ACCESSIBILITY_ROLE: { + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ BUTTON: 'button', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ LINK: 'link', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ MENUITEM: 'menuitem', - TEXT: 'presentation', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + TEXT: 'text', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ RADIO: 'radio', - IMAGEBUTTON: 'img button', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + IMAGEBUTTON: 'imagebutton', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ CHECKBOX: 'checkbox', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + SWITCH: 'switch', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + ADJUSTABLE: 'adjustable', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + IMAGE: 'image', + }, + /** + * Acceptable values for the `role` attribute on react native components. + * + * **IMPORTANT:** Not for use with the `accessibilityRole` prop, as it accepts different values, and new components + * should use the `role` prop instead. + */ + ROLE: { + /** Use for elements with important, time-sensitive information. */ + ALERT: 'alert', + /** Use for elements that act as buttons. */ + BUTTON: 'button', + /** Use for elements representing checkboxes. */ + CHECKBOX: 'checkbox', + /** Use for elements that allow a choice from multiple options. */ + COMBOBOX: 'combobox', + /** Use with scrollable lists to represent a grid layout. */ + GRID: 'grid', + /** Use for section headers or titles. */ + HEADING: 'heading', + /** Use for image elements. */ + IMG: 'img', + /** Use for elements that navigate to other pages or content. */ + LINK: 'link', + /** Use to identify a list of items. */ + LIST: 'list', + /** Use for a list of choices or options. */ + MENU: 'menu', + /** Use for a container of multiple menus. */ + MENUBAR: 'menubar', + /** Use for items within a menu. */ + MENUITEM: 'menuitem', + /** Use when no specific role is needed. */ + NONE: 'none', + /** Use for elements that don't require a specific role. */ + PRESENTATION: 'presentation', + /** Use for elements showing progress of a task. */ + PROGRESSBAR: 'progressbar', + /** Use for radio buttons. */ + RADIO: 'radio', + /** Use for groups of radio buttons. */ + RADIOGROUP: 'radiogroup', + /** Use for scrollbar elements. */ + SCROLLBAR: 'scrollbar', + /** Use for text fields that are used for searching. */ + SEARCHBOX: 'searchbox', + /** Use for adjustable elements like sliders. */ + SLIDER: 'slider', + /** Use for a button that opens a list of choices. */ + SPINBUTTON: 'spinbutton', + /** Use for elements providing a summary of app conditions. */ + SUMMARY: 'summary', + /** Use for on/off switch elements. */ SWITCH: 'switch', - ADJUSTABLE: 'slider', - IMAGE: 'img', + /** Use for tab elements in a tab list. */ + TAB: 'tab', + /** Use for a list of tabs. */ + TABLIST: 'tablist', + /** Use for timer elements. */ + TIMER: 'timer', + /** Use for toolbars containing action buttons or components. */ + TOOLBAR: 'toolbar', }, TRANSLATION_KEYS: { ATTACHMENT: 'common.attachment', diff --git a/src/Expensify.js b/src/Expensify.js index aece93c0ff4d..756df5b79b88 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -112,6 +112,7 @@ function Expensify(props) { }, [props.isCheckingPublicRoom]); const isAuthenticated = useMemo(() => Boolean(lodashGet(props.session, 'authToken', null)), [props.session]); + const autoAuthState = useMemo(() => lodashGet(props.session, 'autoAuthState', ''), [props.session]); const contextValue = useMemo( () => ({ @@ -207,7 +208,10 @@ function Expensify(props) { } return ( - + {shouldInit && ( <> diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a268c008cee8..933ae678da23 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -383,7 +383,7 @@ type OnyxValues = { [ONYXKEYS.COUNTRY]: string; [ONYXKEYS.USER]: OnyxTypes.User; [ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation; - [ONYXKEYS.LOGIN_LIST]: Record; + [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.SESSION]: OnyxTypes.Session; [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; @@ -403,8 +403,8 @@ type OnyxValues = { [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; [ONYXKEYS.WALLET_TERMS]: OnyxTypes.WalletTerms; - [ONYXKEYS.BANK_ACCOUNT_LIST]: Record; - [ONYXKEYS.FUND_LIST]: Record; + [ONYXKEYS.BANK_ACCOUNT_LIST]: OnyxTypes.BankAccountList; + [ONYXKEYS.FUND_LIST]: OnyxTypes.FundList; [ONYXKEYS.CARD_LIST]: Record; [ONYXKEYS.WALLET_STATEMENT]: OnyxTypes.WalletStatement; [ONYXKEYS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount; @@ -440,7 +440,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 53763d6d7cd1..425ff73af56b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -34,7 +34,7 @@ const ROUTES = { VALIDATE_LOGIN: 'v/:accountID/:validateCode', GET_ASSISTANCE: { route: 'get-assistance/:taskID', - getRoute: (taskID: string) => `get-assistance/${taskID}` as const, + getRoute: (taskID: string, backTo: string) => getUrlWithBackToParam(`get-assistance/${taskID}`, backTo), }, UNLINK_LOGIN: 'u/:accountID/:validateCode', APPLE_SIGN_IN: 'sign-in-with-apple', diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index bd88712432a8..5efcc003d853 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -55,7 +55,7 @@ function AmountTextInput(props) { blurOnSubmit={false} selection={props.selection} onSelectionChange={props.onSelectionChange} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} onKeyPress={props.onKeyPress} /> ); diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js index 1e2d18bc4691..6161ba140726 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js @@ -58,7 +58,7 @@ function BaseAnchorForAttachmentsOnly(props) { onPressOut={props.onPressOut} onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} accessibilityLabel={fileName} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > (linkRef = el)} style={StyleSheet.flatten([style, defaultTextStyle])} - role={CONST.ACCESSIBILITY_ROLE.LINK} + role={CONST.ROLE.LINK} hrefAttrs={{ rel, target: isEmail || !linkProps.href ? '_self' : target, diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index fbc49590d5ae..1642fa32734a 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -81,7 +81,7 @@ function CarouselItem({item, isFocused, onPress}) { {children} diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js index 22bcf259ed77..3b080e47e4d1 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js @@ -28,7 +28,7 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, loadComplete, o onPress={onPress} disabled={loadComplete} style={[styles.flex1, styles.flexRow, styles.alignSelfStretch]} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={file.name || translate('attachmentView.unknownFilename')} > {children} diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js index fc443e5ea17b..a61adcf04043 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js @@ -36,7 +36,7 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, isFocused, isUs onPress={onPress} disabled={loadComplete} style={[styles.flex1, styles.flexRow, styles.alignSelfStretch]} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={file.name || translate('attachmentView.unknownFilename')} > {children} diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 62141ecb38c7..6ad81cfb0c9b 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -414,7 +414,7 @@ function AvatarCropModal(props) { onLayout={initializeSliderContainer} onPressIn={(e) => runOnUI(sliderOnPress)(e.nativeEvent.locationX)} accessibilityLabel="slider" - role={CONST.ACCESSIBILITY_ROLE.ADJUSTABLE} + role={CONST.ROLE.SLIDER} > {shouldShowSubscriptAvatar ? ( ReportUtils.navigateToDetailsPage(report)} style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]} accessibilityLabel={title} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {headerView} diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index f19cf8a14b41..337725e91af9 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -298,7 +298,7 @@ function AvatarWithImagePicker({ setIsMenuVisible((prev) => !prev)} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('avatarWithImagePicker.editImage')} disabled={isAvatarCropModalOpen} ref={anchorRef} diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 82212c66db04..b670921dff4c 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -47,7 +47,7 @@ function Badge({success = false, error = false, pressable = false, text, environ {renderContent()} diff --git a/src/components/CollapsibleSection/index.tsx b/src/components/CollapsibleSection/index.tsx index cc854b625ee7..b7a3a0135360 100644 --- a/src/components/CollapsibleSection/index.tsx +++ b/src/components/CollapsibleSection/index.tsx @@ -34,7 +34,7 @@ function CollapsibleSection({title, children}: CollapsibleSectionProps) { {currencySymbol} diff --git a/src/components/DatePicker/index.js b/src/components/DatePicker/index.js index 10a53dc25bbb..ac6454d25975 100644 --- a/src/components/DatePicker/index.js +++ b/src/components/DatePicker/index.js @@ -76,7 +76,7 @@ function DatePicker({containerStyles, defaultValue, disabled, errorText, inputID icon={Expensicons.Calendar} label={label} accessibilityLabel={label} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} value={value || selectedDate || ''} placeholder={placeholder || translate('common.dateFormat')} errorText={errorText} diff --git a/src/components/DeeplinkWrapper/index.website.js b/src/components/DeeplinkWrapper/index.website.js index 166818e4ae27..d81c99657dd8 100644 --- a/src/components/DeeplinkWrapper/index.website.js +++ b/src/components/DeeplinkWrapper/index.website.js @@ -16,6 +16,8 @@ const propTypes = { children: PropTypes.node.isRequired, /** User authentication status */ isAuthenticated: PropTypes.bool.isRequired, + /** The auto authentication status */ + autoAuthState: PropTypes.string, }; function isMacOSWeb() { @@ -36,7 +38,7 @@ function promptToOpenInDesktopApp() { App.beginDeepLinkRedirect(!isMagicLink); } } -function DeeplinkWrapper({children, isAuthenticated}) { +function DeeplinkWrapper({children, isAuthenticated, autoAuthState}) { const [currentScreen, setCurrentScreen] = useState(); const [hasShownPrompt, setHasShownPrompt] = useState(false); const removeListener = useRef(); @@ -69,7 +71,7 @@ function DeeplinkWrapper({children, isAuthenticated}) { return routeRegex.test(window.location.pathname); }); // Making a few checks to exit early before checking authentication status - if (!isMacOSWeb() || isUnsupportedDeeplinkRoute || CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV || hasShownPrompt) { + if (!isMacOSWeb() || isUnsupportedDeeplinkRoute || hasShownPrompt || CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV || autoAuthState === CONST.AUTO_AUTH_STATE.NOT_STARTED) { return; } // We want to show the prompt immediately if the user is already authenticated. @@ -92,7 +94,7 @@ function DeeplinkWrapper({children, isAuthenticated}) { promptToOpenInDesktopApp(); setHasShownPrompt(true); } - }, [currentScreen, hasShownPrompt, isAuthenticated]); + }, [currentScreen, hasShownPrompt, isAuthenticated, autoAuthState]); return children; } diff --git a/src/components/EmojiPicker/CategoryShortcutButton.js b/src/components/EmojiPicker/CategoryShortcutButton.js index 2a9fdff91fb7..e4f48fbb1cc0 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.js +++ b/src/components/EmojiPicker/CategoryShortcutButton.js @@ -42,7 +42,7 @@ function CategoryShortcutButton(props) { onHoverOut={() => setIsHighlighted(false)} style={({pressed}) => [StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.categoryShortcutButton, isHighlighted && styles.emojiItemHighlighted]} accessibilityLabel={`emojiPicker.headers.${props.code}`} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {({hovered, pressed}) => ( diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 95db6eb41167..263f5929d567 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -485,7 +485,7 @@ function EmojiPickerMenu(props) { 0} /> diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index ae2cdf46dfc0..52d4a0db8812 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -103,7 +103,7 @@ class EmojiPickerMenuItem extends PureComponent { this.props.themeStyles.emojiItem, ]} accessibilityLabel={this.props.emoji} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {this.props.emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js index 7934cc0f03d4..1726ff5b6543 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js @@ -80,7 +80,7 @@ class EmojiPickerMenuItem extends PureComponent { this.props.themeStyles.emojiItem, ]} accessibilityLabel={this.props.emoji} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {this.props.emoji} diff --git a/src/components/EmojiPicker/EmojiSkinToneList.js b/src/components/EmojiPicker/EmojiSkinToneList.js index 25fc9ad0836a..69690fa882c9 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.js +++ b/src/components/EmojiPicker/EmojiSkinToneList.js @@ -49,7 +49,7 @@ function EmojiSkinToneList(props) { onPress={toggleIsSkinToneListVisible} style={[styles.flexRow, styles.alignSelfCenter, styles.justifyContentStart, styles.alignItemsCenter]} accessibilityLabel={props.translate('emojiPicker.skinTonePickerLabel')} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {currentSkinTone.code} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 7cbdf8d69831..f5ecc106d629 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -77,7 +77,7 @@ function ImageRenderer(props) { ReportUtils.isArchivedRoom(report), ) } - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} > { onPressIn={props.onPressIn} onPressOut={props.onPressOut} onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} accessibilityLabel={props.translate('accessibilityHints.prestyledText')} > diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index e97dd5889abd..7e1412f1f90b 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -143,7 +143,7 @@ function HeaderWithBackButton({ Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID))))} + onPress={singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.GET_ASSISTANCE.getRoute(guidesCallTaskID, Navigation.getActiveRoute()))))} style={[styles.touchableButtonImage]} role="button" accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} @@ -170,7 +170,7 @@ function HeaderWithBackButton({ policy); - const cleanAllPolicyMembers = _.pick(props.allPolicyMembers, (policyMembers) => policyMembers); - - const paymentCardList = props.fundList || {}; - - // All of the error & info-checking methods are put into an array. This is so that using _.some() will return - // early as soon as the first error / info condition is returned. This makes the checks very efficient since - // we only care if a single error / info condition exists anywhere. - const errorCheckingMethods = [ - () => !_.isEmpty(props.userWallet.errors), - () => PaymentMethods.hasPaymentMethodError(props.bankAccountList, paymentCardList), - () => _.some(cleanPolicies, PolicyUtils.hasPolicyError), - () => _.some(cleanPolicies, PolicyUtils.hasCustomUnitsError), - () => _.some(cleanAllPolicyMembers, PolicyUtils.hasPolicyMemberError), - () => !_.isEmpty(props.reimbursementAccount.errors), - () => UserUtils.hasLoginListError(props.loginList), - - // Wallet term errors that are not caused by an IOU (we show the red brick indicator for those in the LHN instead) - () => !_.isEmpty(props.walletTerms.errors) && !props.walletTerms.chatReportID, - ]; - const infoCheckingMethods = [() => UserUtils.hasLoginListInfo(props.loginList)]; - const shouldShowErrorIndicator = _.some(errorCheckingMethods, (errorCheckingMethod) => errorCheckingMethod()); - const shouldShowInfoIndicator = !shouldShowErrorIndicator && _.some(infoCheckingMethods, (infoCheckingMethod) => infoCheckingMethod()); - - const indicatorColor = shouldShowErrorIndicator ? theme.danger : theme.success; - const indicatorStyles = [styles.alignItemsCenter, styles.justifyContentCenter, styles.statusIndicator(indicatorColor)]; - - return (shouldShowErrorIndicator || shouldShowInfoIndicator) && ; -} - -Indicator.defaultProps = defaultProps; -Indicator.propTypes = propTypes; -Indicator.displayName = 'Indicator'; - -export default withOnyx({ - allPolicyMembers: { - key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, - }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - bankAccountList: { - key: ONYXKEYS.BANK_ACCOUNT_LIST, - }, - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - fundList: { - key: ONYXKEYS.FUND_LIST, - }, - userWallet: { - key: ONYXKEYS.USER_WALLET, - }, - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - loginList: { - key: ONYXKEYS.LOGIN_LIST, - }, -})(Indicator); diff --git a/src/components/Indicator.tsx b/src/components/Indicator.tsx new file mode 100644 index 000000000000..5332c4bd984f --- /dev/null +++ b/src/components/Indicator.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import {StyleSheet, View} from 'react-native'; +import {OnyxCollection, withOnyx} from 'react-native-onyx'; +import {OnyxEntry} from 'react-native-onyx/lib/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as UserUtils from '@libs/UserUtils'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {BankAccountList, FundList, LoginList, Policy, PolicyMembers, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx'; + +type CheckingMethod = () => boolean; + +type IndicatorOnyxProps = { + /** The employee list of all policies (coming from Onyx) */ + allPolicyMembers: OnyxCollection; + + /** All the user's policies (from Onyx via withFullPolicy) */ + policies: OnyxCollection; + + /** List of bank accounts */ + bankAccountList: OnyxEntry; + + /** List of user cards */ + fundList: OnyxEntry; + + /** The user's wallet (coming from Onyx) */ + userWallet: OnyxEntry; + + /** Bank account attached to free plan */ + reimbursementAccount: OnyxEntry; + + /** Information about the user accepting the terms for payments */ + walletTerms: OnyxEntry; + + /** Login list for the user that is signed in */ + loginList: OnyxEntry; +}; + +type IndicatorProps = IndicatorOnyxProps; + +function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + + // If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and + // those should be cleaned out before doing any error checking + const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => !!policy)); + const cleanAllPolicyMembers = Object.fromEntries(Object.entries(allPolicyMembers ?? {}).filter(([, policyMembers]) => !!policyMembers)); + + // All of the error & info-checking methods are put into an array. This is so that using _.some() will return + // early as soon as the first error / info condition is returned. This makes the checks very efficient since + // we only care if a single error / info condition exists anywhere. + const errorCheckingMethods: CheckingMethod[] = [ + () => Object.keys(userWallet?.errors ?? {}).length > 0, + () => PaymentMethods.hasPaymentMethodError(bankAccountList, fundList), + () => Object.values(cleanPolicies).some(PolicyUtils.hasPolicyError), + () => Object.values(cleanPolicies).some(PolicyUtils.hasCustomUnitsError), + () => Object.values(cleanAllPolicyMembers).some(PolicyUtils.hasPolicyMemberError), + () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0, + () => !!loginList && UserUtils.hasLoginListError(loginList), + + // Wallet term errors that are not caused by an IOU (we show the red brick indicator for those in the LHN instead) + () => Object.keys(walletTerms?.errors ?? {}).length > 0 && !walletTerms?.chatReportID, + ]; + const infoCheckingMethods: CheckingMethod[] = [() => !!loginList && UserUtils.hasLoginListInfo(loginList)]; + const shouldShowErrorIndicator = errorCheckingMethods.some((errorCheckingMethod) => errorCheckingMethod()); + const shouldShowInfoIndicator = !shouldShowErrorIndicator && infoCheckingMethods.some((infoCheckingMethod) => infoCheckingMethod()); + + const indicatorColor = shouldShowErrorIndicator ? theme.danger : theme.success; + const indicatorStyles = [styles.alignItemsCenter, styles.justifyContentCenter, styles.statusIndicator(indicatorColor)]; + + return (shouldShowErrorIndicator || shouldShowInfoIndicator) && ; +} + +Indicator.displayName = 'Indicator'; + +export default withOnyx({ + allPolicyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + bankAccountList: { + key: ONYXKEYS.BANK_ACCOUNT_LIST, + }, + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, + fundList: { + key: ONYXKEYS.FUND_LIST, + }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, + loginList: { + key: ONYXKEYS.LOGIN_LIST, + }, +})(Indicator); diff --git a/src/components/InlineCodeBlock/WrappedText.js b/src/components/InlineCodeBlock/WrappedText.js deleted file mode 100644 index f00ec891116b..000000000000 --- a/src/components/InlineCodeBlock/WrappedText.js +++ /dev/null @@ -1,77 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {Fragment} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import Text from '@components/Text'; -import useThemeStyles from '@styles/useThemeStyles'; -import CONST from '@src/CONST'; - -/** - * Breaks the text into matrix - * for eg: My Name is Rajat - * [ - * [My,' ',Name,' ',' ',is,' ',Rajat], - * ] - * - * @param {String} text - * @returns {Array} - */ -function getTextMatrix(text) { - return _.map(text.split('\n'), (row) => _.without(row.split(CONST.REGEX.SPACE_OR_EMOJI), '')); -} - -const propTypes = { - /** Required text */ - children: PropTypes.string.isRequired, - - /** Style to be applied to Text */ - // eslint-disable-next-line react/forbid-prop-types - textStyles: PropTypes.arrayOf(PropTypes.object), - - /** Style for each word(Token) in the text, remember that token also includes whitespaces among words */ - // eslint-disable-next-line react/forbid-prop-types - wordStyles: PropTypes.arrayOf(PropTypes.object), -}; - -const defaultProps = { - textStyles: [], - wordStyles: [], -}; - -function WrappedText(props) { - const styles = useThemeStyles(); - if (!_.isString(props.children)) { - return null; - } - - const textMatrix = getTextMatrix(props.children); - return ( - <> - {_.map(textMatrix, (rowText, rowIndex) => ( - - {_.map(rowText, (colText, colIndex) => ( - // Outer View is important to vertically center the Text - - - {colText} - - - ))} - - ))} - - ); -} - -WrappedText.propTypes = propTypes; -WrappedText.defaultProps = defaultProps; -WrappedText.displayName = 'WrappedText'; - -export default WrappedText; diff --git a/src/components/InlineCodeBlock/WrappedText.tsx b/src/components/InlineCodeBlock/WrappedText.tsx new file mode 100644 index 000000000000..6dbd17f18e2a --- /dev/null +++ b/src/components/InlineCodeBlock/WrappedText.tsx @@ -0,0 +1,66 @@ +import React, {Fragment} from 'react'; +import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import Text from '@components/Text'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; + +type WrappedTextProps = ChildrenProps & { + /** Style to be applied to Text */ + textStyles?: StyleProp; + + /** + * Style for each individual word (token) in the text. Note that a token can also include whitespace characters between words. + */ + wordStyles?: StyleProp; +}; + +/** + * Breaks the text into matrix + * + * @example + * const text = "My Name is Rajat"; + * const resultMatrix = getTextMatrix(text); + * console.log(resultMatrix); + * // Output: + * // [ + * // ['My', ' ', 'Name', ' ', 'is', ' ', 'Rajat'], + * // ] + */ +function getTextMatrix(text: string): string[][] { + return text.split('\n').map((row) => row.split(CONST.REGEX.SPACE_OR_EMOJI).filter((value) => value !== '')); +} + +function WrappedText({children, wordStyles, textStyles}: WrappedTextProps) { + const styles = useThemeStyles(); + + if (typeof children !== 'string') { + return null; + } + + const textMatrix = getTextMatrix(children); + + return textMatrix.map((rowText, rowIndex) => ( + + {rowText.map((colText, colIndex) => ( + // Outer View is important to vertically center the Text + + + {colText} + + + ))} + + )); +} + +WrappedText.displayName = 'WrappedText'; + +export default WrappedText; diff --git a/src/components/InlineCodeBlock/index.js b/src/components/InlineCodeBlock/index.js deleted file mode 100644 index 84666931d9b2..000000000000 --- a/src/components/InlineCodeBlock/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import _ from 'lodash'; -import React from 'react'; -import Text from '@components/Text'; -import inlineCodeBlockPropTypes from './inlineCodeBlockPropTypes'; - -function InlineCodeBlock(props) { - const TDefaultRenderer = props.TDefaultRenderer; - const textStyles = _.omit(props.textStyle, 'textDecorationLine'); - - return ( - - {props.defaultRendererProps.tnode.data} - - ); -} - -InlineCodeBlock.propTypes = inlineCodeBlockPropTypes; -InlineCodeBlock.displayName = 'InlineCodeBlock'; -export default InlineCodeBlock; diff --git a/src/components/InlineCodeBlock/index.native.js b/src/components/InlineCodeBlock/index.native.tsx similarity index 50% rename from src/components/InlineCodeBlock/index.native.js rename to src/components/InlineCodeBlock/index.native.tsx index 983463222532..308b88e76e88 100644 --- a/src/components/InlineCodeBlock/index.native.js +++ b/src/components/InlineCodeBlock/index.native.tsx @@ -1,26 +1,27 @@ import React from 'react'; +import type {TText} from 'react-native-render-html'; import useThemeStyles from '@styles/useThemeStyles'; -import inlineCodeBlockPropTypes from './inlineCodeBlockPropTypes'; +import type InlineCodeBlockProps from './types'; import WrappedText from './WrappedText'; -function InlineCodeBlock(props) { +function InlineCodeBlock({TDefaultRenderer, defaultRendererProps, textStyle, boxModelStyle}: InlineCodeBlockProps) { const styles = useThemeStyles(); - const TDefaultRenderer = props.TDefaultRenderer; + return ( - {props.defaultRendererProps.tnode.data} + {defaultRendererProps.tnode.data} ); } -InlineCodeBlock.propTypes = inlineCodeBlockPropTypes; InlineCodeBlock.displayName = 'InlineCodeBlock'; + export default InlineCodeBlock; diff --git a/src/components/InlineCodeBlock/index.tsx b/src/components/InlineCodeBlock/index.tsx new file mode 100644 index 000000000000..0802d4752661 --- /dev/null +++ b/src/components/InlineCodeBlock/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {StyleSheet} from 'react-native'; +import type {TText} from 'react-native-render-html'; +import Text from '@components/Text'; +import type InlineCodeBlockProps from './types'; + +function InlineCodeBlock({TDefaultRenderer, textStyle, defaultRendererProps, boxModelStyle}: InlineCodeBlockProps) { + const flattenTextStyle = StyleSheet.flatten(textStyle); + const {textDecorationLine, ...textStyles} = flattenTextStyle; + + return ( + + {defaultRendererProps.tnode.data} + + ); +} + +InlineCodeBlock.displayName = 'InlineCodeBlock'; + +export default InlineCodeBlock; diff --git a/src/components/InlineCodeBlock/inlineCodeBlockPropTypes.js b/src/components/InlineCodeBlock/inlineCodeBlockPropTypes.js deleted file mode 100644 index e8430d17d849..000000000000 --- a/src/components/InlineCodeBlock/inlineCodeBlockPropTypes.js +++ /dev/null @@ -1,10 +0,0 @@ -import PropTypes from 'prop-types'; - -const inlineCodeBlockPropTypes = { - TDefaultRenderer: PropTypes.func.isRequired, - defaultRendererProps: PropTypes.object.isRequired, - boxModelStyle: PropTypes.any.isRequired, - textStyle: PropTypes.any.isRequired, -}; - -export default inlineCodeBlockPropTypes; diff --git a/src/components/InlineCodeBlock/types.ts b/src/components/InlineCodeBlock/types.ts new file mode 100644 index 000000000000..a100177e41a7 --- /dev/null +++ b/src/components/InlineCodeBlock/types.ts @@ -0,0 +1,11 @@ +import {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {TDefaultRenderer, TDefaultRendererProps, TText} from 'react-native-render-html'; + +type InlineCodeBlockProps = { + TDefaultRenderer: TDefaultRenderer; + textStyle: StyleProp; + defaultRendererProps: TDefaultRendererProps; + boxModelStyle: StyleProp; +}; + +export default InlineCodeBlockProps; diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index fa96542e877d..af29e1481f9c 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -208,7 +208,7 @@ function OptionRowLHN(props) { props.isFocused ? styles.sidebarLinkActive : null, (hovered || isContextMenuActive) && !props.isFocused ? props.hoverStyle || styles.sidebarLinkHover : null, ]} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2} > diff --git a/src/components/LocationErrorMessage/BaseLocationErrorMessage.js b/src/components/LocationErrorMessage/BaseLocationErrorMessage.js index a9472987a224..89ad869e9588 100644 --- a/src/components/LocationErrorMessage/BaseLocationErrorMessage.js +++ b/src/components/LocationErrorMessage/BaseLocationErrorMessage.js @@ -66,7 +66,7 @@ function BaseLocationErrorMessage({onClose, onAllowLocationLinkPress, locationEr onPress={onClose} onMouseDown={(e) => e.preventDefault()} style={[styles.touchableButtonImage]} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.close')} > { ]} disabled={props.disabled} ref={ref} - role={CONST.ACCESSIBILITY_ROLE.MENUITEM} + role={CONST.ROLE.MENUITEM} accessibilityLabel={props.title ? props.title.toString() : ''} > {({pressed}) => ( diff --git a/src/components/MessagesRow.tsx b/src/components/MessagesRow.tsx index 1b87b95601f3..2e016ca7d416 100644 --- a/src/components/MessagesRow.tsx +++ b/src/components/MessagesRow.tsx @@ -50,7 +50,7 @@ function MessagesRow({messages = {}, type, onClose = () => {}, containerStyles, onUserExit(), - }, - { - text: translate('common.settings'), - onPress: () => { - onUserExit(); - Linking.openSettings(); - }, - }, - ], - {cancelable: false}, - ); - return; - } + if (!_.isEmpty(errorMessage) && getPlatform() === CONST.PLATFORM.IOS) { + checkMultiple([PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.CAMERA]) + .then((statuses) => { + const isMicAllowed = statuses[PERMISSIONS.IOS.MICROPHONE] === RESULTS.GRANTED; + const isCameraAllowed = statuses[PERMISSIONS.IOS.CAMERA] === RESULTS.GRANTED; + let alertTitle = ''; + let alertMessage = ''; + if (!isCameraAllowed) { + alertTitle = 'onfidoStep.cameraPermissionsNotGranted'; + alertMessage = 'onfidoStep.cameraRequestMessage'; + } else if (!isMicAllowed) { + alertTitle = 'onfidoStep.microphonePermissionsNotGranted'; + alertMessage = 'onfidoStep.microphoneRequestMessage'; + } - onError(errorMessage); + if (!_.isEmpty(alertTitle) && !_.isEmpty(alertMessage)) { + Alert.alert( + translate(alertTitle), + translate(alertMessage), + [ + { + text: translate('common.cancel'), + onPress: () => onUserExit(), + }, + { + text: translate('common.settings'), + onPress: () => { + onUserExit(); + Linking.openSettings(); + }, + }, + ], + {cancelable: false}, + ); + return; + } + onError(errorMessage); + }) + .catch(() => { + onError(errorMessage); + }); + } else { + onError(errorMessage); + } }); // Onfido should be initialized only once on mount // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index eaf3ccc7ead2..24f109112679 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -194,7 +194,7 @@ function OptionRow(props) { !props.onSelectRow && !props.isDisabled ? styles.cursorDefault : null, ]} accessibilityLabel={props.option.text} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={!props.optionIsFocused ? props.hoverStyle || styles.sidebarLinkHover : undefined} needsOffscreenAlphaCompositing={lodashGet(props.option, 'icons.length', 0) >= 2} @@ -270,7 +270,7 @@ function OptionRow(props) { props.onSelectedStatePressed(props.option)} disabled={isDisabled} - role={CONST.ACCESSIBILITY_ROLE.CHECKBOX} + role={CONST.ROLE.CHECKBOX} accessibilityLabel={CONST.ACCESSIBILITY_ROLE.CHECKBOX} > diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 72bc5c032139..b054f9a1daea 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -471,7 +471,7 @@ class BaseOptionsSelector extends Component { value={this.props.value} label={this.props.textInputLabel} accessibilityLabel={this.props.textInputLabel} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} onChangeText={this.updateSearchValue} errorText={this.state.errorMessage} onSubmitEditing={this.selectFocusedOption} diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js index e20e2c44a76e..2c33d8d93a31 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.js @@ -123,7 +123,7 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat ref={textInputRef} label={translate('common.password')} accessibilityLabel={translate('common.password')} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} /** * This is a workaround to bypass Safari's autofill odd behaviour. * This tricks the browser not to fill the username somewhere else and still fill the password correctly. diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 50e737ba54d3..e18c52b06972 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -329,7 +329,7 @@ class PDFView extends Component { {this.renderPDFView()} diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 30d5e4c48d68..bfdb80131aa6 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -186,7 +186,7 @@ class PDFView extends Component { {this.renderPDFView()} diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index e65a8617a996..c12cefe6bead 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -31,7 +31,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportID Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(parentReportID)); }} accessibilityLabel={translate('threads.parentNavigationSummary', {rootReportName, workspaceName})} - role={CONST.ACCESSIBILITY_ROLE.LINK} + role={CONST.ROLE.LINK} style={pressableStyles} > {isChecked && ( diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.js index 994d467dfd6e..128eafd51ee8 100644 --- a/src/components/Reactions/AddReactionBubble.js +++ b/src/components/Reactions/AddReactionBubble.js @@ -100,7 +100,7 @@ function AddReactionBubble(props) { e.preventDefault(); }} accessibilityLabel={props.translate('emojiReactions.addReactionTooltip')} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} // disable dimming pressDimmingValue={1} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} diff --git a/src/components/Reactions/EmojiReactionBubble.js b/src/components/Reactions/EmojiReactionBubble.js index 7fcdae8c0a5a..7ec72468ee91 100644 --- a/src/components/Reactions/EmojiReactionBubble.js +++ b/src/components/Reactions/EmojiReactionBubble.js @@ -83,7 +83,7 @@ function EmojiReactionBubble(props) { // Prevent text input blur when emoji reaction is left clicked e.preventDefault(); }} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityLabel={props.emojiCodes.join('')} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 6fd322a24d3c..534a92dae830 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -94,7 +94,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio {receiptImageComponent} diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index c22181fec775..3b04364ee69a 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -108,7 +108,7 @@ function TaskPreview(props) { onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} style={[styles.flexRow, styles.justifyContentBetween]} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityLabel={props.translate('task.task')} > diff --git a/src/components/ReportHeaderSkeletonView.tsx b/src/components/ReportHeaderSkeletonView.tsx index 5ea83990431c..708cf3b930bb 100644 --- a/src/components/ReportHeaderSkeletonView.tsx +++ b/src/components/ReportHeaderSkeletonView.tsx @@ -30,7 +30,7 @@ function ReportHeaderSkeletonView({shouldAnimate = true, onBackButtonPress = () onSelectRow(item)} disabled={isDisabled} accessibilityLabel={item.text} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 79e38b304cdb..ce5d1945fd2a 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -152,7 +152,7 @@ function BaseSelectionList({ const [focusedIndex, setFocusedIndex] = useState(() => _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey)); // Disable `Enter` shortcut if the active element is a button or checkbox - const disableEnterShortcut = activeElement && [CONST.ACCESSIBILITY_ROLE.BUTTON, CONST.ACCESSIBILITY_ROLE.CHECKBOX].includes(activeElement.role); + const disableEnterShortcut = activeElement && [CONST.ROLE.BUTTON, CONST.ROLE.CHECKBOX].includes(activeElement.role); /** * Scrolls to the desired item index in the section list @@ -408,7 +408,7 @@ function BaseSelectionList({ }} label={textInputLabel} accessibilityLabel={textInputLabel} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} value={textInputValue} placeholder={textInputPlaceholder} maxLength={textInputMaxLength} diff --git a/src/components/SignInButtons/GoogleSignIn/index.website.js b/src/components/SignInButtons/GoogleSignIn/index.website.js index ac5018df8c15..7a909c5b1658 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.website.js +++ b/src/components/SignInButtons/GoogleSignIn/index.website.js @@ -76,7 +76,7 @@ function GoogleSignIn({translate, isDesktopFlow}) {
@@ -84,7 +84,7 @@ function GoogleSignIn({translate, isDesktopFlow}) {
diff --git a/src/components/SignInButtons/IconButton.js b/src/components/SignInButtons/IconButton.js index 706ceb2edc3f..ddfde61d6a07 100644 --- a/src/components/SignInButtons/IconButton.js +++ b/src/components/SignInButtons/IconButton.js @@ -38,7 +38,7 @@ function IconButton({onPress, translate, provider}) { onSelectOption(option)} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityState={{checked: selectedOptionKey === option.key}} aria-checked={selectedOptionKey === option.key} accessibilityLabel={option.label} diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx index 2398f531628d..5195c0040add 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -38,7 +38,7 @@ function Switch({isOn, onToggle, accessibilityLabel}: SwitchProps) { style={[styles.switchTrack, !isOn && styles.switchInactive]} onPress={() => onToggle(!isOn)} onLongPress={() => onToggle(!isOn)} - role={CONST.ACCESSIBILITY_ROLE.SWITCH} + role={CONST.ROLE.SWITCH} aria-checked={isOn} accessibilityLabel={accessibilityLabel} // disable hover dim for switch diff --git a/src/components/TextInput/TextInputLabel/index.js b/src/components/TextInput/TextInputLabel/index.js index 43dd1a0ef330..dbd6fa765503 100644 --- a/src/components/TextInput/TextInputLabel/index.js +++ b/src/components/TextInput/TextInputLabel/index.js @@ -19,7 +19,7 @@ function TextInputLabel({for: inputId, label, labelTranslateY, labelScale}) { return ( {label} diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index de145e63a9c7..2b4e3741368a 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -114,7 +114,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me }} ref={buttonRef} style={[styles.touchableButtonImage, ...iconStyles]} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityLabel={translate(iconTooltip)} > New Expensify.', + microphonePermissionsNotGranted: 'Enable microphone access', + microphoneRequestMessage: 'We need access to your microphone to complete bank account verification. Please enable via Settings > New Expensify.', originalDocumentNeeded: 'Please upload an original image of your ID rather than a screenshot or scanned image.', documentNeedsBetterQuality: 'Your ID appears to be damaged or has missing security features. Please upload an original image of an undamaged ID that is entirely visible.', imageNeedsBetterQuality: "There's an issue with the image quality of your ID. Please upload a new image where your entire ID can be seen clearly.", @@ -1971,31 +1973,30 @@ export default { buttonText1: 'Start a chat, ', buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`, header: `Start a chat, get $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `Get paid to talk to your friends! Start a chat with a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer.`, + body: `Get paid to talk to your friends! Start a chat with a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: { buttonText1: 'Request money, ', buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`, header: `Request money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `It pays to get paid! Request money from a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer.`, + body: `It pays to get paid! Request money from a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: { buttonText1: 'Send money, ', buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`, header: `Send money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `You gotta send money to make money! Send money to a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer.`, + body: `You gotta send money to make money! Send money to a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: { buttonText1: 'Invite a friend, ', buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`, header: `Invite a friend, get $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `Send your Expensify invite link to a friend or anyone else you know who spends too much time on expenses. When they start an annual subscription, you'll get $${CONST.REFERRAL_PROGRAM.REVENUE}.`, + body: 'Be the first to invite a friend (or anyone else) to Expensify and get $250 if they become an Expensify customer. Share your invite link by text, email, or post it on social media!', }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]: { buttonText1: `Get $${CONST.REFERRAL_PROGRAM.REVENUE}`, - header: `Get $${CONST.REFERRAL_PROGRAM.REVENUE} for every referral`, - body1: 'If you know anyone who’s spending too much time on expenses (literally anyone – your neighbor, your boss, your friend in accounting), send them your Expensify referral link:', - body2: `When they start an annual subscription, you’ll get $${CONST.REFERRAL_PROGRAM.REVENUE}. Easy as that.`, + header: `Invite a friend, get $${CONST.REFERRAL_PROGRAM.REVENUE}`, + body: `Be the first to invite a friend (or anyone else) to Expensify and get $${CONST.REFERRAL_PROGRAM.REVENUE} if they become an Expensify customer. Share your invite link by text, email, or post it on social media!`, }, copyReferralLink: 'Copy invite link', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 10401d4822f7..a91a8768a3ee 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1285,8 +1285,11 @@ export default { tryAgain: 'Intentar otra vez', verifyIdentity: 'Verificar identidad', genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.', - cameraPermissionsNotGranted: 'No has habilitado los permisos para acceder a la cámara', - cameraRequestMessage: 'No has habilitado los permisos para acceder a la cámara. Necesitamos acceso para completar la verificaciôn.', + cameraPermissionsNotGranted: 'Permiso para acceder a la cámara', + cameraRequestMessage: 'Necesitamos acceso a tu cámara para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > Nuevo Expensify.', + microphonePermissionsNotGranted: 'Permiso para acceder al micrófono', + microphoneRequestMessage: + 'Necesitamos acceso a tu micrófono para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > Nuevo Expensify.', originalDocumentNeeded: 'Por favor, sube una imagen original de tu identificación en lugar de una captura de pantalla o imagen escaneada.', documentNeedsBetterQuality: 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', @@ -2457,31 +2460,30 @@ export default { buttonText1: 'Inicia un chat y ', buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, header: `Inicia un chat y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `¡Gana dinero por hablar con tus amigos! Inicia un chat con una cuenta nueva de Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierten en clientes de Expensify.`, + body: `¡Gana dinero por hablar con tus amigos! Inicia un chat con una cuenta nueva de Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierten en clientes de Expensify.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: { buttonText1: 'Pide dinero, ', buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, header: `Pide dinero y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `¡Vale la pena cobrar! Pide dinero a una cuenta nueva de Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierten en clientes de Expensify.`, + body: `¡Vale la pena cobrar! Pide dinero a una cuenta nueva de Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierten en clientes de Expensify.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: { buttonText1: 'Envía dinero, ', buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, header: `Envía dinero y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `¡Hay que enviar dinero para ganar dinero! Envía dinero a una cuenta nueva de Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierten en clientes de Expensify.`, + body: `¡Hay que enviar dinero para ganar dinero! Envía dinero a una cuenta nueva de Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierten en clientes de Expensify.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: { buttonText1: 'Invita a un amigo y ', buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, - header: `Invita a un amigo y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, - body1: `Envía tu enlace de invitación de Expensify a un amigo o a cualquier otra persona que conozcas que dedique demasiado tiempo a los gastos. Cuando comiencen una suscripción anual, obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE}.`, + header: `Invita a un amigo y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE}`, + body: `Sé el primero en invitar a un amigo (o a cualquier otra persona) a Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierte en cliente de Expensify. Comparte tu enlace de invitación por SMS, email o publícalo en las redes sociales.`, }, [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]: { buttonText1: `Recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`, - header: `Recibe $${CONST.REFERRAL_PROGRAM.REVENUE} por cada recomendación`, - body1: 'Si conoces a alguien que dedique demasiado tiempo a los gastos (literalmente cualquiera: tu vecino, tu jefe, tu amigo de contabilidad), envíale tu enlace de invitación de Expensify:', - body2: `Cuando comiencen una suscripción anual, obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE}. Así de fácil.`, + header: `Invita a un amigo y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE}`, + body: `Sé el primero en invitar a un amigo (o a cualquier otra persona) a Expensify y obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} si se convierte en cliente de Expensify. Comparte tu enlace de invitación por SMS, email o publícalo en las redes sociales.`, }, copyReferralLink: 'Copiar enlace de invitación', }, diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index b0d426c9774a..9b2bd2616e8e 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -107,4 +107,23 @@ function findPhysicalCard(cards: Card[]) { return cards.find((card) => !card.isVirtual); } -export {isExpensifyCard, isCorporateCard, getDomainCards, getMonthFromExpirationDateString, getYearFromExpirationDateString, maskCard, getCardDescription, findPhysicalCard}; +/** + * Checks if any of the cards in the list have detected fraud + * + * @param cardList - collection of assigned cards + */ +function hasDetectedFraud(cardList: Record): boolean { + return Object.values(cardList).some((card) => card.fraud !== CONST.EXPENSIFY_CARD.FRAUD_TYPES.NONE); +} + +export { + isExpensifyCard, + isCorporateCard, + getDomainCards, + getMonthFromExpirationDateString, + getYearFromExpirationDateString, + maskCard, + getCardDescription, + findPhysicalCard, + hasDetectedFraud, +}; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 5e8a9f502dc5..008015db7a90 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -147,7 +147,7 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom const chatShortcutConfig = CONST.KEYBOARD_SHORTCUTS.NEW_CHAT; const currentUrl = getCurrentUrl(); const isLoggingInAsNewUser = !!session?.email && SessionUtils.isLoggingInAsNewUser(currentUrl, session.email); - const shouldGetAllData = !!isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession() || isLoggingInAsNewUser; + const shouldGetAllData = !!isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession(); // Sign out the current user if we're transitioning with a different user const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS); if (isLoggingInAsNewUser && isTransitioning) { diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx index 31eb818b60dc..09099cb493f6 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx @@ -27,14 +27,14 @@ function Overlay({onPress}: OverlayProps) { style={styles.draggableTopBar} onPress={onPress} accessibilityLabel={translate('common.close')} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} nativeID={CONST.OVERLAY.TOP_BUTTON_NATIVE_ID} /> diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index acbc7c8f1702..e9e76f4a2e82 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -7,7 +7,15 @@ import SCREENS from '@src/SCREENS'; import {RootStackParamList} from './types'; const linkingConfig: LinkingOptions = { - prefixes: ['new-expensify://', 'https://www.expensify.cash', 'https://staging.expensify.cash', 'https://dev.new.expensify.com', CONST.NEW_EXPENSIFY_URL, CONST.STAGING_NEW_EXPENSIFY_URL], + prefixes: [ + 'app://-/', + 'new-expensify://', + 'https://www.expensify.cash', + 'https://staging.expensify.cash', + 'https://dev.new.expensify.com', + CONST.NEW_EXPENSIFY_URL, + CONST.STAGING_NEW_EXPENSIFY_URL, + ], config: { initialRouteName: SCREENS.HOME, screens: { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ac7404689df2..d89d42e5775e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3695,8 +3695,8 @@ function getRouteFromLink(url: string | null): string { // Get the reportID from URL let route = url; + const localWebAndroidRegEx = /^(https:\/\/([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3}))/; linkingConfig.prefixes.forEach((prefix) => { - const localWebAndroidRegEx = /^(http:\/\/([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3}))/; if (route.startsWith(prefix)) { route = route.replace(prefix, ''); } else if (localWebAndroidRegEx.test(route)) { diff --git a/src/libs/SessionUtils.ts b/src/libs/SessionUtils.ts index 6cd20e0b56b2..1afa3a75e081 100644 --- a/src/libs/SessionUtils.ts +++ b/src/libs/SessionUtils.ts @@ -45,7 +45,7 @@ Onyx.connect({ }); function resetDidUserLogInDuringSession() { - loggedInDuringSession = undefined; + loggedInDuringSession = true; } function didUserLogInDuringSession() { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 07b61e677b38..adf184729001 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -15,6 +15,7 @@ import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as NumberUtils from '@libs/NumberUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import Permissions from '@libs/Permissions'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -25,6 +26,12 @@ import ROUTES from '@src/ROUTES'; import * as Policy from './Policy'; import * as Report from './Report'; +let betas; +Onyx.connect({ + key: ONYXKEYS.BETAS, + callback: (val) => (betas = val || []), +}); + let allPersonalDetails; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, @@ -54,6 +61,29 @@ Onyx.connect({ }, }); +let allTransactionDrafts = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + allTransactionDrafts = val || {}; + }, +}); + +let allTransactionViolations; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + waitForCollectionCallback: true, + callback: (val) => { + if (!val) { + allTransactionViolations = {}; + return; + } + + allTransactionViolations = val; + }, +}); + let allDraftSplitTransactions; Onyx.connect({ key: ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT, @@ -640,11 +670,12 @@ function getMoneyRequestInformation( // data. This is a big can of worms to change it to `Onyx.merge()` as explored in https://expensify.slack.com/archives/C05DWUDHVK7/p1692139468252109. // I want to clean this up at some point, but it's possible this will live in the code for a while so I've created https://github.com/Expensify/App/issues/25417 // to remind me to do this. - const existingTransaction = existingTransactionID && TransactionUtils.getTransaction(existingTransactionID); - if (existingTransaction) { + const existingTransaction = allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`]; + if (existingTransaction && existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE) { optimisticTransaction = { ...optimisticTransaction, ...existingTransaction, + transactionID: optimisticTransaction.transactionID, }; } @@ -2149,6 +2180,7 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView const chatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`]; const reportPreviewAction = ReportActionsUtils.getReportPreviewAction(iouReport.chatReportID, iouReport.reportID); const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const transactionThreadID = reportAction.childReportID; let transactionThread = null; if (transactionThreadID) { @@ -2229,6 +2261,15 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: null, }, + ...(Permissions.canUseViolations(betas) + ? [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: null, + }, + ] + : []), ...(shouldDeleteTransactionThread ? [ { @@ -2260,6 +2301,17 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView [reportPreviewAction.reportActionID]: updatedReportPreviewAction, }, }, + ...(!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0 + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + hasOutstandingChildRequest: false, + }, + }, + ] + : []), ...(shouldDeleteIOUReport ? [ { @@ -2292,6 +2344,15 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: transaction, }, + ...(Permissions.canUseViolations(betas) + ? [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, + value: transactionViolations, + }, + ] + : []), ...(shouldDeleteTransactionThread ? [ { @@ -2332,6 +2393,17 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView }, ] : []), + ...(!shouldDeleteIOUReport && updatedReportPreviewAction.childMoneyRequestCount === 0 + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + hasOutstandingChildRequest: true, + }, + }, + ] + : []), ]; // STEP 6: Make the API request diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index b3d1e3e23a24..3547a3053a02 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -1,12 +1,14 @@ import {createRef} from 'react'; import Onyx, {OnyxUpdate} from 'react-native-onyx'; +import {OnyxEntry} from 'react-native-onyx/lib/types'; import {ValueOf} from 'type-fest'; import * as API from '@libs/API'; import * as CardUtils from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; -import ONYXKEYS, {OnyxValues} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {BankAccountList, FundList} from '@src/types/onyx'; import PaymentMethod from '@src/types/onyx/PaymentMethod'; import {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer'; @@ -304,7 +306,7 @@ function dismissSuccessfulTransferBalancePage() { * Looks through each payment method to see if there is an existing error * */ -function hasPaymentMethodError(bankList: OnyxValues[typeof ONYXKEYS.BANK_ACCOUNT_LIST], fundList: OnyxValues[typeof ONYXKEYS.FUND_LIST]): boolean { +function hasPaymentMethodError(bankList: OnyxEntry, fundList: OnyxEntry): boolean { const combinedPaymentMethods = {...bankList, ...fundList}; return Object.values(combinedPaymentMethods).some((item) => Object.keys(item.errors ?? {}).length); diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 04f62ab0c393..f33e6637e2de 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -112,13 +112,6 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedTags = val), }); -let networkStatus = {}; -Onyx.connect({ - key: ONYXKEYS.NETWORK, - waitForCollectionCallback: true, - callback: (val) => (networkStatus = val), -}); - /** * Stores in Onyx the policy ID of the last workspace that was accessed by the user * @param {String|null} policyID @@ -957,7 +950,7 @@ function updateWorkspaceCustomUnitAndRate(policyID, currentCustomUnit, newCustom 'UpdateWorkspaceCustomUnitAndRate', { policyID, - ...(!networkStatus.isOffline && {lastModified}), + lastModified, customUnit: JSON.stringify(newCustomUnitParam), customUnitRate: JSON.stringify(newCustomUnitParam.rates), }, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 134b78627c61..bea4ab8aed77 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -19,6 +19,7 @@ import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import LocalNotification from '@libs/Notification/LocalNotification'; import {ReportCommentParams} from '@libs/Notification/LocalNotification/types'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as Pusher from '@libs/Pusher/pusher'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -2126,11 +2127,19 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record typeof accountID === 'number', ); + type PersonalDetailsOnyxData = { + optimisticData: OnyxUpdate[]; + successData: OnyxUpdate[]; + failureData: OnyxUpdate[]; + }; + + const logins = inviteeEmails.map((memberLogin) => OptionsListUtils.addSMSDomainIfPhoneNumber(memberLogin)); + const newPersonalDetailsOnyxData = PersonalDetailsUtils.getNewPersonalDetailsOnyxData(logins, inviteeAccountIDs) as PersonalDetailsOnyxData; + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2139,8 +2148,11 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record { if (!el) { return; diff --git a/src/pages/EditRequestMerchantPage.js b/src/pages/EditRequestMerchantPage.js index 53cb4946d640..af6ff89296f1 100644 --- a/src/pages/EditRequestMerchantPage.js +++ b/src/pages/EditRequestMerchantPage.js @@ -59,7 +59,7 @@ function EditRequestMerchantPage({defaultMerchant, onSubmit}) { defaultValue={defaultMerchant} label={translate('common.merchant')} accessibilityLabel={translate('common.merchant')} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} ref={(e) => (merchantInputRef.current = e)} /> diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index 6d01e601b901..9ed85447a3b3 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -193,7 +193,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP containerStyles={[styles.mt4]} label={translate(fieldNameTranslationKeys.legalFirstName)} accessibilityLabel={translate(fieldNameTranslationKeys.legalFirstName)} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} defaultValue={PersonalDetails.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName} shouldSaveDraft /> @@ -203,7 +203,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP containerStyles={[styles.mt4]} label={translate(fieldNameTranslationKeys.legalLastName)} accessibilityLabel={translate(fieldNameTranslationKeys.legalLastName)} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} defaultValue={PersonalDetails.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).lastName} shouldSaveDraft /> @@ -225,7 +225,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP inputMode={CONST.INPUT_MODE.TEL} label={translate(fieldNameTranslationKeys.phoneNumber)} accessibilityLabel={translate(fieldNameTranslationKeys.phoneNumber)} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} defaultValue={currentUserPersonalDetails.phoneNumber} placeholder={translate('common.phoneNumberPlaceholder')} shouldSaveDraft @@ -245,7 +245,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP containerStyles={[styles.mt4]} label={translate(fieldNameTranslationKeys[shouldAskForFullSSN ? 'ssnFull9' : 'ssn'])} accessibilityLabel={translate(fieldNameTranslationKeys[shouldAskForFullSSN ? 'ssnFull9' : 'ssn'])} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} maxLength={shouldAskForFullSSN ? 9 : 4} inputMode={CONST.INPUT_MODE.NUMERIC} /> diff --git a/src/pages/GetAssistancePage.js b/src/pages/GetAssistancePage.js index 0cb0bbd36dc4..0ea213f2e0e1 100644 --- a/src/pages/GetAssistancePage.js +++ b/src/pages/GetAssistancePage.js @@ -45,6 +45,7 @@ const defaultProps = { function GetAssistancePage(props) { const styles = useThemeStyles(); + const navigateBackTo = lodashGet(props.route, 'params.backTo', ROUTES.SETTINGS_CONTACT_METHODS); const menuItems = [ { title: props.translate('getAssistancePage.chatWithConcierge'), @@ -83,7 +84,7 @@ function GetAssistancePage(props) { Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} + onBackButtonPress={() => Navigation.goBack(navigateBackTo)} />
- Navigation.goBack(getFallbackRoute())} - /> - + - {contentHeader} - {contentBody} - {shouldShowClipboard && ( - - - - )} - {shouldShowBody2 && ( - - {translate(`referralProgram.${CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE}.body2`)} - - )} - {translate('requestorStep.learnMore')} - - -