From 2da64c96f217b7090765a57161a224ed5d7b7644 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 5 Aug 2023 12:52:15 +0200 Subject: [PATCH 001/170] add ts type for theme --- src/styles/themes/ThemeColors.ts | 85 ++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/styles/themes/ThemeColors.ts diff --git a/src/styles/themes/ThemeColors.ts b/src/styles/themes/ThemeColors.ts new file mode 100644 index 000000000000..19fcf76819b0 --- /dev/null +++ b/src/styles/themes/ThemeColors.ts @@ -0,0 +1,85 @@ +type HexColor = `#${string}`; +type Color = HexColor | `rgba(${number}, ${number}, ${number})` | `rgba(${number}, ${number}, ${number}, ${number})` | 'transparent'; + +type ThemeColors = { + appBG: Color; + splashBG: Color; + highlightBG: Color; + border: Color; + borderLighter: Color; + borderFocus: Color; + icon: Color; + iconMenu: Color; + iconHovered: Color; + iconSuccessFill: Color; + iconReversed: Color; + iconColorfulBackground: Color; + textSupporting: Color; + text: Color; + textColorfulBackground: Color; + link: Color; + linkHover: Color; + buttonDefaultBG: Color; + buttonHoveredBG: Color; + buttonPressedBG: Color; + danger: Color; + dangerHover: Color; + dangerPressed: Color; + warning: Color; + success: Color; + successHover: Color; + successPressed: Color; + transparent: Color; + signInPage: Color; + + // Additional keys + overlay: Color; + inverse: Color; + shadow: Color; + componentBG: Color; + hoverComponentBG: Color; + activeComponentBG: Color; + signInSidebar: Color; + sidebar: Color; + sidebarHover: Color; + heading: Color; + textLight: Color; + textDark: Color; + textReversed: Color; + textBackground: Color; + textMutedReversed: Color; + textError: Color; + offline: Color; + modalBackdrop: Color; + modalBackground: Color; + cardBG: Color; + cardBorder: Color; + spinner: Color; + unreadIndicator: Color; + placeholderText: Color; + heroCard: Color; + uploadPreviewActivityIndicator: Color; + dropUIBG: Color; + receiptDropUIBG: Color; + checkBox: Color; + pickerOptionsTextColor: Color; + imageCropBackgroundColor: Color; + fallbackIconColor: Color; + reactionActiveBackground: Color; + reactionActiveText: Color; + badgeAdHoc: Color; + badgeAdHocHover: Color; + mentionText: Color; + mentionBG: Color; + ourMentionText: Color; + ourMentionBG: Color; + tooltipSupportingText: Color; + tooltipPrimaryText: Color; + skeletonLHNIn: Color; + skeletonLHNOut: Color; + QRLogo: Color; + + PAGE_BACKGROUND_COLORS: Record; +}; + +export {type HexColor, type Color, type ThemeColors}; From 899d38e7e6d8c72bda02919a9d0d795c7ded3f44 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 5 Aug 2023 12:52:36 +0200 Subject: [PATCH 002/170] adapt light and dark theme to ts type --- src/styles/themes/{default.js => dark.ts} | 17 +++++++++-------- src/styles/themes/{light.js => light.ts} | 8 ++++---- 2 files changed, 13 insertions(+), 12 deletions(-) rename src/styles/themes/{default.js => dark.ts} (88%) rename src/styles/themes/{light.js => light.ts} (95%) diff --git a/src/styles/themes/default.js b/src/styles/themes/dark.ts similarity index 88% rename from src/styles/themes/default.js rename to src/styles/themes/dark.ts index 8347a7b74cc8..c816693d41e5 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/dark.ts @@ -1,8 +1,9 @@ /* eslint-disable no-unused-vars */ import colors from '../colors'; import SCREENS from '../../SCREENS'; +import {HexColor, ThemeColors} from './ThemeColors'; -const darkTheme = { +const darkTheme: ThemeColors = { // Figma keys appBG: colors.darkAppBackground, splashBG: colors.green400, @@ -15,7 +16,7 @@ const darkTheme = { iconHovered: colors.darkPrimaryText, iconSuccessFill: colors.green400, iconReversed: colors.darkAppBackground, - iconColorfulBackground: `${colors.ivory}cc`, + iconColorfulBackground: `${colors.ivory as HexColor}cc`, textSupporting: colors.darkSupportingText, text: colors.darkPrimaryText, textColorfulBackground: colors.ivory, @@ -61,7 +62,7 @@ const darkTheme = { placeholderText: colors.darkIcons, heroCard: colors.blue400, uploadPreviewActivityIndicator: colors.darkHighlightBackground, - dropUIBG: 'rgba(6,27,9,0.92)', + dropUIBG: 'rgba(6, 27, 9, 0.92)', receiptDropUIBG: 'rgba(3, 212, 124, 0.84)', checkBox: colors.green400, pickerOptionsTextColor: colors.darkPrimaryText, @@ -80,12 +81,12 @@ const darkTheme = { skeletonLHNIn: colors.darkBorders, skeletonLHNOut: colors.darkDefaultButton, QRLogo: colors.green400, -}; -darkTheme.PAGE_BACKGROUND_COLORS = { - [SCREENS.HOME]: darkTheme.sidebar, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + PAGE_BACKGROUND_COLORS: { + [SCREENS.HOME]: colors.darkHighlightBackground, + [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + }, }; export default darkTheme; diff --git a/src/styles/themes/light.js b/src/styles/themes/light.ts similarity index 95% rename from src/styles/themes/light.js rename to src/styles/themes/light.ts index 82717f35f124..c320c14ac6b8 100644 --- a/src/styles/themes/light.js +++ b/src/styles/themes/light.ts @@ -79,11 +79,11 @@ const lightTheme = { skeletonLHNIn: colors.lightBorders, skeletonLHNOut: colors.lightDefaultButtonPressed, QRLogo: colors.green400, -}; -lightTheme.PAGE_BACKGROUND_COLORS = { - [SCREENS.HOME]: lightTheme.sidebar, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + PAGE_BACKGROUND_COLORS: { + [SCREENS.HOME]: colors.lightHighlightBackground, + [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + }, }; export default lightTheme; From e089833582ba9dbca8b50f99fe175b4769ec0981 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 5 Aug 2023 12:53:10 +0200 Subject: [PATCH 003/170] use light theme in ThemeProvider --- src/styles/themes/ThemeContext.js | 4 ++-- src/styles/themes/ThemeProvider.js | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/styles/themes/ThemeContext.js b/src/styles/themes/ThemeContext.js index 30d476c22d9c..e75c48bcab1e 100644 --- a/src/styles/themes/ThemeContext.js +++ b/src/styles/themes/ThemeContext.js @@ -1,6 +1,6 @@ import React from 'react'; -import defaultColors from './default'; +import darkTheme from './dark'; -const ThemeContext = React.createContext(defaultColors); +const ThemeContext = React.createContext(darkTheme); export default ThemeContext; diff --git a/src/styles/themes/ThemeProvider.js b/src/styles/themes/ThemeProvider.js index f4601712a0bc..2dd92e8be729 100644 --- a/src/styles/themes/ThemeProvider.js +++ b/src/styles/themes/ThemeProvider.js @@ -4,9 +4,8 @@ import PropTypes from 'prop-types'; import ThemeContext from './ThemeContext'; import useThemePreference from './useThemePreference'; import CONST from '../../CONST'; - -// Going to eventually import the light theme here too -import darkTheme from './default'; +import darkTheme from './dark'; +import lightTheme from './light'; const propTypes = { /** Rendered child component */ @@ -16,7 +15,7 @@ const propTypes = { function ThemeProvider(props) { const themePreference = useThemePreference(); - const theme = useMemo(() => (themePreference === CONST.THEME.LIGHT ? /* TODO: replace with light theme */ darkTheme : darkTheme), [themePreference]); + const theme = useMemo(() => (themePreference === CONST.THEME.LIGHT ? lightTheme : darkTheme), [themePreference]); return {props.children}; } From b32f538a84fe6b4a9101d39cb05ff274f5c2d623 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 5 Aug 2023 12:53:40 +0200 Subject: [PATCH 004/170] adapt colors to new ts type --- src/styles/{colors.js => colors.ts} | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename src/styles/{colors.js => colors.ts} (95%) diff --git a/src/styles/colors.js b/src/styles/colors.ts similarity index 95% rename from src/styles/colors.js rename to src/styles/colors.ts index 9ac3226a1b80..b17f7fb4cb96 100644 --- a/src/styles/colors.js +++ b/src/styles/colors.ts @@ -1,7 +1,9 @@ +import {Color} from './themes/ThemeColors'; + /** * DO NOT import colors.js into files. Use ../themes/default.js instead. */ -export default { +const colors: Record = { black: '#000000', white: '#FFFFFF', ivory: '#fffaf0', @@ -91,3 +93,5 @@ export default { ice700: '#28736D', ice800: '#134038', }; + +export default colors; From 21787ba652659da893e3d2b3267aaf986bfc86e3 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 5 Aug 2023 12:53:54 +0200 Subject: [PATCH 005/170] change usage of "default.js" to "dark.js" --- src/components/AddPlaidBankAccount.js | 2 +- src/components/AddressSearch/index.js | 2 +- src/components/AttachmentCarousel/index.js | 2 +- src/components/AttachmentModal.js | 2 +- src/components/AttachmentView.js | 2 +- src/components/Avatar.js | 2 +- src/components/AvatarCropModal/AvatarCropModal.js | 2 +- src/components/AvatarWithDisplayName.js | 2 +- src/components/AvatarWithImagePicker.js | 2 +- src/components/BlockingViews/BlockingView.js | 2 +- src/components/Button/index.js | 2 +- src/components/ButtonWithDropdownMenu.js | 2 +- src/components/Checkbox.js | 2 +- src/components/Composer/index.android.js | 2 +- src/components/Composer/index.ios.js | 2 +- src/components/Composer/index.js | 2 +- src/components/CurrentUserPersonalDetailsSkeletonView/index.js | 2 +- src/components/CustomStatusBar/index.js | 2 +- src/components/DatePicker/index.ios.js | 2 +- src/components/EmojiPicker/CategoryShortcutButton.js | 2 +- src/components/ExpensifyWordmark.js | 2 +- src/components/FloatingActionButton.js | 2 +- src/components/FullscreenLoadingIndicator.js | 2 +- src/components/GrowlNotification/index.js | 2 +- .../HTMLEngineProvider/HTMLRenderers/EditedRenderer.js | 2 +- src/components/Icon/index.js | 2 +- src/components/IllustratedHeaderPageLayout.js | 2 +- src/components/Indicator.js | 2 +- src/components/InlineSystemMessage.js | 2 +- src/components/LHNOptionsList/OptionRowLHN.js | 2 +- src/components/LocalePicker.js | 2 +- src/components/MenuItem.js | 2 +- src/components/Modal/BaseModal.js | 2 +- src/components/Modal/index.web.js | 2 +- src/components/MoneyRequestConfirmationList.js | 2 +- src/components/MoneyRequestDetails.js | 2 +- src/components/MultipleAvatars.js | 2 +- src/components/Onfido/BaseOnfidoWeb.js | 2 +- src/components/OptionRow.js | 2 +- src/components/OptionsListSkeletonView.js | 2 +- src/components/Picker/BasePicker.js | 2 +- src/components/PinButton.js | 2 +- src/components/QRCode/index.js | 2 +- src/components/QRShare/index.js | 2 +- src/components/ReportActionItem/IOUPreview.js | 2 +- src/components/ReportActionItem/ReportPreview.js | 2 +- src/components/ReportActionsSkeletonView/SkeletonViewLines.js | 2 +- src/components/ReportHeaderSkeletonView.js | 2 +- src/components/RoomHeaderAvatars.js | 2 +- src/components/SelectCircle.js | 2 +- src/components/SelectionListRadio/RadioListItem.js | 2 +- src/components/SubscriptAvatar.js | 2 +- src/components/TabSelector/TabSelectorItem.js | 2 +- src/components/Text.js | 2 +- src/components/TextInput/BaseTextInput.js | 2 +- .../VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js | 2 +- .../createResponsiveStackNavigator/ThreePaneView.js | 2 +- src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js | 2 +- src/libs/Navigation/NavigationRoot.js | 2 +- src/pages/ErrorPage/GenericErrorPage.js | 2 +- src/pages/home/report/FloatingMessageCounter/index.js | 2 +- src/pages/home/report/LinkPreviewer.js | 2 +- src/pages/home/report/ReportActionCompose.js | 2 +- src/pages/home/report/ReportActionItemFragment.js | 2 +- src/pages/home/report/ReportActionItemMessageEdit.js | 2 +- src/pages/home/sidebar/SidebarLinks.js | 2 +- src/pages/home/sidebar/SidebarScreen/index.js | 2 +- src/pages/iou/IOUCurrencySelection.js | 2 +- src/pages/iou/ReceiptSelector/index.native.js | 2 +- src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js | 2 +- src/pages/settings/Preferences/PreferencesPage.js | 2 +- src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +- .../Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- src/pages/settings/Report/NotificationPreferencePage.js | 2 +- src/pages/settings/Report/WriteCapabilityPage.js | 2 +- src/pages/settings/Security/TwoFactorAuth/CodesPage.js | 2 +- src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js | 2 +- src/pages/signin/SignInPageLayout/Footer.js | 2 +- src/pages/signin/SignInPageLayout/index.js | 2 +- src/pages/signin/Socials.js | 2 +- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- src/pages/workspace/WorkspacesListPage.js | 2 +- src/pages/workspace/reimburse/WorkspaceReimburseSection.js | 2 +- src/stories/Composer.stories.js | 2 +- src/stories/PopoverMenu.stories.js | 2 +- src/styles/StyleUtils.js | 2 +- src/styles/addOutlineWidth/index.js | 2 +- src/styles/getModalStyles/getBaseModalStyles.js | 2 +- src/styles/getReportActionContextMenuStyles.js | 2 +- src/styles/getTooltipStyles.js | 2 +- src/styles/styles.js | 2 +- 91 files changed, 91 insertions(+), 91 deletions(-) diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index ff97c9be24a6..d1d8c42abffa 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -9,7 +9,7 @@ import PlaidLink from './PlaidLink'; import * as BankAccounts from '../libs/actions/BankAccounts'; import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Picker from './Picker'; diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index e8a41ec35435..a29fd4002077 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -6,7 +6,7 @@ import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete' import lodashGet from 'lodash/get'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import TextInput from '../TextInput'; import * as ApiUtils from '../../libs/ApiUtils'; import * as GooglePlacesUtils from '../../libs/GooglePlacesUtils'; diff --git a/src/components/AttachmentCarousel/index.js b/src/components/AttachmentCarousel/index.js index 3f2524a2992e..b15c8a0b7c7e 100644 --- a/src/components/AttachmentCarousel/index.js +++ b/src/components/AttachmentCarousel/index.js @@ -4,7 +4,7 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import * as Expensicons from '../Icon/Expensicons'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import CarouselActions from './CarouselActions'; import Button from '../Button'; import AttachmentView from '../AttachmentView'; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 1b87799d4f5b..1e768d2eefd3 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -12,7 +12,7 @@ import AttachmentCarousel from './AttachmentCarousel'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as FileUtils from '../libs/fileDownload/FileUtils'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import compose from '../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Button from './Button'; diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js index d880ac9b9076..7cf77c08e5e2 100755 --- a/src/components/AttachmentView.js +++ b/src/components/AttachmentView.js @@ -12,7 +12,7 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize'; import compose from '../libs/compose'; import Text from './Text'; import Tooltip from './Tooltip'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import variables from '../styles/variables'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 8dcf7f08c1d1..f4502ddb6a03 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 99262bf12938..f0e7b4ed74be 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -6,7 +6,7 @@ import {runOnUI, interpolate, useAnimatedGestureHandler, useSharedValue, useWork import CONST from '../../CONST'; import compose from '../../libs/compose'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import Button from '../Button'; import HeaderWithBackButton from '../HeaderWithBackButton'; import Icon from '../Icon'; diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 0f1300ebf03d..52ae459bca7a 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -8,7 +8,7 @@ import participantPropTypes from './participantPropTypes'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import SubscriptAvatar from './SubscriptAvatar'; import * as ReportUtils from '../libs/ReportUtils'; import MultipleAvatars from './MultipleAvatars'; diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index fcbfe4f4c4c4..8452afcc0616 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -8,7 +8,7 @@ import Icon from './Icon'; import PopoverMenu from './PopoverMenu'; import * as Expensicons from './Icon/Expensicons'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import AttachmentPicker from './AttachmentPicker'; import AvatarCropModal from './AvatarCropModal/AvatarCropModal'; import OfflineWithFeedback from './OfflineWithFeedback'; diff --git a/src/components/BlockingViews/BlockingView.js b/src/components/BlockingViews/BlockingView.js index d02fa55a6434..a4af9754bbe5 100644 --- a/src/components/BlockingViews/BlockingView.js +++ b/src/components/BlockingViews/BlockingView.js @@ -5,7 +5,7 @@ import styles from '../../styles/styles'; import variables from '../../styles/variables'; import Icon from '../Icon'; import Text from '../Text'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import TextLink from '../TextLink'; import Navigation from '../../libs/Navigation/Navigation'; import AutoEmailLink from '../AutoEmailLink'; diff --git a/src/components/Button/index.js b/src/components/Button/index.js index a850a43d2fb0..03e98b56d5c6 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import Text from '../Text'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; import Icon from '../Icon'; diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js index 1396ab601330..eb1729a45614 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.js @@ -8,7 +8,7 @@ import Button from './Button'; import PopoverMenu from './PopoverMenu'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import CONST from '../CONST'; const propTypes = { diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js index 86b6e05d5ed7..6150e5134e19 100644 --- a/src/components/Checkbox.js +++ b/src/components/Checkbox.js @@ -2,7 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index d0805cbcc7c3..c252d3dcfa62 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -3,7 +3,7 @@ import {StyleSheet} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import RNTextInput from '../RNTextInput'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import * as ComposerUtils from '../../libs/ComposerUtils'; const propTypes = { diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index c0a3859e6d01..a0e372403310 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -3,7 +3,7 @@ import {StyleSheet} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import RNTextInput from '../RNTextInput'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import * as ComposerUtils from '../../libs/ComposerUtils'; const propTypes = { diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index d32246529b6c..4b8144745119 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -6,7 +6,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import RNTextInput from '../RNTextInput'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import Growl from '../../libs/Growl'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import updateIsFullComposerAvailable from '../../libs/ComposerUtils/updateIsFullComposerAvailable'; import * as ComposerUtils from '../../libs/ComposerUtils'; import * as Browser from '../../libs/Browser'; diff --git a/src/components/CurrentUserPersonalDetailsSkeletonView/index.js b/src/components/CurrentUserPersonalDetailsSkeletonView/index.js index 6e6c46e971c0..8e63be848e91 100644 --- a/src/components/CurrentUserPersonalDetailsSkeletonView/index.js +++ b/src/components/CurrentUserPersonalDetailsSkeletonView/index.js @@ -5,7 +5,7 @@ import {Circle, Rect} from 'react-native-svg'; import {View} from 'react-native'; import * as StyleUtils from '../../styles/StyleUtils'; import CONST from '../../CONST'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import variables from '../../styles/variables'; import styles from '../../styles/styles'; diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index 76752cb549e1..4f77691046bd 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import StatusBar from '../../libs/StatusBar'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; function CustomStatusBar() { useEffect(() => { diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 5d87636a9365..410376a387dd 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -10,7 +10,7 @@ import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import Popover from '../Popover'; import CONST from '../../CONST'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import {propTypes, defaultProps} from './datepickerPropTypes'; import withKeyboardState, {keyboardStatePropTypes} from '../withKeyboardState'; diff --git a/src/components/EmojiPicker/CategoryShortcutButton.js b/src/components/EmojiPicker/CategoryShortcutButton.js index a7658ae0542d..ac7f1991ec6a 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.js +++ b/src/components/EmojiPicker/CategoryShortcutButton.js @@ -7,7 +7,7 @@ import variables from '../../styles/variables'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; import getButtonState from '../../libs/getButtonState'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import CONST from '../../CONST'; diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index dde792e87e22..25ff571a7c01 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -8,7 +8,7 @@ import StagingLogo from '../../assets/images/expensify-logo--staging.svg'; import AdHocLogo from '../../assets/images/expensify-logo--adhoc.svg'; import CONST from '../CONST'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import variables from '../styles/variables'; diff --git a/src/components/FloatingActionButton.js b/src/components/FloatingActionButton.js index 706bad59f7b7..4a2db6e2bcfb 100644 --- a/src/components/FloatingActionButton.js +++ b/src/components/FloatingActionButton.js @@ -5,7 +5,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import Tooltip from './Tooltip'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; diff --git a/src/components/FullscreenLoadingIndicator.js b/src/components/FullscreenLoadingIndicator.js index 5c212b6dc29e..96c1246f33c5 100644 --- a/src/components/FullscreenLoadingIndicator.js +++ b/src/components/FullscreenLoadingIndicator.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React from 'react'; import {ActivityIndicator, StyleSheet, View} from 'react-native'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import stylePropTypes from '../styles/stylePropTypes'; const propTypes = { diff --git a/src/components/GrowlNotification/index.js b/src/components/GrowlNotification/index.js index 70cadd5efd8e..750063847ead 100644 --- a/src/components/GrowlNotification/index.js +++ b/src/components/GrowlNotification/index.js @@ -1,7 +1,7 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {Directions, FlingGestureHandler, State} from 'react-native-gesture-handler'; import {View, Animated} from 'react-native'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import Text from '../Text'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js index d91510c3ec6a..3f5ff9a72dc2 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -4,7 +4,7 @@ import htmlRendererPropTypes from './htmlRendererPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; import Text from '../../Text'; import variables from '../../../styles/variables'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import styles from '../../../styles/styles'; import editedLabelStyles from '../../../styles/editedLabelStyles'; diff --git a/src/components/Icon/index.js b/src/components/Icon/index.js index 8c6559451215..044baee944b3 100644 --- a/src/components/Icon/index.js +++ b/src/components/Icon/index.js @@ -2,7 +2,7 @@ import React, {PureComponent} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import variables from '../../styles/variables'; import * as StyleUtils from '../../styles/StyleUtils'; import IconWrapperStyles from './IconWrapperStyles'; diff --git a/src/components/IllustratedHeaderPageLayout.js b/src/components/IllustratedHeaderPageLayout.js index 7fc340426d69..d1563c500671 100644 --- a/src/components/IllustratedHeaderPageLayout.js +++ b/src/components/IllustratedHeaderPageLayout.js @@ -7,7 +7,7 @@ import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBack import HeaderWithBackButton from './HeaderWithBackButton'; import ScreenWrapper from './ScreenWrapper'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import * as StyleUtils from '../styles/StyleUtils'; import useWindowDimensions from '../hooks/useWindowDimensions'; import FixedFooter from './FixedFooter'; diff --git a/src/components/Indicator.js b/src/components/Indicator.js index 765d79e156af..cd66de22f57d 100644 --- a/src/components/Indicator.js +++ b/src/components/Indicator.js @@ -15,7 +15,7 @@ import * as PolicyUtils from '../libs/PolicyUtils'; import * as PaymentMethods from '../libs/actions/PaymentMethods'; import * as ReimbursementAccountProps from '../pages/ReimbursementAccount/reimbursementAccountPropTypes'; import * as UserUtils from '../libs/UserUtils'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; const propTypes = { /* Onyx Props */ diff --git a/src/components/InlineSystemMessage.js b/src/components/InlineSystemMessage.js index a6866fb5a887..ea21c5f65352 100644 --- a/src/components/InlineSystemMessage.js +++ b/src/components/InlineSystemMessage.js @@ -2,7 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; -import theme from '../styles/themes/default'; +import theme from '../styles/themes/dark'; import Text from './Text'; import * as Expensicons from './Icon/Expensicons'; import Icon from './Icon'; diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index e17cf71e5d06..51045cd8876f 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -14,7 +14,7 @@ import colors from '../../styles/colors'; import Text from '../Text'; import SubscriptAvatar from '../SubscriptAvatar'; import CONST from '../../CONST'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import OfflineWithFeedback from '../OfflineWithFeedback'; import PressableWithSecondaryInteraction from '../PressableWithSecondaryInteraction'; import * as ReportActionContextMenu from '../../pages/home/report/ContextMenu/ReportActionContextMenu'; diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js index 532c29535e50..af191080a4ca 100644 --- a/src/components/LocalePicker.js +++ b/src/components/LocalePicker.js @@ -9,7 +9,7 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import Picker from './Picker'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; const propTypes = { /** Indicates which locale the user currently has selected */ diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index c280a75a8ef3..b470b11171f8 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -3,7 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import Text from './Text'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import * as StyleUtils from '../styles/StyleUtils'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index 6d5bd5390416..a9116faa3460 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -5,7 +5,7 @@ import ReactNativeModal from 'react-native-modal'; import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './modalPropTypes'; import * as Modal from '../../libs/actions/Modal'; import getModalStyles from '../../styles/getModalStyles'; diff --git a/src/components/Modal/index.web.js b/src/components/Modal/index.web.js index 065b3a9f210f..8fc5a4ee22fe 100644 --- a/src/components/Modal/index.web.js +++ b/src/components/Modal/index.web.js @@ -3,7 +3,7 @@ import withWindowDimensions from '../withWindowDimensions'; import BaseModal from './BaseModal'; import {propTypes, defaultProps} from './modalPropTypes'; import * as StyleUtils from '../../styles/StyleUtils'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import StatusBar from '../../libs/StatusBar'; import CONST from '../../CONST'; diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index e421dae4e37e..76620c7a0863 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -23,7 +23,7 @@ import optionPropTypes from './optionPropTypes'; import * as CurrencyUtils from '../libs/CurrencyUtils'; import Button from './Button'; import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import Image from './Image'; import ReceiptHTML from '../../assets/images/receipt-html.png'; import ReceiptDoc from '../../assets/images/receipt-doc.png'; diff --git a/src/components/MoneyRequestDetails.js b/src/components/MoneyRequestDetails.js index a690c31c000c..fe6d7afdfe7a 100644 --- a/src/components/MoneyRequestDetails.js +++ b/src/components/MoneyRequestDetails.js @@ -11,7 +11,7 @@ import Text from './Text'; import participantPropTypes from './participantPropTypes'; import Avatar from './Avatar'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import CONST from '../CONST'; import withWindowDimensions from './withWindowDimensions'; import compose from '../libs/compose'; diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index ceb1c5371c7e..c89613c90e19 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -6,7 +6,7 @@ import styles from '../styles/styles'; import Avatar from './Avatar'; import Tooltip from './Tooltip'; import Text from './Text'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import * as StyleUtils from '../styles/StyleUtils'; import CONST from '../CONST'; import variables from '../styles/variables'; diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.js index 394996331d5e..54fb2abba2bf 100644 --- a/src/components/Onfido/BaseOnfidoWeb.js +++ b/src/components/Onfido/BaseOnfidoWeb.js @@ -6,7 +6,7 @@ import * as OnfidoSDK from 'onfido-sdk-ui'; import onfidoPropTypes from './onfidoPropTypes'; import CONST from '../../CONST'; import variables from '../../styles/variables'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import fontWeightBold from '../../styles/fontWeight/bold'; import fontFamily from '../../styles/fontFamily'; import Log from '../../libs/Log'; diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index adaa4457bbd9..d341710945d7 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -11,7 +11,7 @@ import * as Expensicons from './Icon/Expensicons'; import MultipleAvatars from './MultipleAvatars'; import Hoverable from './Hoverable'; import DisplayNames from './DisplayNames'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Text from './Text'; import SelectCircle from './SelectCircle'; diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 15c66affe84d..1cf6a97f8c97 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import {Rect, Circle} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../CONST'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import styles from '../styles/styles'; const propTypes = { diff --git a/src/components/Picker/BasePicker.js b/src/components/Picker/BasePicker.js index 173b863edfcc..c64aad3d0c1a 100644 --- a/src/components/Picker/BasePicker.js +++ b/src/components/Picker/BasePicker.js @@ -8,7 +8,7 @@ import * as Expensicons from '../Icon/Expensicons'; import FormHelpMessage from '../FormHelpMessage'; import Text from '../Text'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import {ScrollContext} from '../ScrollViewWithContext'; const propTypes = { diff --git a/src/components/PinButton.js b/src/components/PinButton.js index 84ad6e22f50b..2074996cfb7b 100644 --- a/src/components/PinButton.js +++ b/src/components/PinButton.js @@ -1,6 +1,6 @@ import React from 'react'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import Icon from './Icon'; import Tooltip from './Tooltip'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; diff --git a/src/components/QRCode/index.js b/src/components/QRCode/index.js index f27cf28066ef..7bc0829d399d 100644 --- a/src/components/QRCode/index.js +++ b/src/components/QRCode/index.js @@ -1,7 +1,7 @@ import React from 'react'; import QRCodeLibrary from 'react-native-qrcode-svg'; import PropTypes from 'prop-types'; -import defaultTheme from '../../styles/themes/default'; +import defaultTheme from '../../styles/themes/dark'; import CONST from '../../CONST'; const propTypes = { diff --git a/src/components/QRShare/index.js b/src/components/QRShare/index.js index d96024ad1046..580151554d63 100644 --- a/src/components/QRShare/index.js +++ b/src/components/QRShare/index.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; -import defaultTheme from '../../styles/themes/default'; +import defaultTheme from '../../styles/themes/dark'; import styles from '../../styles/styles'; import Text from '../Text'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/IOUPreview.js index 85a0b22ac327..fe207476627b 100644 --- a/src/components/ReportActionItem/IOUPreview.js +++ b/src/components/ReportActionItem/IOUPreview.js @@ -10,7 +10,7 @@ import ONYXKEYS from '../../ONYXKEYS'; import MultipleAvatars from '../MultipleAvatars'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import * as Report from '../../libs/actions/Report'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import Icon from '../Icon'; import CONST from '../../CONST'; import * as Expensicons from '../Icon/Expensicons'; diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index d5d85df5e7ee..89b7616d081a 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -23,7 +23,7 @@ import SettlementButton from '../SettlementButton'; import * as IOU from '../../libs/actions/IOU'; import refPropTypes from '../refPropTypes'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import reportPropTypes from '../../pages/reportPropTypes'; const propTypes = { diff --git a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js index ddaa46e0b731..f8dcaf4de34c 100644 --- a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js +++ b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {Rect, Circle} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../../CONST'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import styles from '../../styles/styles'; const propTypes = { diff --git a/src/components/ReportHeaderSkeletonView.js b/src/components/ReportHeaderSkeletonView.js index 5f2d5379419d..c6e712417577 100644 --- a/src/components/ReportHeaderSkeletonView.js +++ b/src/components/ReportHeaderSkeletonView.js @@ -8,7 +8,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import variables from '../styles/variables'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js index 6f78e6ace66d..cb18973e287c 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.js @@ -6,7 +6,7 @@ import styles from '../styles/styles'; import Text from './Text'; import CONST from '../CONST'; import Avatar from './Avatar'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import * as StyleUtils from '../styles/StyleUtils'; import avatarPropTypes from './avatarPropTypes'; diff --git a/src/components/SelectCircle.js b/src/components/SelectCircle.js index 93cf285eab59..60e449479d11 100644 --- a/src/components/SelectCircle.js +++ b/src/components/SelectCircle.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import styles from '../styles/styles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; const propTypes = { /** Should we show the checkmark inside the circle */ diff --git a/src/components/SelectionListRadio/RadioListItem.js b/src/components/SelectionListRadio/RadioListItem.js index c5c4b3aeaf2c..41928c50e05d 100644 --- a/src/components/SelectionListRadio/RadioListItem.js +++ b/src/components/SelectionListRadio/RadioListItem.js @@ -6,7 +6,7 @@ import styles from '../../styles/styles'; import Text from '../Text'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import {radioListItemPropTypes} from './selectionListRadioPropTypes'; const propTypes = { diff --git a/src/components/SubscriptAvatar.js b/src/components/SubscriptAvatar.js index 05202e720bd4..5ca496025f1f 100644 --- a/src/components/SubscriptAvatar.js +++ b/src/components/SubscriptAvatar.js @@ -4,7 +4,7 @@ import {View} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import Avatar from './Avatar'; import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; diff --git a/src/components/TabSelector/TabSelectorItem.js b/src/components/TabSelector/TabSelectorItem.js index cea59bc2ee65..f96d9f2d621e 100644 --- a/src/components/TabSelector/TabSelectorItem.js +++ b/src/components/TabSelector/TabSelectorItem.js @@ -2,7 +2,7 @@ import {Text} from 'react-native'; import React from 'react'; import PropTypes from 'prop-types'; import Icon from '../Icon'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import styles from '../../styles/styles'; import PressableWithFeedback from '../Pressable/PressableWithFeedback'; diff --git a/src/components/Text.js b/src/components/Text.js index 83b6be8fffb0..3c44183cd17b 100644 --- a/src/components/Text.js +++ b/src/components/Text.js @@ -4,7 +4,7 @@ import _ from 'underscore'; // eslint-disable-next-line no-restricted-imports import {Text as RNText} from 'react-native'; import fontFamily from '../styles/fontFamily'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import variables from '../styles/variables'; const propTypes = { diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 68c09e3a7f82..b075672d492e 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -5,7 +5,7 @@ import Str from 'expensify-common/lib/str'; import RNTextInput from '../RNTextInput'; import TextInputLabel from './TextInputLabel'; import * as baseTextInputPropTypes from './baseTextInputPropTypes'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import styles from '../../styles/styles'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; diff --git a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js index 4c0e2b551382..23ad19f53d37 100755 --- a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js +++ b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js @@ -10,7 +10,7 @@ import ZoomIcon from '../../../assets/images/zoom-icon.svg'; import GoogleMeetIcon from '../../../assets/images/google-meet.svg'; import CONST from '../../CONST'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import compose from '../../libs/compose'; diff --git a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js index 2f9a899191bf..bc6ba2cfb3cd 100644 --- a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js +++ b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import SCREENS from '../../../../SCREENS'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; import NAVIGATORS from '../../../../NAVIGATORS'; import * as StyleUtils from '../../../../styles/StyleUtils'; import {withNavigationPropTypes} from '../../../../components/withNavigation'; diff --git a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js index d2de1ba23a01..63aca125dc27 100644 --- a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js +++ b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js @@ -1,7 +1,7 @@ import {Animated} from 'react-native'; import variables from '../../../styles/variables'; import getCardStyles from '../../../styles/cardStyles'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; export default (isSmallScreenWidth, isFullScreenModal, {current: {progress}, inverted, layouts: {screen}}) => { const translateX = Animated.multiply( diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index 23c320eb991c..c22c459b54ab 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -6,7 +6,7 @@ import {useSharedValue, useAnimatedReaction, interpolateColor, withTiming, withD import Navigation, {navigationRef} from './Navigation'; import linkingConfig from './linkingConfig'; import AppNavigator from './AppNavigator'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import Log from '../Log'; import StatusBar from '../StatusBar'; import useCurrentReportID from '../../hooks/useCurrentReportID'; diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.js index 3ff3bc686419..05dddf273547 100644 --- a/src/pages/ErrorPage/GenericErrorPage.js +++ b/src/pages/ErrorPage/GenericErrorPage.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import Icon from '../../components/Icon'; -import defaultTheme from '../../styles/themes/default'; +import defaultTheme from '../../styles/themes/dark'; import * as Expensicons from '../../components/Icon/Expensicons'; import Text from '../../components/Text'; import Button from '../../components/Button'; diff --git a/src/pages/home/report/FloatingMessageCounter/index.js b/src/pages/home/report/FloatingMessageCounter/index.js index 73fe02df129b..4178502a1368 100644 --- a/src/pages/home/report/FloatingMessageCounter/index.js +++ b/src/pages/home/report/FloatingMessageCounter/index.js @@ -6,7 +6,7 @@ import Button from '../../../../components/Button'; import Text from '../../../../components/Text'; import Icon from '../../../../components/Icon'; import * as Expensicons from '../../../../components/Icon/Expensicons'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; import useLocalize from '../../../../hooks/useLocalize'; import FloatingMessageCounterContainer from './FloatingMessageCounterContainer'; diff --git a/src/pages/home/report/LinkPreviewer.js b/src/pages/home/report/LinkPreviewer.js index 4fcbb0dc0569..3859299ea1a7 100644 --- a/src/pages/home/report/LinkPreviewer.js +++ b/src/pages/home/report/LinkPreviewer.js @@ -8,7 +8,7 @@ import TextLink from '../../../components/TextLink'; import * as StyleUtils from '../../../styles/StyleUtils'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; const IMAGE_TYPES = ['jpg', 'jpeg', 'png']; const MAX_IMAGE_HEIGHT = 180; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 1146e3b382f5..e8d26edb40e5 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -7,7 +7,7 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import Composer from '../../../components/Composer'; import ONYXKEYS from '../../../ONYXKEYS'; import Icon from '../../../components/Icon'; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 009c1118400b..6863e75ab894 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -5,7 +5,7 @@ import Str from 'expensify-common/lib/str'; import reportActionFragmentPropTypes from './reportActionFragmentPropTypes'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import RenderHTML from '../../../components/RenderHTML'; import Text from '../../../components/Text'; import * as EmojiUtils from '../../../libs/EmojiUtils'; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 54c5fec4533e..5c7e71d0a877 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -7,7 +7,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import reportActionPropTypes from './reportActionPropTypes'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import * as StyleUtils from '../../../styles/StyleUtils'; import containerComposeStyles from '../../../styles/containerComposeStyles'; import Composer from '../../../components/Composer'; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 132b767a7f70..dc917b1f6c6a 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -24,7 +24,7 @@ import LHNOptionsList from '../../../components/LHNOptionsList/LHNOptionsList'; import SidebarUtils from '../../../libs/SidebarUtils'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import Header from '../../../components/Header'; -import defaultTheme from '../../../styles/themes/default'; +import defaultTheme from '../../../styles/themes/dark'; import OptionsListSkeletonView from '../../../components/OptionsListSkeletonView'; import variables from '../../../styles/variables'; import LogoComponent from '../../../../assets/images/expensify-wordmark.svg'; diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 705d9d1e2d08..20f7e7098f1c 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -7,7 +7,7 @@ import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import FreezeWrapper from '../../../../libs/Navigation/FreezeWrapper'; import withWindowDimensions from '../../../../components/withWindowDimensions'; import StatusBar from '../../../../libs/StatusBar'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; function SidebarScreen(props) { const popoverModal = useRef(null); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 44a7fba5d487..b46919127fb3 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -15,7 +15,7 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import {withNetwork} from '../../components/OnyxProvider'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import ROUTES from '../../ROUTES'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import * as Expensicons from '../../components/Icon/Expensicons'; import reportPropTypes from '../reportPropTypes'; import * as ReportUtils from '../../libs/ReportUtils'; diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 7eeab6e493bd..06f692fa1b33 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -13,7 +13,7 @@ import styles from '../../../styles/styles'; import Shutter from '../../../../assets/images/shutter.svg'; import Hand from '../../../../assets/images/hand.svg'; import * as IOU from '../../../libs/actions/IOU'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import reportPropTypes from '../../reportPropTypes'; import CONST from '../../../CONST'; import Button from '../../../components/Button'; diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js index 346738574da3..c2690e27dd55 100644 --- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js @@ -28,7 +28,7 @@ import * as PaymentUtils from '../../../../libs/PaymentUtils'; import OfflineWithFeedback from '../../../../components/OfflineWithFeedback'; import ConfirmContent from '../../../../components/ConfirmContent'; import Button from '../../../../components/Button'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; import variables from '../../../../styles/variables'; import useLocalize from '../../../../hooks/useLocalize'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index b8bb74295567..e91fcdacaf0e 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -7,7 +7,7 @@ import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import Text from '../../../components/Text'; import CONST from '../../../CONST'; import * as User from '../../../libs/actions/User'; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js index 2289e68e7bd1..dd6f5cf90c4a 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js @@ -21,7 +21,7 @@ import ConfirmModal from '../../../../components/ConfirmModal'; import * as User from '../../../../libs/actions/User'; import CONST from '../../../../CONST'; import * as ErrorUtils from '../../../../libs/ErrorUtils'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; import NotFoundPage from '../../../ErrorPage/NotFoundPage'; import ValidateCodeForm from './ValidateCodeForm'; import ROUTES from '../../../../ROUTES'; diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index ea81413fcbb5..6cf47d37e3af 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -20,7 +20,7 @@ import shouldDelayFocus from '../../../../../libs/shouldDelayFocus'; import Text from '../../../../../components/Text'; import {withNetwork} from '../../../../../components/OnyxProvider'; import PressableWithFeedback from '../../../../../components/Pressable/PressableWithFeedback'; -import themeColors from '../../../../../styles/themes/default'; +import themeColors from '../../../../../styles/themes/dark'; import * as StyleUtils from '../../../../../styles/StyleUtils'; import CONST from '../../../../../CONST'; diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js index 9765cf1ae0b4..6b625f3e13b9 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.js +++ b/src/pages/settings/Report/NotificationPreferencePage.js @@ -14,7 +14,7 @@ import ROUTES from '../../../ROUTES'; import * as Report from '../../../libs/actions/Report'; import * as ReportUtils from '../../../libs/ReportUtils'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; const propTypes = { ...withLocalizePropTypes, diff --git a/src/pages/settings/Report/WriteCapabilityPage.js b/src/pages/settings/Report/WriteCapabilityPage.js index 59ad90a2cd1f..5e1d1192824b 100644 --- a/src/pages/settings/Report/WriteCapabilityPage.js +++ b/src/pages/settings/Report/WriteCapabilityPage.js @@ -15,7 +15,7 @@ import reportPropTypes from '../../reportPropTypes'; import ROUTES from '../../../ROUTES'; import * as Report from '../../../libs/actions/Report'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import * as ReportUtils from '../../../libs/ReportUtils'; import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView'; import * as PolicyUtils from '../../../libs/PolicyUtils'; diff --git a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js index 6780080ff382..8e2b0cff5b69 100644 --- a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js +++ b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js @@ -22,7 +22,7 @@ import Text from '../../../../components/Text'; import Section from '../../../../components/Section'; import ONYXKEYS from '../../../../ONYXKEYS'; import Clipboard from '../../../../libs/Clipboard'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; import localFileDownload from '../../../../libs/localFileDownload'; import * as TwoFactorAuthActions from '../../../../libs/actions/TwoFactorAuthActions'; diff --git a/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js b/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js index 4d49edac1fe4..b5bc98005170 100644 --- a/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js +++ b/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js @@ -9,7 +9,7 @@ import ROUTES from '../../../../ROUTES'; import Section from '../../../../components/Section'; import * as Illustrations from '../../../../components/Icon/Illustrations'; import * as Expensicons from '../../../../components/Icon/Expensicons'; -import themeColors from '../../../../styles/themes/default'; +import themeColors from '../../../../styles/themes/dark'; import styles from '../../../../styles/styles'; import ConfirmModal from '../../../../components/ConfirmModal'; import FullPageOfflineBlockingView from '../../../../components/BlockingViews/FullPageOfflineBlockingView'; diff --git a/src/pages/signin/SignInPageLayout/Footer.js b/src/pages/signin/SignInPageLayout/Footer.js index 35e63b0699b3..0cb5a8dd45b3 100644 --- a/src/pages/signin/SignInPageLayout/Footer.js +++ b/src/pages/signin/SignInPageLayout/Footer.js @@ -5,7 +5,7 @@ import _ from 'underscore'; import Text from '../../../components/Text'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import variables from '../../../styles/variables'; import * as Expensicons from '../../../components/Icon/Expensicons'; import TextLink from '../../../components/TextLink'; diff --git a/src/pages/signin/SignInPageLayout/index.js b/src/pages/signin/SignInPageLayout/index.js index 7acd08a6c693..9f73b828c5e9 100644 --- a/src/pages/signin/SignInPageLayout/index.js +++ b/src/pages/signin/SignInPageLayout/index.js @@ -11,7 +11,7 @@ import styles from '../../../styles/styles'; import SignInPageHero from '../SignInPageHero'; import * as StyleUtils from '../../../styles/StyleUtils'; import scrollViewContentContainerStyles from './signInPageStyles'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import BackgroundImage from './BackgroundImage'; import SignInGradient from '../../../../assets/images/home-fade-gradient.svg'; import variables from '../../../styles/variables'; diff --git a/src/pages/signin/Socials.js b/src/pages/signin/Socials.js index f7a866d2023b..be6305cf1bc6 100644 --- a/src/pages/signin/Socials.js +++ b/src/pages/signin/Socials.js @@ -5,7 +5,7 @@ import * as Link from '../../libs/actions/Link'; import Icon from '../../components/Icon'; import PressableWithoutFeedback from '../../components/Pressable/PressableWithoutFeedback'; import * as Expensicons from '../../components/Icon/Expensicons'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import styles from '../../styles/styles'; import variables from '../../styles/variables'; import CONST from '../../CONST'; diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index a68f99df6d24..e0ccdca636ab 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -7,7 +7,7 @@ import lodashGet from 'lodash/get'; import styles from '../../../styles/styles'; import Button from '../../../components/Button'; import Text from '../../../components/Text'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import * as Session from '../../../libs/actions/Session'; import ONYXKEYS from '../../../ONYXKEYS'; import CONST from '../../../CONST'; diff --git a/src/pages/workspace/WorkspacesListPage.js b/src/pages/workspace/WorkspacesListPage.js index 98ef04836f76..6073836c6e9a 100755 --- a/src/pages/workspace/WorkspacesListPage.js +++ b/src/pages/workspace/WorkspacesListPage.js @@ -10,7 +10,7 @@ import styles from '../../styles/styles'; import compose from '../../libs/compose'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import * as Expensicons from '../../components/Icon/Expensicons'; -import themeColors from '../../styles/themes/default'; +import themeColors from '../../styles/themes/dark'; import * as PolicyUtils from '../../libs/PolicyUtils'; import MenuItem from '../../components/MenuItem'; import * as Policy from '../../libs/actions/Policy'; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js index eb8305f23140..8f48fc216bfd 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js @@ -5,7 +5,7 @@ import lodashGet from 'lodash/get'; import _ from 'underscore'; import Text from '../../../components/Text'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/default'; +import themeColors from '../../../styles/themes/dark'; import * as Expensicons from '../../../components/Icon/Expensicons'; import * as Illustrations from '../../../components/Icon/Illustrations'; import Section from '../../../components/Section'; diff --git a/src/stories/Composer.stories.js b/src/stories/Composer.stories.js index 3dfc5b0e3ead..632901df582f 100644 --- a/src/stories/Composer.stories.js +++ b/src/stories/Composer.stories.js @@ -5,7 +5,7 @@ import Composer from '../components/Composer'; import RenderHTML from '../components/RenderHTML'; import Text from '../components/Text'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; import * as StyleUtils from '../styles/StyleUtils'; import CONST from '../CONST'; diff --git a/src/stories/PopoverMenu.stories.js b/src/stories/PopoverMenu.stories.js index 1098fa9ce226..198c7ef28e83 100644 --- a/src/stories/PopoverMenu.stories.js +++ b/src/stories/PopoverMenu.stories.js @@ -3,7 +3,7 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'; import PopoverMenu from '../components/PopoverMenu'; import * as Expensicons from '../components/Icon/Expensicons'; import MenuItem from '../components/MenuItem'; -import themeColors from '../styles/themes/default'; +import themeColors from '../styles/themes/dark'; /** * We use the Component Story Format for writing stories. Follow the docs here: diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index f8db7272e93f..676796751dc3 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import CONST from '../CONST'; import fontFamily from './fontFamily'; -import themeColors from './themes/default'; +import themeColors from './themes/dark'; import variables from './variables'; import colors from './colors'; import positioning from './utilities/positioning'; diff --git a/src/styles/addOutlineWidth/index.js b/src/styles/addOutlineWidth/index.js index 2a2657b24910..cdb7dce7f926 100644 --- a/src/styles/addOutlineWidth/index.js +++ b/src/styles/addOutlineWidth/index.js @@ -3,7 +3,7 @@ * can be added to the object */ -import themeDefault from '../themes/default'; +import themeDefault from '../themes/dark'; /** * Adds the addOutlineWidth property to an object to be used when styling diff --git a/src/styles/getModalStyles/getBaseModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js index b7a3317963ca..7daf944225c5 100644 --- a/src/styles/getModalStyles/getBaseModalStyles.js +++ b/src/styles/getModalStyles/getBaseModalStyles.js @@ -1,6 +1,6 @@ import CONST from '../../CONST'; import variables from '../variables'; -import themeColors from '../themes/default'; +import themeColors from '../themes/dark'; import styles from '../styles'; const getCenteredModalStyles = (windowWidth, isSmallScreenWidth, isFullScreenWhenSmall = false) => ({ diff --git a/src/styles/getReportActionContextMenuStyles.js b/src/styles/getReportActionContextMenuStyles.js index 026306084ce4..b8084abad976 100644 --- a/src/styles/getReportActionContextMenuStyles.js +++ b/src/styles/getReportActionContextMenuStyles.js @@ -1,6 +1,6 @@ import styles from './styles'; import variables from './variables'; -import themeColors from './themes/default'; +import themeColors from './themes/dark'; const defaultWrapperStyle = { backgroundColor: themeColors.componentBG, diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index bc5fcfe807aa..58ec3f200b2d 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -1,7 +1,7 @@ import spacing from './utilities/spacing'; import styles from './styles'; import colors from './colors'; -import themeColors from './themes/default'; +import themeColors from './themes/dark'; import fontFamily from './fontFamily'; import variables from './variables'; import roundToNearestMultipleOfFour from './roundToNearestMultipleOfFour'; diff --git a/src/styles/styles.js b/src/styles/styles.js index 835082ba98c9..fe47cc70c66a 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2,7 +2,7 @@ import {defaultStyles as defaultPickerStyles} from 'react-native-picker-select/s import lodashClamp from 'lodash/clamp'; import fontFamily from './fontFamily'; import addOutlineWidth from './addOutlineWidth'; -import themeColors from './themes/default'; +import themeColors from './themes/dark'; import fontWeightBold from './fontWeight/bold'; import variables from './variables'; import spacing from './utilities/spacing'; From 0e02b13875976b5bc64bc69da4cc6a302408f5a1 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 16 Sep 2023 11:33:56 +0200 Subject: [PATCH 006/170] fix: rename back dark.ts --- src/components/AddPlaidBankAccount.js | 2 +- src/components/AddressSearch/index.js | 2 +- src/components/AttachmentCarousel/index.js | 2 +- src/components/AttachmentModal.js | 2 +- src/components/AttachmentView.js | 2 +- src/components/Avatar.js | 2 +- src/components/AvatarCropModal/AvatarCropModal.js | 2 +- src/components/AvatarWithDisplayName.js | 2 +- src/components/AvatarWithImagePicker.js | 2 +- src/components/BlockingViews/BlockingView.js | 2 +- src/components/Button/index.js | 2 +- src/components/ButtonWithDropdownMenu.js | 2 +- src/components/Checkbox.js | 2 +- src/components/Composer/index.android.js | 2 +- src/components/Composer/index.ios.js | 2 +- src/components/Composer/index.js | 2 +- src/components/CurrentUserPersonalDetailsSkeletonView/index.js | 2 +- src/components/CustomStatusBar/index.js | 2 +- src/components/DatePicker/index.ios.js | 2 +- src/components/EmojiPicker/CategoryShortcutButton.js | 2 +- src/components/ExpensifyWordmark.js | 2 +- src/components/FloatingActionButton.js | 2 +- src/components/FullscreenLoadingIndicator.js | 2 +- src/components/GrowlNotification/index.js | 2 +- .../HTMLEngineProvider/HTMLRenderers/EditedRenderer.js | 2 +- src/components/Icon/index.js | 2 +- src/components/IllustratedHeaderPageLayout.js | 2 +- src/components/Indicator.js | 2 +- src/components/InlineSystemMessage.js | 2 +- src/components/LHNOptionsList/OptionRowLHN.js | 2 +- src/components/LocalePicker.js | 2 +- src/components/MenuItem.js | 2 +- src/components/Modal/BaseModal.js | 2 +- src/components/Modal/index.web.js | 2 +- src/components/MoneyRequestConfirmationList.js | 2 +- src/components/MoneyRequestDetails.js | 2 +- src/components/MultipleAvatars.js | 2 +- src/components/Onfido/BaseOnfidoWeb.js | 2 +- src/components/OptionRow.js | 2 +- src/components/OptionsListSkeletonView.js | 2 +- src/components/Picker/BasePicker.js | 2 +- src/components/PinButton.js | 2 +- src/components/QRCode/index.js | 2 +- src/components/QRShare/index.js | 2 +- src/components/ReportActionItem/IOUPreview.js | 2 +- src/components/ReportActionItem/ReportPreview.js | 2 +- src/components/ReportActionsSkeletonView/SkeletonViewLines.js | 2 +- src/components/ReportHeaderSkeletonView.js | 2 +- src/components/RoomHeaderAvatars.js | 2 +- src/components/SelectCircle.js | 2 +- src/components/SelectionListRadio/RadioListItem.js | 2 +- src/components/SubscriptAvatar.js | 2 +- src/components/TabSelector/TabSelectorItem.js | 2 +- src/components/Text.js | 2 +- src/components/TextInput/BaseTextInput.js | 2 +- .../VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js | 2 +- .../createResponsiveStackNavigator/ThreePaneView.js | 2 +- src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js | 2 +- src/libs/Navigation/NavigationRoot.js | 2 +- src/pages/ErrorPage/GenericErrorPage.js | 2 +- src/pages/home/report/FloatingMessageCounter/index.js | 2 +- src/pages/home/report/LinkPreviewer.js | 2 +- src/pages/home/report/ReportActionCompose.js | 2 +- src/pages/home/report/ReportActionItemFragment.js | 2 +- src/pages/home/report/ReportActionItemMessageEdit.js | 2 +- src/pages/home/sidebar/SidebarLinks.js | 2 +- src/pages/home/sidebar/SidebarScreen/index.js | 2 +- src/pages/iou/IOUCurrencySelection.js | 2 +- src/pages/iou/ReceiptSelector/index.native.js | 2 +- src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js | 2 +- src/pages/settings/Preferences/PreferencesPage.js | 2 +- src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +- .../Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- src/pages/settings/Report/NotificationPreferencePage.js | 2 +- src/pages/settings/Report/WriteCapabilityPage.js | 2 +- src/pages/settings/Security/TwoFactorAuth/CodesPage.js | 2 +- src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js | 2 +- src/pages/signin/SignInPageLayout/Footer.js | 2 +- src/pages/signin/SignInPageLayout/index.js | 2 +- src/pages/signin/Socials.js | 2 +- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- src/pages/workspace/WorkspacesListPage.js | 2 +- src/pages/workspace/reimburse/WorkspaceReimburseSection.js | 2 +- src/stories/Composer.stories.js | 2 +- src/stories/PopoverMenu.stories.js | 2 +- src/styles/StyleUtils.js | 2 +- src/styles/addOutlineWidth/index.js | 2 +- src/styles/getModalStyles/getBaseModalStyles.js | 2 +- src/styles/getReportActionContextMenuStyles.js | 2 +- src/styles/getTooltipStyles.js | 2 +- src/styles/styles.js | 2 +- src/styles/themes/ThemeContext.js | 2 +- src/styles/themes/ThemeProvider.js | 2 +- src/styles/themes/{dark.ts => default.ts} | 0 94 files changed, 93 insertions(+), 93 deletions(-) rename src/styles/themes/{dark.ts => default.ts} (100%) diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index d1d8c42abffa..ff97c9be24a6 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -9,7 +9,7 @@ import PlaidLink from './PlaidLink'; import * as BankAccounts from '../libs/actions/BankAccounts'; import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Picker from './Picker'; diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index a29fd4002077..e8a41ec35435 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -6,7 +6,7 @@ import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete' import lodashGet from 'lodash/get'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import TextInput from '../TextInput'; import * as ApiUtils from '../../libs/ApiUtils'; import * as GooglePlacesUtils from '../../libs/GooglePlacesUtils'; diff --git a/src/components/AttachmentCarousel/index.js b/src/components/AttachmentCarousel/index.js index b15c8a0b7c7e..3f2524a2992e 100644 --- a/src/components/AttachmentCarousel/index.js +++ b/src/components/AttachmentCarousel/index.js @@ -4,7 +4,7 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import * as Expensicons from '../Icon/Expensicons'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import CarouselActions from './CarouselActions'; import Button from '../Button'; import AttachmentView from '../AttachmentView'; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 1e768d2eefd3..1b87799d4f5b 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -12,7 +12,7 @@ import AttachmentCarousel from './AttachmentCarousel'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import * as FileUtils from '../libs/fileDownload/FileUtils'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Button from './Button'; diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js index 7cf77c08e5e2..d880ac9b9076 100755 --- a/src/components/AttachmentView.js +++ b/src/components/AttachmentView.js @@ -12,7 +12,7 @@ import withLocalize, {withLocalizePropTypes} from './withLocalize'; import compose from '../libs/compose'; import Text from './Text'; import Tooltip from './Tooltip'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import variables from '../styles/variables'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; diff --git a/src/components/Avatar.js b/src/components/Avatar.js index f4502ddb6a03..8dcf7f08c1d1 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index f0e7b4ed74be..99262bf12938 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -6,7 +6,7 @@ import {runOnUI, interpolate, useAnimatedGestureHandler, useSharedValue, useWork import CONST from '../../CONST'; import compose from '../../libs/compose'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import Button from '../Button'; import HeaderWithBackButton from '../HeaderWithBackButton'; import Icon from '../Icon'; diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 52ae459bca7a..0f1300ebf03d 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -8,7 +8,7 @@ import participantPropTypes from './participantPropTypes'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import SubscriptAvatar from './SubscriptAvatar'; import * as ReportUtils from '../libs/ReportUtils'; import MultipleAvatars from './MultipleAvatars'; diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 8452afcc0616..fcbfe4f4c4c4 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -8,7 +8,7 @@ import Icon from './Icon'; import PopoverMenu from './PopoverMenu'; import * as Expensicons from './Icon/Expensicons'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import AttachmentPicker from './AttachmentPicker'; import AvatarCropModal from './AvatarCropModal/AvatarCropModal'; import OfflineWithFeedback from './OfflineWithFeedback'; diff --git a/src/components/BlockingViews/BlockingView.js b/src/components/BlockingViews/BlockingView.js index a4af9754bbe5..d02fa55a6434 100644 --- a/src/components/BlockingViews/BlockingView.js +++ b/src/components/BlockingViews/BlockingView.js @@ -5,7 +5,7 @@ import styles from '../../styles/styles'; import variables from '../../styles/variables'; import Icon from '../Icon'; import Text from '../Text'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import TextLink from '../TextLink'; import Navigation from '../../libs/Navigation/Navigation'; import AutoEmailLink from '../AutoEmailLink'; diff --git a/src/components/Button/index.js b/src/components/Button/index.js index 03e98b56d5c6..a850a43d2fb0 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import Text from '../Text'; import KeyboardShortcut from '../../libs/KeyboardShortcut'; import Icon from '../Icon'; diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js index eb1729a45614..1396ab601330 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.js @@ -8,7 +8,7 @@ import Button from './Button'; import PopoverMenu from './PopoverMenu'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import CONST from '../CONST'; const propTypes = { diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js index 6150e5134e19..86b6e05d5ed7 100644 --- a/src/components/Checkbox.js +++ b/src/components/Checkbox.js @@ -2,7 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index c252d3dcfa62..d0805cbcc7c3 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -3,7 +3,7 @@ import {StyleSheet} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import RNTextInput from '../RNTextInput'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import * as ComposerUtils from '../../libs/ComposerUtils'; const propTypes = { diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index a0e372403310..c0a3859e6d01 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -3,7 +3,7 @@ import {StyleSheet} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import RNTextInput from '../RNTextInput'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import * as ComposerUtils from '../../libs/ComposerUtils'; const propTypes = { diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 4b8144745119..d32246529b6c 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -6,7 +6,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import RNTextInput from '../RNTextInput'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import Growl from '../../libs/Growl'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import updateIsFullComposerAvailable from '../../libs/ComposerUtils/updateIsFullComposerAvailable'; import * as ComposerUtils from '../../libs/ComposerUtils'; import * as Browser from '../../libs/Browser'; diff --git a/src/components/CurrentUserPersonalDetailsSkeletonView/index.js b/src/components/CurrentUserPersonalDetailsSkeletonView/index.js index 8e63be848e91..6e6c46e971c0 100644 --- a/src/components/CurrentUserPersonalDetailsSkeletonView/index.js +++ b/src/components/CurrentUserPersonalDetailsSkeletonView/index.js @@ -5,7 +5,7 @@ import {Circle, Rect} from 'react-native-svg'; import {View} from 'react-native'; import * as StyleUtils from '../../styles/StyleUtils'; import CONST from '../../CONST'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import variables from '../../styles/variables'; import styles from '../../styles/styles'; diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index 4f77691046bd..76752cb549e1 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import StatusBar from '../../libs/StatusBar'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; function CustomStatusBar() { useEffect(() => { diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 410376a387dd..5d87636a9365 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -10,7 +10,7 @@ import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import Popover from '../Popover'; import CONST from '../../CONST'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import {propTypes, defaultProps} from './datepickerPropTypes'; import withKeyboardState, {keyboardStatePropTypes} from '../withKeyboardState'; diff --git a/src/components/EmojiPicker/CategoryShortcutButton.js b/src/components/EmojiPicker/CategoryShortcutButton.js index ac7f1991ec6a..a7658ae0542d 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.js +++ b/src/components/EmojiPicker/CategoryShortcutButton.js @@ -7,7 +7,7 @@ import variables from '../../styles/variables'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; import getButtonState from '../../libs/getButtonState'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import CONST from '../../CONST'; diff --git a/src/components/ExpensifyWordmark.js b/src/components/ExpensifyWordmark.js index 25ff571a7c01..dde792e87e22 100644 --- a/src/components/ExpensifyWordmark.js +++ b/src/components/ExpensifyWordmark.js @@ -8,7 +8,7 @@ import StagingLogo from '../../assets/images/expensify-logo--staging.svg'; import AdHocLogo from '../../assets/images/expensify-logo--adhoc.svg'; import CONST from '../CONST'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import variables from '../styles/variables'; diff --git a/src/components/FloatingActionButton.js b/src/components/FloatingActionButton.js index 4a2db6e2bcfb..706bad59f7b7 100644 --- a/src/components/FloatingActionButton.js +++ b/src/components/FloatingActionButton.js @@ -5,7 +5,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import Tooltip from './Tooltip'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; diff --git a/src/components/FullscreenLoadingIndicator.js b/src/components/FullscreenLoadingIndicator.js index 96c1246f33c5..5c212b6dc29e 100644 --- a/src/components/FullscreenLoadingIndicator.js +++ b/src/components/FullscreenLoadingIndicator.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React from 'react'; import {ActivityIndicator, StyleSheet, View} from 'react-native'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import stylePropTypes from '../styles/stylePropTypes'; const propTypes = { diff --git a/src/components/GrowlNotification/index.js b/src/components/GrowlNotification/index.js index 750063847ead..70cadd5efd8e 100644 --- a/src/components/GrowlNotification/index.js +++ b/src/components/GrowlNotification/index.js @@ -1,7 +1,7 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {Directions, FlingGestureHandler, State} from 'react-native-gesture-handler'; import {View, Animated} from 'react-native'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import Text from '../Text'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js index 3f5ff9a72dc2..d91510c3ec6a 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -4,7 +4,7 @@ import htmlRendererPropTypes from './htmlRendererPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; import Text from '../../Text'; import variables from '../../../styles/variables'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import styles from '../../../styles/styles'; import editedLabelStyles from '../../../styles/editedLabelStyles'; diff --git a/src/components/Icon/index.js b/src/components/Icon/index.js index 044baee944b3..8c6559451215 100644 --- a/src/components/Icon/index.js +++ b/src/components/Icon/index.js @@ -2,7 +2,7 @@ import React, {PureComponent} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import variables from '../../styles/variables'; import * as StyleUtils from '../../styles/StyleUtils'; import IconWrapperStyles from './IconWrapperStyles'; diff --git a/src/components/IllustratedHeaderPageLayout.js b/src/components/IllustratedHeaderPageLayout.js index d1563c500671..7fc340426d69 100644 --- a/src/components/IllustratedHeaderPageLayout.js +++ b/src/components/IllustratedHeaderPageLayout.js @@ -7,7 +7,7 @@ import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBack import HeaderWithBackButton from './HeaderWithBackButton'; import ScreenWrapper from './ScreenWrapper'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import * as StyleUtils from '../styles/StyleUtils'; import useWindowDimensions from '../hooks/useWindowDimensions'; import FixedFooter from './FixedFooter'; diff --git a/src/components/Indicator.js b/src/components/Indicator.js index cd66de22f57d..765d79e156af 100644 --- a/src/components/Indicator.js +++ b/src/components/Indicator.js @@ -15,7 +15,7 @@ import * as PolicyUtils from '../libs/PolicyUtils'; import * as PaymentMethods from '../libs/actions/PaymentMethods'; import * as ReimbursementAccountProps from '../pages/ReimbursementAccount/reimbursementAccountPropTypes'; import * as UserUtils from '../libs/UserUtils'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; const propTypes = { /* Onyx Props */ diff --git a/src/components/InlineSystemMessage.js b/src/components/InlineSystemMessage.js index ea21c5f65352..a6866fb5a887 100644 --- a/src/components/InlineSystemMessage.js +++ b/src/components/InlineSystemMessage.js @@ -2,7 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; -import theme from '../styles/themes/dark'; +import theme from '../styles/themes/default'; import Text from './Text'; import * as Expensicons from './Icon/Expensicons'; import Icon from './Icon'; diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 51045cd8876f..e17cf71e5d06 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -14,7 +14,7 @@ import colors from '../../styles/colors'; import Text from '../Text'; import SubscriptAvatar from '../SubscriptAvatar'; import CONST from '../../CONST'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import OfflineWithFeedback from '../OfflineWithFeedback'; import PressableWithSecondaryInteraction from '../PressableWithSecondaryInteraction'; import * as ReportActionContextMenu from '../../pages/home/report/ContextMenu/ReportActionContextMenu'; diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js index af191080a4ca..532c29535e50 100644 --- a/src/components/LocalePicker.js +++ b/src/components/LocalePicker.js @@ -9,7 +9,7 @@ import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import Picker from './Picker'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; const propTypes = { /** Indicates which locale the user currently has selected */ diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index b470b11171f8..c280a75a8ef3 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -3,7 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import Text from './Text'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import * as StyleUtils from '../styles/StyleUtils'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index a9116faa3460..6d5bd5390416 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -5,7 +5,7 @@ import ReactNativeModal from 'react-native-modal'; import {SafeAreaInsetsContext} from 'react-native-safe-area-context'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './modalPropTypes'; import * as Modal from '../../libs/actions/Modal'; import getModalStyles from '../../styles/getModalStyles'; diff --git a/src/components/Modal/index.web.js b/src/components/Modal/index.web.js index 8fc5a4ee22fe..065b3a9f210f 100644 --- a/src/components/Modal/index.web.js +++ b/src/components/Modal/index.web.js @@ -3,7 +3,7 @@ import withWindowDimensions from '../withWindowDimensions'; import BaseModal from './BaseModal'; import {propTypes, defaultProps} from './modalPropTypes'; import * as StyleUtils from '../../styles/StyleUtils'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import StatusBar from '../../libs/StatusBar'; import CONST from '../../CONST'; diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 76620c7a0863..e421dae4e37e 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -23,7 +23,7 @@ import optionPropTypes from './optionPropTypes'; import * as CurrencyUtils from '../libs/CurrencyUtils'; import Button from './Button'; import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import Image from './Image'; import ReceiptHTML from '../../assets/images/receipt-html.png'; import ReceiptDoc from '../../assets/images/receipt-doc.png'; diff --git a/src/components/MoneyRequestDetails.js b/src/components/MoneyRequestDetails.js index fe6d7afdfe7a..a690c31c000c 100644 --- a/src/components/MoneyRequestDetails.js +++ b/src/components/MoneyRequestDetails.js @@ -11,7 +11,7 @@ import Text from './Text'; import participantPropTypes from './participantPropTypes'; import Avatar from './Avatar'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import CONST from '../CONST'; import withWindowDimensions from './withWindowDimensions'; import compose from '../libs/compose'; diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index c89613c90e19..ceb1c5371c7e 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -6,7 +6,7 @@ import styles from '../styles/styles'; import Avatar from './Avatar'; import Tooltip from './Tooltip'; import Text from './Text'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import * as StyleUtils from '../styles/StyleUtils'; import CONST from '../CONST'; import variables from '../styles/variables'; diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.js index 54fb2abba2bf..394996331d5e 100644 --- a/src/components/Onfido/BaseOnfidoWeb.js +++ b/src/components/Onfido/BaseOnfidoWeb.js @@ -6,7 +6,7 @@ import * as OnfidoSDK from 'onfido-sdk-ui'; import onfidoPropTypes from './onfidoPropTypes'; import CONST from '../../CONST'; import variables from '../../styles/variables'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import fontWeightBold from '../../styles/fontWeight/bold'; import fontFamily from '../../styles/fontFamily'; import Log from '../../libs/Log'; diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js index d341710945d7..adaa4457bbd9 100644 --- a/src/components/OptionRow.js +++ b/src/components/OptionRow.js @@ -11,7 +11,7 @@ import * as Expensicons from './Icon/Expensicons'; import MultipleAvatars from './MultipleAvatars'; import Hoverable from './Hoverable'; import DisplayNames from './DisplayNames'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import Text from './Text'; import SelectCircle from './SelectCircle'; diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 1cf6a97f8c97..15c66affe84d 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import {Rect, Circle} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../CONST'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; const propTypes = { diff --git a/src/components/Picker/BasePicker.js b/src/components/Picker/BasePicker.js index c64aad3d0c1a..173b863edfcc 100644 --- a/src/components/Picker/BasePicker.js +++ b/src/components/Picker/BasePicker.js @@ -8,7 +8,7 @@ import * as Expensicons from '../Icon/Expensicons'; import FormHelpMessage from '../FormHelpMessage'; import Text from '../Text'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import {ScrollContext} from '../ScrollViewWithContext'; const propTypes = { diff --git a/src/components/PinButton.js b/src/components/PinButton.js index 2074996cfb7b..84ad6e22f50b 100644 --- a/src/components/PinButton.js +++ b/src/components/PinButton.js @@ -1,6 +1,6 @@ import React from 'react'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import Icon from './Icon'; import Tooltip from './Tooltip'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; diff --git a/src/components/QRCode/index.js b/src/components/QRCode/index.js index 7bc0829d399d..f27cf28066ef 100644 --- a/src/components/QRCode/index.js +++ b/src/components/QRCode/index.js @@ -1,7 +1,7 @@ import React from 'react'; import QRCodeLibrary from 'react-native-qrcode-svg'; import PropTypes from 'prop-types'; -import defaultTheme from '../../styles/themes/dark'; +import defaultTheme from '../../styles/themes/default'; import CONST from '../../CONST'; const propTypes = { diff --git a/src/components/QRShare/index.js b/src/components/QRShare/index.js index 580151554d63..d96024ad1046 100644 --- a/src/components/QRShare/index.js +++ b/src/components/QRShare/index.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; -import defaultTheme from '../../styles/themes/dark'; +import defaultTheme from '../../styles/themes/default'; import styles from '../../styles/styles'; import Text from '../Text'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/IOUPreview.js index fe207476627b..85a0b22ac327 100644 --- a/src/components/ReportActionItem/IOUPreview.js +++ b/src/components/ReportActionItem/IOUPreview.js @@ -10,7 +10,7 @@ import ONYXKEYS from '../../ONYXKEYS'; import MultipleAvatars from '../MultipleAvatars'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import * as Report from '../../libs/actions/Report'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import Icon from '../Icon'; import CONST from '../../CONST'; import * as Expensicons from '../Icon/Expensicons'; diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 89b7616d081a..d5d85df5e7ee 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -23,7 +23,7 @@ import SettlementButton from '../SettlementButton'; import * as IOU from '../../libs/actions/IOU'; import refPropTypes from '../refPropTypes'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; const propTypes = { diff --git a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js index f8dcaf4de34c..ddaa46e0b731 100644 --- a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js +++ b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {Rect, Circle} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../../CONST'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import styles from '../../styles/styles'; const propTypes = { diff --git a/src/components/ReportHeaderSkeletonView.js b/src/components/ReportHeaderSkeletonView.js index c6e712417577..5f2d5379419d 100644 --- a/src/components/ReportHeaderSkeletonView.js +++ b/src/components/ReportHeaderSkeletonView.js @@ -8,7 +8,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import variables from '../styles/variables'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; diff --git a/src/components/RoomHeaderAvatars.js b/src/components/RoomHeaderAvatars.js index cb18973e287c..6f78e6ace66d 100644 --- a/src/components/RoomHeaderAvatars.js +++ b/src/components/RoomHeaderAvatars.js @@ -6,7 +6,7 @@ import styles from '../styles/styles'; import Text from './Text'; import CONST from '../CONST'; import Avatar from './Avatar'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import * as StyleUtils from '../styles/StyleUtils'; import avatarPropTypes from './avatarPropTypes'; diff --git a/src/components/SelectCircle.js b/src/components/SelectCircle.js index 60e449479d11..93cf285eab59 100644 --- a/src/components/SelectCircle.js +++ b/src/components/SelectCircle.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import styles from '../styles/styles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; const propTypes = { /** Should we show the checkmark inside the circle */ diff --git a/src/components/SelectionListRadio/RadioListItem.js b/src/components/SelectionListRadio/RadioListItem.js index 41928c50e05d..c5c4b3aeaf2c 100644 --- a/src/components/SelectionListRadio/RadioListItem.js +++ b/src/components/SelectionListRadio/RadioListItem.js @@ -6,7 +6,7 @@ import styles from '../../styles/styles'; import Text from '../Text'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import {radioListItemPropTypes} from './selectionListRadioPropTypes'; const propTypes = { diff --git a/src/components/SubscriptAvatar.js b/src/components/SubscriptAvatar.js index 5ca496025f1f..05202e720bd4 100644 --- a/src/components/SubscriptAvatar.js +++ b/src/components/SubscriptAvatar.js @@ -4,7 +4,7 @@ import {View} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import Avatar from './Avatar'; import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; diff --git a/src/components/TabSelector/TabSelectorItem.js b/src/components/TabSelector/TabSelectorItem.js index f96d9f2d621e..cea59bc2ee65 100644 --- a/src/components/TabSelector/TabSelectorItem.js +++ b/src/components/TabSelector/TabSelectorItem.js @@ -2,7 +2,7 @@ import {Text} from 'react-native'; import React from 'react'; import PropTypes from 'prop-types'; import Icon from '../Icon'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import styles from '../../styles/styles'; import PressableWithFeedback from '../Pressable/PressableWithFeedback'; diff --git a/src/components/Text.js b/src/components/Text.js index 3c44183cd17b..83b6be8fffb0 100644 --- a/src/components/Text.js +++ b/src/components/Text.js @@ -4,7 +4,7 @@ import _ from 'underscore'; // eslint-disable-next-line no-restricted-imports import {Text as RNText} from 'react-native'; import fontFamily from '../styles/fontFamily'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import variables from '../styles/variables'; const propTypes = { diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index b075672d492e..68c09e3a7f82 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -5,7 +5,7 @@ import Str from 'expensify-common/lib/str'; import RNTextInput from '../RNTextInput'; import TextInputLabel from './TextInputLabel'; import * as baseTextInputPropTypes from './baseTextInputPropTypes'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import styles from '../../styles/styles'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; diff --git a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js index 23ad19f53d37..4c0e2b551382 100755 --- a/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js +++ b/src/components/VideoChatButtonAndMenu/BaseVideoChatButtonAndMenu.js @@ -10,7 +10,7 @@ import ZoomIcon from '../../../assets/images/zoom-icon.svg'; import GoogleMeetIcon from '../../../assets/images/google-meet.svg'; import CONST from '../../CONST'; import styles from '../../styles/styles'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import compose from '../../libs/compose'; diff --git a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js index bc6ba2cfb3cd..2f9a899191bf 100644 --- a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js +++ b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/ThreePaneView.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import SCREENS from '../../../../SCREENS'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; import NAVIGATORS from '../../../../NAVIGATORS'; import * as StyleUtils from '../../../../styles/StyleUtils'; import {withNavigationPropTypes} from '../../../../components/withNavigation'; diff --git a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js index 63aca125dc27..d2de1ba23a01 100644 --- a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js +++ b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js @@ -1,7 +1,7 @@ import {Animated} from 'react-native'; import variables from '../../../styles/variables'; import getCardStyles from '../../../styles/cardStyles'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; export default (isSmallScreenWidth, isFullScreenModal, {current: {progress}, inverted, layouts: {screen}}) => { const translateX = Animated.multiply( diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index c22c459b54ab..23c320eb991c 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -6,7 +6,7 @@ import {useSharedValue, useAnimatedReaction, interpolateColor, withTiming, withD import Navigation, {navigationRef} from './Navigation'; import linkingConfig from './linkingConfig'; import AppNavigator from './AppNavigator'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import Log from '../Log'; import StatusBar from '../StatusBar'; import useCurrentReportID from '../../hooks/useCurrentReportID'; diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.js index 05dddf273547..3ff3bc686419 100644 --- a/src/pages/ErrorPage/GenericErrorPage.js +++ b/src/pages/ErrorPage/GenericErrorPage.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import Icon from '../../components/Icon'; -import defaultTheme from '../../styles/themes/dark'; +import defaultTheme from '../../styles/themes/default'; import * as Expensicons from '../../components/Icon/Expensicons'; import Text from '../../components/Text'; import Button from '../../components/Button'; diff --git a/src/pages/home/report/FloatingMessageCounter/index.js b/src/pages/home/report/FloatingMessageCounter/index.js index 4178502a1368..73fe02df129b 100644 --- a/src/pages/home/report/FloatingMessageCounter/index.js +++ b/src/pages/home/report/FloatingMessageCounter/index.js @@ -6,7 +6,7 @@ import Button from '../../../../components/Button'; import Text from '../../../../components/Text'; import Icon from '../../../../components/Icon'; import * as Expensicons from '../../../../components/Icon/Expensicons'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; import useLocalize from '../../../../hooks/useLocalize'; import FloatingMessageCounterContainer from './FloatingMessageCounterContainer'; diff --git a/src/pages/home/report/LinkPreviewer.js b/src/pages/home/report/LinkPreviewer.js index 3859299ea1a7..4fcbb0dc0569 100644 --- a/src/pages/home/report/LinkPreviewer.js +++ b/src/pages/home/report/LinkPreviewer.js @@ -8,7 +8,7 @@ import TextLink from '../../../components/TextLink'; import * as StyleUtils from '../../../styles/StyleUtils'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; const IMAGE_TYPES = ['jpg', 'jpeg', 'png']; const MAX_IMAGE_HEIGHT = 180; diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index e8d26edb40e5..1146e3b382f5 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -7,7 +7,7 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import Composer from '../../../components/Composer'; import ONYXKEYS from '../../../ONYXKEYS'; import Icon from '../../../components/Icon'; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 6863e75ab894..009c1118400b 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -5,7 +5,7 @@ import Str from 'expensify-common/lib/str'; import reportActionFragmentPropTypes from './reportActionFragmentPropTypes'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import RenderHTML from '../../../components/RenderHTML'; import Text from '../../../components/Text'; import * as EmojiUtils from '../../../libs/EmojiUtils'; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 5c7e71d0a877..54c5fec4533e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -7,7 +7,7 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; import reportActionPropTypes from './reportActionPropTypes'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import * as StyleUtils from '../../../styles/StyleUtils'; import containerComposeStyles from '../../../styles/containerComposeStyles'; import Composer from '../../../components/Composer'; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index dc917b1f6c6a..132b767a7f70 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -24,7 +24,7 @@ import LHNOptionsList from '../../../components/LHNOptionsList/LHNOptionsList'; import SidebarUtils from '../../../libs/SidebarUtils'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import Header from '../../../components/Header'; -import defaultTheme from '../../../styles/themes/dark'; +import defaultTheme from '../../../styles/themes/default'; import OptionsListSkeletonView from '../../../components/OptionsListSkeletonView'; import variables from '../../../styles/variables'; import LogoComponent from '../../../../assets/images/expensify-wordmark.svg'; diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 20f7e7098f1c..705d9d1e2d08 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -7,7 +7,7 @@ import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import FreezeWrapper from '../../../../libs/Navigation/FreezeWrapper'; import withWindowDimensions from '../../../../components/withWindowDimensions'; import StatusBar from '../../../../libs/StatusBar'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; function SidebarScreen(props) { const popoverModal = useRef(null); diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index b46919127fb3..44a7fba5d487 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -15,7 +15,7 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import {withNetwork} from '../../components/OnyxProvider'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import ROUTES from '../../ROUTES'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import * as Expensicons from '../../components/Icon/Expensicons'; import reportPropTypes from '../reportPropTypes'; import * as ReportUtils from '../../libs/ReportUtils'; diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 06f692fa1b33..7eeab6e493bd 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -13,7 +13,7 @@ import styles from '../../../styles/styles'; import Shutter from '../../../../assets/images/shutter.svg'; import Hand from '../../../../assets/images/hand.svg'; import * as IOU from '../../../libs/actions/IOU'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import reportPropTypes from '../../reportPropTypes'; import CONST from '../../../CONST'; import Button from '../../../components/Button'; diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js index c2690e27dd55..346738574da3 100644 --- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js @@ -28,7 +28,7 @@ import * as PaymentUtils from '../../../../libs/PaymentUtils'; import OfflineWithFeedback from '../../../../components/OfflineWithFeedback'; import ConfirmContent from '../../../../components/ConfirmContent'; import Button from '../../../../components/Button'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; import variables from '../../../../styles/variables'; import useLocalize from '../../../../hooks/useLocalize'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index e91fcdacaf0e..b8bb74295567 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -7,7 +7,7 @@ import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import Text from '../../../components/Text'; import CONST from '../../../CONST'; import * as User from '../../../libs/actions/User'; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js index dd6f5cf90c4a..2289e68e7bd1 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js @@ -21,7 +21,7 @@ import ConfirmModal from '../../../../components/ConfirmModal'; import * as User from '../../../../libs/actions/User'; import CONST from '../../../../CONST'; import * as ErrorUtils from '../../../../libs/ErrorUtils'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; import NotFoundPage from '../../../ErrorPage/NotFoundPage'; import ValidateCodeForm from './ValidateCodeForm'; import ROUTES from '../../../../ROUTES'; diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index 6cf47d37e3af..ea81413fcbb5 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -20,7 +20,7 @@ import shouldDelayFocus from '../../../../../libs/shouldDelayFocus'; import Text from '../../../../../components/Text'; import {withNetwork} from '../../../../../components/OnyxProvider'; import PressableWithFeedback from '../../../../../components/Pressable/PressableWithFeedback'; -import themeColors from '../../../../../styles/themes/dark'; +import themeColors from '../../../../../styles/themes/default'; import * as StyleUtils from '../../../../../styles/StyleUtils'; import CONST from '../../../../../CONST'; diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js index 6b625f3e13b9..9765cf1ae0b4 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.js +++ b/src/pages/settings/Report/NotificationPreferencePage.js @@ -14,7 +14,7 @@ import ROUTES from '../../../ROUTES'; import * as Report from '../../../libs/actions/Report'; import * as ReportUtils from '../../../libs/ReportUtils'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; const propTypes = { ...withLocalizePropTypes, diff --git a/src/pages/settings/Report/WriteCapabilityPage.js b/src/pages/settings/Report/WriteCapabilityPage.js index 5e1d1192824b..59ad90a2cd1f 100644 --- a/src/pages/settings/Report/WriteCapabilityPage.js +++ b/src/pages/settings/Report/WriteCapabilityPage.js @@ -15,7 +15,7 @@ import reportPropTypes from '../../reportPropTypes'; import ROUTES from '../../../ROUTES'; import * as Report from '../../../libs/actions/Report'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import * as ReportUtils from '../../../libs/ReportUtils'; import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView'; import * as PolicyUtils from '../../../libs/PolicyUtils'; diff --git a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js index 8e2b0cff5b69..6780080ff382 100644 --- a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js +++ b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js @@ -22,7 +22,7 @@ import Text from '../../../../components/Text'; import Section from '../../../../components/Section'; import ONYXKEYS from '../../../../ONYXKEYS'; import Clipboard from '../../../../libs/Clipboard'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; import localFileDownload from '../../../../libs/localFileDownload'; import * as TwoFactorAuthActions from '../../../../libs/actions/TwoFactorAuthActions'; diff --git a/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js b/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js index b5bc98005170..4d49edac1fe4 100644 --- a/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js +++ b/src/pages/settings/Security/TwoFactorAuth/IsEnabledPage.js @@ -9,7 +9,7 @@ import ROUTES from '../../../../ROUTES'; import Section from '../../../../components/Section'; import * as Illustrations from '../../../../components/Icon/Illustrations'; import * as Expensicons from '../../../../components/Icon/Expensicons'; -import themeColors from '../../../../styles/themes/dark'; +import themeColors from '../../../../styles/themes/default'; import styles from '../../../../styles/styles'; import ConfirmModal from '../../../../components/ConfirmModal'; import FullPageOfflineBlockingView from '../../../../components/BlockingViews/FullPageOfflineBlockingView'; diff --git a/src/pages/signin/SignInPageLayout/Footer.js b/src/pages/signin/SignInPageLayout/Footer.js index 0cb5a8dd45b3..35e63b0699b3 100644 --- a/src/pages/signin/SignInPageLayout/Footer.js +++ b/src/pages/signin/SignInPageLayout/Footer.js @@ -5,7 +5,7 @@ import _ from 'underscore'; import Text from '../../../components/Text'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import variables from '../../../styles/variables'; import * as Expensicons from '../../../components/Icon/Expensicons'; import TextLink from '../../../components/TextLink'; diff --git a/src/pages/signin/SignInPageLayout/index.js b/src/pages/signin/SignInPageLayout/index.js index 9f73b828c5e9..7acd08a6c693 100644 --- a/src/pages/signin/SignInPageLayout/index.js +++ b/src/pages/signin/SignInPageLayout/index.js @@ -11,7 +11,7 @@ import styles from '../../../styles/styles'; import SignInPageHero from '../SignInPageHero'; import * as StyleUtils from '../../../styles/StyleUtils'; import scrollViewContentContainerStyles from './signInPageStyles'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import BackgroundImage from './BackgroundImage'; import SignInGradient from '../../../../assets/images/home-fade-gradient.svg'; import variables from '../../../styles/variables'; diff --git a/src/pages/signin/Socials.js b/src/pages/signin/Socials.js index be6305cf1bc6..f7a866d2023b 100644 --- a/src/pages/signin/Socials.js +++ b/src/pages/signin/Socials.js @@ -5,7 +5,7 @@ import * as Link from '../../libs/actions/Link'; import Icon from '../../components/Icon'; import PressableWithoutFeedback from '../../components/Pressable/PressableWithoutFeedback'; import * as Expensicons from '../../components/Icon/Expensicons'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import styles from '../../styles/styles'; import variables from '../../styles/variables'; import CONST from '../../CONST'; diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index e0ccdca636ab..a68f99df6d24 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -7,7 +7,7 @@ import lodashGet from 'lodash/get'; import styles from '../../../styles/styles'; import Button from '../../../components/Button'; import Text from '../../../components/Text'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import * as Session from '../../../libs/actions/Session'; import ONYXKEYS from '../../../ONYXKEYS'; import CONST from '../../../CONST'; diff --git a/src/pages/workspace/WorkspacesListPage.js b/src/pages/workspace/WorkspacesListPage.js index 6073836c6e9a..98ef04836f76 100755 --- a/src/pages/workspace/WorkspacesListPage.js +++ b/src/pages/workspace/WorkspacesListPage.js @@ -10,7 +10,7 @@ import styles from '../../styles/styles'; import compose from '../../libs/compose'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import * as Expensicons from '../../components/Icon/Expensicons'; -import themeColors from '../../styles/themes/dark'; +import themeColors from '../../styles/themes/default'; import * as PolicyUtils from '../../libs/PolicyUtils'; import MenuItem from '../../components/MenuItem'; import * as Policy from '../../libs/actions/Policy'; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js index 8f48fc216bfd..eb8305f23140 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js @@ -5,7 +5,7 @@ import lodashGet from 'lodash/get'; import _ from 'underscore'; import Text from '../../../components/Text'; import styles from '../../../styles/styles'; -import themeColors from '../../../styles/themes/dark'; +import themeColors from '../../../styles/themes/default'; import * as Expensicons from '../../../components/Icon/Expensicons'; import * as Illustrations from '../../../components/Icon/Illustrations'; import Section from '../../../components/Section'; diff --git a/src/stories/Composer.stories.js b/src/stories/Composer.stories.js index 632901df582f..3dfc5b0e3ead 100644 --- a/src/stories/Composer.stories.js +++ b/src/stories/Composer.stories.js @@ -5,7 +5,7 @@ import Composer from '../components/Composer'; import RenderHTML from '../components/RenderHTML'; import Text from '../components/Text'; import styles from '../styles/styles'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; import * as StyleUtils from '../styles/StyleUtils'; import CONST from '../CONST'; diff --git a/src/stories/PopoverMenu.stories.js b/src/stories/PopoverMenu.stories.js index 198c7ef28e83..1098fa9ce226 100644 --- a/src/stories/PopoverMenu.stories.js +++ b/src/stories/PopoverMenu.stories.js @@ -3,7 +3,7 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'; import PopoverMenu from '../components/PopoverMenu'; import * as Expensicons from '../components/Icon/Expensicons'; import MenuItem from '../components/MenuItem'; -import themeColors from '../styles/themes/dark'; +import themeColors from '../styles/themes/default'; /** * We use the Component Story Format for writing stories. Follow the docs here: diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 676796751dc3..f8db7272e93f 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import CONST from '../CONST'; import fontFamily from './fontFamily'; -import themeColors from './themes/dark'; +import themeColors from './themes/default'; import variables from './variables'; import colors from './colors'; import positioning from './utilities/positioning'; diff --git a/src/styles/addOutlineWidth/index.js b/src/styles/addOutlineWidth/index.js index cdb7dce7f926..2a2657b24910 100644 --- a/src/styles/addOutlineWidth/index.js +++ b/src/styles/addOutlineWidth/index.js @@ -3,7 +3,7 @@ * can be added to the object */ -import themeDefault from '../themes/dark'; +import themeDefault from '../themes/default'; /** * Adds the addOutlineWidth property to an object to be used when styling diff --git a/src/styles/getModalStyles/getBaseModalStyles.js b/src/styles/getModalStyles/getBaseModalStyles.js index 7daf944225c5..b7a3317963ca 100644 --- a/src/styles/getModalStyles/getBaseModalStyles.js +++ b/src/styles/getModalStyles/getBaseModalStyles.js @@ -1,6 +1,6 @@ import CONST from '../../CONST'; import variables from '../variables'; -import themeColors from '../themes/dark'; +import themeColors from '../themes/default'; import styles from '../styles'; const getCenteredModalStyles = (windowWidth, isSmallScreenWidth, isFullScreenWhenSmall = false) => ({ diff --git a/src/styles/getReportActionContextMenuStyles.js b/src/styles/getReportActionContextMenuStyles.js index b8084abad976..026306084ce4 100644 --- a/src/styles/getReportActionContextMenuStyles.js +++ b/src/styles/getReportActionContextMenuStyles.js @@ -1,6 +1,6 @@ import styles from './styles'; import variables from './variables'; -import themeColors from './themes/dark'; +import themeColors from './themes/default'; const defaultWrapperStyle = { backgroundColor: themeColors.componentBG, diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index 58ec3f200b2d..bc5fcfe807aa 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -1,7 +1,7 @@ import spacing from './utilities/spacing'; import styles from './styles'; import colors from './colors'; -import themeColors from './themes/dark'; +import themeColors from './themes/default'; import fontFamily from './fontFamily'; import variables from './variables'; import roundToNearestMultipleOfFour from './roundToNearestMultipleOfFour'; diff --git a/src/styles/styles.js b/src/styles/styles.js index fe47cc70c66a..835082ba98c9 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2,7 +2,7 @@ import {defaultStyles as defaultPickerStyles} from 'react-native-picker-select/s import lodashClamp from 'lodash/clamp'; import fontFamily from './fontFamily'; import addOutlineWidth from './addOutlineWidth'; -import themeColors from './themes/dark'; +import themeColors from './themes/default'; import fontWeightBold from './fontWeight/bold'; import variables from './variables'; import spacing from './utilities/spacing'; diff --git a/src/styles/themes/ThemeContext.js b/src/styles/themes/ThemeContext.js index e75c48bcab1e..12877a40ab96 100644 --- a/src/styles/themes/ThemeContext.js +++ b/src/styles/themes/ThemeContext.js @@ -1,5 +1,5 @@ import React from 'react'; -import darkTheme from './dark'; +import darkTheme from './default'; const ThemeContext = React.createContext(darkTheme); diff --git a/src/styles/themes/ThemeProvider.js b/src/styles/themes/ThemeProvider.js index 2dd92e8be729..d32a6d203cc5 100644 --- a/src/styles/themes/ThemeProvider.js +++ b/src/styles/themes/ThemeProvider.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ThemeContext from './ThemeContext'; import useThemePreference from './useThemePreference'; import CONST from '../../CONST'; -import darkTheme from './dark'; +import darkTheme from './default'; import lightTheme from './light'; const propTypes = { diff --git a/src/styles/themes/dark.ts b/src/styles/themes/default.ts similarity index 100% rename from src/styles/themes/dark.ts rename to src/styles/themes/default.ts From 2ba934cdcf327b814af931971348f1c55d0d36d1 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 16 Sep 2023 11:56:37 +0200 Subject: [PATCH 007/170] add TODO comment about renaming default.ts --- src/styles/themes/default.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 70f8a9f77b8f..f7087ad558b5 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,3 +1,5 @@ +// TODO: For consistency reasons, rename this file to "dark.ts" after theme switching migration is done (GH issue:) + /* eslint-disable no-unused-vars */ import colors from '../colors'; import SCREENS from '../../SCREENS'; From 4b10b95afb44061921d9a1482d540dbbf45cf4f2 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 16 Sep 2023 12:00:20 +0200 Subject: [PATCH 008/170] fix: update outdated comment --- src/styles/colors.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/styles/colors.ts b/src/styles/colors.ts index b17f7fb4cb96..2d13f930ff0e 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -1,7 +1,9 @@ import {Color} from './themes/ThemeColors'; /** - * DO NOT import colors.js into files. Use ../themes/default.js instead. + * DO NOT import colors.js into files. Use the theme switching hooks and HOCs instead. + * For functional components, you can use the `useTheme` and `useThemeStyles` hooks + * For class components, you can use the `withTheme` and `withThemeStyles` HOCs */ const colors: Record = { black: '#000000', From 84c47315178f4ea68b1a59438084834e7dfce367 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 16 Sep 2023 12:01:54 +0200 Subject: [PATCH 009/170] fix: add TODO comment --- src/styles/colors.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/colors.ts b/src/styles/colors.ts index 2d13f930ff0e..5b05d16bb9b3 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -6,6 +6,7 @@ import {Color} from './themes/ThemeColors'; * For class components, you can use the `withTheme` and `withThemeStyles` HOCs */ const colors: Record = { + // TODO: Find a good name/description for this block of colors. black: '#000000', white: '#FFFFFF', ivory: '#fffaf0', From 429e2b664ea9a3632ba771f12fd8e1dbd3a51727 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 16 Sep 2023 12:28:03 +0200 Subject: [PATCH 010/170] fix: add types for the rest of theme switching logic --- ...StylesContext.ts => ThemeStylesContext.js} | 0 src/styles/ThemeStylesProvider.tsx | 3 ++- src/styles/themes/ThemeColors.ts | 4 +++- src/styles/themes/ThemeContext.js | 6 ------ src/styles/themes/ThemeContext.ts | 7 +++++++ .../{ThemeProvider.js => ThemeProvider.tsx} | 2 +- src/styles/themes/default.ts | 2 +- src/styles/themes/light.ts | 2 +- src/styles/themes/useTheme.js | 14 ------------- src/styles/themes/useTheme.ts | 15 +++++++++++++ ...emePreference.js => useThemePreference.ts} | 21 ++++++++++++------- 11 files changed, 43 insertions(+), 33 deletions(-) rename src/styles/{ThemeStylesContext.ts => ThemeStylesContext.js} (100%) delete mode 100644 src/styles/themes/ThemeContext.js create mode 100644 src/styles/themes/ThemeContext.ts rename src/styles/themes/{ThemeProvider.js => ThemeProvider.tsx} (93%) delete mode 100644 src/styles/themes/useTheme.js create mode 100644 src/styles/themes/useTheme.ts rename src/styles/themes/{useThemePreference.js => useThemePreference.ts} (53%) diff --git a/src/styles/ThemeStylesContext.ts b/src/styles/ThemeStylesContext.js similarity index 100% rename from src/styles/ThemeStylesContext.ts rename to src/styles/ThemeStylesContext.js diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index d0db784ca8ca..1c5c8cf3437a 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -4,8 +4,9 @@ import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; // TODO: Rename this to "styles" once the app is migrated to theme switching hooks and HOCs import {stylesGenerator as stylesUntyped} from './styles'; +import ThemeColors from './themes/ThemeColors'; -const styles = stylesUntyped as (theme: Record) => Record; +const styles = stylesUntyped as (theme: ThemeColors) => Record; type ThemeStylesProviderProps = { children: React.ReactNode; diff --git a/src/styles/themes/ThemeColors.ts b/src/styles/themes/ThemeColors.ts index 555e7ccb2274..382449ebdf62 100644 --- a/src/styles/themes/ThemeColors.ts +++ b/src/styles/themes/ThemeColors.ts @@ -83,4 +83,6 @@ type ThemeColors = ThemeColorsWithoutPageBackgroundColors & { PAGE_BACKGROUND_COLORS: Record; }; -export {type Color, type ThemeColors, type ThemeColorsWithoutPageBackgroundColors}; +export default ThemeColors; + +export {type Color, type ThemeColorsWithoutPageBackgroundColors}; diff --git a/src/styles/themes/ThemeContext.js b/src/styles/themes/ThemeContext.js deleted file mode 100644 index 12877a40ab96..000000000000 --- a/src/styles/themes/ThemeContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import darkTheme from './default'; - -const ThemeContext = React.createContext(darkTheme); - -export default ThemeContext; diff --git a/src/styles/themes/ThemeContext.ts b/src/styles/themes/ThemeContext.ts new file mode 100644 index 000000000000..98bed9ce822c --- /dev/null +++ b/src/styles/themes/ThemeContext.ts @@ -0,0 +1,7 @@ +import React from 'react'; +import darkTheme from './default'; +import ThemeColors from './ThemeColors'; + +const ThemeContext = React.createContext(darkTheme); + +export default ThemeContext; diff --git a/src/styles/themes/ThemeProvider.js b/src/styles/themes/ThemeProvider.tsx similarity index 93% rename from src/styles/themes/ThemeProvider.js rename to src/styles/themes/ThemeProvider.tsx index d32a6d203cc5..e4e316a16af1 100644 --- a/src/styles/themes/ThemeProvider.js +++ b/src/styles/themes/ThemeProvider.tsx @@ -12,7 +12,7 @@ const propTypes = { children: PropTypes.node.isRequired, }; -function ThemeProvider(props) { +function ThemeProvider(props: React.PropsWithChildren) { const themePreference = useThemePreference(); const theme = useMemo(() => (themePreference === CONST.THEME.LIGHT ? lightTheme : darkTheme), [themePreference]); diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index f7087ad558b5..f8b8e7df10b9 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -3,7 +3,7 @@ /* eslint-disable no-unused-vars */ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import {ThemeColors, ThemeColorsWithoutPageBackgroundColors} from './ThemeColors'; +import ThemeColors, {ThemeColorsWithoutPageBackgroundColors} from './ThemeColors'; const darkThemeWithoutPageBackgroundColors = { // Figma keys diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 499e1bbf127c..9b9b897acb1e 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,6 +1,6 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import {ThemeColors, ThemeColorsWithoutPageBackgroundColors} from './ThemeColors'; +import ThemeColors, {ThemeColorsWithoutPageBackgroundColors} from './ThemeColors'; const lightThemeWithoutPageBackgroundColors = { // Figma keys diff --git a/src/styles/themes/useTheme.js b/src/styles/themes/useTheme.js deleted file mode 100644 index 8e88b23a7688..000000000000 --- a/src/styles/themes/useTheme.js +++ /dev/null @@ -1,14 +0,0 @@ -import {useContext} from 'react'; -import ThemeContext from './ThemeContext'; - -function useTheme() { - const theme = useContext(ThemeContext); - - if (!theme) { - throw new Error('StylesContext was null! Are you sure that you wrapped the component under a ?'); - } - - return theme; -} - -export default useTheme; diff --git a/src/styles/themes/useTheme.ts b/src/styles/themes/useTheme.ts new file mode 100644 index 000000000000..51ee044d59c1 --- /dev/null +++ b/src/styles/themes/useTheme.ts @@ -0,0 +1,15 @@ +import {useContext} from 'react'; +import ThemeContext from './ThemeContext'; +import ThemeColors from './ThemeColors'; + +function useTheme(): ThemeColors { + const theme = useContext(ThemeContext); + + if (!theme) { + throw new Error('ThemeContext was null! Are you sure that you wrapped the component under a ?'); + } + + return theme; +} + +export default useTheme; diff --git a/src/styles/themes/useThemePreference.js b/src/styles/themes/useThemePreference.ts similarity index 53% rename from src/styles/themes/useThemePreference.js rename to src/styles/themes/useThemePreference.ts index fbb557423f10..8f68d55143a5 100644 --- a/src/styles/themes/useThemePreference.js +++ b/src/styles/themes/useThemePreference.ts @@ -1,26 +1,31 @@ import {useState, useEffect, useContext} from 'react'; -import {Appearance} from 'react-native'; +import {Appearance, ColorSchemeName} from 'react-native'; import CONST from '../../CONST'; import {PreferredThemeContext} from '../../components/OnyxProvider'; +// TODO: Remove this once "OnyxProvider" is typed +type PreferredThemeContextType = React.Context<(typeof CONST.THEME)[keyof typeof CONST.THEME]>; + +type ThemePreference = typeof CONST.THEME.LIGHT | typeof CONST.THEME.DARK; + function useThemePreference() { - const [themePreference, setThemePreference] = useState(CONST.THEME.DEFAULT); - const [systemTheme, setSystemTheme] = useState(); - const preferredThemeContext = useContext(PreferredThemeContext); + const [themePreference, setThemePreference] = useState(CONST.THEME.DEFAULT); + const [systemTheme, setSystemTheme] = useState(); + const preferredThemeFromStorage = useContext(PreferredThemeContext as PreferredThemeContextType); useEffect(() => { // This is used for getting the system theme, that can be set in the OS's theme settings. This will always return either "light" or "dark" and will update automatically if the OS theme changes. const systemThemeSubscription = Appearance.addChangeListener(({colorScheme}) => setSystemTheme(colorScheme)); - return systemThemeSubscription.remove; + return () => systemThemeSubscription.remove(); }, []); useEffect(() => { - const theme = preferredThemeContext || CONST.THEME.DEFAULT; + const theme = preferredThemeFromStorage || CONST.THEME.DEFAULT; // If the user chooses to use the device theme settings, we need to set the theme preference to the system theme - if (theme === CONST.THEME.SYSTEM) setThemePreference(systemTheme); + if (theme === CONST.THEME.SYSTEM) setThemePreference(systemTheme ?? CONST.THEME.DEFAULT); else setThemePreference(theme); - }, [preferredThemeContext, systemTheme]); + }, [preferredThemeFromStorage, systemTheme]); return themePreference; } From b7b1179f4bce3f0ca23ad471f14875d1f424a3a4 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 16 Sep 2023 12:28:48 +0200 Subject: [PATCH 011/170] fix: type remaining file with TODO --- src/styles/ThemeStylesContext.js | 6 ------ src/styles/ThemeStylesContext.ts | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 src/styles/ThemeStylesContext.js create mode 100644 src/styles/ThemeStylesContext.ts diff --git a/src/styles/ThemeStylesContext.js b/src/styles/ThemeStylesContext.js deleted file mode 100644 index 1c81ab3b39a5..000000000000 --- a/src/styles/ThemeStylesContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import styles from './styles'; - -const ThemeStylesContext = React.createContext(styles); - -export default ThemeStylesContext; diff --git a/src/styles/ThemeStylesContext.ts b/src/styles/ThemeStylesContext.ts new file mode 100644 index 000000000000..c32f994e16da --- /dev/null +++ b/src/styles/ThemeStylesContext.ts @@ -0,0 +1,7 @@ +import React from 'react'; +import styles from './styles'; + +// TODO: Change "uknown" once "styles.js" is typed +const ThemeStylesContext = React.createContext(styles); + +export default ThemeStylesContext; From f69116a29911d67ea1257b90392164e70aa32d2e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 10:23:30 +0200 Subject: [PATCH 012/170] fix: remove theme spreading and abstraction --- src/styles/themes/ThemeColors.ts | 6 ++---- src/styles/themes/default.ts | 9 +++------ src/styles/themes/light.ts | 9 +++------ 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/styles/themes/ThemeColors.ts b/src/styles/themes/ThemeColors.ts index 382449ebdf62..19840a05491b 100644 --- a/src/styles/themes/ThemeColors.ts +++ b/src/styles/themes/ThemeColors.ts @@ -1,6 +1,6 @@ type Color = string; -type ThemeColorsWithoutPageBackgroundColors = { +type ThemeColors = { // Figma keys appBG: Color; splashBG: Color; @@ -77,12 +77,10 @@ type ThemeColorsWithoutPageBackgroundColors = { skeletonLHNIn: Color; skeletonLHNOut: Color; QRLogo: Color; -}; -type ThemeColors = ThemeColorsWithoutPageBackgroundColors & { PAGE_BACKGROUND_COLORS: Record; }; export default ThemeColors; -export {type Color, type ThemeColorsWithoutPageBackgroundColors}; +export {type Color}; diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index f8b8e7df10b9..b8eccb761bfa 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -3,9 +3,9 @@ /* eslint-disable no-unused-vars */ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import ThemeColors, {ThemeColorsWithoutPageBackgroundColors} from './ThemeColors'; +import ThemeColors from './ThemeColors'; -const darkThemeWithoutPageBackgroundColors = { +const darkTheme = { // Figma keys appBG: colors.darkAppBackground, splashBG: colors.green400, @@ -82,12 +82,9 @@ const darkThemeWithoutPageBackgroundColors = { skeletonLHNIn: colors.darkBorders, skeletonLHNOut: colors.darkDefaultButton, QRLogo: colors.green400, -} satisfies ThemeColorsWithoutPageBackgroundColors; -const darkTheme = { - ...darkThemeWithoutPageBackgroundColors, PAGE_BACKGROUND_COLORS: { - [SCREENS.HOME]: darkThemeWithoutPageBackgroundColors.sidebar, + [SCREENS.HOME]: colors.darkHighlightBackground, [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, }, } satisfies ThemeColors; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 9b9b897acb1e..f5edb41e6094 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,8 +1,8 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import ThemeColors, {ThemeColorsWithoutPageBackgroundColors} from './ThemeColors'; +import ThemeColors from './ThemeColors'; -const lightThemeWithoutPageBackgroundColors = { +const lightTheme = { // Figma keys appBG: colors.lightAppBackground, splashBG: colors.green400, @@ -79,12 +79,9 @@ const lightThemeWithoutPageBackgroundColors = { skeletonLHNIn: colors.lightBorders, skeletonLHNOut: colors.lightDefaultButtonPressed, QRLogo: colors.green500, -} satisfies ThemeColorsWithoutPageBackgroundColors; -const lightTheme = { - ...lightThemeWithoutPageBackgroundColors, PAGE_BACKGROUND_COLORS: { - [SCREENS.HOME]: lightThemeWithoutPageBackgroundColors.sidebar, + [SCREENS.HOME]: colors.lightBorders, [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, }, } satisfies ThemeColors; From 08f5b4fd9101053e38db965c839acbe576fa6e17 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 10:37:51 +0200 Subject: [PATCH 013/170] fix: changed colors --- src/styles/themes/ThemeColors.ts | 5 ++++- src/styles/themes/default.ts | 4 +++- src/styles/themes/light.ts | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/styles/themes/ThemeColors.ts b/src/styles/themes/ThemeColors.ts index 19840a05491b..f4cdc3445c1f 100644 --- a/src/styles/themes/ThemeColors.ts +++ b/src/styles/themes/ThemeColors.ts @@ -16,6 +16,7 @@ type ThemeColors = { iconColorfulBackground: Color; textSupporting: Color; text: Color; + textColorfulBackground: Color; link: Color; linkHover: Color; buttonDefaultBG: Color; @@ -59,7 +60,8 @@ type ThemeColors = { heroCard: Color; uploadPreviewActivityIndicator: Color; dropUIBG: Color; - dropTransparentOverlay: Color; + receiptDropUIBG?: Color; + dropTransparentOverlay?: Color; checkBox: Color; pickerOptionsTextColor: Color; imageCropBackgroundColor: Color; @@ -77,6 +79,7 @@ type ThemeColors = { skeletonLHNIn: Color; skeletonLHNOut: Color; QRLogo: Color; + starDefaultBG: Color; PAGE_BACKGROUND_COLORS: Record; }; diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index b8eccb761bfa..f521efba6047 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -21,6 +21,7 @@ const darkTheme = { iconColorfulBackground: `${colors.ivory}cc`, textSupporting: colors.darkSupportingText, text: colors.darkPrimaryText, + textColorfulBackground: colors.ivory, link: colors.blue300, linkHover: colors.blue100, buttonDefaultBG: colors.darkDefaultButton, @@ -64,7 +65,7 @@ const darkTheme = { heroCard: colors.blue400, uploadPreviewActivityIndicator: colors.darkHighlightBackground, dropUIBG: 'rgba(6,27,9,0.92)', - dropTransparentOverlay: 'rgba(255,255,255,0)', + receiptDropUIBG: 'rgba(3, 212, 124, 0.84)', checkBox: colors.green400, pickerOptionsTextColor: colors.darkPrimaryText, imageCropBackgroundColor: colors.darkIcons, @@ -82,6 +83,7 @@ const darkTheme = { skeletonLHNIn: colors.darkBorders, skeletonLHNOut: colors.darkDefaultButton, QRLogo: colors.green400, + starDefaultBG: 'rgb(254, 228, 94)', PAGE_BACKGROUND_COLORS: { [SCREENS.HOME]: colors.darkHighlightBackground, diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index f5edb41e6094..d9c2eef42840 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -18,6 +18,7 @@ const lightTheme = { iconColorfulBackground: `${colors.ivory}cc`, textSupporting: colors.lightSupportingText, text: colors.lightPrimaryText, + textColorfulBackground: colors.ivory, link: colors.blue600, linkHover: colors.blue500, buttonDefaultBG: colors.lightDefaultButton, @@ -78,7 +79,8 @@ const lightTheme = { tooltipPrimaryText: colors.darkPrimaryText, skeletonLHNIn: colors.lightBorders, skeletonLHNOut: colors.lightDefaultButtonPressed, - QRLogo: colors.green500, + QRLogo: colors.green400, + starDefaultBG: 'rgb(254, 228, 94)', PAGE_BACKGROUND_COLORS: { [SCREENS.HOME]: colors.lightBorders, From fd4e5aa701d0400d9e7393fabaa086bea3b46ee3 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 10:41:24 +0200 Subject: [PATCH 014/170] fix: remove more diffs to main --- src/styles/themes/default.ts | 5 +++++ src/styles/themes/light.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index f521efba6047..ce96213a6bd5 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -4,6 +4,7 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; import ThemeColors from './ThemeColors'; +import ROUTES from '../../ROUTES'; const darkTheme = { // Figma keys @@ -88,6 +89,10 @@ const darkTheme = { PAGE_BACKGROUND_COLORS: { [SCREENS.HOME]: colors.darkHighlightBackground, [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + [ROUTES.SETTINGS_STATUS]: colors.green700, + [ROUTES.I_KNOW_A_TEACHER]: colors.tangerine800, + [ROUTES.SETTINGS_SECURITY]: colors.ice500, }, } satisfies ThemeColors; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index d9c2eef42840..154c717dee89 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,6 +1,7 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; import ThemeColors from './ThemeColors'; +import ROUTES from '../../ROUTES'; const lightTheme = { // Figma keys @@ -85,6 +86,10 @@ const lightTheme = { PAGE_BACKGROUND_COLORS: { [SCREENS.HOME]: colors.lightBorders, [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + [ROUTES.SETTINGS_STATUS]: colors.green700, + [ROUTES.I_KNOW_A_TEACHER]: colors.tangerine800, + [ROUTES.SETTINGS_SECURITY]: colors.ice500, }, } satisfies ThemeColors; From c794fd19a17285281137c4b59c02b154e628c2af Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 10:43:49 +0200 Subject: [PATCH 015/170] fix: typo --- src/styles/ThemeStylesContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/ThemeStylesContext.ts b/src/styles/ThemeStylesContext.ts index c32f994e16da..49f9eb127af9 100644 --- a/src/styles/ThemeStylesContext.ts +++ b/src/styles/ThemeStylesContext.ts @@ -1,7 +1,7 @@ import React from 'react'; import styles from './styles'; -// TODO: Change "uknown" once "styles.js" is typed +// TODO: Change "unknown" once "styles.js" is typed const ThemeStylesContext = React.createContext(styles); export default ThemeStylesContext; From 10f4778e1a75132a4864400192dcf8b770219f10 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 10:44:48 +0200 Subject: [PATCH 016/170] update todo comments --- src/styles/ThemeStylesProvider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index 1c5c8cf3437a..37f43c2c4f3f 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -2,10 +2,11 @@ import React, {useMemo} from 'react'; import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; -// TODO: Rename this to "styles" once the app is migrated to theme switching hooks and HOCs +// TODO: Rename this to "styles" once the styles are fully typed import {stylesGenerator as stylesUntyped} from './styles'; import ThemeColors from './themes/ThemeColors'; +// TODO: Remove this once the styles are fully typed const styles = stylesUntyped as (theme: ThemeColors) => Record; type ThemeStylesProviderProps = { From dba25a2a693737b6261cd6e73a46d07cdd9fc253 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 10:45:43 +0200 Subject: [PATCH 017/170] fix: naming --- src/styles/useThemeStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/useThemeStyles.ts b/src/styles/useThemeStyles.ts index a5b3baebbaec..69ba43692f49 100644 --- a/src/styles/useThemeStyles.ts +++ b/src/styles/useThemeStyles.ts @@ -5,7 +5,7 @@ function useThemeStyles() { const themeStyles = useContext(ThemeStylesContext); if (!themeStyles) { - throw new Error('StylesContext was null! Are you sure that you wrapped the component under a ?'); + throw new Error('ThemeStylesContext was null! Are you sure that you wrapped the component under a ?'); } // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337) From 0bbcd568dced2640225b31ec018dc39e278aa345 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 16:31:53 +0200 Subject: [PATCH 018/170] fix: update styles.ts --- src/styles/styles.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 632c02530e23..aa66d1a51c58 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -18,7 +18,6 @@ import overflowXHidden from './overflowXHidden'; import pointerEventsAuto from './pointerEventsAuto'; import pointerEventsNone from './pointerEventsNone'; import defaultTheme from './themes/default'; -import {ThemeDefault} from './themes/types'; import cursor from './utilities/cursor'; import display from './utilities/display'; import flex from './utilities/flex'; @@ -33,6 +32,7 @@ import whiteSpace from './utilities/whiteSpace'; import wordBreak from './utilities/wordBreak'; import writingDirection from './utilities/writingDirection'; import variables from './variables'; +import ThemeColors from './themes/ThemeColors'; type AnchorPosition = { horizontal: number; @@ -76,7 +76,7 @@ const touchCalloutNone: Pick = Browser.isMobile // to prevent vertical text offset in Safari for badges, new lineHeight values have been added const lineHeightBadge: Pick = Browser.isSafari() ? {lineHeight: variables.lineHeightXSmall} : {lineHeight: variables.lineHeightNormal}; -const picker = (theme: ThemeDefault) => +const picker = (theme: ThemeColors) => ({ backgroundColor: theme.transparent, color: theme.text, @@ -92,14 +92,14 @@ const picker = (theme: ThemeDefault) => textAlign: 'left', } satisfies TextStyle); -const link = (theme: ThemeDefault) => +const link = (theme: ThemeColors) => ({ color: theme.link, textDecorationColor: theme.link, fontFamily: fontFamily.EXP_NEUE, } satisfies ViewStyle & MixedStyleDeclaration); -const baseCodeTagStyles = (theme: ThemeDefault) => +const baseCodeTagStyles = (theme: ThemeColors) => ({ borderWidth: 1, borderRadius: 5, @@ -112,7 +112,7 @@ const headlineFont = { fontWeight: '500', } satisfies TextStyle; -const webViewStyles = (theme: ThemeDefault) => +const webViewStyles = (theme: ThemeColors) => ({ // As of react-native-render-html v6, don't declare distinct styles for // custom renderers, the API for custom renderers has changed. Declare the @@ -206,7 +206,7 @@ const webViewStyles = (theme: ThemeDefault) => }, } satisfies WebViewStyle); -const styles = (theme: ThemeDefault) => +const styles = (theme: ThemeColors) => ({ // Add all of our utility and helper styles ...spacing, From 2b6f41222ca7190095b2be743558e046c0089c6f Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Sep 2023 16:37:00 +0200 Subject: [PATCH 019/170] feat: update types int ThemeStylesProvider logic --- src/styles/ThemeStylesContext.ts | 5 ++--- src/styles/ThemeStylesProvider.tsx | 3 +-- src/styles/styles.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/styles/ThemeStylesContext.ts b/src/styles/ThemeStylesContext.ts index 49f9eb127af9..3df2b19b31bf 100644 --- a/src/styles/ThemeStylesContext.ts +++ b/src/styles/ThemeStylesContext.ts @@ -1,7 +1,6 @@ import React from 'react'; -import styles from './styles'; +import styles, {type Styles} from './styles'; -// TODO: Change "unknown" once "styles.js" is typed -const ThemeStylesContext = React.createContext(styles); +const ThemeStylesContext = React.createContext(styles); export default ThemeStylesContext; diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index 09b7fd2837be..581edab55f3f 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -2,9 +2,8 @@ import React, {useMemo} from 'react'; import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; -// TODO: Rename this to "styles" once the styles are fully typed +// TODO: Replace this import with "styles" once the static style export from "styles.js" isn't used anymore import {stylesGenerator} from './styles'; -import ThemeColors from './themes/ThemeColors'; type ThemeStylesProviderProps = { children: React.ReactNode; diff --git a/src/styles/styles.ts b/src/styles/styles.ts index aa66d1a51c58..e2db4989661c 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -4054,4 +4054,4 @@ const stylesGenerator = styles; const defaultStyles = styles(defaultTheme); export default defaultStyles; -export {stylesGenerator}; +export {stylesGenerator, type Styles}; From e4d4a107a271619e8746bb0ba82441b95a06b57f Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 20 Sep 2023 08:30:55 +0200 Subject: [PATCH 020/170] fix: merge main --- src/styles/styles.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index e2db4989661c..e15779f78d0d 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -3743,27 +3743,26 @@ const styles = (theme: ThemeColors) => }, loginButtonRow: { - justifyContent: 'center', width: '100%', + gap: 12, ...flex.flexRow, + ...flex.justifyContentCenter, }, loginButtonRowSmallScreen: { - justifyContent: 'center', width: '100%', - marginBottom: 10, + gap: 12, ...flex.flexRow, + ...flex.justifyContentCenter, + marginBottom: 10, }, - appleButtonContainer: { + desktopSignInButtonContainer: { width: 40, height: 40, - marginRight: 20, }, signInIconButton: { - margin: 10, - marginTop: 0, padding: 2, }, @@ -3771,7 +3770,6 @@ const styles = (theme: ThemeColors) => colorScheme: 'light', width: 40, height: 40, - marginLeft: 12, alignItems: 'center', overflow: 'hidden', }, From 3f2830063ab69f0e0aa8ea2bc03e25189215e287 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 20 Sep 2023 08:31:01 +0200 Subject: [PATCH 021/170] enforce consistent themes --- src/styles/themes/ThemeColors.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/themes/ThemeColors.ts b/src/styles/themes/ThemeColors.ts index f4cdc3445c1f..2347cbd3633f 100644 --- a/src/styles/themes/ThemeColors.ts +++ b/src/styles/themes/ThemeColors.ts @@ -61,7 +61,6 @@ type ThemeColors = { uploadPreviewActivityIndicator: Color; dropUIBG: Color; receiptDropUIBG?: Color; - dropTransparentOverlay?: Color; checkBox: Color; pickerOptionsTextColor: Color; imageCropBackgroundColor: Color; From fb20af781e1c2c8425fadd49cad2f248a4e6d0b1 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 20 Sep 2023 08:39:36 +0200 Subject: [PATCH 022/170] fix: move ThemeColors to types.ts --- src/styles/colors.ts | 2 +- src/styles/styles.ts | 2 +- src/styles/themes/ThemeColors.ts | 88 ------------------------------- src/styles/themes/ThemeContext.ts | 2 +- src/styles/themes/default.ts | 2 +- src/styles/themes/light.ts | 2 +- src/styles/themes/types.ts | 88 +++++++++++++++++++++++++++++-- src/styles/themes/useTheme.ts | 2 +- 8 files changed, 89 insertions(+), 99 deletions(-) delete mode 100644 src/styles/themes/ThemeColors.ts diff --git a/src/styles/colors.ts b/src/styles/colors.ts index 5b05d16bb9b3..aa12699ebdea 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -1,4 +1,4 @@ -import {Color} from './themes/ThemeColors'; +import {Color} from './themes/types'; /** * DO NOT import colors.js into files. Use the theme switching hooks and HOCs instead. diff --git a/src/styles/styles.ts b/src/styles/styles.ts index e15779f78d0d..ce3e2938d26d 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -32,7 +32,7 @@ import whiteSpace from './utilities/whiteSpace'; import wordBreak from './utilities/wordBreak'; import writingDirection from './utilities/writingDirection'; import variables from './variables'; -import ThemeColors from './themes/ThemeColors'; +import {ThemeColors} from './themes/types'; type AnchorPosition = { horizontal: number; diff --git a/src/styles/themes/ThemeColors.ts b/src/styles/themes/ThemeColors.ts deleted file mode 100644 index 2347cbd3633f..000000000000 --- a/src/styles/themes/ThemeColors.ts +++ /dev/null @@ -1,88 +0,0 @@ -type Color = string; - -type ThemeColors = { - // Figma keys - appBG: Color; - splashBG: Color; - highlightBG: Color; - border: Color; - borderLighter: Color; - borderFocus: Color; - icon: Color; - iconMenu: Color; - iconHovered: Color; - iconSuccessFill: Color; - iconReversed: Color; - iconColorfulBackground: Color; - textSupporting: Color; - text: Color; - textColorfulBackground: Color; - link: Color; - linkHover: Color; - buttonDefaultBG: Color; - buttonHoveredBG: Color; - buttonPressedBG: Color; - danger: Color; - dangerHover: Color; - dangerPressed: Color; - warning: Color; - success: Color; - successHover: Color; - successPressed: Color; - transparent: Color; - signInPage: Color; - - // Additional keys - overlay: Color; - inverse: Color; - shadow: Color; - componentBG: Color; - hoverComponentBG: Color; - activeComponentBG: Color; - signInSidebar: Color; - sidebar: Color; - sidebarHover: Color; - heading: Color; - textLight: Color; - textDark: Color; - textReversed: Color; - textBackground: Color; - textMutedReversed: Color; - textError: Color; - offline: Color; - modalBackdrop: Color; - modalBackground: Color; - cardBG: Color; - cardBorder: Color; - spinner: Color; - unreadIndicator: Color; - placeholderText: Color; - heroCard: Color; - uploadPreviewActivityIndicator: Color; - dropUIBG: Color; - receiptDropUIBG?: Color; - checkBox: Color; - pickerOptionsTextColor: Color; - imageCropBackgroundColor: Color; - fallbackIconColor: Color; - reactionActiveBackground: Color; - reactionActiveText: Color; - badgeAdHoc: Color; - badgeAdHocHover: Color; - mentionText: Color; - mentionBG: Color; - ourMentionText: Color; - ourMentionBG: Color; - tooltipSupportingText: Color; - tooltipPrimaryText: Color; - skeletonLHNIn: Color; - skeletonLHNOut: Color; - QRLogo: Color; - starDefaultBG: Color; - - PAGE_BACKGROUND_COLORS: Record; -}; - -export default ThemeColors; - -export {type Color}; diff --git a/src/styles/themes/ThemeContext.ts b/src/styles/themes/ThemeContext.ts index 98bed9ce822c..8c57cc9c7e9f 100644 --- a/src/styles/themes/ThemeContext.ts +++ b/src/styles/themes/ThemeContext.ts @@ -1,6 +1,6 @@ import React from 'react'; import darkTheme from './default'; -import ThemeColors from './ThemeColors'; +import {ThemeColors} from './types'; const ThemeContext = React.createContext(darkTheme); diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index ce96213a6bd5..2fa17b832a72 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -3,7 +3,7 @@ /* eslint-disable no-unused-vars */ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import ThemeColors from './ThemeColors'; +import {ThemeColors} from './types'; import ROUTES from '../../ROUTES'; const darkTheme = { diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 0a9fda7344b8..9b39a94f02a6 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,6 +1,6 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import ThemeColors from './ThemeColors'; +import {ThemeColors} from './types'; import ROUTES from '../../ROUTES'; const lightTheme = { diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index 40b8da361654..1ffe3e776a7e 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,8 +1,86 @@ -import DeepRecord from '../../types/utils/DeepRecord'; -import defaultTheme from './default'; +type Color = string; -type ThemeBase = DeepRecord; +type ThemeColors = { + // Figma keys + appBG: Color; + splashBG: Color; + highlightBG: Color; + border: Color; + borderLighter: Color; + borderFocus: Color; + icon: Color; + iconMenu: Color; + iconHovered: Color; + iconSuccessFill: Color; + iconReversed: Color; + iconColorfulBackground: Color; + textSupporting: Color; + text: Color; + textColorfulBackground: Color; + link: Color; + linkHover: Color; + buttonDefaultBG: Color; + buttonHoveredBG: Color; + buttonPressedBG: Color; + danger: Color; + dangerHover: Color; + dangerPressed: Color; + warning: Color; + success: Color; + successHover: Color; + successPressed: Color; + transparent: Color; + signInPage: Color; -type ThemeDefault = typeof defaultTheme; + // Additional keys + overlay: Color; + inverse: Color; + shadow: Color; + componentBG: Color; + hoverComponentBG: Color; + activeComponentBG: Color; + signInSidebar: Color; + sidebar: Color; + sidebarHover: Color; + heading: Color; + textLight: Color; + textDark: Color; + textReversed: Color; + textBackground: Color; + textMutedReversed: Color; + textError: Color; + offline: Color; + modalBackdrop: Color; + modalBackground: Color; + cardBG: Color; + cardBorder: Color; + spinner: Color; + unreadIndicator: Color; + placeholderText: Color; + heroCard: Color; + uploadPreviewActivityIndicator: Color; + dropUIBG: Color; + receiptDropUIBG?: Color; + checkBox: Color; + pickerOptionsTextColor: Color; + imageCropBackgroundColor: Color; + fallbackIconColor: Color; + reactionActiveBackground: Color; + reactionActiveText: Color; + badgeAdHoc: Color; + badgeAdHocHover: Color; + mentionText: Color; + mentionBG: Color; + ourMentionText: Color; + ourMentionBG: Color; + tooltipSupportingText: Color; + tooltipPrimaryText: Color; + skeletonLHNIn: Color; + skeletonLHNOut: Color; + QRLogo: Color; + starDefaultBG: Color; -export type {ThemeBase, ThemeDefault}; + PAGE_BACKGROUND_COLORS: Record; +}; + +export {type ThemeColors, type Color}; diff --git a/src/styles/themes/useTheme.ts b/src/styles/themes/useTheme.ts index 51ee044d59c1..8bb4fe73c106 100644 --- a/src/styles/themes/useTheme.ts +++ b/src/styles/themes/useTheme.ts @@ -1,6 +1,6 @@ import {useContext} from 'react'; import ThemeContext from './ThemeContext'; -import ThemeColors from './ThemeColors'; +import {ThemeColors} from './types'; function useTheme(): ThemeColors { const theme = useContext(ThemeContext); From 33d0bc8e0d69338e1a337a54ed52aa1f953f351d Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 20 Sep 2023 08:40:30 +0200 Subject: [PATCH 023/170] fix: make colors non optional --- src/styles/themes/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index 1ffe3e776a7e..eb72685bbd80 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -60,7 +60,7 @@ type ThemeColors = { heroCard: Color; uploadPreviewActivityIndicator: Color; dropUIBG: Color; - receiptDropUIBG?: Color; + receiptDropUIBG: Color; checkBox: Color; pickerOptionsTextColor: Color; imageCropBackgroundColor: Color; From 0b7a8724bcc2295e00915181e9b868c7770785d3 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 20 Sep 2023 08:45:31 +0200 Subject: [PATCH 024/170] fix: add remaining color in light theme --- src/styles/themes/light.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 9b39a94f02a6..fb1747b2d77b 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -63,7 +63,7 @@ const lightTheme = { heroCard: colors.blue400, uploadPreviewActivityIndicator: colors.lightHighlightBackground, dropUIBG: 'rgba(252, 251, 249, 0.92)', - receiptDropUIBG: '', // TODO: add color + receiptDropUIBG: 'rgba(3, 212, 124, 0.84)', checkBox: colors.green400, pickerOptionsTextColor: colors.lightPrimaryText, imageCropBackgroundColor: colors.lightIcons, From f2475fee355d83ffc6a7c5af19a2880d6d33e81b Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 11:16:45 +0200 Subject: [PATCH 025/170] Migrate localize lib to TS --- .../{BaseLocaleListener.js => BaseLocaleListener.ts} | 10 +++------- .../{index.desktop.js => index.desktop.ts} | 3 ++- .../Localize/LocaleListener/{index.js => index.ts} | 0 src/libs/Localize/LocaleListener/types.ts | 6 ++++++ src/libs/Localize/{index.js => index.ts} | 4 +--- tsconfig.json | 3 ++- 6 files changed, 14 insertions(+), 12 deletions(-) rename src/libs/Localize/LocaleListener/{BaseLocaleListener.js => BaseLocaleListener.ts} (79%) rename src/libs/Localize/LocaleListener/{index.desktop.js => index.desktop.ts} (80%) rename src/libs/Localize/LocaleListener/{index.js => index.ts} (100%) create mode 100644 src/libs/Localize/LocaleListener/types.ts rename src/libs/Localize/{index.js => index.ts} (98%) diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.js b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts similarity index 79% rename from src/libs/Localize/LocaleListener/BaseLocaleListener.js rename to src/libs/Localize/LocaleListener/BaseLocaleListener.ts index 9d4e62c374b0..8cf8319109b6 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.js +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -1,15 +1,14 @@ import Onyx from 'react-native-onyx'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; +import BaseLocale from './types'; -let preferredLocale = CONST.LOCALES.DEFAULT; +let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT; /** * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx. - * - * @param {Function} [callbackAfterChange] */ -const connect = (callbackAfterChange = () => {}) => { +const connect = (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => { Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => { @@ -23,9 +22,6 @@ const connect = (callbackAfterChange = () => {}) => { }); }; -/* - * @return {String} - */ function getPreferredLocale() { return preferredLocale; } diff --git a/src/libs/Localize/LocaleListener/index.desktop.js b/src/libs/Localize/LocaleListener/index.desktop.ts similarity index 80% rename from src/libs/Localize/LocaleListener/index.desktop.js rename to src/libs/Localize/LocaleListener/index.desktop.ts index 0c0d723122da..a75372207b6a 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.js +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,8 +1,9 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; +import BaseLocale from './types'; export default { - connect: (callbackAfterChange = () => {}) => + connect: (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); diff --git a/src/libs/Localize/LocaleListener/index.js b/src/libs/Localize/LocaleListener/index.ts similarity index 100% rename from src/libs/Localize/LocaleListener/index.js rename to src/libs/Localize/LocaleListener/index.ts diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts new file mode 100644 index 000000000000..bce58a6b9ac9 --- /dev/null +++ b/src/libs/Localize/LocaleListener/types.ts @@ -0,0 +1,6 @@ +import {ValueOf} from 'type-fest'; +import CONST from '../../../CONST'; + +type BaseLocale = ValueOf; + +export default BaseLocale; diff --git a/src/libs/Localize/index.js b/src/libs/Localize/index.ts similarity index 98% rename from src/libs/Localize/index.js rename to src/libs/Localize/index.ts index db371301f43f..5143c2337ad9 100644 --- a/src/libs/Localize/index.js +++ b/src/libs/Localize/index.ts @@ -134,10 +134,8 @@ function arrayToString(anArray) { /** * Returns the user device's preferred language. - * - * @return {String} */ -function getDevicePreferredLocale() { +function getDevicePreferredLocale(): string { return lodashGet(RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES]), 'languageTag', CONST.LOCALES.DEFAULT); } diff --git a/tsconfig.json b/tsconfig.json index 0c88512b9749..c0065dc30c78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "es2021.weakref", "es2022.array", "es2022.object", - "es2022.string" + "es2022.string", + "ES2021.Intl" ], "allowJs": true, "checkJs": false, From ddcbd5279505a754cc55190a4d9c802b9b1057e0 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 2 Oct 2023 13:26:26 +0200 Subject: [PATCH 026/170] Resolve lodash conflicts --- src/libs/Localize/index.ts | 78 +++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 5143c2337ad9..c790ea3fa435 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,5 +1,3 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import * as RNLocalize from 'react-native-localize'; import Log from '../Log'; @@ -14,49 +12,47 @@ LocaleListener.connect(); // Note: This has to be initialized inside a function and not at the top level of the file, because Intl is polyfilled, // and if React Native executes this code upon import, then the polyfill will not be available yet and it will barf -let CONJUNCTION_LIST_FORMATS_FOR_LOCALES; +let CONJUNCTION_LIST_FORMATS_FOR_LOCALES: Record; function init() { - CONJUNCTION_LIST_FORMATS_FOR_LOCALES = _.reduce( - CONST.LOCALES, - (memo, locale) => { - // This is not a supported locale, so we'll use ES_ES instead - if (locale === CONST.LOCALES.ES_ES_ONFIDO) { - // eslint-disable-next-line no-param-reassign - memo[locale] = new Intl.ListFormat(CONST.LOCALES.ES_ES, {style: 'long', type: 'conjunction'}); - return memo; - } - + CONJUNCTION_LIST_FORMATS_FOR_LOCALES = Object.values(CONST.LOCALES).reduce((memo: Record, locale) => { + // This is not a supported locale, so we'll use ES_ES instead + if (locale === CONST.LOCALES.ES_ES_ONFIDO) { // eslint-disable-next-line no-param-reassign - memo[locale] = new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'}); + memo[locale] = new Intl.ListFormat(CONST.LOCALES.ES_ES, {style: 'long', type: 'conjunction'}); return memo; - }, - {}, - ); + } + + // eslint-disable-next-line no-param-reassign + memo[locale] = new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'}); + return memo; + }, {}); } /** * Return translated string for given locale and phrase * - * @param {String} [desiredLanguage] eg 'en', 'es-ES' - * @param {String} phraseKey - * @param {Object} [phraseParameters] Parameters to supply if the phrase is a template literal. - * @returns {String} + * @param [desiredLanguage] eg 'en', 'es-ES' + * @param [phraseParameters] Parameters to supply if the phrase is a template literal. */ -function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phraseParameters = {}) { - const languageAbbreviation = desiredLanguage.substring(0, 2); +function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: string, phraseParameters: unknown = {}): string { + const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; let translatedPhrase; // Search phrase in full locale e.g. es-ES - const desiredLanguageDictionary = translations[desiredLanguage] || {}; - translatedPhrase = desiredLanguageDictionary[phraseKey]; + const desiredLanguageDictionary = translations?.[desiredLanguage as keyof typeof translations] ?? {}; + translatedPhrase = desiredLanguageDictionary?.[phraseKey as keyof typeof desiredLanguageDictionary]; if (translatedPhrase) { + // console.log('1translatedPhrase=', translatedPhrase); + // console.log('1phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in full locale, search it in fallback language e.g. es const fallbackLanguageDictionary = translations[languageAbbreviation] || {}; - translatedPhrase = fallbackLanguageDictionary[phraseKey]; + translatedPhrase = fallbackLanguageDictionary?.[phraseKey as keyof typeof fallbackLanguageDictionary] ?? ''; if (translatedPhrase) { + // console.log('2translatedPhrase=', translatedPhrase); + // console.log('2phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } if (languageAbbreviation !== CONST.LOCALES.DEFAULT) { @@ -67,13 +63,17 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; translatedPhrase = defaultLanguageDictionary[phraseKey]; if (translatedPhrase) { + // console.log('3translatedPhrase=', translatedPhrase); + // console.log('3phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in default language, on production log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION) { - const phraseString = _.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; + // console.log('4translatedPhrase=', translatedPhrase); + // console.log('4phraseParameters=', phraseParameters); + const phraseString = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); return phraseString; } @@ -82,29 +82,22 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar /** * Uses the locale in this file updated by the Onyx subscriber. - * - * @param {String|Array} phrase - * @param {Object} [variables] - * @returns {String} */ -function translateLocal(phrase, variables) { +function translateLocal(phrase: string, variables: unknown) { return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables); } /** * Return translated string for given error. - * - * @param {String|Array} message - * @returns {String} */ -function translateIfPhraseKey(message) { - if (_.isEmpty(message)) { +function translateIfPhraseKey(message: string | [string, {isTranslated: boolean}]): string { + if (!message || (Array.isArray(message) && message.length > 0)) { return ''; } try { // check if error message has a variable parameter - const [phrase, variables] = _.isArray(message) ? message : [message]; + const [phrase, variables] = Array.isArray(message) ? message : [message]; // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. @@ -114,17 +107,14 @@ function translateIfPhraseKey(message) { return translateLocal(phrase, variables); } catch (error) { - return message; + return Array.isArray(message) ? message[0] : message; } } /** * Format an array into a string with comma and "and" ("a dog, a cat and a chicken") - * - * @param {Array} anArray - * @return {String} */ -function arrayToString(anArray) { +function arrayToString(anArray: string[]) { if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { init(); } @@ -136,7 +126,7 @@ function arrayToString(anArray) { * Returns the user device's preferred language. */ function getDevicePreferredLocale(): string { - return lodashGet(RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES]), 'languageTag', CONST.LOCALES.DEFAULT); + return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale}; From 3f1cf4b266bf38176240f864505dae51be5913da Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:39:52 -0600 Subject: [PATCH 027/170] Update The-Expenses-Page.md re: https://github.com/Expensify/Expensify/issues/311354 --- .../The-Expenses-Page.md | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md index f30dde9efc3d..892d0c4fd330 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -1,5 +1,76 @@ --- title: The Expenses Page -description: The Expenses Page +description: Details on Expenses Page filters --- -## Resource Coming Soon! +--- +Expenses Page +--- +# Overview + +The Expenses page allows you to see all of your personal expenses. If you are an admin, you can view all submitter’s expenses on the Expensify page. The Expenses page can be filtered in several ways to give you spending visibility, find expenses to submit and export to a spreadsheet (CSV). + +## Expense filters +Here are the available filters you can use on the Expenses Page: + +- **Date Range:** Find expenses within a specific time frame. +- **Merchant Name:** Search for expenses from a particular merchant. (Partial search terms also work if you need clarification on the exact name match.) +- **Workspace:** Locate specific Group/Individual Workspace expenses. +- **Categories:** Group expenses by category or identify those without a category. +- **Tags:** Filter expenses with specific tags. +- **Submitters:** Narrow expenses by submitter (employee or vendor). +- **Personal Expenses:** Find all expenses yet to be included in a report. A Workspace admin can see these expenses once they are on a Processing, Approved, or Reimbursed report. +- **Open:** Display expenses on reports that still need to be submitted (not submitted). +- **Processing, Approved, Reimbursed:** See expenses on reports at various stages – processing, approved, or reimbursed. +- **Closed:** View expenses on closed reports (not submitted for approval). + +Here's how to make the most of these filters: + +1. Log into your web account +2. Go to the **Expenses** page +3. At the top of the page, click on **Show Filters** +4. Adjust the filters to match your specific needs + +Note, you might notice that not all expense filters are always visible. They adapt based on the data you're currently filtering and persist from the last time you logged in. For instance, you won't see the deleted filter if there are no **Deleted** expenses to filter out. + +If you are not seeing what you expected, you may have too many filters applied. Click **Reset** at the top to clear your filters. + + +# How to add an expense to a report from the Expenses Page +The submitter (and their copilot) can add expenses to a report from the Expenses page. + +Note, when expenses aren’t on a report, they are **personal expenses**. So you’ll want to make sure you haven’t filtered out **personal expenses** expenses, or you won’t be able to see them. + +1. Find the expense you want to add. (Hint: Use the filters to sort expenses by the desired date range if it is not a recent expense.) +2. Then, select the expense you want to add to a report. You can click Select All to select multiple expenses. +3. Click **Add to Report** in the upper right corner, and choose either an existing report or create a new one. + +# How to code expenses from the Expenses Page +To code expenses from the Expenses page, do the following: + +1. Look for the **Tag**, **Category**, and **Description** columns on the **Expenses** page. +2. Click on the relevant field for a specific expense and add or update the **Category**, **Tag**, or **Description**. + +Note, you can also open up individual expenses by clicking on them to see a detailed look, but coding the expenses from the Expense list is even faster and more convenient! + +# How to export expenses to a CSV file or spreadsheet +If you want to export multiple expenses, run through the below steps: +Select the expenses you want to export by checking the box to the left of each expense. +Then, click **Export To** in the upper right corner of the page, and choose our default CSV format or create your own custom CSV template. + + +# FAQ + +## Can I use the filters and analytics features on the mobile app? +The various features on the Expenses Page are only available while logged into your web account. + +## As a Workspace admin, what submitter expenses can you see? +A Workspace admin can see Processing, Approved, and Reimbursed expenses as long as they were submitted on the workspace that you are an admin. + +If employees submit expense reports on a workspace where you are not an admin, you will not have visibility into those expenses. Additionally, if an expense is left unreported, a workspace admin will not be able to see that expense until it’s been added to a report. + +A Workspace admin can edit the tags and categories on an expense, but if they want to edit the amount, date, or merchant name, the expense will need to be in a Processing state or rejected back to the submitter for changes. +We have more about company card expense reconciliation in this support article. + +## Can I edit multiple expenses at once? +Yes! Select the expenses you want to edit and click **Edit Multiple**. + From caadaeb57f6168f32d36413d35d515b77926839f Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 11:29:29 +0200 Subject: [PATCH 028/170] Migrate localize to TS --- src/libs/Localize/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index c790ea3fa435..0a87a8579f58 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -61,7 +61,7 @@ function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: // Phrase is not translated, search it in default language (en) const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; - translatedPhrase = defaultLanguageDictionary[phraseKey]; + translatedPhrase = defaultLanguageDictionary[phraseKey as keyof typeof defaultLanguageDictionary] ?? ''; if (translatedPhrase) { // console.log('3translatedPhrase=', translatedPhrase); // console.log('3phraseParameters=', phraseParameters); From 444710247dba60d2f42e9d3e39a5d94a853151e4 Mon Sep 17 00:00:00 2001 From: Al Garces Date: Thu, 12 Oct 2023 16:52:06 -0700 Subject: [PATCH 029/170] Close Account on Mobile --- .../ExpensifyHelp_CloseAccount_Mobile.png | Bin 0 -> 132318 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/images/ExpensifyHelp_CloseAccount_Mobile.png diff --git a/docs/assets/images/ExpensifyHelp_CloseAccount_Mobile.png b/docs/assets/images/ExpensifyHelp_CloseAccount_Mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..6853953a5c6b703274dc800883e5d81f38573b29 GIT binary patch literal 132318 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 Ts?MuWcj3rE&jaQAgKqr~HD@(5 literal 0 HcmV?d00001 From 8600a05567232ee1e51c1fc1d8e44eaade4d1787 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 13 Oct 2023 19:08:38 +0200 Subject: [PATCH 030/170] ref: moved LinearGradient component to TS --- src/components/LinearGradient/index.js | 3 --- src/components/LinearGradient/index.native.js | 3 --- src/components/LinearGradient/index.native.ts | 6 ++++++ src/components/LinearGradient/index.ts | 6 ++++++ src/components/LinearGradient/types.ts | 6 ++++++ .../react-native-web-linear-gradient.d.ts | 18 ++++++++++++++++++ 6 files changed, 36 insertions(+), 6 deletions(-) delete mode 100644 src/components/LinearGradient/index.js delete mode 100644 src/components/LinearGradient/index.native.js create mode 100644 src/components/LinearGradient/index.native.ts create mode 100644 src/components/LinearGradient/index.ts create mode 100644 src/components/LinearGradient/types.ts create mode 100644 src/types/modules/react-native-web-linear-gradient.d.ts diff --git a/src/components/LinearGradient/index.js b/src/components/LinearGradient/index.js deleted file mode 100644 index 8270681641d0..000000000000 --- a/src/components/LinearGradient/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import LinearGradient from 'react-native-web-linear-gradient'; - -export default LinearGradient; diff --git a/src/components/LinearGradient/index.native.js b/src/components/LinearGradient/index.native.js deleted file mode 100644 index c8d5af2646b2..000000000000 --- a/src/components/LinearGradient/index.native.js +++ /dev/null @@ -1,3 +0,0 @@ -import LinearGradient from 'react-native-linear-gradient'; - -export default LinearGradient; diff --git a/src/components/LinearGradient/index.native.ts b/src/components/LinearGradient/index.native.ts new file mode 100644 index 000000000000..46bed24ebc10 --- /dev/null +++ b/src/components/LinearGradient/index.native.ts @@ -0,0 +1,6 @@ +import LinearGradientNative from 'react-native-linear-gradient'; +import LinearGradient from './types'; + +const LinearGradientImplementation: LinearGradient = LinearGradientNative; + +export default LinearGradientImplementation; diff --git a/src/components/LinearGradient/index.ts b/src/components/LinearGradient/index.ts new file mode 100644 index 000000000000..7246ccf2fb69 --- /dev/null +++ b/src/components/LinearGradient/index.ts @@ -0,0 +1,6 @@ +import LinearGradientWeb from 'react-native-web-linear-gradient'; +import LinearGradient from './types'; + +const LinearGradientImplementation: LinearGradient = LinearGradientWeb; + +export default LinearGradientImplementation; diff --git a/src/components/LinearGradient/types.ts b/src/components/LinearGradient/types.ts new file mode 100644 index 000000000000..555a12f1657c --- /dev/null +++ b/src/components/LinearGradient/types.ts @@ -0,0 +1,6 @@ +import LinearGradientWeb from 'react-native-web-linear-gradient'; +import LinearGradientNative from 'react-native-linear-gradient'; + +type LinearGradient = typeof LinearGradientWeb | typeof LinearGradientNative; + +export default LinearGradient; diff --git a/src/types/modules/react-native-web-linear-gradient.d.ts b/src/types/modules/react-native-web-linear-gradient.d.ts new file mode 100644 index 000000000000..a7c43d401670 --- /dev/null +++ b/src/types/modules/react-native-web-linear-gradient.d.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ +declare module 'react-native-web-linear-gradient' { + import type {ViewProps} from 'react-native'; + + interface LinearGradientProps extends ViewProps { + colors: string[]; + start?: {x: number; y: number}; + end?: {x: number; y: number}; + locations?: number[]; + useAngle?: boolean; + angle?: number; + } + + class LinearGradient extends React.PureComponent {} + + export default LinearGradient; +} From 46608d0bd1b9de33d9f592b38df733c751485b82 Mon Sep 17 00:00:00 2001 From: kimkurta Date: Fri, 13 Oct 2023 15:23:46 -0500 Subject: [PATCH 031/170] Update CSV-Import.md Resolving help site migration https://github.com/Expensify/Expensify/issues/309825 --- .../company-cards/CSV-Import.md | 99 ++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md index 6debce6240ff..14e6633d32c1 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md @@ -1,5 +1,98 @@ --- -title: CSV Import -description: CSV Import +title: Import and assign company cards from CSV file +description: uploading a CSV file containing your company card transactions --- -## Resource Coming Soon! + +# Overview +Expensify offers a convenient CSV import feature for managing company card expenses when direct connections or commercial card feeds aren't available. This feature allows you to upload a CSV file containing your company card transactions and assign them to cardholders within your Expensify domain. +This feature is available on Group Workspaces and requires Domain Admin access. + +# How to import company cards via CSV +1. Download a CSV of transactions from your bank by logging into their website and finding the relevant statement. +2. Format the CSV for upload using [this template](https://s3-us-west-1.amazonaws.com/concierge-responses-expensify-com/uploads%2F1594908368712-Best+Example+CSV+for+Domains.csv) as a guide. +- At a minimum, your file must include the following columns: + - **Card Number** - each number in this column should display at least the last four digits, and you can obscure up to 12 characters +(e.g., 543212XXXXXX12334). + - **Posted Date** - use the YYYY-MM-DD format in this column (and any other date column in your spreadsheet). + - **Merchant** - the name of the individual or business that provided goods or services for the transaction. This is a free-text field. + - **Posted Amount** - use the number format in this column, and indicate negative amounts with parentheses (e.g., (335.98) for -$335.98). + - **Posted Currency** - use currency codes (e.g., USD, GBP, EUR) to indicate the currency of the posted transactions. +- You can also add mapping for Categories and Tags, but those parameters are optional. +3. Log into Expensify on your web browser. +4. Head to Settings > Domains > Domain Name > Company Cards +5. Click Manage/Import CSV +6. Create a Company Card Layout Name for your spreadsheet +7. Click Upload CSV +8. Review the mapping of your spreadsheet to ensure that the Card Number, Date, Merchant, Amount, and Currency match your data. +9. Double-check the Output Preview for any errors and, if needed, refer to the common error solutions listed in the FAQ below. +10. Once the mapping is correct, click Submit Spreadsheet to complete the import. +11. After submitting the spreadsheet, click I'll wait a minute. Then, wait about 1-2 minutes for the import to process. The domain page will refresh once the upload is complete. + +# How to assign new cards +If you’re assigning cards via CSV upload for the first time: +1. Head to **Settings > Domains > Domain Name > Company Cards** +2. Find the new CSV feed in the drop-down list underneath **Imported Cards** +3. Click **Assign New Cards** +4. Under **Assign a Card**, enter the relevant info +5. Click **Assign** +From there, transactions will be imported to the cardholder's account, where they can add receipts, code the expenses, and submit them for review and approval. +# How to upload new expenses for existing assigned cards +There's no need to create a new upload layout for subsequent CSV uploads. Instead, add new expenses to the existing CSV: +1. Head to **Settings > Domains > Domain Name > Company Cards** +2. Click **Manage/Import CSV** +3. Select the saved layout from the drop-down list +4. Click **Upload CSV** +5. After uploading the more recent CSV, click **Update All Cards** to retrieve the new expenses for the assigned cards. + +# Deep dive +If the CSV upload isn’t formatted correctly, it will cause issues when you try to import or assign cards. Let’s go over some common issues and how to fix them. + +## Error: “Attribute value mapping is missing” +If you encounter an error that says "Attribute-value mapping is missing,” the spreadsheet likely lacks critical details like Card Number, Date, Merchant, Amount, or Currency. To resolve: +1. Click the **X** at the top of the page to close the mapping window +2. Confirm what’s missing from the spreadsheet +3. Add a new column to your spreadsheet and add the missing detail +4. Upload the revised spreadsheet by clicking **Manage Spreadsheet** +5. Enter a **Company Card Layout Name** for the contents of your spreadsheet +6. Click **Upload CSV** + +## Error: “We’ve detected an error while processing your spreadsheet feed” +This error usually occurs when there’s an upload issue. +To troubleshoot this: +1. Head to **Settings > Domains > Domain Name > Company Cards** and click **Manage/Import CSV** +2. In the **Upload Company Card transactions for** dropdown list, look for the layout name you previously created. +3. If the layout is listed, wait at least one hour and then sync the cards to see if new transactions are imported. +4. If the layout isn’t listed, create a new **Company Card Layout Name** and upload the spreadsheet again. + +## Error: “An unexpected error occurred, and we could not retrieve the list of cards” +This error occurs when there’s an issue uploading the spreadsheet or the upload fails. +To troubleshoot this: +1. Head to **Settings > Domains > Domain Name > Company Cards** and click **Manage/Import CSV** +2. In the **Upload Company Card transactions for** dropdown list, look for the layout name you previously created. +3. If the layout is listed, wait at least one hour and then sync the cards to see if new transactions are imported. +4. If the layout isn’t listed, create a new **Company Card Layout Name** and upload the spreadsheet again. + + +## I added a new parameter to an existing spreadsheet, but the data isn’t showing in Expensify after the upload completes. What’s going on? +If you added a new card to an existing spreadsheet and imported it via a saved layout, but it isn’t showing up for assignment, this suggests that the modification may have caused an issue. +The next step in troubleshooting this issue is to compare the number of rows on the revised spreadsheet to the Output Preview to ensure the row count matches the revised spreadsheet. +To check this: +1. Head to **Settings > Domains > Domain Name > Company Cards** and click **Manage/Import CSV** +2. Select your saved layout in the dropdown list +3. Click **Upload CSV** and select the revised spreadsheet +4. Compare the Output Preview row count to your revised spreadsheet to ensure they match +[insert image here] +If they don’t match, you’ll need to revise the spreadsheet by following the CSV formatting guidelines in step 2 of “How to import company cards via CSV” above. +Once you do that, save the revised spreadsheet with a new layout name. +Then, try to upload the revised spreadsheet again: +1. Click **Upload CSV** +2. Upload the revised file +3. Check the row count again on the Output Preview to confirm it matches the spreadsheet +4. Click **Submit Spreadsheet** +# FAQ +## Why can’t I see my CSV transactions immediately after uploading them? +Don’t worry! You’ll typically need to wait 1-2 minutes after clicking **I understand, I'll wait!** + +## I'm trying to import a credit. Why isn't it uploading? +Negative expenses shouldn’t include a minus sign. Instead, they should just be wrapped in parentheses. For example, to indicate “-335.98,” you’ll want to make sure it’s formatted as “(335.98).” + From 645650bb5689f1114f0c6b1efe5a281d3f9b82f6 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 17 Oct 2023 11:05:18 +0200 Subject: [PATCH 032/170] Cleanup --- src/languages/translations.ts | 2 +- src/languages/types.ts | 1 + .../LocaleListener/BaseLocaleListener.ts | 2 +- .../Localize/LocaleListener/index.desktop.ts | 2 +- src/libs/Localize/index.ts | 28 +++++++++---------- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/languages/translations.ts b/src/languages/translations.ts index d228394589b2..4d89f1f529de 100644 --- a/src/languages/translations.ts +++ b/src/languages/translations.ts @@ -46,5 +46,5 @@ export default { en: flattenObject(en), es: flattenObject(es), // eslint-disable-next-line @typescript-eslint/naming-convention - 'es-ES': esES, + 'es-ES': flattenObject(esES), }; diff --git a/src/languages/types.ts b/src/languages/types.ts index 52f2df8b3765..a7d219cc8201 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -239,6 +239,7 @@ export type { EnglishTranslation, TranslationFlatObject, AddressLineParams, + TranslationPaths, CharacterLimitParams, MaxParticipantsReachedParams, ZipCodeExampleFormatParams, diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts index 8cf8319109b6..03b59c470cd7 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -8,7 +8,7 @@ let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT; /** * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx. */ -const connect = (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => { +const connect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => { Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => { diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index a75372207b6a..424d65a347b3 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -3,7 +3,7 @@ import BaseLocaleListener from './BaseLocaleListener'; import BaseLocale from './types'; export default { - connect: (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => + connect: (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 0a87a8579f58..114a0c09f71a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -6,6 +6,7 @@ import translations from '../../languages/translations'; import CONST from '../../CONST'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; +import {TranslationFlatObject, TranslationPaths} from '../../languages/types'; // Listener when an update in Onyx happens so we use the updated locale when translating/localizing items. LocaleListener.connect(); @@ -28,31 +29,32 @@ function init() { }, {}); } +type PhraseParameters = T extends (arg: infer A) => string ? A : never; + /** * Return translated string for given locale and phrase * * @param [desiredLanguage] eg 'en', 'es-ES' * @param [phraseParameters] Parameters to supply if the phrase is a template literal. */ -function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: string, phraseParameters: unknown = {}): string { +function translate( + desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', + phraseKey: TKey, + phraseParameters: PhraseParameters = {} as PhraseParameters, +): string { const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; - let translatedPhrase; // Search phrase in full locale e.g. es-ES - const desiredLanguageDictionary = translations?.[desiredLanguage as keyof typeof translations] ?? {}; - translatedPhrase = desiredLanguageDictionary?.[phraseKey as keyof typeof desiredLanguageDictionary]; + const desiredLanguageDictionary = translations?.[desiredLanguage]; + let translatedPhrase = desiredLanguageDictionary[phraseKey]; if (translatedPhrase) { - // console.log('1translatedPhrase=', translatedPhrase); - // console.log('1phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in full locale, search it in fallback language e.g. es const fallbackLanguageDictionary = translations[languageAbbreviation] || {}; - translatedPhrase = fallbackLanguageDictionary?.[phraseKey as keyof typeof fallbackLanguageDictionary] ?? ''; + translatedPhrase = fallbackLanguageDictionary?.[phraseKey] ?? ''; if (translatedPhrase) { - // console.log('2translatedPhrase=', translatedPhrase); - // console.log('2phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } if (languageAbbreviation !== CONST.LOCALES.DEFAULT) { @@ -61,18 +63,14 @@ function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: // Phrase is not translated, search it in default language (en) const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; - translatedPhrase = defaultLanguageDictionary[phraseKey as keyof typeof defaultLanguageDictionary] ?? ''; + translatedPhrase = defaultLanguageDictionary[phraseKey] ?? ''; if (translatedPhrase) { - // console.log('3translatedPhrase=', translatedPhrase); - // console.log('3phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in default language, on production log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION) { - // console.log('4translatedPhrase=', translatedPhrase); - // console.log('4phraseParameters=', phraseParameters); const phraseString = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); return phraseString; @@ -83,7 +81,7 @@ function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: string, variables: unknown) { +function translateLocal(phrase: TKey, variables: PhraseParameters = {} as PhraseParameters) { return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables); } From f3182a20c6a84d968a46c7a9ed4551a953ae6d0f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 17 Oct 2023 16:46:40 +0200 Subject: [PATCH 033/170] fix: removed uncessary eslint disable --- src/types/modules/react-native-web-linear-gradient.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/modules/react-native-web-linear-gradient.d.ts b/src/types/modules/react-native-web-linear-gradient.d.ts index a7c43d401670..f41ef6de3a13 100644 --- a/src/types/modules/react-native-web-linear-gradient.d.ts +++ b/src/types/modules/react-native-web-linear-gradient.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ declare module 'react-native-web-linear-gradient' { import type {ViewProps} from 'react-native'; From 7df66227dfdd3b3d63bbbbda6a00ce036941f7e1 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 18 Oct 2023 15:08:42 +0700 Subject: [PATCH 034/170] fix: 27902 --- src/pages/workspace/WorkspaceInviteMessagePage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 5e7efadd3778..f59cab53bca8 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -76,7 +76,7 @@ class WorkspaceInviteMessagePage extends React.Component { this.validate = this.validate.bind(this); this.openPrivacyURL = this.openPrivacyURL.bind(this); this.state = { - welcomeNote: this.getDefaultWelcomeNote(), + welcomeNote: this.props.savedWelcomeMessage || this.getDefaultWelcomeNote(), }; } @@ -228,6 +228,7 @@ class WorkspaceInviteMessagePage extends React.Component { defaultValue={this.state.welcomeNote} value={this.state.welcomeNote} onChangeText={(text) => this.setState({welcomeNote: text})} + shouldSaveDraft /> @@ -250,6 +251,10 @@ export default compose( invitedEmailsToAccountIDsDraft: { key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, }, + savedWelcomeMessage: { + key: `${ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM}Draft`, + selector: (draft) => draft ? draft.welcomeMessage : '' + } }), withNavigationFocus, )(WorkspaceInviteMessagePage); From 806d3442624541a500d1c201054cbbc0db5404dd Mon Sep 17 00:00:00 2001 From: kimkurta Date: Wed, 18 Oct 2023 08:25:44 -0500 Subject: [PATCH 035/170] Update docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md Co-authored-by: Rushat Gabhane --- .../bank-accounts-and-credit-cards/company-cards/CSV-Import.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md index 14e6633d32c1..892cfdde1bde 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md @@ -89,6 +89,7 @@ Then, try to upload the revised spreadsheet again: 2. Upload the revised file 3. Check the row count again on the Output Preview to confirm it matches the spreadsheet 4. Click **Submit Spreadsheet** + # FAQ ## Why can’t I see my CSV transactions immediately after uploading them? Don’t worry! You’ll typically need to wait 1-2 minutes after clicking **I understand, I'll wait!** From 6d61ffd80cb1a814c8a2dd1d56e86d6cdaa46f8e Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 18 Oct 2023 18:56:16 +0200 Subject: [PATCH 036/170] New Localize.translate typing --- src/libs/Localize/index.ts | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 5682e19a8ebf..e3ea30bb869e 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,4 +1,3 @@ -import Str from 'expensify-common/lib/str'; import * as RNLocalize from 'react-native-localize'; import Onyx from 'react-native-onyx'; import Log from '../Log'; @@ -14,12 +13,11 @@ import ONYXKEYS from '../../ONYXKEYS'; let userEmail = ''; Onyx.connect({ key: ONYXKEYS.SESSION, - waitForCollectionCallback: true, callback: (val) => { if (!val) { return; } - userEmail = val?.email; + userEmail = val?.email ?? ''; }, }); @@ -44,7 +42,8 @@ function init() { }, {}); } -type PhraseParameters = T extends (arg: infer A) => string ? A : never; +type PhraseParameters = T extends (...args: infer A) => string ? A : never[]; +type Phrase = TranslationFlatObject[TKey] extends (...args: infer A) => unknown ? (...args: A) => string : string; /** * Return translated string for given locale and phrase @@ -52,35 +51,29 @@ type PhraseParameters = T extends (arg: infer A) => string ? A : never; * @param [desiredLanguage] eg 'en', 'es-ES' * @param [phraseParameters] Parameters to supply if the phrase is a template literal. */ -function translate( - desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', - phraseKey: TKey, - phraseParameters: PhraseParameters = {} as PhraseParameters, -): string { - const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; - +function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: TKey, ...phraseParameters: PhraseParameters>): string { // Search phrase in full locale e.g. es-ES - const desiredLanguageDictionary = translations?.[desiredLanguage]; - let translatedPhrase = desiredLanguageDictionary[phraseKey]; + const language = desiredLanguage === CONST.LOCALES.ES_ES_ONFIDO ? CONST.LOCALES.ES_ES : desiredLanguage; + let translatedPhrase = translations?.[language]?.[phraseKey] as Phrase; if (translatedPhrase) { - return Str.result(translatedPhrase, phraseParameters); + return typeof translatedPhrase === 'function' ? translatedPhrase(...phraseParameters) : translatedPhrase; } // Phrase is not found in full locale, search it in fallback language e.g. es - const fallbackLanguageDictionary = translations[languageAbbreviation] || {}; - translatedPhrase = fallbackLanguageDictionary?.[phraseKey] ?? ''; + const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; + translatedPhrase = translations?.[languageAbbreviation]?.[phraseKey] as Phrase; if (translatedPhrase) { - return Str.result(translatedPhrase, phraseParameters); + return typeof translatedPhrase === 'function' ? translatedPhrase(...phraseParameters) : translatedPhrase; } + if (languageAbbreviation !== CONST.LOCALES.DEFAULT) { Log.alert(`${phraseKey} was not found in the ${languageAbbreviation} locale`); } // Phrase is not translated, search it in default language (en) - const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; - translatedPhrase = defaultLanguageDictionary[phraseKey] ?? ''; + translatedPhrase = translations?.[CONST.LOCALES.DEFAULT]?.[phraseKey] as Phrase; if (translatedPhrase) { - return Str.result(translatedPhrase, phraseParameters); + return typeof translatedPhrase === 'function' ? translatedPhrase(...phraseParameters) : translatedPhrase; } // Phrase is not found in default language, on production and staging log an alert to server From cb37d5f91bee686598236ffb1eafeb01a7d21fe6 Mon Sep 17 00:00:00 2001 From: John Schuster Date: Wed, 18 Oct 2023 15:27:02 -0500 Subject: [PATCH 037/170] Update Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md --- ...ok-For-Small-To-Medium-Sized-Businesses.md | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index d933e66cc2d1..c275e05dba7b 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -22,23 +22,23 @@ If you don't already have one, go to *[new.expensify.com](https://new.expensify. > **Robyn Gresham** > Senior Accounting Systems Manager at SunCommon -## Step 2: Create a Control Policy -There are three policy types, but for your small business needs we recommend the *Control Plan* for the following reasons: +## Step 2: Create a Control Workspace +There are three workspace types, but for your small business needs we recommend the *Control Plan* for the following reasons: - *The Control Plan* is designed for organizations with a high volume of employee expense submissions, who also rely on compliance controls - The ease of use and mobile-first design of the Control plan can increase employee adoption and participation, leading to better expense tracking and management. - The plan integrates with a variety of tools, including accounting software and payroll systems, providing a seamless and integrated experience - Accounting integrations include QuickBooks Online, Xero, NetSuite, and Sage Intacct, with indirect support from Microsoft Dynamics and any other accounting solution you work with -We recommend creating one single policy for your US entity. This allows you to centrally manage all employees in one “group” while enforcing compliance controls and syncing with your accounting package accordingly. +We recommend creating one single workspace for your US entity. This allows you to centrally manage all employees in one “group” while enforcing compliance controls and syncing with your accounting package accordingly. -To create your Control Policy: +To create your Control Workspace: -1. Go to *Settings > Policies* -2. Select *Group* and click the button that says *New Policy* +1. Go to *Settings > Workspace* +2. Select *Group* and click the button that says *New Workspace* 3. Click *Select* under Control -The Control Plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and in your company’s policy settings in the *Overview* tab, where you can chat with them and schedule an onboarding call to walk through any setup questions. The Control Plan bundled with the Expensify Card is only *$9 per user per month* (not taking into account cash back your earn) when you commit annually. That’s a 75% discount off the unbundled price point if you choose to use a different Corporate Card (or no) provider. +The Control Plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your workspace's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and in your company’s workspace settings in the *Overview* tab, where you can chat with them and schedule an onboarding call to walk through any setup questions. The Control Plan bundled with the Expensify Card is only *$9 per user per month* (not taking into account cash back your earn) when you commit annually. That’s a 75% discount off the unbundled price point if you choose to use a different Corporate Card (or no) provider. ## Step 3: Connect your accounting system As a small to medium-sized business, it's important to maintain proper spend management to ensure the success and stability of your organization. This requires paying close attention to your expenses, streamlining your financial processes, and making sure that your financial information is accurate, compliant, and transparent. Include best practices such as: @@ -49,15 +49,15 @@ As a small to medium-sized business, it's important to maintain proper spend man You do this by synchronizing Expensify and your accounting package as follows: -1. Click *Settings > Policies* +1. Click *Settings > Workspace* 2. Navigate to the *Connections* tab 3. Select your accounting system 4. Follow the prompts to connect your accounting package Check out the links below for more information on how to connect to your accounting solution: -- *[QuickBooks Online](https://community.expensify.com/discussion/4833/how-to-connect-your-policy-to-quickbooks-online)* -- *[Xero](https://community.expensify.com/discussion/5282/how-to-connect-your-policy-to-xero)* -- *[NetSuite](https://community.expensify.com/discussion/5212/how-to-connect-your-policy-to-netsuite-token-based-authentication)* +- *[QuickBooks Online](https://community.expensify.com/discussion/4833/how-to-connect-your-workspace-to-quickbooks-online)* +- *[Xero](https://community.expensify.com/discussion/5282/how-to-connect-your-workspace-to-xero)* +- *[NetSuite](https://community.expensify.com/discussion/5212/how-to-connect-your-workspace-to-netsuite-token-based-authentication)* - *[Sage Intacct](https://community.expensify.com/discussion/4777/how-to-connect-to-sage-intacct-user-based-permissions-expense-reports)* - *[Other Accounting System](https://community.expensify.com/discussion/5271/how-to-set-up-an-indirect-accounting-integration) @@ -82,15 +82,15 @@ Head over to the *Categories* tab to set compliance controls on your newly impor Tags in Expensify often relate to departments, projects/customers, classes, and so on. And in some cases they are *required* to be selected on every transactions. And in others, something like *departments* is a static field, meaning we could set it as an employee default and not enforce the tag selection with each expense. *Make Tags Required* -In the tags tab in your policy settings, you’ll notice the option to enable the “Required” field. This makes it so any time an employee doesn’t assign a tag to an expense, we’ll flag a violation on it and notify both the employee and the approver. +In the tags tab in your workspace settings, you’ll notice the option to enable the “Required” field. This makes it so any time an employee doesn’t assign a tag to an expense, we’ll flag a violation on it and notify both the employee and the approver. - *Note:* In general, we take prior selection into account, so anytime you select a tag in Expensify, we’ll pre-populate that same field for any subsequent expense. It’s completely interchangeable, and there for convenience. *Set Tags as an Employee Default* -Separately, if your policy is connected to NetSuite or Sage Intacct, you can set departments, for example, as an employee default. All that means is we’ll apply the department (for example) that’s assigned to the employee record in your accounting package and apply that to every exported transaction, eliminating the need for the employee to have to manually select a department for each expense. +Separately, if your workspace is connected to NetSuite or Sage Intacct, you can set departments, for example, as an employee default. All that means is we’ll apply the department (for example) that’s assigned to the employee record in your accounting package and apply that to every exported transaction, eliminating the need for the employee to have to manually select a department for each expense. ## Step 6: Set rules for all expenses regardless of categorization -In the Expenses tab in your group Control policy, you’ll notice a *Violations* section designed to enforce top-level compliance controls that apply to every expense, for every employee in your policy. We recommend the following confiuration: +In the Expenses tab in your group Control workspace, you’ll notice a *Violations* section designed to enforce top-level compliance controls that apply to every expense, for every employee in your workspace. We recommend the following confiuration: *Max Expense Age: 90 days (or leave it blank)* This will enable Expensify to catch employee reimbursement requests that are far too outdated for reimbursement, and present them as a violations. If you’d prefer a different time window, you can edit it accordingly @@ -106,17 +106,17 @@ Receipts are important, and in most cases you prefer an itemized receipt. Howeve At this point, you’ve set enough compliance controls around categorical spend and general expenses for all employees, such that you can put trust in our solution to audit all expenses up front so you don’t have to. Next, let’s dive into how we can comfortably take on more automation, while relying on compliance controls to capture bad behavior (or better yet, instill best practices in our employees). ## Step 7: Set up scheduled submit -For an efficient company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy) on a *Daily* frequency: +For an efficient company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-workspace) on a *Daily* frequency: -- Click *Settings > Policies* -- From here, select your group collect policy -- Within your policy settings, select the *Reports* tab +- Click *Settings > Workspace* +- From here, select your group collect workspace +- Within your workspace settings, select the *Reports* tab - You’ll notice *Scheduled Submit* is located directly under *Report Basics* - Choose *Daily* Between Expensify's SmartScan technology, automatic categorization, and [DoubleCheck](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work) features, your employees shouldn't need to do anything more than swipe their Expensify Card or take a photo of their receipt. -Expenses with violations will stay behind for the employee to fix, while expenses that are “in-policy” will move into an approver’s queue to mitigate any potential for delays. Scheduled Submit will ensure all expenses are submitted automatically for approval. +Expenses with violations will stay behind for the employee to fix, while expenses that are “in-workspace” will move into an approver’s queue to mitigate any potential for delays. Scheduled Submit will ensure all expenses are submitted automatically for approval. ![Scheduled submit](https://help.expensify.com/assets/images/playbook-scheduled-submit.png){:width="100%"} @@ -162,8 +162,8 @@ In this case we recommend setting *Manually approve all expenses over: $0* ## Step 10: Configure Auto-Approval Knowing you have all the control you need to review reports, we recommend configuring auto-approval for *all reports*. Why? Because you’ve already put reports through an entire approval workflow, and manually triggering reimbursement is an unnecessary action at this stage. -1. Navigate to *Settings > Policies > Group > [Policy Name] > Reimbursement* -2. Set your *Manual Reimbursement threshold to $20,0000* +1. Navigate to *Settings > Workspace > Group > [Workspace Name] > Reimbursement* +2. Set your *Manual Reimbursement threshold to $20,000* ## Step 11: Enable Domains and set up your corporate card feed for employees Expensify is optimized to work with corporate cards from all banks – or even better, use our own perfectly integrated *[Expensify Card](https://use.expensify.com/company-credit-card)*. The first step for connecting to any bank you use for corporate cards, and the Expensify Card is to validate your company’s domain in Domain settings. @@ -203,7 +203,7 @@ The Expensify Card is recommended as the most efficient way to manage your compa Here’s how to enable it: 1. There are *two ways* you can [apply for the Expensify Card](https://community.expensify.com/discussion/4874/how-to-apply-for-the-expensify-card) - - *Via your Inbox* + - *Via your tasks on the Home page* - *Via Domain Settings* - Go to Settings > Domain > Company Cards > Enable Expensify Card 2. Assign the cards to your employees 3. Set *SmartLimits*: @@ -219,7 +219,7 @@ As a small business, managing bills and invoices can be a complex and time-consu Here are some of the key benefits of using Expensify for bill payments and invoicing: - Flexible payment options: Expensify allows you to pay your bills via ACH, credit card, or check, so you can choose the option that works best for you (US businesses only). -- Free, No Fees: The bill pay and invoicing features come included with every policy and workspace, so you won't need to pay any additional fees. +- Free, No Fees: The bill pay and invoicing features come included with every workspace and workspace, so you won't need to pay any additional fees. - Integration with your business bank account: With your business bank account verified, you can easily link your finances to receive payment from customers when invoices are paid. Let’s first chat through how Bill Pay works @@ -244,7 +244,7 @@ Reports, invoices, and bills are largely the same, in theory, just with differen 2. Add all of the expenses/transactions tied to the Invoice 3. Enter the recipient’s email address, a memo if needed, and a due date for when it needs to get paid, and click *Send* -You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your policy settings under the Invoices tab. Your customers can pay their invoice in Expensify via ACH, or Check, or Credit Card. +You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your workspace settings under the Invoices tab. Your customers can pay their invoice in Expensify via ACH, or Check, or Credit Card. ## Step 13: Run monthly, quarterly and annual reporting At this stage, reporting is important and given that Expensify is the primary point of entry for all employee spend, we make reporting visually appealing and wildly customizable. @@ -266,7 +266,7 @@ Our pricing model is unique in the sense that you are in full control of your bi To set your subscription, head to: -1. Settings > Policies +1. Settings > Workspace 2. Select *Group* 3. Scroll down to *Subscription* 4. Select *Annual Subscription* @@ -281,4 +281,4 @@ Now that we’ve gone through all of the steps for setting up your account, let 4. Click *Accept Terms* # You’re all set! -Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. +Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Workspace, and we’ll automatically assign a dedicated Setup Specialist to you. From 58a59de71799c2c9e2208d20bb72a79719ef6f39 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 19 Oct 2023 09:16:41 +0200 Subject: [PATCH 038/170] remove TODO --- src/styles/ThemeStylesProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index 581edab55f3f..2ef9a8521e4d 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -2,7 +2,6 @@ import React, {useMemo} from 'react'; import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; -// TODO: Replace this import with "styles" once the static style export from "styles.js" isn't used anymore import {stylesGenerator} from './styles'; type ThemeStylesProviderProps = { From 2caad032d15b66b39fe3ca6e21046a0023add5a7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 19 Oct 2023 09:17:28 +0200 Subject: [PATCH 039/170] replace comment --- src/styles/colors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/colors.ts b/src/styles/colors.ts index aa12699ebdea..fbe694e051ee 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -6,7 +6,7 @@ import {Color} from './themes/types'; * For class components, you can use the `withTheme` and `withThemeStyles` HOCs */ const colors: Record = { - // TODO: Find a good name/description for this block of colors. + // Brand Colors black: '#000000', white: '#FFFFFF', ivory: '#fffaf0', From ef2771c22e51956bb195eb259176b22ffe9d7929 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 19 Oct 2023 09:17:48 +0200 Subject: [PATCH 040/170] remove TODO --- src/styles/themes/default.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 0edd7f90e1df..f8be30a9d881 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,4 +1,3 @@ -// TODO: For consistency reasons, rename this file to "dark.ts" after theme switching migration is done (GH issue:) import colors from '../colors'; import SCREENS from '../../SCREENS'; import {ThemeColors} from './types'; From 23bcb925508d4a2e56d25195f1c2876bb1b5c37d Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 19 Oct 2023 09:25:27 +0200 Subject: [PATCH 041/170] remove TODO block --- src/styles/themes/useThemePreference.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/styles/themes/useThemePreference.ts b/src/styles/themes/useThemePreference.ts index 4a7dd067f0d5..6e07ab35f65c 100644 --- a/src/styles/themes/useThemePreference.ts +++ b/src/styles/themes/useThemePreference.ts @@ -3,15 +3,12 @@ import {Appearance, ColorSchemeName} from 'react-native'; import CONST from '../../CONST'; import {PreferredThemeContext} from '../../components/OnyxProvider'; -// TODO: Remove this once "OnyxProvider" is typed -type PreferredThemeContextType = React.Context<(typeof CONST.THEME)[keyof typeof CONST.THEME]>; - type ThemePreference = typeof CONST.THEME.LIGHT | typeof CONST.THEME.DARK; function useThemePreference() { const [themePreference, setThemePreference] = useState(CONST.THEME.DEFAULT); const [systemTheme, setSystemTheme] = useState(); - const preferredThemeFromStorage = useContext(PreferredThemeContext as PreferredThemeContextType); + const preferredThemeFromStorage = useContext(PreferredThemeContext); useEffect(() => { // This is used for getting the system theme, that can be set in the OS's theme settings. This will always return either "light" or "dark" and will update automatically if the OS theme changes. @@ -20,7 +17,7 @@ function useThemePreference() { }, []); useEffect(() => { - const theme = preferredThemeFromStorage || CONST.THEME.DEFAULT; + const theme = preferredThemeFromStorage ?? CONST.THEME.DEFAULT; // If the user chooses to use the device theme settings, we need to set the theme preference to the system theme if (theme === CONST.THEME.SYSTEM) { From 02d15a8d183eb5370642a0dfb06982475cb375e0 Mon Sep 17 00:00:00 2001 From: John Schuster Date: Thu, 19 Oct 2023 13:36:55 -0500 Subject: [PATCH 042/170] Update Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md --- ...-Playbook-For-Small-To-Medium-Sized-Businesses.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index c275e05dba7b..9e6ea7ef68e7 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -55,11 +55,11 @@ You do this by synchronizing Expensify and your accounting package as follows: 4. Follow the prompts to connect your accounting package Check out the links below for more information on how to connect to your accounting solution: -- *[QuickBooks Online](https://community.expensify.com/discussion/4833/how-to-connect-your-workspace-to-quickbooks-online)* -- *[Xero](https://community.expensify.com/discussion/5282/how-to-connect-your-workspace-to-xero)* -- *[NetSuite](https://community.expensify.com/discussion/5212/how-to-connect-your-workspace-to-netsuite-token-based-authentication)* -- *[Sage Intacct](https://community.expensify.com/discussion/4777/how-to-connect-to-sage-intacct-user-based-permissions-expense-reports)* -- *[Other Accounting System](https://community.expensify.com/discussion/5271/how-to-set-up-an-indirect-accounting-integration) +- *[QuickBooks Online](https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online#gsc.tab=0)* +- *[Xero](https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Xero#gsc.tab=0)* +- *[NetSuite](https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/NetSuite#gsc.tab=0)* +- *[Sage Intacct](https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct#gsc.tab=0)* +- *[Other Accounting System](https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations#gsc.tab=0) *“Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical.”* @@ -106,7 +106,7 @@ Receipts are important, and in most cases you prefer an itemized receipt. Howeve At this point, you’ve set enough compliance controls around categorical spend and general expenses for all employees, such that you can put trust in our solution to audit all expenses up front so you don’t have to. Next, let’s dive into how we can comfortably take on more automation, while relying on compliance controls to capture bad behavior (or better yet, instill best practices in our employees). ## Step 7: Set up scheduled submit -For an efficient company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-workspace) on a *Daily* frequency: +For an efficient company, we recommend setting up [Scheduled Submit](https://help.expensify.com/articles/expensify-classic/policy-and-domain-settings/reports/Scheduled-Submit#gsc.tab=0) on a *Daily* frequency: - Click *Settings > Workspace* - From here, select your group collect workspace From c66ab48ae28542fd3a1e58832740a4334ebb866d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 20 Oct 2023 10:24:38 +0200 Subject: [PATCH 043/170] Last typecheck fixes --- .../Localize/LocaleListener/index.desktop.ts | 20 ++++++++++--------- src/libs/Localize/LocaleListener/index.ts | 5 ++++- src/libs/Localize/LocaleListener/types.ts | 3 +++ src/libs/Localize/index.ts | 17 ++++++++-------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index 424d65a347b3..8445d6152ea7 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,14 +1,16 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; -import BaseLocale from './types'; +import BaseLocale, {LocaleListenerConnect} from './types'; -export default { - connect: (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => - BaseLocaleListener.connect((val) => { - // Send the updated locale to the Electron main process - window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); +const localeListener: LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => + BaseLocaleListener.connect((val) => { + // Send the updated locale to the Electron main process + window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); + + // Then execute the callback provided for the renderer process + callbackAfterChange(val); + }); - // Then execute the callback provided for the renderer process - callbackAfterChange(val); - }), +export default { + connect: localeListener, }; diff --git a/src/libs/Localize/LocaleListener/index.ts b/src/libs/Localize/LocaleListener/index.ts index e5f1ea03f93f..6a725be29662 100644 --- a/src/libs/Localize/LocaleListener/index.ts +++ b/src/libs/Localize/LocaleListener/index.ts @@ -1,5 +1,8 @@ import BaseLocaleListener from './BaseLocaleListener'; +import {LocaleListenerConnect} from './types'; + +const localeListener: LocaleListenerConnect = BaseLocaleListener.connect; export default { - connect: BaseLocaleListener.connect, + connect: localeListener, }; diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index bce58a6b9ac9..44dd3dcc359a 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -3,4 +3,7 @@ import CONST from '../../../CONST'; type BaseLocale = ValueOf; +type LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void) => void; + +export type {LocaleListenerConnect}; export default BaseLocale; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index e3ea30bb869e..1493a9bbf788 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -79,7 +79,7 @@ function translate(desiredLanguage: 'en' | 'es' | // Phrase is not found in default language, on production and staging log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION || Config.IS_IN_STAGING) { - const phraseString = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; + const phraseString: string = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); if (userEmail.includes(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) { return CONST.MISSING_TRANSLATION; @@ -92,14 +92,14 @@ function translate(desiredLanguage: 'en' | 'es' | /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: TKey, variables: PhraseParameters = {} as PhraseParameters) { - return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables); +function translateLocal(phrase: TKey, variables: PhraseParameters>) { + return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } /** * Return translated string for given error. */ -function translateIfPhraseKey(message: string | [string, {isTranslated: boolean}]): string { +function translateIfPhraseKey(message: TKey | [TKey, PhraseParameters> & {isTranslated?: true}]): string { if (!message || (Array.isArray(message) && message.length > 0)) { return ''; } @@ -110,13 +110,14 @@ function translateIfPhraseKey(message: string | [string, {isTranslated: boolean} // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. - if (variables && variables.isTranslated) { - return phrase; + if (variables?.isTranslated) { + return phrase as string; } - return translateLocal(phrase, variables); + return translateLocal(phrase, variables as PhraseParameters>); } catch (error) { - return Array.isArray(message) ? message[0] : message; + const result: string = Array.isArray(message) ? message[0] : message; + return result; } } From 56172e9b4987d6d5328fe000b807f29f7236dcaa Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Fri, 20 Oct 2023 14:57:38 +0200 Subject: [PATCH 044/170] Migrate to TS --- src/components/SelectCircle.js | 40 --------------------------------- src/components/SelectCircle.tsx | 29 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 40 deletions(-) delete mode 100644 src/components/SelectCircle.js create mode 100644 src/components/SelectCircle.tsx diff --git a/src/components/SelectCircle.js b/src/components/SelectCircle.js deleted file mode 100644 index 55e410f8baa1..000000000000 --- a/src/components/SelectCircle.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import styles from '../styles/styles'; -import Icon from './Icon'; -import * as Expensicons from './Icon/Expensicons'; -import themeColors from '../styles/themes/default'; - -const propTypes = { - /** Should we show the checkmark inside the circle */ - isChecked: PropTypes.bool, - - /** Additional styles to pass to SelectCircle */ - // eslint-disable-next-line react/forbid-prop-types - styles: PropTypes.arrayOf(PropTypes.object), -}; - -const defaultProps = { - isChecked: false, - styles: [], -}; - -function SelectCircle(props) { - return ( - - {props.isChecked && ( - - )} - - ); -} - -SelectCircle.propTypes = propTypes; -SelectCircle.defaultProps = defaultProps; -SelectCircle.displayName = 'SelectCircle'; - -export default SelectCircle; diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx new file mode 100644 index 000000000000..5e8d12cb7308 --- /dev/null +++ b/src/components/SelectCircle.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import {View} from 'react-native'; +import globalStyles from '../styles/styles'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import themeColors from '../styles/themes/default'; + +type SelectCircleProps = { + /** Should we show the checkmark inside the circle */ + isChecked: boolean; + + /** Additional styles to pass to SelectCircle */ + styles: Array>; +}; + +function SelectCircle({isChecked, styles}: SelectCircleProps) { + return ( + + {isChecked && ( + + )} + + ); +} + +export default SelectCircle; From dcb7386abe23c70b18457faf8272421bc70495fc Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Fri, 20 Oct 2023 15:04:36 +0200 Subject: [PATCH 045/170] Restore default values --- src/components/SelectCircle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx index 5e8d12cb7308..97939f7acff5 100644 --- a/src/components/SelectCircle.tsx +++ b/src/components/SelectCircle.tsx @@ -13,7 +13,7 @@ type SelectCircleProps = { styles: Array>; }; -function SelectCircle({isChecked, styles}: SelectCircleProps) { +function SelectCircle({isChecked = false, styles = []}: SelectCircleProps) { return ( {isChecked && ( From e8c96dbb91a53b99b01945526c44064f60e391b0 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 20 Oct 2023 16:07:59 -0400 Subject: [PATCH 046/170] Use OSBotify App Token in cherryPick --- .github/workflows/cherryPick.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index e6da6fff1446..43f3c64554bc 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -41,6 +41,7 @@ jobs: token: ${{ secrets.OS_BOTIFY_TOKEN }} - name: Set up git for OSBotify + id: setupGitForOSBotify uses: Expensify/App/.github/actions/composite/setupGitForOSBotifyApp@8c19d6da4a3d7ce3b15c9cd89a802187d208ecab with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} @@ -119,7 +120,7 @@ jobs: **Important:** There may be conflicts that GitHub is not able to detect, so please _carefully_ review this pull request before approving." gh pr edit --add-assignee "${{ github.actor }},${{ steps.getCPMergeCommit.outputs.MERGE_ACTOR }}" env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} - name: "Announces a CP failure in the #announce Slack room" uses: 8398a7/action-slack@v3 From c0a57b91e18bf71ba1eddca884f482ca8eb5620f Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 20 Oct 2023 16:10:39 -0400 Subject: [PATCH 047/170] use new token in finishReleaseCycle --- .github/workflows/finishReleaseCycle.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml index 4fe6249edacc..f8b68786aaab 100644 --- a/.github/workflows/finishReleaseCycle.yml +++ b/.github/workflows/finishReleaseCycle.yml @@ -34,13 +34,13 @@ jobs: echo "IS_DEPLOYER=false" >> "$GITHUB_OUTPUT" fi env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} - name: Reopen and comment on issue (not a team member) if: ${{ !fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) }} uses: Expensify/App/.github/actions/javascript/reopenIssueWithComment@main with: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number }} COMMENT: | Sorry, only members of @Expensify/Mobile-Deployers can close deploy checklists. @@ -51,14 +51,14 @@ jobs: id: checkDeployBlockers uses: Expensify/App/.github/actions/javascript/checkDeployBlockers@main with: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number }} - name: Reopen and comment on issue (has blockers) if: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) && fromJSON(steps.checkDeployBlockers.outputs.HAS_DEPLOY_BLOCKERS || 'false') }} uses: Expensify/App/.github/actions/javascript/reopenIssueWithComment@main with: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number }} COMMENT: | This issue either has unchecked items or has not yet been marked with the `:shipit:` emoji of approval. From 482d0016179a4fb7f1a9fc47ef9205a58c3a8c66 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Mon, 9 Oct 2023 09:46:59 +0300 Subject: [PATCH 048/170] Wrap ReportActionItemFragment inside a Text component --- .../home/report/ReportActionItemMessage.js | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 88223e6480ff..41f955dc6b0e 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -49,24 +49,31 @@ function ReportActionItemMessage(props) { return ( - {!props.isHidden ? ( - _.map(messages, (fragment, index) => ( - + {!props.isHidden ? ( + _.map(messages, (fragment, index) => ( + - )) - ) : ( - {props.translate('moderation.flaggedContent')} - )} + /> + )) + ) : ( + {props.translate('moderation.flaggedContent')} + )} + ); } From b591c52ed738a86d7092a0398cc10bc9256323b1 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Mon, 23 Oct 2023 12:46:55 +0300 Subject: [PATCH 049/170] Restore grey color and regular font weight to APPROVED/SUBMITTED messages --- src/pages/home/report/ReportActionItemFragment.js | 12 +++++++++++- src/pages/home/report/ReportActionItemMessage.js | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 57b51ef50519..2d750786f722 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import React, {memo} from 'react'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; @@ -63,6 +64,9 @@ const propTypes = { /** Whether the comment is a thread parent message/the first message in a thread */ isThreadParentMessage: PropTypes.bool, + /** The report's action name/type e.g. APPROVED, SUBMITTED, etc. */ + actionName: PropTypes.string, + ...windowDimensionsPropTypes, /** localization props */ @@ -86,6 +90,7 @@ const defaultProps = { delegateAccountID: 0, actorIcon: {}, isThreadParentMessage: false, + actionName: '', displayAsGroup: false, }; @@ -161,7 +166,12 @@ function ReportActionItemFragment(props) { > {props.fragment.text} diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 41f955dc6b0e..2919e598ce04 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -67,7 +67,8 @@ function ReportActionItemMessage(props) { source={lodashGet(props.action, 'originalMessage.source')} accountID={props.action.actorAccountID} style={props.style} - displayAsGroup={props.displayAsGroup} + displayAsGroup={props.displayAsGroup} + actionName={props.action.actionName} /> )) ) : ( From 51a4f16f67e7702df308038ae7815b3598700d34 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Mon, 23 Oct 2023 21:20:46 +0300 Subject: [PATCH 050/170] Conditionally surround ReportActionItemFragment with Text component --- .../home/report/ReportActionItemFragment.js | 3 +- .../home/report/ReportActionItemMessage.js | 60 +++++++++++-------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 2d750786f722..27dfbfc7095e 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -169,8 +169,7 @@ function ReportActionItemFragment(props) { style={[ styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap, - _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.actionName) - && {color: styles.colorMuted.color, fontWeight: 'normal'}, + _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.actionName) && {color: styles.colorMuted.color, fontWeight: 'normal'}, ]} > {props.fragment.text} diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 2919e598ce04..635426c4c790 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -9,6 +9,7 @@ import * as ReportUtils from '../../../libs/ReportUtils'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import reportActionPropTypes from './reportActionPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import CONST from '../../../CONST'; const propTypes = { /** The report action */ @@ -47,34 +48,41 @@ function ReportActionItemMessage(props) { } } + const isApprovedOrSubmittedReportActionType = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); + + const flaggedContentText = {props.translate('moderation.flaggedContent')}; + + const getReportActionItemFragment = (fragment, index) => { + return ( + + ); + }; + + const content = !props.isHidden ? _.map(messages, (fragment, index) => getReportActionItemFragment(fragment, index)) : flaggedContentText; + return ( - {/* - Wrapping ReportActionItemFragment inside 'Text' so that text isn't broken up into separate lines when - there are multiple messages of type 'TEXT', as seen when approving a report from a policy on Old - Dot and then viewing the report on New Dot. - */} - - {!props.isHidden ? ( - _.map(messages, (fragment, index) => ( - - )) - ) : ( - {props.translate('moderation.flaggedContent')} - )} - + {isApprovedOrSubmittedReportActionType ? ( + // Wrapping 'ReportActionItemFragment' inside '' so that text isn't broken up into separate lines when + // there are multiple messages of type 'TEXT', as seen when a report is submitted/approved from a + // policy on Old Dot and then viewed on New Dot. + + {content} + ) : ( + <>{content} + )} ); } From 0b620d03f96c8c88c68e7fbf4c34b9581d091877 Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Mon, 23 Oct 2023 21:30:07 +0200 Subject: [PATCH 051/170] Use StyleProp --- src/components/SelectCircle.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx index 97939f7acff5..776fed4180dc 100644 --- a/src/components/SelectCircle.tsx +++ b/src/components/SelectCircle.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {View} from 'react-native'; +import {StyleProp, View, ViewStyle} from 'react-native'; import globalStyles from '../styles/styles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; @@ -10,12 +10,12 @@ type SelectCircleProps = { isChecked: boolean; /** Additional styles to pass to SelectCircle */ - styles: Array>; + styles: StyleProp; }; function SelectCircle({isChecked = false, styles = []}: SelectCircleProps) { return ( - + {isChecked && ( Date: Mon, 23 Oct 2023 22:43:35 +0300 Subject: [PATCH 052/170] Remove return statement surrounding ReportActionItemFragment This was causing a lint error during PR checks --- .../home/report/ReportActionItemMessage.js | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 635426c4c790..a9690bb601e1 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -52,23 +52,21 @@ function ReportActionItemMessage(props) { const flaggedContentText = {props.translate('moderation.flaggedContent')}; - const getReportActionItemFragment = (fragment, index) => { - return ( - - ); - }; + const getReportActionItemFragment = (fragment, index) => ( + + ); const content = !props.isHidden ? _.map(messages, (fragment, index) => getReportActionItemFragment(fragment, index)) : flaggedContentText; From 9c0677861ff6b7eddd86ad0fe3d4ba8a61351944 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Tue, 24 Oct 2023 09:02:49 +0300 Subject: [PATCH 053/170] Surround 'props.fragment.text' in curly braces Caught this little error currently on latest main that would've rendered 'props.framgent.text' literally instead of the actual text --- src/pages/home/report/ReportActionItemFragment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 27dfbfc7095e..d6b3a40812a7 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -191,7 +191,7 @@ function ReportActionItemFragment(props) { case 'OLD_MESSAGE': return OLD_MESSAGE; default: - return props.fragment.text; + return {props.fragment.text}; } } From 0bcf5eae6674fe1dc06b26bc860d0e79b797cf10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 11:23:28 +0200 Subject: [PATCH 054/170] wip: adding adb commands --- .../nativeCommands/NativeCommandsAction.js | 16 ++++++++++++++++ tests/e2e/nativeCommands/adbTypeText.js | 10 ++++++++++ tests/e2e/nativeCommands/index.js | 19 +++++++++++++++++++ tests/e2e/server/index.js | 15 +++++++++++++++ tests/e2e/server/routes.js | 3 +++ 5 files changed, 63 insertions(+) create mode 100644 tests/e2e/nativeCommands/NativeCommandsAction.js create mode 100644 tests/e2e/nativeCommands/adbTypeText.js create mode 100644 tests/e2e/nativeCommands/index.js diff --git a/tests/e2e/nativeCommands/NativeCommandsAction.js b/tests/e2e/nativeCommands/NativeCommandsAction.js new file mode 100644 index 000000000000..ce6b38079527 --- /dev/null +++ b/tests/e2e/nativeCommands/NativeCommandsAction.js @@ -0,0 +1,16 @@ +const NativeCommandsAction = { + scroll: 'scroll', + type: 'type', +}; + +const makeTypeTextCommand = (text) => ({ + command: NativeCommandsAction.type, + payload: { + text, + }, +}); + +module.exports = { + NativeCommandsAction, + makeTypeTextCommand, +}; diff --git a/tests/e2e/nativeCommands/adbTypeText.js b/tests/e2e/nativeCommands/adbTypeText.js new file mode 100644 index 000000000000..cbaa9f4434a2 --- /dev/null +++ b/tests/e2e/nativeCommands/adbTypeText.js @@ -0,0 +1,10 @@ +const execAsync = require('../utils/execAsync'); +const Logger = require('../utils/logger'); + +const adbTypeText = async (text) => { + Logger.log(`📝 Typing text: ${text}`); + execAsync(`adb shell input text "${text}"`); + return true; +}; + +module.exports = adbTypeText; diff --git a/tests/e2e/nativeCommands/index.js b/tests/e2e/nativeCommands/index.js new file mode 100644 index 000000000000..7452dce3067c --- /dev/null +++ b/tests/e2e/nativeCommands/index.js @@ -0,0 +1,19 @@ +const adbTypeText = require('./adbTypeText'); +const {NativeCommandsAction} = require('./NativeCommandsAction'); + +const executeFromPayload = (actionName, payload) => { + switch (actionName) { + case NativeCommandsAction.scroll: + throw new Error('Not implemented yet'); + case NativeCommandsAction.type: + return adbTypeText(payload.text); + default: + throw new Error(`Unknown action: ${actionName}`); + } +}; + +module.exports = { + NativeCommandsAction, + executeFromPayload, + adbTypeText, +}; diff --git a/tests/e2e/server/index.js b/tests/e2e/server/index.js index 3910ef43f798..94b4e8abec09 100644 --- a/tests/e2e/server/index.js +++ b/tests/e2e/server/index.js @@ -2,6 +2,7 @@ const {createServer} = require('http'); const Routes = require('./routes'); const Logger = require('../utils/logger'); const {SERVER_PORT} = require('../config'); +const {executeFromPayload} = require('../nativeCommands'); const PORT = process.env.PORT || SERVER_PORT; @@ -125,6 +126,20 @@ const createServerInstance = () => { return res.end('ok'); } + case Routes.testNativeCommand: { + getPostJSONRequestData(req, res).then((data) => { + const status = executeFromPayload(data.actionName, data.payload); + if (status) { + res.end('ok'); + } else { + res.statusCode = 500; + res.end('Error executing command'); + } + }); + + break; + } + default: res.statusCode = 404; res.end('Page not found!'); diff --git a/tests/e2e/server/routes.js b/tests/e2e/server/routes.js index 5aac2fef4dc2..84fc2f89fd9b 100644 --- a/tests/e2e/server/routes.js +++ b/tests/e2e/server/routes.js @@ -7,4 +7,7 @@ module.exports = { // When the app is done running a test it calls this endpoint testDone: '/test_done', + + // Commands to execute from the host machine (there are pre-defined types like scroll or type) + testNativeCommand: '/test_native_command', }; From 947b43ec83fa411e59df5ddfc91842c5bc8e98e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 13:20:22 +0200 Subject: [PATCH 055/170] remove branch args --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 02dbf576764d..c1b4deff017c 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "test:e2e:main": "node tests/e2e/testRunner.js --development --branch main --skipCheckout", - "test:e2e:delta": "node tests/e2e/testRunner.js --development --branch main --label delta --skipCheckout", + "test:e2e:main": "node tests/e2e/testRunner.js --development --skipCheckout", + "test:e2e:delta": "node tests/e2e/testRunner.js --development --label delta --skipCheckout --skipInstallDeps", "test:e2e:compare": "node tests/e2e/merge.js", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", From 9165187411f5a9f6790dfddbe9c48754d09bef4a Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Tue, 24 Oct 2023 14:23:41 +0300 Subject: [PATCH 056/170] Describe function using JS Docs syntax & use inline ternary statement --- src/pages/home/report/ReportActionItemMessage.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index a9690bb601e1..1aa38e2fe19c 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -52,7 +52,13 @@ function ReportActionItemMessage(props) { const flaggedContentText = {props.translate('moderation.flaggedContent')}; - const getReportActionItemFragment = (fragment, index) => ( + /** + * Get a ReportActionItemFragment + * @param {Object} fragment the current message fragment + * @param {Number} index the current message fragment's index + * @returns {Object} report action item fragment + */ + const renderReportActionItemFragment = (fragment, index) => ( ); - const content = !props.isHidden ? _.map(messages, (fragment, index) => getReportActionItemFragment(fragment, index)) : flaggedContentText; - return ( {isApprovedOrSubmittedReportActionType ? ( @@ -77,9 +81,9 @@ function ReportActionItemMessage(props) { // there are multiple messages of type 'TEXT', as seen when a report is submitted/approved from a // policy on Old Dot and then viewed on New Dot. - {content} + {!props.isHidden ? _.map(messages, (fragment, index) => renderReportActionItemFragment(fragment, index)) : flaggedContentText} ) : ( - <>{content} + <>{!props.isHidden ? _.map(messages, (fragment, index) => renderReportActionItemFragment(fragment, index)) : flaggedContentText} )} ); From 51d2ffcd36240526692a9adf2a5b37fedb0ed611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 13:26:43 +0200 Subject: [PATCH 057/170] add package scripts to help run in dev mode --- package.json | 1 + tests/e2e/ADDING_TESTS.md | 2 +- tests/e2e/config.dev.js | 5 +++++ tests/e2e/config.local.js | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/config.dev.js diff --git a/package.json b/package.json index c1b4deff017c..f2b2ffef70c3 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", + "test:e2e:dev": "node tests/e2e/testRunner.js --development --skipCheckout --config ./config.dev.js --buildMode skip --skipInstallDeps", "test:e2e:main": "node tests/e2e/testRunner.js --development --skipCheckout", "test:e2e:delta": "node tests/e2e/testRunner.js --development --label delta --skipCheckout --skipInstallDeps", "test:e2e:compare": "node tests/e2e/merge.js", diff --git a/tests/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md index dcd08aeee441..ef7f6cf01a39 100644 --- a/tests/e2e/ADDING_TESTS.md +++ b/tests/e2e/ADDING_TESTS.md @@ -35,7 +35,7 @@ CAPTURE_METRICS=TRUE E2E_Testing=true npm start -- --reset-cache Then we can execute our test with: ``` -npm run test:e2e -- --development --skipInstallDeps --buildMode skip --includes "My new test name" +npm run test:e2e:dev -- --includes "My new test name" ``` > - `--development` will run the tests with a local config, which will run the tests with fewer iterations diff --git a/tests/e2e/config.dev.js b/tests/e2e/config.dev.js new file mode 100644 index 000000000000..46191ebdee48 --- /dev/null +++ b/tests/e2e/config.dev.js @@ -0,0 +1,5 @@ +module.exports = { + APP_PACKAGE: 'com.expensify.chat.dev', + APP_PATH: './android/app/build/outputs/apk/development/debug/app-development-debug.apk', + RUNS: 8, +}; diff --git a/tests/e2e/config.local.js b/tests/e2e/config.local.js index 15b091d8ba70..8cdfc50ac625 100644 --- a/tests/e2e/config.local.js +++ b/tests/e2e/config.local.js @@ -1,5 +1,5 @@ module.exports = { APP_PACKAGE: 'com.expensify.chat.adhoc', APP_PATH: './android/app/build/outputs/apk/e2e/release/app-e2e-release.apk', - RUNS: 8, + RUNS: 4, }; From 4347739219909a59c900cc5b7adae2dcc2df9e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 13:28:29 +0200 Subject: [PATCH 058/170] fix docs --- tests/e2e/ADDING_TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md index ef7f6cf01a39..bbebd4d76a33 100644 --- a/tests/e2e/ADDING_TESTS.md +++ b/tests/e2e/ADDING_TESTS.md @@ -15,7 +15,7 @@ I recommend doing the following. 1. Rename `./index.js` to `./appIndex.js` 2. Create a new `./index.js` with the following content: ```js -requrire("./src/libs/E2E/reactNativeLaunchingTest.js"); +require('./src/libs/E2E/reactNativeLaunchingTest'); ``` 3. In `./src/libs/E2E/reactNativeLaunchingTest.js` change the main app import to the new `./appIndex.js` file: ```diff From 89baf40e1fc7f55b8c37c5027fcdc7582a764a0e Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Tue, 24 Oct 2023 14:32:37 +0300 Subject: [PATCH 059/170] Revert "Surround 'props.fragment.text' in curly braces" This reverts commit 9c0677861ff6b7eddd86ad0fe3d4ba8a61351944. --- src/pages/home/report/ReportActionItemFragment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index d6b3a40812a7..27dfbfc7095e 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -191,7 +191,7 @@ function ReportActionItemFragment(props) { case 'OLD_MESSAGE': return OLD_MESSAGE; default: - return {props.fragment.text}; + return props.fragment.text; } } From 951c92a387ed1fed3e7fa865057f877f3b972bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 15:58:55 +0200 Subject: [PATCH 060/170] update documentation --- tests/e2e/ADDING_TESTS.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md index bbebd4d76a33..5bede6ada72a 100644 --- a/tests/e2e/ADDING_TESTS.md +++ b/tests/e2e/ADDING_TESTS.md @@ -12,12 +12,20 @@ I recommend doing the following. > [!NOTE] > All of the steps can be executed at once by running XXX (todo) -1. Rename `./index.js` to `./appIndex.js` -2. Create a new `./index.js` with the following content: +1. We need to compile a android development app version that has capturing metrics enabled: +```bash +# Make sure that your .env file is the one we need for e2e testing: +cp ./tests/e2e/.env.e2e .env + +# Build the android app like you normally would with +npm run android +``` +2. Rename `./index.js` to `./appIndex.js` +3. Create a new `./index.js` with the following content: ```js require('./src/libs/E2E/reactNativeLaunchingTest'); ``` -3. In `./src/libs/E2E/reactNativeLaunchingTest.js` change the main app import to the new `./appIndex.js` file: +4. In `./src/libs/E2E/reactNativeLaunchingTest.js` change the main app import to the new `./appIndex.js` file: ```diff - import '../../../index'; + import '../../../appIndex'; @@ -29,7 +37,7 @@ require('./src/libs/E2E/reactNativeLaunchingTest'); Now you can start the metro bundler in e2e mode with: ``` -CAPTURE_METRICS=TRUE E2E_Testing=true npm start -- --reset-cache +CAPTURE_METRICS=true E2E_TESTING=true npm start -- --reset-cache ``` Then we can execute our test with: @@ -38,11 +46,7 @@ Then we can execute our test with: npm run test:e2e:dev -- --includes "My new test name" ``` -> - `--development` will run the tests with a local config, which will run the tests with fewer iterations -> - `--skipInstallDeps` will skip the `npm install` step, which you probably don't need -> - `--buildMode skip` will skip rebuilding the app, and just run the existing app -> - `--includes "MyTestName"` will only run the test with the name "MyTestName" - +> - `--includes "MyTestName"` will only run the test with the name "MyTestName", but is optional ## Creating a new test From e16b21866ce5085d1fca13b33989c93efd067b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 15:59:33 +0200 Subject: [PATCH 061/170] doc: improve wording --- tests/e2e/ADDING_TESTS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md index 5bede6ada72a..73d474882269 100644 --- a/tests/e2e/ADDING_TESTS.md +++ b/tests/e2e/ADDING_TESTS.md @@ -2,10 +2,10 @@ ## Running your new test in development mode -Typically you'd run all the tests with `npm run test:e2e` on your machine, -this will run the tests with some local settings, however that is not -optimal when you add a new test for which you want to quickly test if it works, as it -still runs the release version of the app. +Typically you'd run all the tests with `npm run test:e2e` on your machine. +This will run the tests with some local settings, however that is not +optimal when you add a new test for which you want to quickly test if it works, as the prior command +still runs the release version of the app, which is hard to debug. I recommend doing the following. From 37622920e5cb84dc8b16d87e1bd37b30be3e07b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 24 Oct 2023 15:59:43 +0200 Subject: [PATCH 062/170] remove note --- tests/e2e/ADDING_TESTS.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md index 73d474882269..ac43e61f60ed 100644 --- a/tests/e2e/ADDING_TESTS.md +++ b/tests/e2e/ADDING_TESTS.md @@ -9,9 +9,6 @@ still runs the release version of the app, which is hard to debug. I recommend doing the following. -> [!NOTE] -> All of the steps can be executed at once by running XXX (todo) - 1. We need to compile a android development app version that has capturing metrics enabled: ```bash # Make sure that your .env file is the one we need for e2e testing: From 8351917696c610adf0daa499d00596d1c0bc4c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 08:40:48 +0200 Subject: [PATCH 063/170] wip adding test --- src/libs/E2E/tests/chatTypingTest.e2e.js | 26 ++++++++++++++++++++++++ tests/e2e/config.js | 1 + 2 files changed, 27 insertions(+) create mode 100644 src/libs/E2E/tests/chatTypingTest.e2e.js diff --git a/src/libs/E2E/tests/chatTypingTest.e2e.js b/src/libs/E2E/tests/chatTypingTest.e2e.js new file mode 100644 index 000000000000..50b003e966d5 --- /dev/null +++ b/src/libs/E2E/tests/chatTypingTest.e2e.js @@ -0,0 +1,26 @@ +import E2ELogin from '../actions/e2eLogin'; +import Performance from '../../Performance'; +import E2EClient from '../client'; + +const test = () => { + // check for login (if already logged in the action will simply resolve) + console.debug('[E2E] Logging in for typing'); + + E2ELogin().then((neededLogin) => { + if (neededLogin) { + // we don't want to submit the first login to the results + return E2EClient.submitTestDone(); + } + + console.debug('[E2E] Logged in, getting typing metrics and submitting them…'); + + Performance.subscribeToMeasurements((entry) => { + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + console.debug(`[E2E] Sidebar loaded, navigating to a report…`); + Navigation.navigate(ROUTES.SEARCH); + } + }); + }); +}; + +export default test; diff --git a/tests/e2e/config.js b/tests/e2e/config.js index 3b1856ab8ad8..270509e3df20 100644 --- a/tests/e2e/config.js +++ b/tests/e2e/config.js @@ -9,6 +9,7 @@ const OUTPUT_DIR = process.env.WORKING_DIRECTORY || './tests/e2e/results'; const TEST_NAMES = { AppStartTime: 'App start time', OpenSearchPage: 'Open search page TTI', + ReportTyping: 'Report typing', }; /** From abeb8f18cbc7f2be22b9fc8981cecd866dc48648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 08:44:51 +0200 Subject: [PATCH 064/170] add missing config entry --- tests/e2e/config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e/config.js b/tests/e2e/config.js index 270509e3df20..34cd13a8f6db 100644 --- a/tests/e2e/config.js +++ b/tests/e2e/config.js @@ -64,5 +64,8 @@ module.exports = { [TEST_NAMES.OpenSearchPage]: { name: TEST_NAMES.OpenSearchPage, }, + [TEST_NAMES.ReportTyping]: { + name: TEST_NAMES.ReportTyping, + }, }, }; From dad39571ddef69b6316425918242b98dc454f3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 09:13:18 +0200 Subject: [PATCH 065/170] fix api mock not returning data when none onyx request --- src/libs/E2E/API.mock.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/E2E/API.mock.js b/src/libs/E2E/API.mock.js index 47f445f72222..ba85cd19aac3 100644 --- a/src/libs/E2E/API.mock.js +++ b/src/libs/E2E/API.mock.js @@ -26,12 +26,16 @@ const mocks = { function mockCall(command, apiCommandParameters, tag) { const mockResponse = mocks[command] && mocks[command](apiCommandParameters); - if (!mockResponse || !_.isArray(mockResponse.onyxData)) { - Log.warn(`[${tag}] for command ${command} is not mocked yet!`); + if (!mockResponse) { + Log.warn(`[${tag}] for command ${command} is not mocked yet! ⚠️`); return; } - return Onyx.update(mockResponse.onyxData); + if (_.isArray(mockResponse.onyxData)) { + return Onyx.update(mockResponse.onyxData); + } + + return Promise.resolve(mockResponse); } /** From 47f7e7305c57cbee8cc845296384eedf60d309e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 09:23:17 +0200 Subject: [PATCH 066/170] wip: navigate to report --- src/libs/E2E/tests/chatTypingTest.e2e.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/E2E/tests/chatTypingTest.e2e.js b/src/libs/E2E/tests/chatTypingTest.e2e.js index 50b003e966d5..95d0766ff6c2 100644 --- a/src/libs/E2E/tests/chatTypingTest.e2e.js +++ b/src/libs/E2E/tests/chatTypingTest.e2e.js @@ -1,6 +1,9 @@ import E2ELogin from '../actions/e2eLogin'; import Performance from '../../Performance'; import E2EClient from '../client'; +import Navigation from '../../Navigation/Navigation'; +import ROUTES from '../../../ROUTES'; +import CONST from '../../../CONST'; const test = () => { // check for login (if already logged in the action will simply resolve) @@ -17,7 +20,7 @@ const test = () => { Performance.subscribeToMeasurements((entry) => { if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { console.debug(`[E2E] Sidebar loaded, navigating to a report…`); - Navigation.navigate(ROUTES.SEARCH); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute('98345625')); } }); }); From e35656b864208db60e8920c449269400ca8084e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 09:32:00 +0200 Subject: [PATCH 067/170] add more mock data --- src/libs/E2E/apiMocks/openReport.js | 1938 ++++++++++++++++++++++++++- 1 file changed, 1908 insertions(+), 30 deletions(-) diff --git a/src/libs/E2E/apiMocks/openReport.js b/src/libs/E2E/apiMocks/openReport.js index 936f9d77ef06..b20b3df35bad 100644 --- a/src/libs/E2E/apiMocks/openReport.js +++ b/src/libs/E2E/apiMocks/openReport.js @@ -6,91 +6,1969 @@ export default () => ({ value: { reportID: '98345625', reportName: 'Chat Report', + type: 'chat', chatType: '', + ownerEmail: '__fake__', ownerAccountID: 0, + managerEmail: '__fake__', + managerID: 0, policyID: '_FAKE_', - participantAccountIDs: [2, 1, 4, 3, 5, 16, 18, 19], + participantAccountIDs: [14567013], isPinned: false, - lastReadCreated: '1980-01-01 00:00:00.000', - lastVisibleActionCreated: '2022-08-01 20:49:11', - lastMessageTimestamp: 1659386951000, - lastMessageText: 'Say hello\ud83d\ude10', - lastActorAccountID: 10773236, + lastReadTime: '2023-09-14 11:50:21.768', + lastMentionedTime: '2023-07-27 07:37:43.100', + lastReadSequenceNumber: 0, + lastVisibleActionCreated: '2023-08-29 12:38:16.070', + lastVisibleActionLastModified: '2023-08-29 12:38:16.070', + lastMessageText: 'terry+hightraffic@margelo.io owes \u20ac12.00', + lastActorAccountID: 14567013, notificationPreference: 'always', + welcomeMessage: '', stateNum: 0, statusNum: 0, oldPolicyName: '', visibility: null, isOwnPolicyExpenseChat: false, - lastMessageHtml: 'Say hello\ud83d\ude10', + lastMessageHtml: 'terry+hightraffic@margelo.io owes \u20ac12.00', + iouReportID: 206636935813547, hasOutstandingIOU: false, + hasOutstandingChildRequest: false, + policyName: null, + hasParentAccess: null, + parentReportID: null, + parentReportActionID: null, + writeCapability: 'all', + description: null, + isDeletedParentAction: null, + total: 0, + currency: 'USD', + submitterPayPalMeAddress: '', + chatReportID: null, + isWaitingOnBankAccount: false, + }, + }, + { + onyxMethod: 'mergecollection', + key: 'transactions_', + value: { + transactions_5509240412000765850: { + amount: 1200, + billable: false, + cardID: 15467728, + category: '', + comment: { + comment: '', + }, + created: '2023-08-29', + currency: 'EUR', + filename: '', + merchant: 'Request', + modifiedAmount: 0, + modifiedCreated: '', + modifiedCurrency: '', + modifiedMerchant: '', + originalAmount: 0, + originalCurrency: '', + parentTransactionID: '', + receipt: {}, + reimbursable: true, + reportID: '206636935813547', + status: 'Pending', + tag: '', + transactionID: '5509240412000765850', + hasEReceipt: false, + }, }, }, { onyxMethod: 'merge', key: 'reportActions_98345625', value: { - 226245034: { - reportActionID: '226245034', - actionName: 'CREATED', - created: '2022-08-01 20:48:58', - timestamp: 1659386938, - reportActionTimestamp: 0, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + '885570376575240776': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + edits: [], + html: '', + lastModified: '2023-09-01 07:43:29.374', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-31 07:23:52.892', + timestamp: 1693466632, + reportActionTimestamp: 1693466632892, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '885570376575240776', + previousReportActionID: '6576518341807837187', + lastModified: '2023-09-01 07:43:29.374', + whisperedToAccountIDs: [], + }, + '6576518341807837187': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'terry+hightraffic@margelo.io owes \u20ac12.00', + text: 'terry+hightraffic@margelo.io owes \u20ac12.00', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + lastModified: '2023-08-29 12:38:16.070', + linkedReportID: '206636935813547', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-08-29 12:38:16.070', + timestamp: 1693312696, + reportActionTimestamp: 1693312696070, + automatic: false, + actionName: 'REPORTPREVIEW', + shouldShow: true, + reportActionID: '6576518341807837187', + previousReportActionID: '2658221912430757962', + lastModified: '2023-08-29 12:38:16.070', + childReportID: 206636935813547, + childType: 'iou', + childStatusNum: 1, + childStateNum: 1, + childMoneyRequestCount: 1, + whisperedToAccountIDs: [], + }, + '2658221912430757962': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'Hshshdhdhejje
Cuududdke

F
D
R
D
R
Jfj c
D

D
D
R
D
R', + text: 'Hshshdhdhejje\nCuududdke\n\nF\nD\nR\nD\nR\nJfj c\nD\n\nD\nD\nR\nD\nR', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [ + { + emoji: 'heart', + users: [ + { + accountID: 12883048, + skinTone: -1, + }, + ], + }, + ], + }, + ], + originalMessage: { + html: 'Hshshdhdhejje
Cuududdke

F
D
R
D
R
Jfj c
D

D
D
R
D
R', + lastModified: '2023-08-25 12:39:48.121', + reactions: [ + { + emoji: 'heart', + users: [ + { + accountID: 12883048, + skinTone: -1, + }, + ], + }, + ], + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-25 08:54:06.972', + timestamp: 1692953646, + reportActionTimestamp: 1692953646972, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '2658221912430757962', + previousReportActionID: '6551789403725495383', + lastModified: '2023-08-25 12:39:48.121', + childReportID: 1411015346900020, + childType: 'chat', + childOldestFourAccountIDs: '12883048', + childCommenterCount: 1, + childLastVisibleActionCreated: '2023-08-29 06:08:59.247', + childVisibleActionCount: 1, + whisperedToAccountIDs: [], + }, + '6551789403725495383': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'Typing with the composer is now also reasonably fast again', + text: 'Typing with the composer is now also reasonably fast again', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'Typing with the composer is now also reasonably fast again', + lastModified: '2023-08-25 08:53:57.490', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-25 08:53:57.490', + timestamp: 1692953637, + reportActionTimestamp: 1692953637490, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6551789403725495383', + previousReportActionID: '6184477005811241106', + lastModified: '2023-08-25 08:53:57.490', + whisperedToAccountIDs: [], + }, + '6184477005811241106': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: '\ud83d\ude3a', + text: '\ud83d\ude3a', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: '\ud83d\ude3a', + lastModified: '2023-08-25 08:53:41.689', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-25 08:53:41.689', + timestamp: 1692953621, + reportActionTimestamp: 1692953621689, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6184477005811241106', + previousReportActionID: '7473953427765241164', + lastModified: '2023-08-25 08:53:41.689', + whisperedToAccountIDs: [], + }, + '7473953427765241164': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'Skkkkkkrrrrrrrr', + text: 'Skkkkkkrrrrrrrr', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'Skkkkkkrrrrrrrr', + lastModified: '2023-08-25 08:53:31.900', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-25 08:53:31.900', + timestamp: 1692953611, + reportActionTimestamp: 1692953611900, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '7473953427765241164', + previousReportActionID: '872421684593496491', + lastModified: '2023-08-25 08:53:31.900', + whisperedToAccountIDs: [], + }, + '872421684593496491': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'hello this is a new test will my version sync though? i doubt it lolasdasdasdaoe f t asdasd okay und das ging jetzt eh oder? ja schaut ganz gut aus okay geht das immer noch ? schaut gut aus ja true ghw test test 2 test 4 tse 3 oida', + text: 'hello this is a new test will my version sync though? i doubt it lolasdasdasdaoe f t asdasd okay und das ging jetzt eh oder? ja schaut ganz gut aus okay geht das immer noch ? schaut gut aus ja true ghw test test 2 test 4 tse 3 oida', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'hello this is a new test will my version sync though? i doubt it lolasdasdasdaoe f t asdasd okay und das ging jetzt eh oder? ja schaut ganz gut aus okay geht das immer noch ? schaut gut aus ja true ghw test test 2 test 4 tse 3 oida', + lastModified: '2023-08-11 13:35:03.962', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-11 13:35:03.962', + timestamp: 1691760903, + reportActionTimestamp: 1691760903962, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '872421684593496491', + previousReportActionID: '175680146540578558', + lastModified: '2023-08-11 13:35:03.962', + whisperedToAccountIDs: [], + }, + '175680146540578558': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: '', + text: '[Attachment]', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: '', + lastModified: '2023-08-10 06:59:21.381', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-10 06:59:21.381', + timestamp: 1691650761, + reportActionTimestamp: 1691650761381, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '175680146540578558', + previousReportActionID: '1264289784533901723', + lastModified: '2023-08-10 06:59:21.381', + whisperedToAccountIDs: [], + }, + '1264289784533901723': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: '', + text: '[Attachment]', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: '', + lastModified: '2023-08-10 06:59:16.922', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-10 06:59:16.922', + timestamp: 1691650756, + reportActionTimestamp: 1691650756922, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '1264289784533901723', + previousReportActionID: '4870277010164688289', + lastModified: '2023-08-10 06:59:16.922', + whisperedToAccountIDs: [], + }, + '4870277010164688289': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'send test', + text: 'send test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'send test', + lastModified: '2023-08-09 06:43:25.209', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-09 06:43:25.209', + timestamp: 1691563405, + reportActionTimestamp: 1691563405209, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '4870277010164688289', + previousReportActionID: '7931783095143103530', + lastModified: '2023-08-09 06:43:25.209', + whisperedToAccountIDs: [], + }, + '7931783095143103530': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'hello terry \ud83d\ude04 this is a test @terry+hightraffic@margelo.io', + text: 'hello terry \ud83d\ude04 this is a test @terry+hightraffic@margelo.io', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'hello terry \ud83d\ude04 this is a test @terry+hightraffic@margelo.io', + lastModified: '2023-08-08 14:38:45.035', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-08 14:38:45.035', + timestamp: 1691505525, + reportActionTimestamp: 1691505525035, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '7931783095143103530', + previousReportActionID: '4598496324774172433', + lastModified: '2023-08-08 14:38:45.035', + whisperedToAccountIDs: [], + }, + '4598496324774172433': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, message: [ + { + type: 'COMMENT', + html: '\ud83d\uddff', + text: '\ud83d\uddff', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: '\ud83d\uddff', + lastModified: '2023-08-08 13:21:42.102', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-08 13:21:42.102', + timestamp: 1691500902, + reportActionTimestamp: 1691500902102, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '4598496324774172433', + previousReportActionID: '3324110555952451144', + lastModified: '2023-08-08 13:21:42.102', + whisperedToAccountIDs: [], + }, + '3324110555952451144': { + person: [ { type: 'TEXT', style: 'strong', - text: '__fake__', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'test \ud83d\uddff', + text: 'test \ud83d\uddff', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], }, + ], + originalMessage: { + html: 'test \ud83d\uddff', + lastModified: '2023-08-08 13:21:32.101', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-08 13:21:32.101', + timestamp: 1691500892, + reportActionTimestamp: 1691500892101, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '3324110555952451144', + previousReportActionID: '5389364980227777980', + lastModified: '2023-08-08 13:21:32.101', + whisperedToAccountIDs: [], + }, + '5389364980227777980': { + person: [ { type: 'TEXT', - style: 'normal', - text: ' created this report', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'okay now it will work again y \ud83d\udc42', + text: 'okay now it will work again y \ud83d\udc42', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], }, ], + originalMessage: { + html: 'okay now it will work again y \ud83d\udc42', + lastModified: '2023-08-07 10:54:38.141', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-08-07 10:54:38.141', + timestamp: 1691405678, + reportActionTimestamp: 1691405678141, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '5389364980227777980', + previousReportActionID: '4717622390560689493', + lastModified: '2023-08-07 10:54:38.141', + whisperedToAccountIDs: [], + }, + '4717622390560689493': { person: [ { type: 'TEXT', style: 'strong', - text: '__fake__', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'hmmmm', + text: 'hmmmm', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], }, ], + originalMessage: { + html: 'hmmmm', + lastModified: '2023-07-27 18:13:45.322', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 18:13:45.322', + timestamp: 1690481625, + reportActionTimestamp: 1690481625322, automatic: false, + actionName: 'ADDCOMMENT', shouldShow: true, + reportActionID: '4717622390560689493', + previousReportActionID: '745721424446883075', + lastModified: '2023-07-27 18:13:45.322', + whisperedToAccountIDs: [], }, - 1082059149: { + '745721424446883075': { person: [ { type: 'TEXT', style: 'strong', - text: '123 Ios', + text: 'Hanno J. G\u00f6decke', }, ], - actorAccountID: 10773236, + actorAccountID: 12883048, message: [ { type: 'COMMENT', - html: 'Say hello\ud83d\ude10', - text: 'Say hello\ud83d\ude10', + html: 'test', + text: 'test', isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], }, ], originalMessage: { - html: 'Say hello\ud83d\ude10', + html: 'test', + lastModified: '2023-07-27 18:13:32.595', }, - avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/301e37631eca9e3127d6b668822e3a53771551f6_128.jpeg', - created: '2022-08-01 20:49:11', - timestamp: 1659386951, - reportActionTimestamp: 1659386951000, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 18:13:32.595', + timestamp: 1690481612, + reportActionTimestamp: 1690481612595, automatic: false, actionName: 'ADDCOMMENT', shouldShow: true, - reportActionID: '1082059149', + reportActionID: '745721424446883075', + previousReportActionID: '3986429677777110818', + lastModified: '2023-07-27 18:13:32.595', + whisperedToAccountIDs: [], + }, + '3986429677777110818': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'I will', + text: 'I will', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'I will', + lastModified: '2023-07-27 17:03:11.250', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 17:03:11.250', + timestamp: 1690477391, + reportActionTimestamp: 1690477391250, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '3986429677777110818', + previousReportActionID: '7317910228472011573', + lastModified: '2023-07-27 17:03:11.250', + childReportID: 3338245207149134, + childType: 'chat', + whisperedToAccountIDs: [], + }, + '7317910228472011573': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'will you>', + text: 'will you>', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'will you>', + lastModified: '2023-07-27 16:46:58.988', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 16:46:58.988', + timestamp: 1690476418, + reportActionTimestamp: 1690476418988, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '7317910228472011573', + previousReportActionID: '6779343397958390319', + lastModified: '2023-07-27 16:46:58.988', + whisperedToAccountIDs: [], + }, + '6779343397958390319': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'i will always send :#', + text: 'i will always send :#', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'i will always send :#', + lastModified: '2023-07-27 07:55:33.468', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:55:33.468', + timestamp: 1690444533, + reportActionTimestamp: 1690444533468, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6779343397958390319', + previousReportActionID: '5084145419388195535', + lastModified: '2023-07-27 07:55:33.468', + whisperedToAccountIDs: [], + }, + '5084145419388195535': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'new test', + text: 'new test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'new test', + lastModified: '2023-07-27 07:55:22.309', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:55:22.309', + timestamp: 1690444522, + reportActionTimestamp: 1690444522309, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '5084145419388195535', + previousReportActionID: '6742067600980190659', + lastModified: '2023-07-27 07:55:22.309', + whisperedToAccountIDs: [], + }, + '6742067600980190659': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'okay good', + text: 'okay good', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'okay good', + lastModified: '2023-07-27 07:55:15.362', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:55:15.362', + timestamp: 1690444515, + reportActionTimestamp: 1690444515362, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6742067600980190659', + previousReportActionID: '7811212427986810247', + lastModified: '2023-07-27 07:55:15.362', + whisperedToAccountIDs: [], + }, + '7811212427986810247': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'test 2', + text: 'test 2', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test 2', + lastModified: '2023-07-27 07:55:10.629', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:55:10.629', + timestamp: 1690444510, + reportActionTimestamp: 1690444510629, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '7811212427986810247', + previousReportActionID: '4544757211729131829', + lastModified: '2023-07-27 07:55:10.629', + whisperedToAccountIDs: [], + }, + '4544757211729131829': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'new test', + text: 'new test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'new test', + lastModified: '2023-07-27 07:53:41.960', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:53:41.960', + timestamp: 1690444421, + reportActionTimestamp: 1690444421960, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '4544757211729131829', + previousReportActionID: '8290114634148431001', + lastModified: '2023-07-27 07:53:41.960', + whisperedToAccountIDs: [], + }, + '8290114634148431001': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'something was real', + text: 'something was real', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'something was real', + lastModified: '2023-07-27 07:53:27.836', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:53:27.836', + timestamp: 1690444407, + reportActionTimestamp: 1690444407836, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '8290114634148431001', + previousReportActionID: '5597494166918965742', + lastModified: '2023-07-27 07:53:27.836', + whisperedToAccountIDs: [], + }, + '5597494166918965742': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'oida', + text: 'oida', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'oida', + lastModified: '2023-07-27 07:53:20.783', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:53:20.783', + timestamp: 1690444400, + reportActionTimestamp: 1690444400783, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '5597494166918965742', + previousReportActionID: '7445709165354739065', + lastModified: '2023-07-27 07:53:20.783', + whisperedToAccountIDs: [], + }, + '7445709165354739065': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'test 12', + text: 'test 12', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test 12', + lastModified: '2023-07-27 07:53:17.393', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:53:17.393', + timestamp: 1690444397, + reportActionTimestamp: 1690444397393, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '7445709165354739065', + previousReportActionID: '1985264407541504554', + lastModified: '2023-07-27 07:53:17.393', + whisperedToAccountIDs: [], + }, + '1985264407541504554': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'new test', + text: 'new test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'new test', + lastModified: '2023-07-27 07:53:07.894', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:53:07.894', + timestamp: 1690444387, + reportActionTimestamp: 1690444387894, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '1985264407541504554', + previousReportActionID: '6101278009725036288', + lastModified: '2023-07-27 07:53:07.894', + whisperedToAccountIDs: [], + }, + '6101278009725036288': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'grrr', + text: 'grrr', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'grrr', + lastModified: '2023-07-27 07:52:56.421', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:52:56.421', + timestamp: 1690444376, + reportActionTimestamp: 1690444376421, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6101278009725036288', + previousReportActionID: '6913024396112106680', + lastModified: '2023-07-27 07:52:56.421', + whisperedToAccountIDs: [], + }, + '6913024396112106680': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'ne w test', + text: 'ne w test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'ne w test', + lastModified: '2023-07-27 07:52:53.352', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:52:53.352', + timestamp: 1690444373, + reportActionTimestamp: 1690444373352, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6913024396112106680', + previousReportActionID: '3663318486255461038', + lastModified: '2023-07-27 07:52:53.352', + whisperedToAccountIDs: [], + }, + '3663318486255461038': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'well', + text: 'well', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'well', + lastModified: '2023-07-27 07:52:47.044', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:52:47.044', + timestamp: 1690444367, + reportActionTimestamp: 1690444367044, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '3663318486255461038', + previousReportActionID: '6652909175804277965', + lastModified: '2023-07-27 07:52:47.044', + whisperedToAccountIDs: [], + }, + '6652909175804277965': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'hu', + text: 'hu', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'hu', + lastModified: '2023-07-27 07:52:43.489', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:52:43.489', + timestamp: 1690444363, + reportActionTimestamp: 1690444363489, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6652909175804277965', + previousReportActionID: '4738491624635492834', + lastModified: '2023-07-27 07:52:43.489', + whisperedToAccountIDs: [], + }, + '4738491624635492834': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'test', + text: 'test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test', + lastModified: '2023-07-27 07:52:40.145', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:52:40.145', + timestamp: 1690444360, + reportActionTimestamp: 1690444360145, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '4738491624635492834', + previousReportActionID: '1621235410433805703', + lastModified: '2023-07-27 07:52:40.145', + whisperedToAccountIDs: [], + }, + '1621235410433805703': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'test 4', + text: 'test 4', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test 4', + lastModified: '2023-07-27 07:48:36.809', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 07:48:36.809', + timestamp: 1690444116, + reportActionTimestamp: 1690444116809, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '1621235410433805703', + previousReportActionID: '1024550225871474566', + lastModified: '2023-07-27 07:48:36.809', + whisperedToAccountIDs: [], + }, + '1024550225871474566': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'test 3', + text: 'test 3', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test 3', + lastModified: '2023-07-27 07:48:24.183', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:48:24.183', + timestamp: 1690444104, + reportActionTimestamp: 1690444104183, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '1024550225871474566', + previousReportActionID: '5598482410513625723', + lastModified: '2023-07-27 07:48:24.183', + whisperedToAccountIDs: [], + }, + '5598482410513625723': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'test2', + text: 'test2', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test2', + lastModified: '2023-07-27 07:42:25.340', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:42:25.340', + timestamp: 1690443745, + reportActionTimestamp: 1690443745340, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '5598482410513625723', + previousReportActionID: '115121137377026405', + lastModified: '2023-07-27 07:42:25.340', + whisperedToAccountIDs: [], + }, + '115121137377026405': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'test', + text: 'test', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'test', + lastModified: '2023-07-27 07:42:22.583', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 07:42:22.583', + timestamp: 1690443742, + reportActionTimestamp: 1690443742583, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '115121137377026405', + previousReportActionID: '2167420855737359171', + lastModified: '2023-07-27 07:42:22.583', + whisperedToAccountIDs: [], + }, + '2167420855737359171': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'new message', + text: 'new message', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'new message', + lastModified: '2023-07-27 07:42:09.177', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:42:09.177', + timestamp: 1690443729, + reportActionTimestamp: 1690443729177, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '2167420855737359171', + previousReportActionID: '6106926938128802897', + lastModified: '2023-07-27 07:42:09.177', + whisperedToAccountIDs: [], + }, + '6106926938128802897': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'oh', + text: 'oh', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'oh', + lastModified: '2023-07-27 07:42:03.902', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:42:03.902', + timestamp: 1690443723, + reportActionTimestamp: 1690443723902, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '6106926938128802897', + previousReportActionID: '4366704007455141347', + lastModified: '2023-07-27 07:42:03.902', + whisperedToAccountIDs: [], + }, + '4366704007455141347': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'hm lol', + text: 'hm lol', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'hm lol', + lastModified: '2023-07-27 07:42:00.734', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:42:00.734', + timestamp: 1690443720, + reportActionTimestamp: 1690443720734, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '4366704007455141347', + previousReportActionID: '2078794664797360607', + lastModified: '2023-07-27 07:42:00.734', + whisperedToAccountIDs: [], + }, + '2078794664797360607': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'hi?', + text: 'hi?', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'hi?', + lastModified: '2023-07-27 07:41:49.724', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:41:49.724', + timestamp: 1690443709, + reportActionTimestamp: 1690443709724, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '2078794664797360607', + previousReportActionID: '2030060194258527427', + lastModified: '2023-07-27 07:41:49.724', + whisperedToAccountIDs: [], + }, + '2030060194258527427': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'lets have a thread about it, will ya?', + text: 'lets have a thread about it, will ya?', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'lets have a thread about it, will ya?', + lastModified: '2023-07-27 07:40:49.146', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 07:40:49.146', + timestamp: 1690443649, + reportActionTimestamp: 1690443649146, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '2030060194258527427', + previousReportActionID: '5540483153987237906', + lastModified: '2023-07-27 07:40:49.146', + childReportID: 5860710623453234, + childType: 'chat', + childOldestFourAccountIDs: '14567013,12883048', + childCommenterCount: 2, + childLastVisibleActionCreated: '2023-07-27 07:41:03.550', + childVisibleActionCount: 2, + whisperedToAccountIDs: [], + }, + '5540483153987237906': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: '@hanno@margelo.io i mention you lasagna :)', + text: '@hanno@margelo.io i mention you lasagna :)', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: '@hanno@margelo.io i mention you lasagna :)', + lastModified: '2023-07-27 07:37:43.100', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:37:43.100', + timestamp: 1690443463, + reportActionTimestamp: 1690443463100, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '5540483153987237906', + previousReportActionID: '8050559753491913991', + lastModified: '2023-07-27 07:37:43.100', + whisperedToAccountIDs: [], + }, + '8050559753491913991': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: '@terry+hightraffic@margelo.io', + text: '@terry+hightraffic@margelo.io', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: '@terry+hightraffic@margelo.io', + lastModified: '2023-07-27 07:36:41.708', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:36:41.708', + timestamp: 1690443401, + reportActionTimestamp: 1690443401708, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '8050559753491913991', + previousReportActionID: '881015235172878574', + lastModified: '2023-07-27 07:36:41.708', + whisperedToAccountIDs: [], + }, + '881015235172878574': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'yeah lets see', + text: 'yeah lets see', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'yeah lets see', + lastModified: '2023-07-27 07:25:15.997', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-27 07:25:15.997', + timestamp: 1690442715, + reportActionTimestamp: 1690442715997, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '881015235172878574', + previousReportActionID: '4800357767877651330', + lastModified: '2023-07-27 07:25:15.997', + whisperedToAccountIDs: [], + }, + '4800357767877651330': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'asdasdasd', + text: 'asdasdasd', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'asdasdasd', + lastModified: '2023-07-27 07:25:03.093', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-27 07:25:03.093', + timestamp: 1690442703, + reportActionTimestamp: 1690442703093, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '4800357767877651330', + previousReportActionID: '9012557872554910346', + lastModified: '2023-07-27 07:25:03.093', + whisperedToAccountIDs: [], + }, + '9012557872554910346': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'yeah', + text: 'yeah', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'yeah', + lastModified: '2023-07-26 19:49:40.471', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-26 19:49:40.471', + timestamp: 1690400980, + reportActionTimestamp: 1690400980471, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '9012557872554910346', + previousReportActionID: '8440677969068645500', + lastModified: '2023-07-26 19:49:40.471', + whisperedToAccountIDs: [], + }, + '8440677969068645500': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'hello motor', + text: 'hello motor', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'hello motor', + lastModified: '2023-07-26 19:49:36.262', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-26 19:49:36.262', + timestamp: 1690400976, + reportActionTimestamp: 1690400976262, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '8440677969068645500', + previousReportActionID: '306887996337608775', + lastModified: '2023-07-26 19:49:36.262', + whisperedToAccountIDs: [], + }, + '306887996337608775': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'a new messagfe', + text: 'a new messagfe', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'a new messagfe', + lastModified: '2023-07-26 19:49:29.512', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-26 19:49:29.512', + timestamp: 1690400969, + reportActionTimestamp: 1690400969512, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '306887996337608775', + previousReportActionID: '587892433077506227', + lastModified: '2023-07-26 19:49:29.512', + whisperedToAccountIDs: [], + }, + '587892433077506227': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Hanno J. G\u00f6decke', + }, + ], + actorAccountID: 12883048, + message: [ + { + type: 'COMMENT', + html: 'good', + text: 'good', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'good', + lastModified: '2023-07-26 19:49:20.473', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/fc1b8880216a5a76c8fd9998aaa33c080dacda5d_128.jpeg', + created: '2023-07-26 19:49:20.473', + timestamp: 1690400960, + reportActionTimestamp: 1690400960473, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '587892433077506227', + previousReportActionID: '1433103421804347060', + lastModified: '2023-07-26 19:49:20.473', + whisperedToAccountIDs: [], + }, + '1433103421804347060': { + person: [ + { + type: 'TEXT', + style: 'strong', + text: 'Terry Hightraffic1337', + }, + ], + actorAccountID: 14567013, + message: [ + { + type: 'COMMENT', + html: 'ah', + text: 'ah', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + reactions: [], + }, + ], + originalMessage: { + html: 'ah', + lastModified: '2023-07-26 19:49:12.762', + }, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + created: '2023-07-26 19:49:12.762', + timestamp: 1690400952, + reportActionTimestamp: 1690400952762, + automatic: false, + actionName: 'ADDCOMMENT', + shouldShow: true, + reportActionID: '1433103421804347060', + previousReportActionID: '8774157052628183778', + lastModified: '2023-07-26 19:49:12.762', + whisperedToAccountIDs: [], + }, + }, + }, + { + onyxMethod: 'mergecollection', + key: 'reportActionsReactions_', + value: { + reportActionsReactions_2658221912430757962: { + heart: { + createdAt: '2023-08-25 12:37:45', + users: { + 12883048: { + skinTones: { + '-1': '2023-08-25 12:37:45', + }, + }, + }, + }, + }, + }, + }, + { + onyxMethod: 'merge', + key: 'personalDetailsList', + value: { + 14567013: { + accountID: 14567013, + avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/49a4c96c366f9a32905b30462f91ea39e5eee5e8_128.jpeg', + displayName: 'Terry Hightraffic1337', + firstName: 'Terry', + lastName: 'Hightraffic1337', + status: null, + login: 'terry+hightraffic@margelo.io', + pronouns: '', + timezone: { + automatic: true, + selected: 'Europe/Kiev', + }, + payPalMeAddress: '', + phoneNumber: '', + validated: true, }, }, }, ], jsonCode: 200, - requestID: '783ef80a3fc5969a-SJC', + requestID: '81b8b8509a7f5b54-VIE', }); From 881630798fbea58673ad023fd60a802dde9138e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 09:34:25 +0200 Subject: [PATCH 068/170] add mock for read newest action --- src/libs/E2E/API.mock.js | 2 ++ src/libs/E2E/apiMocks/readNewestAction.js | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/libs/E2E/apiMocks/readNewestAction.js diff --git a/src/libs/E2E/API.mock.js b/src/libs/E2E/API.mock.js index ba85cd19aac3..fda1c0d86073 100644 --- a/src/libs/E2E/API.mock.js +++ b/src/libs/E2E/API.mock.js @@ -9,6 +9,7 @@ import mockSigninUser from './apiMocks/signinUser'; import mockAuthenticatePusher from './apiMocks/authenticatePusher'; import mockOpenApp from './apiMocks/openApp'; import mockOpenReport from './apiMocks/openReport'; +import mockReadNewestAction from './apiMocks/readNewestAction'; /** * A dictionary which has the name of a API command as key, and a function which @@ -22,6 +23,7 @@ const mocks = { ReconnectApp: mockOpenApp, OpenReport: mockOpenReport, AuthenticatePusher: mockAuthenticatePusher, + ReadNewestAction: mockReadNewestAction, }; function mockCall(command, apiCommandParameters, tag) { diff --git a/src/libs/E2E/apiMocks/readNewestAction.js b/src/libs/E2E/apiMocks/readNewestAction.js new file mode 100644 index 000000000000..04270a8d93f4 --- /dev/null +++ b/src/libs/E2E/apiMocks/readNewestAction.js @@ -0,0 +1,13 @@ +export default () => ({ + jsonCode: 200, + requestID: '81b8c48e3bfe5a84-VIE', + onyxData: [ + { + onyxMethod: 'merge', + key: 'report_98345625', + value: { + lastReadTime: '2023-10-25 07:32:48.915', + }, + }, + ], +}); From 9655acdaf6810dbc31fdfd3bb1f9cb68302a8975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 25 Oct 2023 09:37:07 +0200 Subject: [PATCH 069/170] add mock for ReconnectToReport --- src/libs/E2E/API.mock.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/E2E/API.mock.js b/src/libs/E2E/API.mock.js index fda1c0d86073..cbe3687ac3c5 100644 --- a/src/libs/E2E/API.mock.js +++ b/src/libs/E2E/API.mock.js @@ -22,6 +22,7 @@ const mocks = { OpenApp: mockOpenApp, ReconnectApp: mockOpenApp, OpenReport: mockOpenReport, + ReconnectToReport: mockOpenReport, AuthenticatePusher: mockAuthenticatePusher, ReadNewestAction: mockReadNewestAction, }; From e24cb355fb60a3e1300fc90188161cd343a2f3ef Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 25 Oct 2023 16:39:39 +0200 Subject: [PATCH 070/170] Refactor workspace settings page --- src/pages/workspace/WorkspaceSettingsPage.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index fd975ebc9247..f3964534a96d 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -26,6 +26,8 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/ import MenuItemWithTopDescription from '../../components/MenuItemWithTopDescription'; import Text from '../../components/Text'; import useLocalize from '../../hooks/useLocalize'; +import FormProvider from "../../components/Form/FormProvider"; +import InputWrapper from "../../components/Form/InputWrapper"; const propTypes = { /** Constant, list of available currencies */ @@ -98,7 +100,7 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_SETTINGS} > {(hasVBA) => ( -

- - + )} ); From f8b0614b6af1211bed7ce314d08b0149921a3c65 Mon Sep 17 00:00:00 2001 From: MaciejSWM <85628901+MaciejSWM@users.noreply.github.com> Date: Wed, 25 Oct 2023 21:30:41 +0200 Subject: [PATCH 071/170] Make styles optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fábio Henriques --- src/components/SelectCircle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx index 776fed4180dc..0afb18988bf3 100644 --- a/src/components/SelectCircle.tsx +++ b/src/components/SelectCircle.tsx @@ -10,7 +10,7 @@ type SelectCircleProps = { isChecked: boolean; /** Additional styles to pass to SelectCircle */ - styles: StyleProp; + styles?: StyleProp; }; function SelectCircle({isChecked = false, styles = []}: SelectCircleProps) { From 8196502b0cdf451a1d249e34f22f221c238e850f Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Wed, 25 Oct 2023 21:48:19 +0200 Subject: [PATCH 072/170] Optional styles --- src/components/SelectCircle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx index 776fed4180dc..0afb18988bf3 100644 --- a/src/components/SelectCircle.tsx +++ b/src/components/SelectCircle.tsx @@ -10,7 +10,7 @@ type SelectCircleProps = { isChecked: boolean; /** Additional styles to pass to SelectCircle */ - styles: StyleProp; + styles?: StyleProp; }; function SelectCircle({isChecked = false, styles = []}: SelectCircleProps) { From 286ed532a9c7132be46648ad1a04f5f32c23eb7b Mon Sep 17 00:00:00 2001 From: Vicktor <79470910+Victor-Nyagudi@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:41:36 +0300 Subject: [PATCH 073/170] Reword comment explaining reason for wrapping ReportActionItemFragment with This is done as per @jjcoffee's suggestion. Co-authored-by: Joel Davies --- src/pages/home/report/ReportActionItemMessage.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 1aa38e2fe19c..b6f270c5b9cd 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -77,9 +77,8 @@ function ReportActionItemMessage(props) { return ( {isApprovedOrSubmittedReportActionType ? ( - // Wrapping 'ReportActionItemFragment' inside '' so that text isn't broken up into separate lines when - // there are multiple messages of type 'TEXT', as seen when a report is submitted/approved from a - // policy on Old Dot and then viewed on New Dot. + // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type + // which we need to wrap in `` to prevent them rendering on separate lines. {!props.isHidden ? _.map(messages, (fragment, index) => renderReportActionItemFragment(fragment, index)) : flaggedContentText} ) : ( From 1d698f3d0a362bd67fd46ee48fcbd37c14a8004f Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Thu, 26 Oct 2023 10:59:08 +0300 Subject: [PATCH 074/170] Pass isApprovedOrSubmittedReportActionType prop instead of actionName to ReportActionItemFragment --- src/pages/home/report/ReportActionItemFragment.js | 8 ++++---- src/pages/home/report/ReportActionItemMessage.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 27dfbfc7095e..fb9b3a6aa8ac 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -64,8 +64,8 @@ const propTypes = { /** Whether the comment is a thread parent message/the first message in a thread */ isThreadParentMessage: PropTypes.bool, - /** The report's action name/type e.g. APPROVED, SUBMITTED, etc. */ - actionName: PropTypes.string, + /** Whether the report action type is 'APPROVED' or 'SUBMITTED'. Used to style system messages from Old Dot */ + isApprovedOrSubmittedReportActionType: PropTypes.bool, ...windowDimensionsPropTypes, @@ -90,7 +90,7 @@ const defaultProps = { delegateAccountID: 0, actorIcon: {}, isThreadParentMessage: false, - actionName: '', + isApprovedOrSubmittedReportActionType: false, displayAsGroup: false, }; @@ -169,7 +169,7 @@ function ReportActionItemFragment(props) { style={[ styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap, - _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.actionName) && {color: styles.colorMuted.color, fontWeight: 'normal'}, + props.isApprovedOrSubmittedReportActionType && {color: styles.colorMuted.color, fontWeight: 'normal'}, ]} > {props.fragment.text} diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index b6f270c5b9cd..51f8dd2b762e 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -70,7 +70,7 @@ function ReportActionItemMessage(props) { accountID={props.action.actorAccountID} style={props.style} displayAsGroup={props.displayAsGroup} - actionName={props.action.actionName} + isApprovedOrSubmittedReportActionType={isApprovedOrSubmittedReportActionType} /> ); From e51c0f071facb16fb8b48e813587caee5fc71fac Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Thu, 26 Oct 2023 10:04:06 +0200 Subject: [PATCH 075/170] Resolve typecheck probems --- src/libs/DateUtils.ts | 8 +++++++- src/libs/Localize/index.ts | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index a6f2860310c2..df8f51be8d76 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -133,7 +133,13 @@ function isYesterday(date: Date, timeZone: string): boolean { * Jan 20 at 5:30 PM within the past year * Jan 20, 2019 at 5:30 PM anything over 1 year ago */ -function datetimeToCalendarTime(locale: string, datetime: string, includeTimeZone = false, currentSelectedTimezone = timezone.selected, isLowercase = false): string { +function datetimeToCalendarTime( + locale: 'en' | 'es' | 'es-ES' | 'es_ES', + datetime: string, + includeTimeZone = false, + currentSelectedTimezone = timezone.selected, + isLowercase = false, +): string { const date = getLocalDateFromDatetime(locale, datetime, currentSelectedTimezone); const tz = includeTimeZone ? ' [UTC]Z' : ''; let todayAt = Localize.translate(locale, 'common.todayAt'); diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 1493a9bbf788..9c444dd8e2c0 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -92,7 +92,7 @@ function translate(desiredLanguage: 'en' | 'es' | /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: TKey, variables: PhraseParameters>) { +function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } @@ -114,7 +114,7 @@ function translateIfPhraseKey(message: TKey | [TK return phrase as string; } - return translateLocal(phrase, variables as PhraseParameters>); + return translateLocal(phrase, ...variables); } catch (error) { const result: string = Array.isArray(message) ? message[0] : message; return result; From 533fbf76c1690c83de1d400215908703bde32d16 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Thu, 26 Oct 2023 11:20:58 +0300 Subject: [PATCH 076/170] Remove unused import statement --- src/pages/home/report/ReportActionItemFragment.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index fb9b3a6aa8ac..194a2ace8097 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import React, {memo} from 'react'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; From cc9360548a6374717ca69e0c3a55f875bf3b969d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 26 Oct 2023 10:33:36 +0200 Subject: [PATCH 077/170] Fix type errors --- src/libs/ErrorUtils.ts | 4 ++-- src/libs/Localize/LocaleListener/BaseLocaleListener.ts | 6 +++--- src/libs/Localize/LocaleListener/index.desktop.ts | 4 ++-- src/libs/Localize/LocaleListener/types.ts | 2 +- src/libs/Localize/index.ts | 6 ++---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index bf4fc0d810a4..bf1d5131f722 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -3,7 +3,7 @@ import DateUtils from './DateUtils'; import * as Localize from './Localize'; import Response from '../types/onyx/Response'; import {ErrorFields, Errors} from '../types/onyx/OnyxCommon'; -import {TranslationFlatObject} from '../languages/types'; +import {TranslationFlatObject, TranslationPaths} from '../languages/types'; function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { switch (response.jsonCode) { @@ -93,7 +93,7 @@ type ErrorsList = Record; * @param errorList - An object containing current errors in the form * @param message - Message to assign to the inputID errors */ -function addErrorMessage(errors: ErrorsList, inputID?: string, message?: string) { +function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey) { if (!message || !inputID) { return; } diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts index 03b59c470cd7..d91dccb7156d 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -1,14 +1,14 @@ import Onyx from 'react-native-onyx'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; -import BaseLocale from './types'; +import BaseLocale, {LocaleListenerConnect} from './types'; let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT; /** * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx. */ -const connect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => { +const connect: LocaleListenerConnect = (callbackAfterChange = () => {}) => { Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => { @@ -22,7 +22,7 @@ const connect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) }); }; -function getPreferredLocale() { +function getPreferredLocale(): BaseLocale { return preferredLocale; } diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index 8445d6152ea7..f28cc74cf692 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,8 +1,8 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; -import BaseLocale, {LocaleListenerConnect} from './types'; +import {LocaleListenerConnect} from './types'; -const localeListener: LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => +const localeListener: LocaleListenerConnect = (callbackAfterChange = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index 44dd3dcc359a..4a8954f2675d 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -3,7 +3,7 @@ import CONST from '../../../CONST'; type BaseLocale = ValueOf; -type LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void) => void; +type LocaleListenerConnect = (callbackAfterChange?: (locale?: BaseLocale) => void) => void; export type {LocaleListenerConnect}; export default BaseLocale; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 9c444dd8e2c0..85177537534b 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -109,15 +109,13 @@ function translateIfPhraseKey(message: TKey | [TK const [phrase, variables] = Array.isArray(message) ? message : [message]; // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. - if (variables?.isTranslated) { return phrase as string; } - return translateLocal(phrase, ...variables); + return translateLocal(phrase, ...(variables as PhraseParameters>)); } catch (error) { - const result: string = Array.isArray(message) ? message[0] : message; - return result; + return Array.isArray(message) ? message[0] : message; } } From fd366982524512102e66097439163d57eef89d61 Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Thu, 26 Oct 2023 13:02:00 +0200 Subject: [PATCH 078/170] Drop default value - undefined works too --- src/components/SelectCircle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx index 0afb18988bf3..489f8774b61e 100644 --- a/src/components/SelectCircle.tsx +++ b/src/components/SelectCircle.tsx @@ -13,7 +13,7 @@ type SelectCircleProps = { styles?: StyleProp; }; -function SelectCircle({isChecked = false, styles = []}: SelectCircleProps) { +function SelectCircle({isChecked = false, styles}: SelectCircleProps) { return ( {isChecked && ( From 34f179e47364e09723f54fecc6633ee31b075bf3 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:14:53 +0200 Subject: [PATCH 079/170] Add FormProvider in KnowATeacherPage --- src/pages/TeachersUnite/KnowATeacherPage.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/TeachersUnite/KnowATeacherPage.js b/src/pages/TeachersUnite/KnowATeacherPage.js index f6506b96d3f2..c78d861e6d75 100644 --- a/src/pages/TeachersUnite/KnowATeacherPage.js +++ b/src/pages/TeachersUnite/KnowATeacherPage.js @@ -7,7 +7,6 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; -import Form from '../../components/Form'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import * as LoginUtils from '../../libs/LoginUtils'; @@ -20,6 +19,8 @@ import Navigation from '../../libs/Navigation/Navigation'; import TeachersUnite from '../../libs/actions/TeachersUnite'; import useLocalize from '../../hooks/useLocalize'; import * as ValidationUtils from '../../libs/ValidationUtils'; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Login list for the user that is signed in */ @@ -99,7 +100,7 @@ function KnowATeacherPage(props) { title={translate('teachersUnitePage.iKnowATeacher')} onBackButtonPress={() => Navigation.goBack(ROUTES.TEACHERS_UNITE)} /> -
{translate('teachersUnitePage.getInTouch')} - - - -
+ ); } From 20e26f0f912eb73fc207080d6a6b934429132961 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Thu, 26 Oct 2023 17:01:37 +0300 Subject: [PATCH 080/170] Wrap initial ReportActionItemFragments in Text This commit also gets rid of the renderReportActionItemFragment() method and flaggedContentText variable that are no longer necessary --- .../home/report/ReportActionItemMessage.js | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 51f8dd2b762e..3d55f6e4fa08 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -50,39 +50,31 @@ function ReportActionItemMessage(props) { const isApprovedOrSubmittedReportActionType = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); - const flaggedContentText = {props.translate('moderation.flaggedContent')}; - - /** - * Get a ReportActionItemFragment - * @param {Object} fragment the current message fragment - * @param {Number} index the current message fragment's index - * @returns {Object} report action item fragment - */ - const renderReportActionItemFragment = (fragment, index) => ( - - ); - return ( - {isApprovedOrSubmittedReportActionType ? ( + {!props.isHidden ? ( // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type // which we need to wrap in `` to prevent them rendering on separate lines. - - {!props.isHidden ? _.map(messages, (fragment, index) => renderReportActionItemFragment(fragment, index)) : flaggedContentText} + + + {_.map(messages, (fragment, index) => ( + + ))} + ) : ( - <>{!props.isHidden ? _.map(messages, (fragment, index) => renderReportActionItemFragment(fragment, index)) : flaggedContentText} + {props.translate('moderation.flaggedContent')} )} ); From 11d669e5f7538ac6a00e17f6b2e19b257a965197 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Thu, 26 Oct 2023 17:12:49 +0300 Subject: [PATCH 081/170] Ran prettier again so linter is happy --- src/pages/home/report/ReportActionItemMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 3d55f6e4fa08..41dfb7e39ec4 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -55,7 +55,7 @@ function ReportActionItemMessage(props) { {!props.isHidden ? ( // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type // which we need to wrap in `` to prevent them rendering on separate lines. - + {_.map(messages, (fragment, index) => ( Date: Thu, 26 Oct 2023 17:38:38 +0300 Subject: [PATCH 082/170] Create helper method to replace inline stlyes --- src/pages/home/report/ReportActionItemFragment.js | 2 +- src/styles/styles.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 194a2ace8097..cf720cb4afd3 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -168,7 +168,7 @@ function ReportActionItemFragment(props) { style={[ styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap, - props.isApprovedOrSubmittedReportActionType && {color: styles.colorMuted.color, fontWeight: 'normal'}, + styles.approvedOrSubmittedMessage(props.isApprovedOrSubmittedReportActionType), ]} > {props.fragment.text} diff --git a/src/styles/styles.ts b/src/styles/styles.ts index d08b947ff680..6977a7f97100 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -4022,6 +4022,8 @@ const styles = (theme: ThemeDefault) => singleOptionSelectorCircle: { borderColor: theme.icon, }, + + approvedOrSubmittedMessage: (isApprovedOrSubmittedMessage: boolean) => (isApprovedOrSubmittedMessage ? {color: theme.textSupporting, fontWeight: 'normal'} : {}), } satisfies Styles); // For now we need to export the styles function that takes the theme as an argument From 244dabfd50f3449a050dfbcec9443a2967e030b3 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Thu, 26 Oct 2023 17:44:12 +0300 Subject: [PATCH 083/170] Rename isApprovedOrSubmittedReportActionType The 'type' at the end felt redundant. This can inferred from reading the comments above the prop types wherever it's used --- src/pages/home/report/ReportActionItemFragment.js | 6 +++--- src/pages/home/report/ReportActionItemMessage.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index cf720cb4afd3..0261785cdab8 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -64,7 +64,7 @@ const propTypes = { isThreadParentMessage: PropTypes.bool, /** Whether the report action type is 'APPROVED' or 'SUBMITTED'. Used to style system messages from Old Dot */ - isApprovedOrSubmittedReportActionType: PropTypes.bool, + isApprovedOrSubmittedReportAction: PropTypes.bool, ...windowDimensionsPropTypes, @@ -89,7 +89,7 @@ const defaultProps = { delegateAccountID: 0, actorIcon: {}, isThreadParentMessage: false, - isApprovedOrSubmittedReportActionType: false, + isApprovedOrSubmittedReportAction: false, displayAsGroup: false, }; @@ -168,7 +168,7 @@ function ReportActionItemFragment(props) { style={[ styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap, - styles.approvedOrSubmittedMessage(props.isApprovedOrSubmittedReportActionType), + styles.approvedOrSubmittedMessage(props.isApprovedOrSubmittedReportAction), ]} > {props.fragment.text} diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 41dfb7e39ec4..51cc2f44cd82 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -48,7 +48,7 @@ function ReportActionItemMessage(props) { } } - const isApprovedOrSubmittedReportActionType = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); + const isApprovedOrSubmittedReportAction = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); return ( @@ -69,7 +69,7 @@ function ReportActionItemMessage(props) { accountID={props.action.actorAccountID} style={props.style} displayAsGroup={props.displayAsGroup} - isApprovedOrSubmittedReportActionType={isApprovedOrSubmittedReportActionType} + isApprovedOrSubmittedReportAction={isApprovedOrSubmittedReportAction} /> ))} From 4356a4b7ac4cc5fcc21d3b53ae1e042845dea3be Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Thu, 26 Oct 2023 17:00:21 +0200 Subject: [PATCH 084/170] Fix lint --- src/pages/workspace/WorkspaceSettingsPage.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index f3964534a96d..93fd525e2d0a 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -16,7 +16,6 @@ import WorkspacePageWithSections from './WorkspacePageWithSections'; import withPolicy, {policyPropTypes, policyDefaultProps} from './withPolicy'; import {withNetwork} from '../../components/OnyxProvider'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; -import Form from '../../components/Form'; import * as ReportUtils from '../../libs/ReportUtils'; import * as UserUtils from '../../libs/UserUtils'; import Avatar from '../../components/Avatar'; @@ -26,8 +25,8 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/ import MenuItemWithTopDescription from '../../components/MenuItemWithTopDescription'; import Text from '../../components/Text'; import useLocalize from '../../hooks/useLocalize'; -import FormProvider from "../../components/Form/FormProvider"; -import InputWrapper from "../../components/Form/InputWrapper"; +import FormProvider from '../../components/Form/FormProvider'; +import InputWrapper from '../../components/Form/InputWrapper'; const propTypes = { /** Constant, list of available currencies */ From 87ad389ad009b6b7bd788e4950f3d73e23406d45 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 27 Oct 2023 12:44:36 +0700 Subject: [PATCH 085/170] fix: 30457 --- src/pages/home/report/ReportActionsView.js | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 108e75051696..d18f55d854be 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -21,6 +21,10 @@ import PopoverReactionList from './ReactionList/PopoverReactionList'; import getIsReportFullyVisible from '../../../libs/getIsReportFullyVisible'; import {ReactionListContext} from '../ReportScreenContext'; import useInitialValue from '../../../hooks/useInitialValue'; +import { withOnyx } from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; +import { didUserLogInDuringSession } from '../../../libs/SessionUtils'; +import { isUserCreatedPolicyRoom } from '../../../libs/ReportUtils'; const propTypes = { /** The report currently being looked at */ @@ -64,6 +68,9 @@ const defaultProps = { isLoadingInitialReportActions: false, isLoadingOlderReportActions: false, isLoadingNewerReportActions: false, + session:{ + authTokenType: '' + } }; function ReportActionsView(props) { @@ -76,6 +83,8 @@ function ReportActionsView(props) { const mostRecentIOUReportActionID = useInitialValue(() => ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions)); const prevNetworkRef = useRef(props.network); + const prevAuthTokenType = useRef(props.session.authTokenType); + const prevIsSmallScreenWidthRef = useRef(props.isSmallScreenWidth); const isFocused = useIsFocused(); @@ -118,6 +127,21 @@ function ReportActionsView(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.network, props.report, isReportFullyVisible]); + useEffect(() => { + const prevTokenType = prevAuthTokenType.current; + const wasLoginChangedDetected = prevTokenType === 'anonymousAccount' && !props.session.authTokenType + if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(props.report)) { + if (isReportFullyVisible) { + openReportIfNecessary(); + } else { + Report.reconnect(reportID); + } + } + // update ref with current network state + prevAuthTokenType.current = props.session.authTokenType; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.session, props.report, isReportFullyVisible]); + useEffect(() => { const prevIsSmallScreenWidth = prevIsSmallScreenWidthRef.current; // If the view is expanded from mobile to desktop layout @@ -338,4 +362,10 @@ function arePropsEqual(oldProps, newProps) { const MemoizedReportActionsView = React.memo(ReportActionsView, arePropsEqual); -export default compose(Performance.withRenderTrace({id: ' rendering'}), withWindowDimensions, withLocalize, withNetwork())(MemoizedReportActionsView); +export default compose(Performance.withRenderTrace({id: ' rendering'}), withWindowDimensions, withLocalize, withNetwork(), +withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, +}) +)(MemoizedReportActionsView); From 8dfd4ab09e182482b92e4fd48833543ea781579e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 10:52:04 +0200 Subject: [PATCH 086/170] wip: having a .e2e.js component --- metro.config.js | 11 +-- src/libs/E2E/client.js | 22 ++++++ src/libs/E2E/tests/chatTypingTest.e2e.js | 11 ++- .../ComposerWithSuggestions.js | 67 ++++++++++--------- .../composerWithSuggestionsProps.js | 4 +- .../ComposerWithSuggestions/index.e2e.js | 13 ++++ tests/e2e/ADDING_TESTS.md | 2 +- .../nativeCommands/NativeCommandsAction.js | 2 +- 8 files changed, 89 insertions(+), 43 deletions(-) rename src/pages/home/report/ReportActionCompose/{ => ComposerWithSuggestions}/ComposerWithSuggestions.js (91%) rename src/pages/home/report/ReportActionCompose/{ => ComposerWithSuggestions}/composerWithSuggestionsProps.js (97%) create mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js diff --git a/metro.config.js b/metro.config.js index 62ca2a25c6b2..dd391c86c34c 100644 --- a/metro.config.js +++ b/metro.config.js @@ -6,13 +6,15 @@ require('dotenv').config(); const defaultConfig = getDefaultConfig(__dirname); -const isUsingMockAPI = process.env.E2E_TESTING === 'true'; +const isE2ETesting = process.env.E2E_TESTING === 'true'; -if (isUsingMockAPI) { +if (isE2ETesting) { // eslint-disable-next-line no-console console.log('⚠️⚠️⚠️⚠️ Using mock API ⚠️⚠️⚠️⚠️'); } +const e2eSourceExts = ['e2e.js', 'e2e.ts']; + /** * Metro configuration * https://facebook.github.io/metro/docs/configuration @@ -22,10 +24,11 @@ if (isUsingMockAPI) { const config = { resolver: { assetExts: _.filter(defaultAssetExts, (ext) => ext !== 'svg'), - sourceExts: [...defaultSourceExts, 'jsx', 'svg'], + // When we run the e2e tests we want files that have the extension e2e.js to be resolved as source files + sourceExts: [...(isE2ETesting ? e2eSourceExts : []), ...defaultSourceExts, 'jsx', 'svg'], resolveRequest: (context, moduleName, platform) => { const resolution = context.resolveRequest(context, moduleName, platform); - if (isUsingMockAPI && moduleName.includes('/API')) { + if (isE2ETesting && moduleName.includes('/API')) { const originalPath = resolution.filePath; const mockPath = originalPath.replace('src/libs/API.ts', 'src/libs/E2E/API.mock.js').replace('/src/libs/API.js/', 'src/libs/E2E/API.mock.js'); // eslint-disable-next-line no-console diff --git a/src/libs/E2E/client.js b/src/libs/E2E/client.js index c948c7c2c6d2..37d339aad48f 100644 --- a/src/libs/E2E/client.js +++ b/src/libs/E2E/client.js @@ -42,8 +42,30 @@ const getTestConfig = () => .then((res) => res.json()) .then((config) => config); +const sendNativeCommand = (payload) => + fetch(`${SERVER_ADDRESS}${Routes.testNativeCommand}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }).then((res) => { + if (res.statusCode === 200) { + return; + } + const errorMsg = `Test result submission failed with status code ${res.statusCode}`; + res.json() + .then((responseText) => { + throw new Error(`${errorMsg}: ${responseText}`); + }) + .catch(() => { + throw new Error(errorMsg); + }); + }); + export default { submitTestResults, submitTestDone, getTestConfig, + sendNativeCommand, }; diff --git a/src/libs/E2E/tests/chatTypingTest.e2e.js b/src/libs/E2E/tests/chatTypingTest.e2e.js index 95d0766ff6c2..4788e463c91c 100644 --- a/src/libs/E2E/tests/chatTypingTest.e2e.js +++ b/src/libs/E2E/tests/chatTypingTest.e2e.js @@ -4,6 +4,7 @@ import E2EClient from '../client'; import Navigation from '../../Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import CONST from '../../../CONST'; +import * as NativeCommands from '../../../../tests/e2e/nativeCommands/NativeCommandsAction'; const test = () => { // check for login (if already logged in the action will simply resolve) @@ -18,10 +19,14 @@ const test = () => { console.debug('[E2E] Logged in, getting typing metrics and submitting them…'); Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { - console.debug(`[E2E] Sidebar loaded, navigating to a report…`); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute('98345625')); + if (entry.name !== CONST.TIMING.SIDEBAR_LOADED) { + return; } + + console.debug(`[E2E] Sidebar loaded, navigating to a report…`); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute('98345625')); + + E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('Hi')); }); }); }; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js similarity index 91% rename from src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js rename to src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index e194d0870885..28fc5a4c5f24 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -4,38 +4,38 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {useIsFocused, useNavigation} from '@react-navigation/native'; -import styles from '../../../../styles/styles'; -import themeColors from '../../../../styles/themes/default'; -import Composer from '../../../../components/Composer'; -import containerComposeStyles from '../../../../styles/containerComposeStyles'; -import useWindowDimensions from '../../../../hooks/useWindowDimensions'; -import CONST from '../../../../CONST'; -import * as Browser from '../../../../libs/Browser'; -import ONYXKEYS from '../../../../ONYXKEYS'; -import * as KeyDownListener from '../../../../libs/KeyboardShortcut/KeyDownPressListener'; -import * as EmojiPickerActions from '../../../../libs/actions/EmojiPickerAction'; -import willBlurTextInputOnTapOutsideFunc from '../../../../libs/willBlurTextInputOnTapOutside'; -import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; -import * as ComposerUtils from '../../../../libs/ComposerUtils'; -import * as Report from '../../../../libs/actions/Report'; -import usePrevious from '../../../../hooks/usePrevious'; -import * as EmojiUtils from '../../../../libs/EmojiUtils'; -import * as User from '../../../../libs/actions/User'; -import * as ReportUtils from '../../../../libs/ReportUtils'; -import * as SuggestionUtils from '../../../../libs/SuggestionUtils'; -import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils'; -import canFocusInputOnScreenFocus from '../../../../libs/canFocusInputOnScreenFocus'; -import SilentCommentUpdater from './SilentCommentUpdater'; -import Suggestions from './Suggestions'; -import getDraftComment from '../../../../libs/ComposerUtils/getDraftComment'; -import useLocalize from '../../../../hooks/useLocalize'; -import compose from '../../../../libs/compose'; -import withKeyboardState from '../../../../components/withKeyboardState'; +import styles from '../../../../../styles/styles'; +import themeColors from '../../../../../styles/themes/default'; +import Composer from '../../../../../components/Composer'; +import containerComposeStyles from '../../../../../styles/containerComposeStyles'; +import useWindowDimensions from '../../../../../hooks/useWindowDimensions'; +import CONST from '../../../../../CONST'; +import * as Browser from '../../../../../libs/Browser'; +import ONYXKEYS from '../../../../../ONYXKEYS'; +import * as KeyDownListener from '../../../../../libs/KeyboardShortcut/KeyDownPressListener'; +import * as EmojiPickerActions from '../../../../../libs/actions/EmojiPickerAction'; +import willBlurTextInputOnTapOutsideFunc from '../../../../../libs/willBlurTextInputOnTapOutside'; +import ReportActionComposeFocusManager from '../../../../../libs/ReportActionComposeFocusManager'; +import * as ComposerUtils from '../../../../../libs/ComposerUtils'; +import * as Report from '../../../../../libs/actions/Report'; +import usePrevious from '../../../../../hooks/usePrevious'; +import * as EmojiUtils from '../../../../../libs/EmojiUtils'; +import * as User from '../../../../../libs/actions/User'; +import * as ReportUtils from '../../../../../libs/ReportUtils'; +import * as SuggestionUtils from '../../../../../libs/SuggestionUtils'; +import * as ReportActionsUtils from '../../../../../libs/ReportActionsUtils'; +import canFocusInputOnScreenFocus from '../../../../../libs/canFocusInputOnScreenFocus'; +import SilentCommentUpdater from '../SilentCommentUpdater'; +import Suggestions from '../Suggestions'; +import getDraftComment from '../../../../../libs/ComposerUtils/getDraftComment'; +import useLocalize from '../../../../../hooks/useLocalize'; +import compose from '../../../../../libs/compose'; +import withKeyboardState from '../../../../../components/withKeyboardState'; import {propTypes, defaultProps} from './composerWithSuggestionsProps'; -import focusWithDelay from '../../../../libs/focusWithDelay'; -import useDebounce from '../../../../hooks/useDebounce'; -import updateMultilineInputRange from '../../../../libs/UpdateMultilineInputRange'; -import * as InputFocus from '../../../../libs/actions/InputFocus'; +import focusWithDelay from '../../../../../libs/focusWithDelay'; +import useDebounce from '../../../../../hooks/useDebounce'; +import updateMultilineInputRange from '../../../../../libs/UpdateMultilineInputRange'; +import * as InputFocus from '../../../../../libs/actions/InputFocus'; const {RNTextInputReset} = NativeModules; @@ -522,7 +522,10 @@ function ComposerWithSuggestions({ return ( <> - + { + console.log('⚠️⚡️🤡 JOOOOO LOADING FROM e2E file brother'); + return ( + + ); +}); diff --git a/tests/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md index ac43e61f60ed..6a4ea3edd1ef 100644 --- a/tests/e2e/ADDING_TESTS.md +++ b/tests/e2e/ADDING_TESTS.md @@ -33,7 +33,7 @@ require('./src/libs/E2E/reactNativeLaunchingTest'); Now you can start the metro bundler in e2e mode with: -``` +```bash CAPTURE_METRICS=true E2E_TESTING=true npm start -- --reset-cache ``` diff --git a/tests/e2e/nativeCommands/NativeCommandsAction.js b/tests/e2e/nativeCommands/NativeCommandsAction.js index ce6b38079527..eb3566b88b1c 100644 --- a/tests/e2e/nativeCommands/NativeCommandsAction.js +++ b/tests/e2e/nativeCommands/NativeCommandsAction.js @@ -4,7 +4,7 @@ const NativeCommandsAction = { }; const makeTypeTextCommand = (text) => ({ - command: NativeCommandsAction.type, + actionName: NativeCommandsAction.type, payload: { text, }, From 337bc06a37a5e227d92420fb906d5a1a4c529358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 11:30:30 +0200 Subject: [PATCH 087/170] implemented typing into composer --- src/libs/E2E/actions/waitForKeyboard.js | 15 ++++++++++++ src/libs/E2E/client.js | 9 ++++++- ...ingTest.e2e.js => reportTypingTest.e2e.js} | 7 +++++- .../ComposerWithSuggestions/index.e2e.js | 24 ++++++++++++++++--- tests/e2e/config.js | 3 +++ 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 src/libs/E2E/actions/waitForKeyboard.js rename src/libs/E2E/tests/{chatTypingTest.e2e.js => reportTypingTest.e2e.js} (76%) diff --git a/src/libs/E2E/actions/waitForKeyboard.js b/src/libs/E2E/actions/waitForKeyboard.js new file mode 100644 index 000000000000..4bc0f492e3a3 --- /dev/null +++ b/src/libs/E2E/actions/waitForKeyboard.js @@ -0,0 +1,15 @@ +import {Keyboard} from 'react-native'; + +export default function waitForKeyboard() { + return new Promise((resolve) => { + function checkKeyboard() { + if (Keyboard.isVisible()) { + resolve(); + } else { + console.debug(`[E2E] Waiting for keyboard to appear…`); + setTimeout(checkKeyboard, 1000); + } + } + checkKeyboard(); + }); +} diff --git a/src/libs/E2E/client.js b/src/libs/E2E/client.js index 37d339aad48f..9221919bf427 100644 --- a/src/libs/E2E/client.js +++ b/src/libs/E2E/client.js @@ -34,13 +34,19 @@ const submitTestResults = (testResult) => const submitTestDone = () => fetch(`${SERVER_ADDRESS}${Routes.testDone}`); +let currentActiveTestConfig = null; /** * @returns {Promise} */ const getTestConfig = () => fetch(`${SERVER_ADDRESS}${Routes.testConfig}`) .then((res) => res.json()) - .then((config) => config); + .then((config) => { + currentActiveTestConfig = config; + return config; + }); + +const getCurrentActiveTestConfig = () => currentActiveTestConfig; const sendNativeCommand = (payload) => fetch(`${SERVER_ADDRESS}${Routes.testNativeCommand}`, { @@ -67,5 +73,6 @@ export default { submitTestResults, submitTestDone, getTestConfig, + getCurrentActiveTestConfig, sendNativeCommand, }; diff --git a/src/libs/E2E/tests/chatTypingTest.e2e.js b/src/libs/E2E/tests/reportTypingTest.e2e.js similarity index 76% rename from src/libs/E2E/tests/chatTypingTest.e2e.js rename to src/libs/E2E/tests/reportTypingTest.e2e.js index 4788e463c91c..4829b65a8b93 100644 --- a/src/libs/E2E/tests/chatTypingTest.e2e.js +++ b/src/libs/E2E/tests/reportTypingTest.e2e.js @@ -5,6 +5,7 @@ import Navigation from '../../Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import CONST from '../../../CONST'; import * as NativeCommands from '../../../../tests/e2e/nativeCommands/NativeCommandsAction'; +import waitForKeyboard from '../actions/waitForKeyboard'; const test = () => { // check for login (if already logged in the action will simply resolve) @@ -26,7 +27,11 @@ const test = () => { console.debug(`[E2E] Sidebar loaded, navigating to a report…`); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute('98345625')); - E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('Hi')); + // Wait until keyboard is visible (so we are focused on the input): + waitForKeyboard().then(() => { + console.debug(`[E2E] Keyboard visible, typing…`); + E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('Hi')); + }); }); }); }; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js index 63be4d49a628..276e5a82a8c3 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js @@ -1,13 +1,31 @@ -import React from 'react'; +import React, {useEffect} from 'react'; +import _ from 'lodash'; import ComposerWithSuggestions from './ComposerWithSuggestions'; +import E2EClient from '../../../../../libs/E2E/client'; export default React.forwardRef((props, ref) => { - console.log('⚠️⚡️🤡 JOOOOO LOADING FROM e2E file brother'); + // Auto focus on e2e tests + useEffect(() => { + if (_.get(E2EClient.getCurrentActiveTestConfig(), 'reportScreen.autoFocus', false) === false) { + return; + } + + // We need to wait for the component to be mounted before focusing + setTimeout(() => { + if (!ref || !ref.current) { + console.log('No ref ⛈️'); + return; + } + + ref.current.focus(true); + }, 1); + }, [ref]); + return ( ); }); diff --git a/tests/e2e/config.js b/tests/e2e/config.js index 34cd13a8f6db..6095927d0174 100644 --- a/tests/e2e/config.js +++ b/tests/e2e/config.js @@ -66,6 +66,9 @@ module.exports = { }, [TEST_NAMES.ReportTyping]: { name: TEST_NAMES.ReportTyping, + reportScreen: { + autoFocus: true, + }, }, }, }; From 79e367fcbc06c59f5d934a18c84c8b1e069e9c10 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 27 Oct 2023 11:40:36 +0200 Subject: [PATCH 088/170] Move localeListener to separate type --- src/libs/Localize/LocaleListener/index.desktop.ts | 10 ++++++---- src/libs/Localize/LocaleListener/index.ts | 10 ++++++---- src/libs/Localize/LocaleListener/types.ts | 6 +++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index f28cc74cf692..6974d3ed4879 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,8 +1,8 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; -import {LocaleListenerConnect} from './types'; +import {LocaleListener, LocaleListenerConnect} from './types'; -const localeListener: LocaleListenerConnect = (callbackAfterChange = () => {}) => +const localeListenerConnect: LocaleListenerConnect = (callbackAfterChange = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); @@ -11,6 +11,8 @@ const localeListener: LocaleListenerConnect = (callbackAfterChange = () => {}) = callbackAfterChange(val); }); -export default { - connect: localeListener, +const localeListener: LocaleListener = { + connect: localeListenerConnect, }; + +export default localeListener; diff --git a/src/libs/Localize/LocaleListener/index.ts b/src/libs/Localize/LocaleListener/index.ts index 6a725be29662..b0dda5d5fabc 100644 --- a/src/libs/Localize/LocaleListener/index.ts +++ b/src/libs/Localize/LocaleListener/index.ts @@ -1,8 +1,10 @@ import BaseLocaleListener from './BaseLocaleListener'; -import {LocaleListenerConnect} from './types'; +import {LocaleListener, LocaleListenerConnect} from './types'; -const localeListener: LocaleListenerConnect = BaseLocaleListener.connect; +const localeListenerConnect: LocaleListenerConnect = BaseLocaleListener.connect; -export default { - connect: localeListener, +const localizeListener: LocaleListener = { + connect: localeListenerConnect, }; + +export default localizeListener; diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index 4a8954f2675d..b932f83f6cfb 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -5,5 +5,9 @@ type BaseLocale = ValueOf; type LocaleListenerConnect = (callbackAfterChange?: (locale?: BaseLocale) => void) => void; -export type {LocaleListenerConnect}; +type LocaleListener = { + connect: LocaleListenerConnect; +}; + +export type {LocaleListenerConnect, LocaleListener}; export default BaseLocale; From 953b91d1e9ae4201f8d4fccc4b51da6561b1fcd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 12:03:51 +0200 Subject: [PATCH 089/170] send rerender results to client --- src/libs/E2E/client.js | 14 ++++++----- src/libs/E2E/tests/reportTypingTest.e2e.js | 17 ++++++++++++- .../ComposerWithSuggestions.js | 5 ++++ .../ComposerWithSuggestions/index.e2e.js | 24 ++++++++++++++++--- tests/e2e/server/index.js | 15 ++++++------ 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/libs/E2E/client.js b/src/libs/E2E/client.js index 9221919bf427..0da87094683a 100644 --- a/src/libs/E2E/client.js +++ b/src/libs/E2E/client.js @@ -10,19 +10,20 @@ const SERVER_ADDRESS = `http://localhost:${Config.SERVER_PORT}`; * @param {TestResult} testResult * @returns {Promise} */ -const submitTestResults = (testResult) => - fetch(`${SERVER_ADDRESS}${Routes.testResults}`, { +const submitTestResults = (testResult) => { + console.debug(`[E2E] Submitting test result '${testResult.name}'…`); + return fetch(`${SERVER_ADDRESS}${Routes.testResults}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(testResult), }).then((res) => { - if (res.statusCode === 200) { + if (res.status === 200) { console.debug(`[E2E] Test result '${testResult.name}' submitted successfully`); return; } - const errorMsg = `Test result submission failed with status code ${res.statusCode}`; + const errorMsg = `Test result submission failed with status code ${res.status}`; res.json() .then((responseText) => { throw new Error(`${errorMsg}: ${responseText}`); @@ -31,6 +32,7 @@ const submitTestResults = (testResult) => throw new Error(errorMsg); }); }); +}; const submitTestDone = () => fetch(`${SERVER_ADDRESS}${Routes.testDone}`); @@ -57,9 +59,9 @@ const sendNativeCommand = (payload) => body: JSON.stringify(payload), }).then((res) => { if (res.statusCode === 200) { - return; + return true; } - const errorMsg = `Test result submission failed with status code ${res.statusCode}`; + const errorMsg = `Sending native command failed with status code ${res.statusCode}`; res.json() .then((responseText) => { throw new Error(`${errorMsg}: ${responseText}`); diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.js b/src/libs/E2E/tests/reportTypingTest.e2e.js index 4829b65a8b93..5c1cd425f60a 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.js +++ b/src/libs/E2E/tests/reportTypingTest.e2e.js @@ -6,6 +6,7 @@ import ROUTES from '../../../ROUTES'; import CONST from '../../../CONST'; import * as NativeCommands from '../../../../tests/e2e/nativeCommands/NativeCommandsAction'; import waitForKeyboard from '../actions/waitForKeyboard'; +import {resetRerenderCount, getRerenderCount} from '../../../pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e'; const test = () => { // check for login (if already logged in the action will simply resolve) @@ -29,8 +30,22 @@ const test = () => { // Wait until keyboard is visible (so we are focused on the input): waitForKeyboard().then(() => { + resetRerenderCount(); console.debug(`[E2E] Keyboard visible, typing…`); - E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('Hi')); + E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('A')) + .then(() => { + setTimeout(() => { + const rerenderCount = getRerenderCount(); + + E2EClient.submitTestResults({ + name: 'Composer typing rerender count', + duration: rerenderCount, + }).then(E2EClient.submitTestDone); + }, 3000); + }) + .catch(() => { + // TODO: error handling + }); }); }); }); diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 28fc5a4c5f24..6f885f8c6009 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -103,6 +103,8 @@ function ComposerWithSuggestions({ forwardedRef, isNextModalWillOpenRef, editFocused, + // For testing + children, }) { const {preferredLocale} = useLocalize(); const isFocused = useIsFocused(); @@ -589,6 +591,9 @@ function ComposerWithSuggestions({ updateComment={updateComment} commentRef={commentRef} /> + + {/* Only used for testing so far */} + {children} ); } diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js index 276e5a82a8c3..ab626ac0f2e8 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js @@ -3,8 +3,19 @@ import _ from 'lodash'; import ComposerWithSuggestions from './ComposerWithSuggestions'; import E2EClient from '../../../../../libs/E2E/client'; +let rerenderCount = 0; +const getRerenderCount = () => rerenderCount; +const resetRerenderCount = () => { + rerenderCount = 0; +}; + +function IncrementRenderCount() { + rerenderCount += 1; + return null; +} + export default React.forwardRef((props, ref) => { - // Auto focus on e2e tests + // Eventually Auto focus on e2e tests useEffect(() => { if (_.get(E2EClient.getCurrentActiveTestConfig(), 'reportScreen.autoFocus', false) === false) { return; @@ -13,7 +24,6 @@ export default React.forwardRef((props, ref) => { // We need to wait for the component to be mounted before focusing setTimeout(() => { if (!ref || !ref.current) { - console.log('No ref ⛈️'); return; } @@ -26,6 +36,14 @@ export default React.forwardRef((props, ref) => { // eslint-disable-next-line react/jsx-props-no-spreading {...props} ref={ref} - /> + > + {/* Important: + this has to be a child, as this container might not + re-render while the actual ComposerWithSuggestions will. + */} + + ); }); + +export {getRerenderCount, resetRerenderCount}; diff --git a/tests/e2e/server/index.js b/tests/e2e/server/index.js index 94b4e8abec09..f12a982745bb 100644 --- a/tests/e2e/server/index.js +++ b/tests/e2e/server/index.js @@ -127,16 +127,15 @@ const createServerInstance = () => { } case Routes.testNativeCommand: { - getPostJSONRequestData(req, res).then((data) => { - const status = executeFromPayload(data.actionName, data.payload); - if (status) { - res.end('ok'); - } else { + getPostJSONRequestData(req, res).then((data) => + executeFromPayload(data.actionName, data.payload).then((status) => { + if (status) { + return res.end('ok'); + } res.statusCode = 500; res.end('Error executing command'); - } - }); - + }), + ); break; } From 602bdbd564b8c48cbdc4b77caabe79d8ea0ce892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 12:26:11 +0200 Subject: [PATCH 090/170] remove the connotation of "duration" --- src/libs/E2E/tests/reportTypingTest.e2e.js | 2 +- tests/e2e/testRunner.js | 24 ++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.js b/src/libs/E2E/tests/reportTypingTest.e2e.js index 5c1cd425f60a..d70579c2e733 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.js +++ b/src/libs/E2E/tests/reportTypingTest.e2e.js @@ -39,7 +39,7 @@ const test = () => { E2EClient.submitTestResults({ name: 'Composer typing rerender count', - duration: rerenderCount, + renderCount: rerenderCount, }).then(E2EClient.submitTestDone); }, 3000); }) diff --git a/tests/e2e/testRunner.js b/tests/e2e/testRunner.js index 5c6c33bdf7e9..00cf46a6eaea 100644 --- a/tests/e2e/testRunner.js +++ b/tests/e2e/testRunner.js @@ -170,20 +170,28 @@ const runTests = async () => { const server = createServerInstance(); await server.start(); - // Create a dict in which we will store the run durations for all tests - const durationsByTestName = {}; + // Create a dict in which we will store the collected metrics for all tests + const resultsByTestName = {}; // Collect results while tests are being executed server.addTestResultListener((testResult) => { if (testResult.error != null) { throw new Error(`Test '${testResult.name}' failed with error: ${testResult.error}`); } - if (testResult.duration < 0) { - return; + let result = 0; + + if ('duration' in testResult) { + if (testResult.duration < 0) { + return; + } + result = testResult.duration; + } + if ('renderCount' in testResult) { + result = testResult.renderCount; } - Logger.log(`[LISTENER] Test '${testResult.name}' took ${testResult.duration}ms`); - durationsByTestName[testResult.name] = (durationsByTestName[testResult.name] || []).concat(testResult.duration); + Logger.log(`[LISTENER] Test '${testResult.name}' measured ${result}`); + resultsByTestName[testResult.name] = (resultsByTestName[testResult.name] || []).concat(result); }); // Run the tests @@ -255,8 +263,8 @@ const runTests = async () => { // Calculate statistics and write them to our work file progressLog = Logger.progressInfo('Calculating statics and writing results'); - for (const testName of _.keys(durationsByTestName)) { - const stats = math.getStats(durationsByTestName[testName]); + for (const testName of _.keys(resultsByTestName)) { + const stats = math.getStats(resultsByTestName[testName]); await writeTestStats( { name: testName, From 8152008640c3e06de9a1b3976faa5023bca25d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 13:11:34 +0200 Subject: [PATCH 091/170] add clearing of input --- src/libs/E2E/tests/reportTypingTest.e2e.js | 13 ++++++++---- .../nativeCommands/NativeCommandsAction.js | 6 ++++++ tests/e2e/nativeCommands/adbBackspace.js | 10 ++++++++++ tests/e2e/nativeCommands/index.js | 3 +++ tests/e2e/server/index.js | 20 ++++++++++++------- 5 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 tests/e2e/nativeCommands/adbBackspace.js diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.js b/src/libs/E2E/tests/reportTypingTest.e2e.js index d70579c2e733..ec2ddbea013c 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.js +++ b/src/libs/E2E/tests/reportTypingTest.e2e.js @@ -30,9 +30,13 @@ const test = () => { // Wait until keyboard is visible (so we are focused on the input): waitForKeyboard().then(() => { - resetRerenderCount(); console.debug(`[E2E] Keyboard visible, typing…`); - E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('A')) + E2EClient.sendNativeCommand(NativeCommands.makeBackspaceCommand()) + .then(() => { + resetRerenderCount(); + return Promise.resolve(); + }) + .then(() => E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('A'))) .then(() => { setTimeout(() => { const rerenderCount = getRerenderCount(); @@ -43,8 +47,9 @@ const test = () => { }).then(E2EClient.submitTestDone); }, 3000); }) - .catch(() => { - // TODO: error handling + .catch((error) => { + console.error('[E2E] Error while test', error); + E2EClient.submitTestDone(); }); }); }); diff --git a/tests/e2e/nativeCommands/NativeCommandsAction.js b/tests/e2e/nativeCommands/NativeCommandsAction.js index eb3566b88b1c..f2aa4644f7ff 100644 --- a/tests/e2e/nativeCommands/NativeCommandsAction.js +++ b/tests/e2e/nativeCommands/NativeCommandsAction.js @@ -1,6 +1,7 @@ const NativeCommandsAction = { scroll: 'scroll', type: 'type', + backspace: 'backspace', }; const makeTypeTextCommand = (text) => ({ @@ -10,7 +11,12 @@ const makeTypeTextCommand = (text) => ({ }, }); +const makeBackspaceCommand = () => ({ + actionName: NativeCommandsAction.backspace, +}); + module.exports = { NativeCommandsAction, makeTypeTextCommand, + makeBackspaceCommand, }; diff --git a/tests/e2e/nativeCommands/adbBackspace.js b/tests/e2e/nativeCommands/adbBackspace.js new file mode 100644 index 000000000000..8f41364daed3 --- /dev/null +++ b/tests/e2e/nativeCommands/adbBackspace.js @@ -0,0 +1,10 @@ +const execAsync = require('../utils/execAsync'); +const Logger = require('../utils/logger'); + +const adbBackspace = async () => { + Logger.log(`🔙 Pressing backspace`); + execAsync(`adb shell input keyevent KEYCODE_DEL`); + return true; +}; + +module.exports = adbBackspace; diff --git a/tests/e2e/nativeCommands/index.js b/tests/e2e/nativeCommands/index.js index 7452dce3067c..bb87c16a6f42 100644 --- a/tests/e2e/nativeCommands/index.js +++ b/tests/e2e/nativeCommands/index.js @@ -1,3 +1,4 @@ +const adbBackspace = require('./adbBackspace'); const adbTypeText = require('./adbTypeText'); const {NativeCommandsAction} = require('./NativeCommandsAction'); @@ -7,6 +8,8 @@ const executeFromPayload = (actionName, payload) => { throw new Error('Not implemented yet'); case NativeCommandsAction.type: return adbTypeText(payload.text); + case NativeCommandsAction.backspace: + return adbBackspace(); default: throw new Error(`Unknown action: ${actionName}`); } diff --git a/tests/e2e/server/index.js b/tests/e2e/server/index.js index f12a982745bb..51a227ed1c33 100644 --- a/tests/e2e/server/index.js +++ b/tests/e2e/server/index.js @@ -127,15 +127,21 @@ const createServerInstance = () => { } case Routes.testNativeCommand: { - getPostJSONRequestData(req, res).then((data) => - executeFromPayload(data.actionName, data.payload).then((status) => { - if (status) { - return res.end('ok'); - } + getPostJSONRequestData(req, res) + .then((data) => + executeFromPayload(data.actionName, data.payload).then((status) => { + if (status) { + return res.end('ok'); + } + res.statusCode = 500; + res.end('Error executing command'); + }), + ) + .catch((error) => { + Logger.error('Error executing command', error); res.statusCode = 500; res.end('Error executing command'); - }), - ); + }); break; } From 9ea214d523bbef6b4e97a775a125ab1ae60bc53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 13:31:14 +0200 Subject: [PATCH 092/170] fix warnings thrown --- src/libs/E2E/client.js | 4 ++-- tests/e2e/server/index.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/E2E/client.js b/src/libs/E2E/client.js index 0da87094683a..56447fd09133 100644 --- a/src/libs/E2E/client.js +++ b/src/libs/E2E/client.js @@ -58,10 +58,10 @@ const sendNativeCommand = (payload) => }, body: JSON.stringify(payload), }).then((res) => { - if (res.statusCode === 200) { + if (res.status === 200) { return true; } - const errorMsg = `Sending native command failed with status code ${res.statusCode}`; + const errorMsg = `Sending native command failed with status code ${res.status}`; res.json() .then((responseText) => { throw new Error(`${errorMsg}: ${responseText}`); diff --git a/tests/e2e/server/index.js b/tests/e2e/server/index.js index 51a227ed1c33..4c2e00126fd5 100644 --- a/tests/e2e/server/index.js +++ b/tests/e2e/server/index.js @@ -131,7 +131,8 @@ const createServerInstance = () => { .then((data) => executeFromPayload(data.actionName, data.payload).then((status) => { if (status) { - return res.end('ok'); + res.end('ok'); + return; } res.statusCode = 500; res.end('Error executing command'); From 31b677e5b660c07c0b5db668a4dcd4b34e4d4e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 13:41:16 +0200 Subject: [PATCH 093/170] remove unused native ID --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 6f885f8c6009..1bc6688a9db5 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -524,10 +524,7 @@ function ComposerWithSuggestions({ return ( <> - + Date: Fri, 27 Oct 2023 14:12:23 +0200 Subject: [PATCH 094/170] add index file --- .../ReportActionCompose/ComposerWithSuggestions/index.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.js diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.js new file mode 100644 index 000000000000..f2aebd390ba6 --- /dev/null +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.js @@ -0,0 +1,3 @@ +import ComposerWithSuggestions from './ComposerWithSuggestions'; + +export default ComposerWithSuggestions; From e01cf4f5515808967146faab54613ad7a822ac61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 27 Oct 2023 14:14:12 +0200 Subject: [PATCH 095/170] add test --- src/libs/E2E/reactNativeLaunchingTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/E2E/reactNativeLaunchingTest.js b/src/libs/E2E/reactNativeLaunchingTest.js index 13183c1044db..a2c8ba3aec31 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.js +++ b/src/libs/E2E/reactNativeLaunchingTest.js @@ -24,6 +24,7 @@ if (!Metrics.canCapturePerformanceMetrics()) { const tests = { [E2EConfig.TEST_NAMES.AppStartTime]: require('./tests/appStartTimeTest.e2e').default, [E2EConfig.TEST_NAMES.OpenSearchPage]: require('./tests/openSearchPageTest.e2e').default, + [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, }; // Once we receive the TII measurement we know that the app is initialized and ready to be used: From affaeb2135053b609137ac69b77b424d40eccadb Mon Sep 17 00:00:00 2001 From: Pujan Date: Fri, 27 Oct 2023 19:29:42 +0530 Subject: [PATCH 096/170] added background for task views --- src/pages/home/report/ReportActionItem.js | 39 ++++++++++++++--------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index d44c7b8ee4d1..13b4d6247d43 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -73,6 +73,7 @@ import ROUTES from '../../../ROUTES'; import Navigation from '../../../libs/Navigation/Navigation'; import KYCWall from '../../../components/KYCWall'; import userWalletPropTypes from '../../EnablePayments/userWalletPropTypes'; +import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; const propTypes = { ...windowDimensionsPropTypes, @@ -570,24 +571,32 @@ function ReportActionItem(props) { if (ReportUtils.isTaskReport(props.report)) { if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { content = ( - <> - - ${props.translate('parentReportAction.deletedTask')}`} /> - - - + + + + + ${props.translate('parentReportAction.deletedTask')}`} /> + + + + ); } else { content = ( - + + + + + + ); } } From 9dc5c8b5909f49811108fdce46f846809729f497 Mon Sep 17 00:00:00 2001 From: Pujan Date: Fri, 27 Oct 2023 20:02:26 +0530 Subject: [PATCH 097/170] prettier --- src/pages/home/report/ReportActionItem.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 13b4d6247d43..8b518f971dfe 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -571,8 +571,8 @@ function ReportActionItem(props) { if (ReportUtils.isTaskReport(props.report)) { if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { content = ( - - + + - + + Date: Sat, 28 Oct 2023 16:22:55 +0700 Subject: [PATCH 098/170] fix: 30268 --- src/components/ScreenWrapper/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index e2af40589a8a..200dd5074c4f 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -45,6 +45,10 @@ function ScreenWrapper({ const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; const isKeyboardShown = lodashGet(keyboardState, 'isKeyboardShown', false); + const isKeyboardShownRef = useRef(); + + isKeyboardShownRef.current = lodashGet(keyboardState, 'isKeyboardShown', false); + const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponderCapture: (e, gestureState) => gestureState.numberActiveTouches === CONST.TEST_TOOL.NUMBER_OF_TAPS, @@ -80,7 +84,7 @@ function ScreenWrapper({ // described here https://reactnavigation.org/docs/preventing-going-back/#limitations const beforeRemoveSubscription = shouldDismissKeyboardBeforeClose ? navigation.addListener('beforeRemove', () => { - if (!isKeyboardShown) { + if (!isKeyboardShownRef.current) { return; } Keyboard.dismiss(); From 57ab21420f9d0077f646008849ef44cb15bdb63b Mon Sep 17 00:00:00 2001 From: John Schuster Date: Sat, 28 Oct 2023 08:02:14 -0500 Subject: [PATCH 099/170] Update Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md Updated the link to US-based Backed Startups --- .../Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index 9e6ea7ef68e7..bd890bda9bb6 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -6,7 +6,7 @@ redirect_from: articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-B # Overview This guide provides practical tips and recommendations for small businesses with 100 to 250 employees to effectively use Expensify to improve spend visibility, facilitate employee reimbursements, and reduce the risk of fraudulent expenses. -- See our [US-based VC-Backed Startups](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups) if you are more concerned with top-line revenue growth +- See our [US-based VC-Backed Startups](https://help.expensify.com/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups) if you are more concerned with top-line revenue growth # Who you are As a small to medium-sized business owner, your main aim is to achieve success and grow your business. To achieve your goals, it is crucial that you make worthwhile investments in both your workforce and your business processes. This means providing your employees with the resources they need to generate revenue effectively, while also adopting measures to guarantee that expenses are compliant. From 3cb96a322576e793abcdb2e24feda5f89f0b1bb6 Mon Sep 17 00:00:00 2001 From: John Schuster Date: Sat, 28 Oct 2023 08:30:13 -0500 Subject: [PATCH 100/170] Update Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md Updating links in the article --- ...fy-Playbook-For-Small-To-Medium-Sized-Businesses.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index bd890bda9bb6..dd479fa1891b 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -147,10 +147,10 @@ You only need to do this once: you are fully set up for not only reimbursing exp ## Step 9: Invite employees and set an approval workflow *Select an Approval Mode* -We recommend you select *Advanced Approval* as your Approval Mode to set up a middle-management layer of a approval. If you have a single layer of approval, we recommend selecting [Submit & Approve](https://community.expensify.com/discussion/5643/deep-dive-submit-and-approve). But if *Advanced Approval* if your jam, keep reading! +We recommend you select *Advanced Approval* as your Approval Mode to set up a middle-management layer of approval. If you have a single layer of approval, we recommend selecting [Submit & Approve](https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows#gsc.tab=0). But if *Advanced Approval* is your jam, keep reading! *Import your employees in bulk via CSV* -Given the amount of employees you have, it’s best you import employees in bulk via CSV. You can learn more about using a CSV file to bulk upload employees with *Advanced Approval [here](https://community.expensify.com/discussion/5735/deep-dive-the-ins-and-outs-of-advanced-approval)* +Given the amount of employees you have, it’s best you import employees in bulk via CSV. You can learn more about using a CSV file to bulk upload employees with *Advanced Approval [here](https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows#gsc.tab=0)* ![Bulk import your employees](https://help.expensify.com/assets/images/playbook-impoort-employees.png){:width="100%"} @@ -191,7 +191,7 @@ As mentioned above, we’ll be able to pull in transactions as they post (daily) Expensify provides a corporate card with the following features: - Up to 2% cash back (up to 4% in your first 3 months!) -- [SmartLimits](https://community.expensify.com/discussion/4851/deep-dive-what-are-unapproved-expense-limits#latest) to control what each individual cardholder can spend +- [SmartLimits](https://help.expensify.com/articles/expensify-classic/expensify-card/Card-Settings) to control what each individual cardholder can spend - A stable, unbreakable real-time connection (third-party bank feeds can run into connectivity issues) - Receipt compliance - informing notifications (eg. add a receipt!) for users *as soon as the card is swiped* - A 50% discount on the price of all Expensify plans @@ -202,7 +202,7 @@ The Expensify Card is recommended as the most efficient way to manage your compa Here’s how to enable it: -1. There are *two ways* you can [apply for the Expensify Card](https://community.expensify.com/discussion/4874/how-to-apply-for-the-expensify-card) +1. There are *two ways* you can [apply for the Expensify Card](https://help.expensify.com/articles/expensify-classic/expensify-card/Set-Up-the-Card-for-Your-Company) - *Via your tasks on the Home page* - *Via Domain Settings* - Go to Settings > Domain > Company Cards > Enable Expensify Card 2. Assign the cards to your employees @@ -212,7 +212,7 @@ Here’s how to enable it: Once the Expensify Cards have been assigned, each employee will be prompted to enter their mailing address so they can receive their physical card. In the meantime, a virtual card will be ready to use immediately. -If you have an accounting system we directly integrate with, check out how we take automation a step further with [Continuous Reconciliation](https://community.expensify.com/discussion/7335/faq-what-is-the-expensify-card-auto-reconciliation-process). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. +If you have an accounting system we directly integrate with, check out how we take automation a step further with [Continuous Reconciliation](https://help.expensify.com/articles/expensify-classic/expensify-card/Auto-Reconciliation). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. ## Step 12: Set up Bill Pay and Invoicing As a small business, managing bills and invoices can be a complex and time-consuming task. Whether you receive bills from vendors or need to invoice clients, it's important to have a solution that makes the process simple, efficient, and cost-effective. From dbe5c08e063634e75f0b2aa0658da9dbec7dcd58 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Sat, 28 Oct 2023 18:37:04 +0300 Subject: [PATCH 101/170] Move styling helper method to StyleUtils This felt like the most logical place to put the helper method seeing how all the other helper methods are located here. --- src/pages/home/report/ReportActionItemFragment.js | 3 ++- src/styles/StyleUtils.ts | 9 +++++++++ src/styles/styles.ts | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 0261785cdab8..3a5dcc196dde 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import reportActionFragmentPropTypes from './reportActionFragmentPropTypes'; import styles from '../../../styles/styles'; +import * as StyleUtils from '../../../styles/StyleUtils'; import variables from '../../../styles/variables'; import themeColors from '../../../styles/themes/default'; import RenderHTML from '../../../components/RenderHTML'; @@ -168,7 +169,7 @@ function ReportActionItemFragment(props) { style={[ styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap, - styles.approvedOrSubmittedMessage(props.isApprovedOrSubmittedReportAction), + StyleUtils.getApprovedOrSubmittedReportTextStyles(props.isApprovedOrSubmittedReportAction), ]} > {props.fragment.text} diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 48100b2efb60..5f65ffc55c50 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1316,11 +1316,20 @@ function getTransparentColor(color: string) { return `${color}00`; } +/** + * Get the styles of reports submitted or approved in Old Dot in order to style them like system messages + */ +function getApprovedOrSubmittedReportTextStyles(isApprovedOrSubmittedReport = false): TextStyle { + // Font family is restored back to a regular font since text with "fontWeight: 'normal'" on Android still appears boldened + return isApprovedOrSubmittedReport ? {color: themeColors.textSupporting, fontFamily: fontFamily.EXP_NEUE, fontWeight: 'normal'} : {}; +} + export { combineStyles, displayIfTrue, getAmountFontSizeAndLineHeight, getAnimatedFABStyle, + getApprovedOrSubmittedReportTextStyles, getAutoCompleteSuggestionContainerStyle, getAutoCompleteSuggestionItemStyle, getAutoGrowHeightInputStyle, diff --git a/src/styles/styles.ts b/src/styles/styles.ts index d8b22ff6275d..c5946801abd7 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -4027,8 +4027,6 @@ const styles = (theme: ThemeDefault) => singleOptionSelectorCircle: { borderColor: theme.icon, }, - - approvedOrSubmittedMessage: (isApprovedOrSubmittedMessage: boolean) => (isApprovedOrSubmittedMessage ? {color: theme.textSupporting, fontWeight: 'normal'} : {}), } satisfies Styles); // For now we need to export the styles function that takes the theme as an argument From 5dda804beb9bda9d75ba24aa99c1996b1222eca6 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Sat, 28 Oct 2023 18:58:09 +0300 Subject: [PATCH 102/170] Conditionally wrap fragment text in a tooltip --- .../home/report/ReportActionItemFragment.js | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 3a5dcc196dde..1356ddab1947 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -157,25 +157,32 @@ function ReportActionItemFragment(props) { ); } - case 'TEXT': - return ( + case 'TEXT': { + const textFragment = ( + + {props.fragment.text} + + ); + + return props.isApprovedOrSubmittedReportAction ? ( + textFragment + ) : ( - - {props.fragment.text} - + {textFragment} ); + } case 'LINK': return LINK; case 'INTEGRATION_COMMENT': From cb548ebfff79b7da83ffbbb689f9a721cb8290b1 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Sun, 29 Oct 2023 09:40:12 +0300 Subject: [PATCH 103/170] Run pretteir to fix diffs --- src/pages/home/report/ReportActionItemFragment.js | 2 +- src/pages/home/report/ReportActionItemMessage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index b84fdbaf3e49..af7d14f341eb 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -13,9 +13,9 @@ import compose from '@libs/compose'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as EmojiUtils from '@libs/EmojiUtils'; -import * as StyleUtils from '@styles/StyleUtils'; import editedLabelStyles from '@styles/editedLabelStyles'; import styles from '@styles/styles'; +import * as StyleUtils from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 5a142429e3ed..73ce64365be4 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -7,9 +7,9 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import styles from '@styles/styles'; +import CONST from '@src/CONST'; import ReportActionItemFragment from './ReportActionItemFragment'; import reportActionPropTypes from './reportActionPropTypes'; -import CONST from '@src/CONST'; const propTypes = { /** The report action */ From a63719ca57577747f54e22c947d01ae030877b1a Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Sun, 29 Oct 2023 19:48:11 +0100 Subject: [PATCH 104/170] Fix lint problems for localize lib --- src/libs/Localize/LocaleListener/BaseLocaleListener.ts | 2 +- src/libs/Localize/LocaleListener/types.ts | 2 +- src/libs/Localize/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts index cf716dc10c05..c5eba18af422 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; -import BaseLocale, {LocaleListenerConnect} from './types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import BaseLocale, {LocaleListenerConnect} from './types'; let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT; diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index b932f83f6cfb..4daf90af0483 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -1,5 +1,5 @@ import {ValueOf} from 'type-fest'; -import CONST from '../../../CONST'; +import CONST from '@src/CONST'; type BaseLocale = ValueOf; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ed77e1c498f2..fe7faaad1148 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -4,10 +4,10 @@ import Log from '@libs/Log'; import Config from '@src/CONFIG'; import CONST from '@src/CONST'; import translations from '@src/languages/translations'; +import {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; -import {TranslationFlatObject, TranslationPaths} from '../../languages/types'; // Current user mail is needed for handling missing translations let userEmail = ''; From 00b83aa50421c8f9d974697f96b7e020e0055c1a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 30 Oct 2023 15:15:41 +0700 Subject: [PATCH 105/170] merge main --- src/pages/workspace/WorkspaceInviteMessagePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 5599fd3ada5d..bab9e526ace5 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -254,8 +254,8 @@ export default compose( }, savedWelcomeMessage: { key: `${ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM}Draft`, - selector: (draft) => draft ? draft.welcomeMessage : '' - } + selector: (draft) => (draft ? draft.welcomeMessage : ''), + }, }), withNavigationFocus, )(WorkspaceInviteMessagePage); From fb274a90b68fb5a4f1cfb9ce345b25c2e9fcbce6 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 30 Oct 2023 09:47:42 +0100 Subject: [PATCH 106/170] fix: resolved review comment --- .../modules/react-native-web-linear-gradient.d.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/types/modules/react-native-web-linear-gradient.d.ts b/src/types/modules/react-native-web-linear-gradient.d.ts index f41ef6de3a13..6909ce3dbde2 100644 --- a/src/types/modules/react-native-web-linear-gradient.d.ts +++ b/src/types/modules/react-native-web-linear-gradient.d.ts @@ -1,17 +1,6 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ declare module 'react-native-web-linear-gradient' { - import type {ViewProps} from 'react-native'; - - interface LinearGradientProps extends ViewProps { - colors: string[]; - start?: {x: number; y: number}; - end?: {x: number; y: number}; - locations?: number[]; - useAngle?: boolean; - angle?: number; - } - - class LinearGradient extends React.PureComponent {} + import LinearGradient from 'react-native-linear-gradient'; export default LinearGradient; } From d3d21026e084423cfcb29acd29c664f29e6fb4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 30 Oct 2023 09:50:06 +0100 Subject: [PATCH 107/170] fix e2e tests after eslint/prettier import changes --- .prettierignore | 2 ++ src/libs/E2E/reactNativeLaunchingTest.js | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.prettierignore b/.prettierignore index 5f6292b551c1..80888b18a317 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,3 +15,5 @@ package-lock.json *.css *.scss *.md +# We need to modify the import here specifically, hence we disable prettier to get rid of the sorted imports +src/libs/E2E/reactNativeLaunchingTest.js diff --git a/src/libs/E2E/reactNativeLaunchingTest.js b/src/libs/E2E/reactNativeLaunchingTest.js index 13a1db1d499c..f9ff4383f86d 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.js +++ b/src/libs/E2E/reactNativeLaunchingTest.js @@ -7,7 +7,6 @@ */ import * as Metrics from '@libs/Metrics'; import Performance from '@libs/Performance'; -import '../../../index'; import E2EConfig from '../../../tests/e2e/config'; import E2EClient from './client'; @@ -66,5 +65,5 @@ E2EClient.getTestConfig() // start the usual app Performance.markStart('regularAppStart'); - +import '../../../index'; Performance.markEnd('regularAppStart'); From 347d2a7bf17e42dddafced4c8efe9afd8f70f9c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 30 Oct 2023 10:00:53 +0100 Subject: [PATCH 108/170] fix eslint warnings --- src/libs/E2E/tests/reportTypingTest.e2e.js | 16 ++++++++-------- .../ComposerWithSuggestions/index.e2e.js | 9 ++++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.js b/src/libs/E2E/tests/reportTypingTest.e2e.js index ec2ddbea013c..b79166063b4f 100644 --- a/src/libs/E2E/tests/reportTypingTest.e2e.js +++ b/src/libs/E2E/tests/reportTypingTest.e2e.js @@ -1,12 +1,12 @@ -import E2ELogin from '../actions/e2eLogin'; -import Performance from '../../Performance'; -import E2EClient from '../client'; -import Navigation from '../../Navigation/Navigation'; -import ROUTES from '../../../ROUTES'; -import CONST from '../../../CONST'; +import E2ELogin from '@libs/E2E/actions/e2eLogin'; +import waitForKeyboard from '@libs/E2E/actions/waitForKeyboard'; +import E2EClient from '@libs/E2E/client'; +import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; +import {getRerenderCount, resetRerenderCount} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import * as NativeCommands from '../../../../tests/e2e/nativeCommands/NativeCommandsAction'; -import waitForKeyboard from '../actions/waitForKeyboard'; -import {resetRerenderCount, getRerenderCount} from '../../../pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e'; const test = () => { // check for login (if already logged in the action will simply resolve) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js index ab626ac0f2e8..cbbd1758c9cb 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js @@ -1,7 +1,7 @@ -import React, {useEffect} from 'react'; import _ from 'lodash'; +import React, {useEffect} from 'react'; +import E2EClient from '@libs/E2E/client'; import ComposerWithSuggestions from './ComposerWithSuggestions'; -import E2EClient from '../../../../../libs/E2E/client'; let rerenderCount = 0; const getRerenderCount = () => rerenderCount; @@ -14,7 +14,7 @@ function IncrementRenderCount() { return null; } -export default React.forwardRef((props, ref) => { +const ComposerWithSuggestionsE2e = React.forwardRef((props, ref) => { // Eventually Auto focus on e2e tests useEffect(() => { if (_.get(E2EClient.getCurrentActiveTestConfig(), 'reportScreen.autoFocus', false) === false) { @@ -46,4 +46,7 @@ export default React.forwardRef((props, ref) => { ); }); +ComposerWithSuggestionsE2e.displayName = 'ComposerWithSuggestionsE2e'; + +export default ComposerWithSuggestionsE2e; export {getRerenderCount, resetRerenderCount}; From d486d32c4267da8bffbdb91e964c0c3304b1dec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 30 Oct 2023 10:12:18 +0100 Subject: [PATCH 109/170] fix imports --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 576267b80011..fe8fec187da7 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -25,6 +25,8 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionUtils from '@libs/SuggestionUtils'; import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; +import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater'; +import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; import containerComposeStyles from '@styles/containerComposeStyles'; import styles from '@styles/styles'; import themeColors from '@styles/themes/default'; @@ -35,8 +37,6 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './composerWithSuggestionsProps'; -import SilentCommentUpdater from './SilentCommentUpdater'; -import Suggestions from './Suggestions'; const {RNTextInputReset} = NativeModules; From d9d7df4b46664ef14d2f467c847864e43486ea92 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 30 Oct 2023 11:53:51 +0100 Subject: [PATCH 110/170] Fix typecheck --- src/libs/SidebarUtils.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 79d3280e859e..260d02b981db 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -418,10 +418,21 @@ function getOptionData( const reportAction = lastReportActions?.[report.reportID]; if (result.isArchivedRoom) { const archiveReason = (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED && reportAction?.originalMessage?.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; - lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), - policyName: ReportUtils.getPolicyName(report, false, policy), - }); + + switch (archiveReason) { + case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED: + case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY: + case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: { + lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { + policyName: ReportUtils.getPolicyName(report, false, policy), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + }); + break; + } + default: { + lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.default`); + } + } } if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport) && !result.isArchivedRoom) { From 0ead8f9ee7e04b3c8674a22d9cc4e1be2753d1d0 Mon Sep 17 00:00:00 2001 From: John Schuster Date: Mon, 30 Oct 2023 14:24:54 -0500 Subject: [PATCH 111/170] Update Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md --- .../Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index dd479fa1891b..3ad3110bf09b 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -114,7 +114,7 @@ For an efficient company, we recommend setting up [Scheduled Submit](https://hel - You’ll notice *Scheduled Submit* is located directly under *Report Basics* - Choose *Daily* -Between Expensify's SmartScan technology, automatic categorization, and [DoubleCheck](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work) features, your employees shouldn't need to do anything more than swipe their Expensify Card or take a photo of their receipt. +Between Expensify's SmartScan technology, automatic categorization, and [DoubleCheck](https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports) features, your employees shouldn't need to do anything more than swipe their Expensify Card or take a photo of their receipt. Expenses with violations will stay behind for the employee to fix, while expenses that are “in-workspace” will move into an approver’s queue to mitigate any potential for delays. Scheduled Submit will ensure all expenses are submitted automatically for approval. From 53796b639cac3071c9a8bc1b5343dc89eea42b04 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Tue, 31 Oct 2023 10:02:30 +0300 Subject: [PATCH 112/170] Remove check in getApprovedOrSubmittedReportTextStyles Since this helper method is only used in one place so far, it's better to conditionally call it in the place it's being used rather than have the check in the method and return an empty object --- src/pages/home/report/ReportActionItemFragment.js | 2 +- src/styles/StyleUtils.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index af7d14f341eb..24301af52a76 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -164,7 +164,7 @@ function ReportActionItemFragment(props) { style={[ styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap, - StyleUtils.getApprovedOrSubmittedReportTextStyles(props.isApprovedOrSubmittedReportAction), + props.isApprovedOrSubmittedReportAction && StyleUtils.getApprovedOrSubmittedReportTextStyles(), ]} > {props.fragment.text} diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 6d29180b13c6..25b87e8f04af 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1319,9 +1319,9 @@ function getTransparentColor(color: string) { /** * Get the styles of reports submitted or approved in Old Dot in order to style them like system messages */ -function getApprovedOrSubmittedReportTextStyles(isApprovedOrSubmittedReport = false): TextStyle { - // Font family is restored back to a regular font since text with "fontWeight: 'normal'" on Android still appears boldened - return isApprovedOrSubmittedReport ? {color: themeColors.textSupporting, fontFamily: fontFamily.EXP_NEUE, fontWeight: 'normal'} : {}; +function getApprovedOrSubmittedReportTextStyles(): TextStyle { + // Font family is restored back to a regular font since text with "fontWeight: 'normal'" on Android still appears in bold + return {color: themeColors.textSupporting, fontFamily: fontFamily.EXP_NEUE, fontWeight: 'normal'}; } /** From 7c68bde38f2485b1fb0c2037776ae2effe021d46 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 31 Oct 2023 12:35:12 +0100 Subject: [PATCH 113/170] Make translateIfPhraseKey much more broad --- src/libs/Localize/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index fe7faaad1148..ba7dda8a76b9 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -99,7 +99,7 @@ function translateLocal(phrase: TKey, ...variable /** * Return translated string for given error. */ -function translateIfPhraseKey(message: TKey | [TKey, PhraseParameters> & {isTranslated?: true}]): string { +function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}]): string { if (!message || (Array.isArray(message) && message.length > 0)) { return ''; } @@ -110,10 +110,10 @@ function translateIfPhraseKey(message: TKey | [TK // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. if (variables?.isTranslated) { - return phrase as string; + return phrase; } - return translateLocal(phrase, ...(variables as PhraseParameters>)); + return translateLocal(phrase as TranslationPaths, ...(variables as PhraseParameters>)); } catch (error) { return Array.isArray(message) ? message[0] : message; } From 1a569e420b59f3a50de924689a14f38ca0298be9 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 31 Oct 2023 12:38:41 +0100 Subject: [PATCH 114/170] Fix typecheck --- src/libs/Localize/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ba7dda8a76b9..cc424113857e 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -113,7 +113,7 @@ function translateIfPhraseKey(message: string | [string, Record return phrase; } - return translateLocal(phrase as TranslationPaths, ...(variables as PhraseParameters>)); + return translateLocal(phrase as TranslationPaths, variables as never); } catch (error) { return Array.isArray(message) ? message[0] : message; } From 8635361183fc3df9d2f988bb0e03b251e6f2d450 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:02:54 +0100 Subject: [PATCH 115/170] fix proper submit button styling in FormWrapper --- src/components/Form/FormWrapper.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 82e70b68b3f0..8e113b00ceb3 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -57,6 +57,9 @@ const propTypes = { /** Container styles */ style: stylePropTypes, + /** Submit button styles */ + submitButtonStyles: stylePropTypes, + /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), @@ -75,10 +78,11 @@ const defaultProps = { scrollContextEnabled: false, footerContent: null, style: [], + submitButtonStyles: [], }; function FormWrapper(props) { - const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; + const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, submitButtonStyles, enabledWhenOffline, isSubmitActionDangerous, formID} = props; const formRef = useRef(null); const formContentRef = useRef(null); const errorMessage = useMemo(() => { @@ -130,7 +134,7 @@ function FormWrapper(props) { focusInput.focus(); } }} - containerStyles={[styles.mh0, styles.mt5, styles.flex1]} + containerStyles={[styles.mh0, styles.mt5, styles.flex1, ...submitButtonStyles]} enabledWhenOffline={enabledWhenOffline} isSubmitActionDangerous={isSubmitActionDangerous} disablePressOnEnter From 6e2f9e9529de6f534954c57182b615201c1ffc9b Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 31 Oct 2023 16:34:10 +0100 Subject: [PATCH 116/170] remove unused TextPill.js file --- src/components/TextPill.js | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 src/components/TextPill.js diff --git a/src/components/TextPill.js b/src/components/TextPill.js deleted file mode 100644 index 81bb0ca1e037..000000000000 --- a/src/components/TextPill.js +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import stylePropTypes from '@styles/stylePropTypes'; -import styles from '@styles/styles'; -import * as StyleUtils from '@styles/StyleUtils'; -import Text from './Text'; - -const propTypes = { - text: PropTypes.string.isRequired, - - /** Text additional style */ - style: stylePropTypes, -}; - -const defaultProps = { - style: [], -}; - -function TextPill(props) { - const propsStyle = StyleUtils.parseStyleAsArray(props.style); - - return ( - - {props.text} - - ); -} - -TextPill.propTypes = propTypes; -TextPill.defaultProps = defaultProps; -TextPill.displayName = 'TextPill'; - -export default TextPill; From eeec0adcb0e3a139446cf3270a564776f7ec2982 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 31 Oct 2023 20:38:53 +0100 Subject: [PATCH 117/170] Fix Left line/border of quoted text is not visible when hovering over it in Notes --- src/styles/StyleUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index faece4f44335..84b718614d79 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -521,9 +521,9 @@ function getBadgeColorStyle(isSuccess: boolean, isError: boolean, isPressed = fa function getButtonBackgroundColorStyle(buttonState: ButtonStateName = CONST.BUTTON_STATES.DEFAULT, isMenuItem = false): ViewStyle { switch (buttonState) { case CONST.BUTTON_STATES.PRESSED: - return {backgroundColor: themeColors.buttonPressedBG}; + return isMenuItem ? {backgroundColor: themeColors.border} : {backgroundColor: themeColors.buttonPressedBG}; case CONST.BUTTON_STATES.ACTIVE: - return isMenuItem ? {backgroundColor: themeColors.border} : {backgroundColor: themeColors.buttonHoveredBG}; + return isMenuItem ? {backgroundColor: themeColors.highlightBG} : {backgroundColor: themeColors.buttonHoveredBG}; case CONST.BUTTON_STATES.DISABLED: case CONST.BUTTON_STATES.DEFAULT: default: From 85b4bfc7396ec00d59da3213c26f182865617462 Mon Sep 17 00:00:00 2001 From: Stephanie Elliott <31225194+stephanieelliott@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:15:41 -1000 Subject: [PATCH 118/170] Update Reimbursement.md add new reimbursement article to helpdot navigation --- .../Reimbursement.md | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Reimbursement.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Reimbursement.md index 3ee1c8656b4b..a1916465fca8 100644 --- a/docs/articles/expensify-classic/workspace-and-domain-settings/Reimbursement.md +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Reimbursement.md @@ -1,5 +1,47 @@ --- -title: Coming Soon -description: Coming Soon +title: Reimbursement +description: Enable reimbursement and reimburse expense reports --- -## Resource Coming Soon! + + +# Overview +Reimbursement in Expensify is quick, easy, and completely free. Let Expensify do the tedious work for you by taking advantage of features to automate employee reimbursement. + +# How to Enable Reimbursement +There are several options for reimbursing employees in Expensify. The options available will depend on which country your business bank account is domiciled in. + +## Direct Reimbursement + +Direct reimbursement is available to companies who have a verified US bank account and are reimbursing employees within the US. To use direct reimbursement, you must have a US business bank account verified in Expensify. + +A Workspace admin can enable direct reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Direct**. + +**Additional features under Reimbursement > Direct:** + - Select a **default reimburser** for the Workspace from the dropdown menu. The default reimburser is the person who will receive notifications to reimburse reports in Expensify. You’ll be able to choose among all Workspace Admins who have access to the business bank account. + - Set a **default withdrawal account** for the Workspace. This will set a default bank account that report reimbursements are withdrawn from. + - Set a **manual reimbursement threshold** to automate reimbursement. Reports whose total falls under the manual reimbursement threshhold will be reimbursed automatocally upon final approval; reports whose total falls above the threshhold will need to be reimbursed manually by the default reimburser. + +Expensify also offers direct global reimbursement to some companies with verified bank accounts in USD, GBP, EUR and AUD who are reimbursing employees internationally. For more information about Global Reimbursement, see LINK + +## Indirect Reimbursement + +Indirect reimbursement is available to all companies in Expensify and no bank account is required. Indirect reimbursement indicates that the report will be reimbursed outside of Expensify. + +A Workspace admin can enanble indirect reimbursement via **Settings > Workspaces > Workspace Name > Reimbursement > Indirect**. + +**Additional features under Reimbursement > Indirect:** +If you reimburse through a seperate system or through payroll, Expensify can collect and export employee bank account details for you. Just reach out to your Account Manager or concierge@expensify.com for us to add the Reimbursement Details Export format to the account. + +# FAQ + +## How do I export employee bank account details once the Reimbursement Details Export format is added to my account? + +Employee bank account details can be exported from the Reports page by selecting the relevant Approved reports and then clicking **Export to > Reimbursement Details Export**. + +## Is it possible to change the name of a verified business bank account in Expensify? + +Bank account names can be updated via **Settings > Accounts > Payments** and clicking the pencil icon next to the bank account name. + +## What is the benefit of setting a default reimburser? + +The main benefit of being defined as the "reimburser" in the Workspace settings is that this user will receive notifications on their Home page alerting them when reports need to be reimbursed. From 0a1d1ee11ced005f2edd7b88864d2b928b40f4f9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 1 Nov 2023 14:12:56 +0700 Subject: [PATCH 119/170] fix: merchant is not focused when back from search page --- src/pages/iou/MoneyRequestMerchantPage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/MoneyRequestMerchantPage.js b/src/pages/iou/MoneyRequestMerchantPage.js index 5c01484310ff..eff65d6ae238 100644 --- a/src/pages/iou/MoneyRequestMerchantPage.js +++ b/src/pages/iou/MoneyRequestMerchantPage.js @@ -8,6 +8,7 @@ import Form from '@components/Form'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; import styles from '@styles/styles'; @@ -47,7 +48,7 @@ const defaultProps = { function MoneyRequestMerchantPage({iou, route}) { const {translate} = useLocalize(); - const inputRef = useRef(null); + const {inputCallbackRef} = useAutoFocusInput(); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); @@ -92,7 +93,6 @@ function MoneyRequestMerchantPage({iou, route}) { inputRef.current && inputRef.current.focus()} testID={MoneyRequestMerchantPage.displayName} > (inputRef.current = el)} + ref={inputCallbackRef} />
From 6643f8804e6ebfe00cda5141743a42712b8971bc Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 1 Nov 2023 16:09:00 +0700 Subject: [PATCH 120/170] focus on navigating back --- src/hooks/useAutoFocusInput.js | 1 + src/pages/iou/MoneyRequestMerchantPage.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hooks/useAutoFocusInput.js b/src/hooks/useAutoFocusInput.js index 275fed67f52d..181df9359fe8 100644 --- a/src/hooks/useAutoFocusInput.js +++ b/src/hooks/useAutoFocusInput.js @@ -14,6 +14,7 @@ export default function useAutoFocusInput() { return; } inputRef.current.focus(); + setIsScreenTransitionEnded(false); }, [isScreenTransitionEnded, isInputInitialized]); useFocusEffect( diff --git a/src/pages/iou/MoneyRequestMerchantPage.js b/src/pages/iou/MoneyRequestMerchantPage.js index eff65d6ae238..f072c0f78535 100644 --- a/src/pages/iou/MoneyRequestMerchantPage.js +++ b/src/pages/iou/MoneyRequestMerchantPage.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; From 3dc8403aefd741fb5fafc5e49c53d446e2779563 Mon Sep 17 00:00:00 2001 From: Eric Han Date: Wed, 1 Nov 2023 20:25:38 +0800 Subject: [PATCH 121/170] fix: private note draft not cleanup after saving --- src/pages/PrivateNotes/PrivateNotesEditPage.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index b31e9b58cbe9..ddfa6095f40b 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -105,8 +105,15 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { const savePrivateNote = () => { const originalNote = lodashGet(report, ['privateNotes', route.params.accountID, 'note'], ''); - const editedNote = Report.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim()); - Report.updatePrivateNotes(report.reportID, route.params.accountID, editedNote); + + if (privateNote.trim() !== originalNote.trim()) { + const editedNote = Report.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim()); + Report.updatePrivateNotes(report.reportID, route.params.accountID, editedNote); + } + + // We want to delete saved private note draft after saving the note + debouncedSavePrivateNote(''); + Keyboard.dismiss(); // Take user back to the PrivateNotesView page From 5b90859c332dd07e6628bea7d2ff2e576b908975 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:42:10 -0400 Subject: [PATCH 122/170] Delete docs/articles/expensify-classic/getting-started/Policy-Admins.md No longer necessary / deleting placeholder --- .../expensify-classic/getting-started/Policy-Admins.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/articles/expensify-classic/getting-started/Policy-Admins.md diff --git a/docs/articles/expensify-classic/getting-started/Policy-Admins.md b/docs/articles/expensify-classic/getting-started/Policy-Admins.md deleted file mode 100644 index 484350f101a5..000000000000 --- a/docs/articles/expensify-classic/getting-started/Policy-Admins.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Policy Admins -description: Policy Admins ---- -## Resource Coming Soon! From 14ef831dc2bfc6515c21e36dcfe1dc8870beaa60 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:46:48 -0400 Subject: [PATCH 123/170] Update and rename Personal-Credit-Cards.md to Personal-Cards.md updating title and description of article (article still not published) --- .../bank-accounts-and-credit-cards/Personal-Cards.md | 5 +++++ .../bank-accounts-and-credit-cards/Personal-Credit-Cards.md | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md delete mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md new file mode 100644 index 000000000000..71edcdeba00d --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md @@ -0,0 +1,5 @@ +--- +title: Personal Cards +description: Connect your credit card directly to Expensify to easily track your personal finances. +--- +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md deleted file mode 100644 index f89729b69586..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Personal Credit Cards -description: Personal Credit Cards ---- -## Resource Coming Soon! From c77fbfee2bf7f994b0eeec727af5f429821f2823 Mon Sep 17 00:00:00 2001 From: Pujan Date: Thu, 2 Nov 2023 11:31:28 +0530 Subject: [PATCH 124/170] removed min height style for parent --- src/pages/home/report/ReportActionItem.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3c599e8b7401..3485bcbd7a60 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -58,6 +58,7 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; @@ -73,7 +74,6 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import reportActionPropTypes from './reportActionPropTypes'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; const propTypes = { ...windowDimensionsPropTypes, @@ -571,9 +571,9 @@ function ReportActionItem(props) { if (ReportUtils.isTaskReport(props.report)) { if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { content = ( - + <> - + - + ); } else { content = ( - + <> - + ); } } From bf322ac3816196383668c09f77a1408489a8b14d Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 2 Nov 2023 16:49:37 +0700 Subject: [PATCH 125/170] add session to memo dependencies --- src/pages/home/report/ReportActionsView.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index eee3e3eeedae..4a95157224a4 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -143,7 +143,6 @@ function ReportActionsView(props) { Report.reconnect(reportID); } } - // update ref with current network state prevAuthTokenType.current = props.session.authTokenType; // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.session, props.report, isReportFullyVisible]); @@ -291,6 +290,10 @@ function arePropsEqual(oldProps, newProps) { return false; } + if (lodashGet(oldProps.session, 'authTokenType') !== lodashGet(newProps.session, 'authTokenType')) { + return false; + } + if (oldProps.isLoadingInitialReportActions !== newProps.isLoadingInitialReportActions) { return false; } From 6535ceaf10edb94cc37d396d6e73f9ce599fa60f Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Thu, 2 Nov 2023 10:51:42 +0100 Subject: [PATCH 126/170] Reorder imports - eslint --- src/components/SelectCircle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectCircle.tsx b/src/components/SelectCircle.tsx index 6bf43edbc67a..cf8ee6af975d 100644 --- a/src/components/SelectCircle.tsx +++ b/src/components/SelectCircle.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; -import themeColors from '@styles/themes/default'; import globalStyles from '@styles/styles'; +import themeColors from '@styles/themes/default'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; From 986a514af8f1b58d69790315bcaaaa803f418998 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 2 Nov 2023 13:36:14 +0300 Subject: [PATCH 127/170] fix: HeaderWithBackButton storybook error --- src/stories/HeaderWithBackButton.stories.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/stories/HeaderWithBackButton.stories.js b/src/stories/HeaderWithBackButton.stories.js index 38d816b8fdd2..eb31413de1d5 100644 --- a/src/stories/HeaderWithBackButton.stories.js +++ b/src/stories/HeaderWithBackButton.stories.js @@ -1,5 +1,8 @@ import React from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import withNavigationFallback from '@components/withNavigationFallback'; + +const HeaderWithBackButtonWithNavigation = withNavigationFallback(HeaderWithBackButton); /** * We use the Component Story Format for writing stories. Follow the docs here: @@ -8,12 +11,12 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; */ const story = { title: 'Components/HeaderWithBackButton', - component: HeaderWithBackButton, + component: HeaderWithBackButtonWithNavigation, }; function Template(args) { // eslint-disable-next-line react/jsx-props-no-spreading - return ; + return ; } // Arguments can be passed to the component by binding From 2e17ee72e29c317376e4a62e8458a31de3457d52 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 2 Nov 2023 07:50:45 -0700 Subject: [PATCH 128/170] Add missing displayName to components --- .../Attachments/AttachmentCarousel/CarouselButtons.js | 1 + src/components/Attachments/AttachmentCarousel/CarouselItem.js | 1 + .../AttachmentCarousel/Pager/AttachmentCarouselPage.js | 2 ++ .../Attachments/AttachmentCarousel/Pager/ImageTransformer.js | 1 + .../Attachments/AttachmentCarousel/Pager/ImageWrapper.js | 3 ++- src/components/Attachments/AttachmentCarousel/Pager/index.js | 3 ++- src/components/Attachments/AttachmentCarousel/index.js | 2 ++ src/components/Attachments/AttachmentCarousel/index.native.js | 1 + .../Attachments/AttachmentView/AttachmentViewImage/index.js | 1 + .../AttachmentView/AttachmentViewImage/index.native.js | 1 + src/components/Avatar.js | 3 +++ src/components/Composer/index.js | 1 + src/components/InvertedFlatList/index.js | 1 + src/components/LHNOptionsList/LHNOptionsList.js | 1 + src/components/MagicCodeInput.js | 1 + src/components/MapView/Direction.tsx | 2 ++ src/components/MapView/Direction.web.tsx | 2 ++ src/components/MapView/PendingMapView.tsx | 2 ++ src/components/MoneyRequestConfirmationList.js | 1 + src/components/NewDatePicker/index.js | 1 + src/components/OptionRow.js | 1 + src/components/Pressable/PressableWithDelayToggle.js | 1 + src/components/ValidateCode/JustSignedInModal.js | 2 ++ src/components/ValidateCode/ValidateCodeModal.js | 2 ++ src/components/WalletStatementModal/index.js | 1 + src/pages/EnablePayments/OnfidoPrivacy.js | 1 + .../home/report/ReportActionCompose/ReportActionCompose.js | 1 + src/pages/settings/InitialSettingsPage.js | 1 + .../Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 1 + src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js | 1 + .../settings/Security/TwoFactorAuth/Steps/DisabledStep.js | 2 ++ src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.js | 2 ++ src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js | 1 + src/pages/signin/SAMLSignInPage/index.js | 1 + src/pages/workspace/WorkspacesListPage.js | 1 + 35 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js index 9bef889e61a1..f11bbcc9b187 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js @@ -82,5 +82,6 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward CarouselButtons.propTypes = propTypes; CarouselButtons.defaultProps = defaultProps; +CarouselButtons.displayName = 'CarouselButtons'; export default CarouselButtons; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 2d271aa6d4c4..9b69838b74b5 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -116,5 +116,6 @@ function CarouselItem({item, isFocused, onPress}) { CarouselItem.propTypes = propTypes; CarouselItem.defaultProps = defaultProps; +CarouselItem.displayName = 'displayName'; export default CarouselItem; diff --git a/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPage.js b/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPage.js index 2ded34829a08..7a083d71b591 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPage.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPage.js @@ -181,7 +181,9 @@ function AttachmentCarouselPage({source, isAuthTokenRequired, isActive: initialI ); } + AttachmentCarouselPage.propTypes = pagePropTypes; AttachmentCarouselPage.defaultProps = defaultProps; +AttachmentCarouselPage.displayName = 'AttachmentCarouselPage'; export default AttachmentCarouselPage; diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js index 5bf8b79dae77..0839462d4f23 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js @@ -574,5 +574,6 @@ function ImageTransformer({imageWidth, imageHeight, imageScaleX, imageScaleY, sc } ImageTransformer.propTypes = imageTransformerPropTypes; ImageTransformer.defaultProps = imageTransformerDefaultProps; +ImageTransformer.displayName = 'ImageTransformer'; export default ImageTransformer; diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js index 10f2ae94340a..3a27d80c5509 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js @@ -1,4 +1,3 @@ -/* eslint-disable es/no-optional-chaining */ import PropTypes from 'prop-types'; import React from 'react'; import {StyleSheet} from 'react-native'; @@ -19,6 +18,8 @@ function ImageWrapper({children}) { ); } + ImageWrapper.propTypes = imageWrapperPropTypes; +ImageWrapper.displayName = 'ImageWrapper'; export default ImageWrapper; diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.js b/src/components/Attachments/AttachmentCarousel/Pager/index.js index e4659caf24f0..59fd7596f0ad 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/index.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/index.js @@ -1,4 +1,3 @@ -/* eslint-disable es/no-optional-chaining */ import PropTypes from 'prop-types'; import React, {useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; @@ -168,8 +167,10 @@ function AttachmentCarouselPager({ ); } + AttachmentCarouselPager.propTypes = pagerPropTypes; AttachmentCarouselPager.defaultProps = pagerDefaultProps; +AttachmentCarouselPager.displayName = 'AttachmentCarouselPager'; const AttachmentCarouselPagerWithRef = React.forwardRef((props, ref) => ( ); } + AttachmentCarousel.propTypes = propTypes; AttachmentCarousel.defaultProps = defaultProps; +AttachmentCarousel.displayName = 'AttachmentCarousel'; export default compose( withOnyx({ diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index 7088a5c7057c..b86c9b1c786e 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -169,6 +169,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, onClose, } AttachmentCarousel.propTypes = propTypes; AttachmentCarousel.defaultProps = defaultProps; +AttachmentCarousel.displayName = 'AttachmentCarousel'; export default compose( withOnyx({ diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js index 23049915a8d9..307dbe8e9ddb 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js @@ -39,5 +39,6 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, loadComplete, o AttachmentViewImage.propTypes = propTypes; AttachmentViewImage.defaultProps = attachmentViewImageDefaultProps; +AttachmentViewImage.displayName = 'AttachmentViewImage'; export default compose(memo, withLocalize)(AttachmentViewImage); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js index faf2f21c133d..cb1190fa1fdd 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js @@ -47,5 +47,6 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, isFocused, isUs AttachmentViewImage.propTypes = propTypes; AttachmentViewImage.defaultProps = attachmentViewImageDefaultProps; +AttachmentViewImage.displayName = 'AttachmentViewImage'; export default compose(memo, withLocalize)(AttachmentViewImage); diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 546387031643..4b8ddd45aa95 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -119,6 +119,9 @@ function Avatar(props) { ); } + Avatar.defaultProps = defaultProps; Avatar.propTypes = propTypes; +Avatar.displayName = 'Avatar'; + export default Avatar; diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index f8045eb87f9f..924705e0fd39 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -490,6 +490,7 @@ function Composer({ Composer.propTypes = propTypes; Composer.defaultProps = defaultProps; +Composer.displayName = 'Composer'; const ComposerWithRef = React.forwardRef((props, ref) => ( {}, }; +InvertedFlatList.displayName = 'InvertedFlatList'; const InvertedFlatListWithRef = forwardRef((props, ref) => ( ( ( Date: Thu, 2 Nov 2023 17:40:35 +0100 Subject: [PATCH 129/170] fix: imports and remove TODO blocks --- src/styles/ThemeStylesProvider.tsx | 3 --- src/styles/styles.ts | 4 ---- src/styles/themes/default.ts | 2 +- src/styles/themes/light.ts | 2 +- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index 78bea4d413c3..2ef9a8521e4d 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -3,9 +3,6 @@ import React, {useMemo} from 'react'; import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; import {stylesGenerator} from './styles'; -import {stylesGenerator as stylesUntyped} from './styles'; - -const styles = stylesUntyped; type ThemeStylesProviderProps = { children: React.ReactNode; diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 54b2f8280a44..cda2545a9178 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -4013,10 +4013,6 @@ const styles = (theme: ThemeColors) => }, } satisfies Styles); -// For now we need to export the styles function that takes the theme as an argument -// as something named different than "styles", because a lot of files import the "defaultStyles" -// as "styles", which causes ESLint to throw an error. -// TODO: Remove "stylesGenerator" and instead only return "styles" once the app is migrated to theme switching hooks and HOCs and "styles/theme/default.js" is not used anywhere anymore (GH issue: https://github.com/Expensify/App/issues/27337) const stylesGenerator = styles; const defaultStyles = styles(defaultTheme); diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index bbeb720edbab..dd92b1ce71d9 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,6 +1,6 @@ -import {ThemeColors} from './types'; import colors from '@styles/colors'; import SCREENS from '@src/SCREENS'; +import {ThemeColors} from './types'; const darkTheme = { // Figma keys diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 33b1851e343f..97fe2322945a 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,6 +1,6 @@ -import {ThemeColors} from './types'; import colors from '@styles/colors'; import SCREENS from '@src/SCREENS'; +import {ThemeColors} from './types'; const lightTheme = { // Figma keys From 8e79bc7ca2d33bbcf1251e913a447be29c293d6b Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:12:26 -0600 Subject: [PATCH 130/170] Update docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md Co-authored-by: Rushat Gabhane --- .../expense-and-report-features/The-Expenses-Page.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md index 892d0c4fd330..8b4c82bc98c4 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -2,7 +2,6 @@ title: The Expenses Page description: Details on Expenses Page filters --- ---- Expenses Page --- # Overview From eca00d64dd272c30576afe2589b57082c81b4379 Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:12:36 -0600 Subject: [PATCH 131/170] Update docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md Co-authored-by: Rushat Gabhane --- .../expense-and-report-features/The-Expenses-Page.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md index 8b4c82bc98c4..192c9251c6f6 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -2,7 +2,6 @@ title: The Expenses Page description: Details on Expenses Page filters --- -Expenses Page --- # Overview From 1fe0bd7fb551f7e60b788186ed916ef4088220a0 Mon Sep 17 00:00:00 2001 From: Christina Dobrzynski <51066321+Christinadobrzyn@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:35:29 -0600 Subject: [PATCH 132/170] Update docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md Co-authored-by: Rushat Gabhane --- .../expense-and-report-features/The-Expenses-Page.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md index 192c9251c6f6..42a8a914e5bc 100644 --- a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -2,7 +2,6 @@ title: The Expenses Page description: Details on Expenses Page filters --- ---- # Overview The Expenses page allows you to see all of your personal expenses. If you are an admin, you can view all submitter’s expenses on the Expensify page. The Expenses page can be filtered in several ways to give you spending visibility, find expenses to submit and export to a spreadsheet (CSV). From d5821f059d92a4ffc784cc1a676e74dabe284359 Mon Sep 17 00:00:00 2001 From: kimkurta Date: Thu, 2 Nov 2023 12:48:37 -0500 Subject: [PATCH 133/170] Update CSV-Import.md --- .../company-cards/CSV-Import.md | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md index 892cfdde1bde..fc1e83701caf 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md @@ -29,13 +29,14 @@ This feature is available on Group Workspaces and requires Domain Admin access. 11. After submitting the spreadsheet, click I'll wait a minute. Then, wait about 1-2 minutes for the import to process. The domain page will refresh once the upload is complete. # How to assign new cards -If you’re assigning cards via CSV upload for the first time: +If you're assigning cards via CSV upload for the first time: 1. Head to **Settings > Domains > Domain Name > Company Cards** 2. Find the new CSV feed in the drop-down list underneath **Imported Cards** 3. Click **Assign New Cards** 4. Under **Assign a Card**, enter the relevant info 5. Click **Assign** From there, transactions will be imported to the cardholder's account, where they can add receipts, code the expenses, and submit them for review and approval. + # How to upload new expenses for existing assigned cards There's no need to create a new upload layout for subsequent CSV uploads. Instead, add new expenses to the existing CSV: 1. Head to **Settings > Domains > Domain Name > Company Cards** @@ -45,55 +46,56 @@ There's no need to create a new upload layout for subsequent CSV uploads. Instea 5. After uploading the more recent CSV, click **Update All Cards** to retrieve the new expenses for the assigned cards. # Deep dive -If the CSV upload isn’t formatted correctly, it will cause issues when you try to import or assign cards. Let’s go over some common issues and how to fix them. +If the CSV upload isn't formatted correctly, it will cause issues when you try to import or assign cards. Let's go over some common issues and how to fix them. -## Error: “Attribute value mapping is missing” -If you encounter an error that says "Attribute-value mapping is missing,” the spreadsheet likely lacks critical details like Card Number, Date, Merchant, Amount, or Currency. To resolve: +## Error: "Attribute value mapping is missing" +If you encounter an error that says "Attribute-value mapping is missing," the spreadsheet likely lacks critical details like Card Number, Date, Merchant, Amount, or Currency. To resolve: 1. Click the **X** at the top of the page to close the mapping window -2. Confirm what’s missing from the spreadsheet +2. Confirm what's missing from the spreadsheet 3. Add a new column to your spreadsheet and add the missing detail 4. Upload the revised spreadsheet by clicking **Manage Spreadsheet** 5. Enter a **Company Card Layout Name** for the contents of your spreadsheet 6. Click **Upload CSV** -## Error: “We’ve detected an error while processing your spreadsheet feed” -This error usually occurs when there’s an upload issue. +## Error: "We've detected an error while processing your spreadsheet feed" +This error usually occurs when there's an upload issue. To troubleshoot this: 1. Head to **Settings > Domains > Domain Name > Company Cards** and click **Manage/Import CSV** 2. In the **Upload Company Card transactions for** dropdown list, look for the layout name you previously created. 3. If the layout is listed, wait at least one hour and then sync the cards to see if new transactions are imported. -4. If the layout isn’t listed, create a new **Company Card Layout Name** and upload the spreadsheet again. +4. If the layout isn't listed, create a new **Company Card Layout Name** and upload the spreadsheet again. -## Error: “An unexpected error occurred, and we could not retrieve the list of cards” -This error occurs when there’s an issue uploading the spreadsheet or the upload fails. +## Error: "An unexpected error occurred, and we could not retrieve the list of cards" +This error occurs when there's an issue uploading the spreadsheet or the upload fails. To troubleshoot this: 1. Head to **Settings > Domains > Domain Name > Company Cards** and click **Manage/Import CSV** 2. In the **Upload Company Card transactions for** dropdown list, look for the layout name you previously created. 3. If the layout is listed, wait at least one hour and then sync the cards to see if new transactions are imported. -4. If the layout isn’t listed, create a new **Company Card Layout Name** and upload the spreadsheet again. +4. If the layout isn't listed, create a new **Company Card Layout Name** and upload the spreadsheet again. -## I added a new parameter to an existing spreadsheet, but the data isn’t showing in Expensify after the upload completes. What’s going on? -If you added a new card to an existing spreadsheet and imported it via a saved layout, but it isn’t showing up for assignment, this suggests that the modification may have caused an issue. +## I added a new parameter to an existing spreadsheet, but the data isn't showing in Expensify after the upload completes. What's going on? +If you added a new card to an existing spreadsheet and imported it via a saved layout, but it isn't showing up for assignment, this suggests that the modification may have caused an issue. The next step in troubleshooting this issue is to compare the number of rows on the revised spreadsheet to the Output Preview to ensure the row count matches the revised spreadsheet. To check this: 1. Head to **Settings > Domains > Domain Name > Company Cards** and click **Manage/Import CSV** 2. Select your saved layout in the dropdown list 3. Click **Upload CSV** and select the revised spreadsheet 4. Compare the Output Preview row count to your revised spreadsheet to ensure they match -[insert image here] -If they don’t match, you’ll need to revise the spreadsheet by following the CSV formatting guidelines in step 2 of “How to import company cards via CSV” above. + + +If they don't match, you'll need to revise the spreadsheet by following the CSV formatting guidelines in step 2 of "How to import company cards via CSV" above. Once you do that, save the revised spreadsheet with a new layout name. Then, try to upload the revised spreadsheet again: + 1. Click **Upload CSV** 2. Upload the revised file 3. Check the row count again on the Output Preview to confirm it matches the spreadsheet 4. Click **Submit Spreadsheet** # FAQ -## Why can’t I see my CSV transactions immediately after uploading them? -Don’t worry! You’ll typically need to wait 1-2 minutes after clicking **I understand, I'll wait!** +## Why can't I see my CSV transactions immediately after uploading them? +Don't worry! You'll typically need to wait 1-2 minutes after clicking **I understand, I'll wait!** ## I'm trying to import a credit. Why isn't it uploading? -Negative expenses shouldn’t include a minus sign. Instead, they should just be wrapped in parentheses. For example, to indicate “-335.98,” you’ll want to make sure it’s formatted as “(335.98).” - +Negative expenses shouldn't include a minus sign. Instead, they should just be wrapped in parentheses. For example, to indicate "-335.98," you'll want to make sure it's formatted as "(335.98)." From f669405e5edda8668d0c4dbfd1d2e56c37622f91 Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Fri, 3 Nov 2023 00:04:19 +0300 Subject: [PATCH 134/170] Conditionally wrap report action fragments in Text and account for RTL scenarios Arabic display names caused the text to start from the right side of the screen even though the system message is in English --- .../home/report/ReportActionItemFragment.js | 26 ++++----- .../home/report/ReportActionItemMessage.js | 54 ++++++++++++------- src/styles/StyleUtils.ts | 9 ---- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 24301af52a76..322abad0ad1d 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -15,7 +15,6 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as EmojiUtils from '@libs/EmojiUtils'; import editedLabelStyles from '@styles/editedLabelStyles'; import styles from '@styles/styles'; -import * as StyleUtils from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -67,6 +66,9 @@ const propTypes = { /** Whether the report action type is 'APPROVED' or 'SUBMITTED'. Used to style system messages from Old Dot */ isApprovedOrSubmittedReportAction: PropTypes.bool, + /** Used to format RTL display names in Old Dot system messages e.g. Arabic */ + shouldConvertToLTR: PropTypes.bool, + ...windowDimensionsPropTypes, /** localization props */ @@ -91,6 +93,7 @@ const defaultProps = { actorIcon: {}, isThreadParentMessage: false, isApprovedOrSubmittedReportAction: false, + shouldConvertToLTR: false, displayAsGroup: false, }; @@ -158,28 +161,25 @@ function ReportActionItemFragment(props) { ); } case 'TEXT': { - const textFragment = ( + return props.isApprovedOrSubmittedReportAction ? ( - {props.fragment.text} + {props.shouldConvertToLTR ? convertToLTR(props.fragment.text) : props.fragment.text} - ); - - return props.isApprovedOrSubmittedReportAction ? ( - textFragment ) : ( - {textFragment} + + {props.fragment.text} + ); } diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 73ce64365be4..5ae0e6501e0f 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -50,29 +50,43 @@ function ReportActionItemMessage(props) { const isApprovedOrSubmittedReportAction = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); + /** + * Get the ReportActionItemFragments + * @param {Boolean} shouldWrapInText determines whether the fragments are wrapped in a Text component + * @returns {Object} report action item fragments + */ + const renderReportActionItemFragments = (shouldWrapInText) => { + const reportActionItemFragments = _.map(messages, (fragment, index) => ( + + )); + + // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type + // which we need to wrap in `` to prevent them rendering on separate lines. + + return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments; + }; + return ( {!props.isHidden ? ( - // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type - // which we need to wrap in `` to prevent them rendering on separate lines. - - - {_.map(messages, (fragment, index) => ( - - ))} - + renderReportActionItemFragments(isApprovedOrSubmittedReportAction) ) : ( {props.translate('moderation.flaggedContent')} )} diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 25b87e8f04af..faece4f44335 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1316,14 +1316,6 @@ function getTransparentColor(color: string) { return `${color}00`; } -/** - * Get the styles of reports submitted or approved in Old Dot in order to style them like system messages - */ -function getApprovedOrSubmittedReportTextStyles(): TextStyle { - // Font family is restored back to a regular font since text with "fontWeight: 'normal'" on Android still appears in bold - return {color: themeColors.textSupporting, fontFamily: fontFamily.EXP_NEUE, fontWeight: 'normal'}; -} - /** * Get the styles of the text next to dot indicators */ @@ -1336,7 +1328,6 @@ export { displayIfTrue, getAmountFontSizeAndLineHeight, getAnimatedFABStyle, - getApprovedOrSubmittedReportTextStyles, getAutoCompleteSuggestionContainerStyle, getAutoCompleteSuggestionItemStyle, getAutoGrowHeightInputStyle, From 18945da8288b35911f21f22a86c7b4ed55b686b8 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Fri, 3 Nov 2023 08:12:15 +0000 Subject: [PATCH 135/170] Use ternary instead due to empty string --- src/styles/StyleUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index faece4f44335..60c47838e5d1 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -273,7 +273,8 @@ function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle { * Helper method to return eReceipt color code */ function getEReceiptColorCode(transaction: Transaction): EReceiptColorName { - const transactionID = transaction.parentTransactionID ?? transaction.transactionID ?? ''; + const parentTransactionID = transaction.parentTransactionID || ""; + const transactionID = parentTransactionID ? parentTransactionID : transaction.transactionID || ""; const colorHash = UserUtils.hashText(transactionID.trim(), eReceiptColors.length); From f9e55337716941e1133b5ee7dd07258055f67747 Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Fri, 3 Nov 2023 08:38:49 +0000 Subject: [PATCH 136/170] prettier --- src/styles/StyleUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 60c47838e5d1..94a4dee3e2cd 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -273,8 +273,8 @@ function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle { * Helper method to return eReceipt color code */ function getEReceiptColorCode(transaction: Transaction): EReceiptColorName { - const parentTransactionID = transaction.parentTransactionID || ""; - const transactionID = parentTransactionID ? parentTransactionID : transaction.transactionID || ""; + const parentTransactionID = transaction.parentTransactionID || ''; + const transactionID = parentTransactionID ? parentTransactionID : transaction.transactionID || ''; const colorHash = UserUtils.hashText(transactionID.trim(), eReceiptColors.length); From be30678985e07b0f48269a1058051e2f377fc5ba Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:31:41 +0100 Subject: [PATCH 137/170] fix proper margin in styling --- src/pages/workspace/WorkspaceSettingsPage.js | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index 1738dcf43567..1f8ff88bfb64 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -141,17 +141,18 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { originalFileName={policy.originalFileName} /> - + + + Date: Fri, 3 Nov 2023 09:11:35 +0000 Subject: [PATCH 138/170] es-lint --- src/styles/StyleUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 94a4dee3e2cd..120230d88e58 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -273,8 +273,8 @@ function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle { * Helper method to return eReceipt color code */ function getEReceiptColorCode(transaction: Transaction): EReceiptColorName { - const parentTransactionID = transaction.parentTransactionID || ''; - const transactionID = parentTransactionID ? parentTransactionID : transaction.transactionID || ''; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const transactionID = transaction.parentTransactionID || transaction.transactionID || ''; const colorHash = UserUtils.hashText(transactionID.trim(), eReceiptColors.length); From 040c8a76ed881307f0f6eeb9d71f2ce82aea289d Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 3 Nov 2023 17:52:55 +0700 Subject: [PATCH 139/170] fix zipcode does not clear when country change --- .../Profile/PersonalDetails/AddressPage.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/PersonalDetails/AddressPage.js b/src/pages/settings/Profile/PersonalDetails/AddressPage.js index a6cb069780b2..e44b00920544 100644 --- a/src/pages/settings/Profile/PersonalDetails/AddressPage.js +++ b/src/pages/settings/Profile/PersonalDetails/AddressPage.js @@ -81,6 +81,7 @@ function AddressPage({privatePersonalDetails, route}) { const [street1, street2] = (address.street || '').split('\n'); const [state, setState] = useState(address.state); const [city, setCity] = useState(address.city); + const [zipcode, setZipcode] = useState(address.zip); useEffect(() => { if (!address) { @@ -89,6 +90,7 @@ function AddressPage({privatePersonalDetails, route}) { setState(address.state); setCurrentCountry(address.country); setCity(address.city); + setZipcode(address.zip); }, [address]); /** @@ -137,20 +139,28 @@ function AddressPage({privatePersonalDetails, route}) { }, []); const handleAddressChange = useCallback((value, key) => { - if (key !== 'country' && key !== 'state' && key !== 'city') { + if (key !== 'country' && key !== 'state' && key !== 'city' && key !== 'zipPostCode') { return; } if (key === 'country') { setCurrentCountry(value); setState(''); setCity(''); + setZipcode(''); return; } if (key === 'state') { setState(value); + setCity(''); + setZipcode(''); + return; + } + if (key === 'city') { + setCity(value); + setZipcode(''); return; } - setCity(value); + setZipcode(value); }, []); useEffect(() => { @@ -254,9 +264,10 @@ function AddressPage({privatePersonalDetails, route}) { accessibilityLabel={translate('common.zipPostCode')} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} autoCapitalize="characters" - defaultValue={address.zip || ''} + value={zipcode || ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} hint={zipFormat} + onValueChange={handleAddressChange} /> )} From 96438d695fae25beadb2f38d9a40e8340497c1d4 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 3 Nov 2023 14:34:10 +0100 Subject: [PATCH 140/170] fix: lint issue --- src/components/LinearGradient/types.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/LinearGradient/types.ts b/src/components/LinearGradient/types.ts index 555a12f1657c..cf6661eaecaa 100644 --- a/src/components/LinearGradient/types.ts +++ b/src/components/LinearGradient/types.ts @@ -1,6 +1,5 @@ -import LinearGradientWeb from 'react-native-web-linear-gradient'; import LinearGradientNative from 'react-native-linear-gradient'; -type LinearGradient = typeof LinearGradientWeb | typeof LinearGradientNative; +type LinearGradient = typeof LinearGradientNative; export default LinearGradient; From ffb1e9acc21efbed3b200568cdc2d57a714e4dbf Mon Sep 17 00:00:00 2001 From: Victor Nyagudi Date: Fri, 3 Nov 2023 16:40:55 +0300 Subject: [PATCH 141/170] Rename shouldConvertToLTR prop to isFragmentContainingDisplayName --- src/pages/home/report/ReportActionItemFragment.js | 6 +++--- src/pages/home/report/ReportActionItemMessage.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 322abad0ad1d..bbaa13484614 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -67,7 +67,7 @@ const propTypes = { isApprovedOrSubmittedReportAction: PropTypes.bool, /** Used to format RTL display names in Old Dot system messages e.g. Arabic */ - shouldConvertToLTR: PropTypes.bool, + isFragmentContainingDisplayName: PropTypes.bool, ...windowDimensionsPropTypes, @@ -93,7 +93,7 @@ const defaultProps = { actorIcon: {}, isThreadParentMessage: false, isApprovedOrSubmittedReportAction: false, - shouldConvertToLTR: false, + isFragmentContainingDisplayName: false, displayAsGroup: false, }; @@ -166,7 +166,7 @@ function ReportActionItemFragment(props) { numberOfLines={props.isSingleLine ? 1 : undefined} style={[styles.chatItemMessage, styles.colorMuted]} > - {props.shouldConvertToLTR ? convertToLTR(props.fragment.text) : props.fragment.text} + {props.isFragmentContainingDisplayName ? convertToLTR(props.fragment.text) : props.fragment.text} ) : ( )); From 932267f3bfc699f68c18a7541a81d2908841ef47 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 3 Nov 2023 18:12:20 +0100 Subject: [PATCH 142/170] fix: rerun jobs --- src/components/LinearGradient/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LinearGradient/types.ts b/src/components/LinearGradient/types.ts index cf6661eaecaa..cb89599dcd6c 100644 --- a/src/components/LinearGradient/types.ts +++ b/src/components/LinearGradient/types.ts @@ -1,5 +1,5 @@ import LinearGradientNative from 'react-native-linear-gradient'; type LinearGradient = typeof LinearGradientNative; - +console.log(LinearGradientNative); export default LinearGradient; From 4429722cc8f50057e07c0c0307459647b721a088 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 3 Nov 2023 18:14:08 +0100 Subject: [PATCH 143/170] fix: remove log --- src/components/LinearGradient/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LinearGradient/types.ts b/src/components/LinearGradient/types.ts index cb89599dcd6c..cf6661eaecaa 100644 --- a/src/components/LinearGradient/types.ts +++ b/src/components/LinearGradient/types.ts @@ -1,5 +1,5 @@ import LinearGradientNative from 'react-native-linear-gradient'; type LinearGradient = typeof LinearGradientNative; -console.log(LinearGradientNative); + export default LinearGradient; From aa5cc609e22b28185ec81ac7342f68963776d509 Mon Sep 17 00:00:00 2001 From: Rory Abraham <47436092+roryabraham@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:37:24 -0700 Subject: [PATCH 144/170] Update src/components/Attachments/AttachmentCarousel/CarouselItem.js Co-authored-by: Georgia Monahan <38015950+grgia@users.noreply.github.com> --- src/components/Attachments/AttachmentCarousel/CarouselItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 9b69838b74b5..53a8606c927f 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -116,6 +116,6 @@ function CarouselItem({item, isFocused, onPress}) { CarouselItem.propTypes = propTypes; CarouselItem.defaultProps = defaultProps; -CarouselItem.displayName = 'displayName'; +CarouselItem.displayName = 'CarouselItem'; export default CarouselItem; From 26edafa8a20cf438b339c0c285b2295eaf755dd3 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 3 Nov 2023 23:53:07 +0100 Subject: [PATCH 145/170] revert adding a wrapper view with styling --- src/pages/workspace/WorkspaceSettingsPage.js | 23 ++++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index c7dcea7b0992..2ec17294b17a 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -140,18 +140,17 @@ function WorkspaceSettingsPage({policy, currencyList, windowWidth, route}) { originalFileName={policy.originalFileName} /> - - - + Date: Sat, 4 Nov 2023 22:34:45 +0400 Subject: [PATCH 146/170] fix an issue with min height of screen when Offline Indicator is shown --- src/components/ScreenWrapper/index.js | 4 ++-- src/hooks/useInitialWindowDimensions/index.js | 5 +---- src/pages/EditRequestAmountPage.js | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 4563c7149e97..01a19f0a87a4 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -118,7 +118,7 @@ function ScreenWrapper({ return ( diff --git a/src/hooks/useInitialWindowDimensions/index.js b/src/hooks/useInitialWindowDimensions/index.js index 487b4e498228..5878c8b3371f 100644 --- a/src/hooks/useInitialWindowDimensions/index.js +++ b/src/hooks/useInitialWindowDimensions/index.js @@ -1,7 +1,6 @@ // eslint-disable-next-line no-restricted-imports import {useEffect, useState} from 'react'; import {Dimensions} from 'react-native'; -import {initialWindowMetrics} from 'react-native-safe-area-context'; /** * A convenience hook that provides initial size (width and height). @@ -50,10 +49,8 @@ export default function () { }; }, []); - const bottomInset = initialWindowMetrics && initialWindowMetrics.insets && initialWindowMetrics.insets.bottom ? initialWindowMetrics.insets.bottom : 0; - return { initialWidth: dimensions.initialWidth, - initialHeight: dimensions.initialHeight - bottomInset, + initialHeight: dimensions.initialHeight, }; } diff --git a/src/pages/EditRequestAmountPage.js b/src/pages/EditRequestAmountPage.js index 5fb26e961fad..b5db4f2faa78 100644 --- a/src/pages/EditRequestAmountPage.js +++ b/src/pages/EditRequestAmountPage.js @@ -43,6 +43,7 @@ function EditRequestAmountPage({defaultAmount, defaultCurrency, onNavigateToCurr return ( Date: Sun, 5 Nov 2023 01:45:41 +0400 Subject: [PATCH 147/170] remove shouldEnableMaxHeight, because it interferes with work of minHeight --- src/pages/EditRequestAmountPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EditRequestAmountPage.js b/src/pages/EditRequestAmountPage.js index b5db4f2faa78..b6b33774e0fe 100644 --- a/src/pages/EditRequestAmountPage.js +++ b/src/pages/EditRequestAmountPage.js @@ -44,7 +44,6 @@ function EditRequestAmountPage({defaultAmount, defaultCurrency, onNavigateToCurr From 30359afe2f4b0f6ba7921707efffac961987be4d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Sun, 5 Nov 2023 14:39:49 +0100 Subject: [PATCH 148/170] Fix check for empty array --- src/libs/Localize/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index cc424113857e..fd49902af369 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -99,8 +99,8 @@ function translateLocal(phrase: TKey, ...variable /** * Return translated string for given error. */ -function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}]): string { - if (!message || (Array.isArray(message) && message.length > 0)) { +function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}] | []): string { + if (!message || (Array.isArray(message) && message.length === 0)) { return ''; } From b46f0b20912aa2f2161d2bdbc7bf9e838d449e4a Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sun, 5 Nov 2023 16:38:28 +0100 Subject: [PATCH 149/170] fix: prettier --- src/styles/ThemeStylesProvider.tsx | 2 +- src/styles/themes/ThemeProvider.tsx | 4 ++-- src/styles/themes/useThemePreference.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index 2ef9a8521e4d..7f26422e98ce 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -1,8 +1,8 @@ /* eslint-disable react/jsx-props-no-spreading */ import React, {useMemo} from 'react'; +import {stylesGenerator} from './styles'; import useTheme from './themes/useTheme'; import ThemeStylesContext from './ThemeStylesContext'; -import {stylesGenerator} from './styles'; type ThemeStylesProviderProps = { children: React.ReactNode; diff --git a/src/styles/themes/ThemeProvider.tsx b/src/styles/themes/ThemeProvider.tsx index 68413ab944a1..50bfb3b045f4 100644 --- a/src/styles/themes/ThemeProvider.tsx +++ b/src/styles/themes/ThemeProvider.tsx @@ -2,10 +2,10 @@ import PropTypes from 'prop-types'; import React, {useMemo} from 'react'; import CONST from '@src/CONST'; -import ThemeContext from './ThemeContext'; -import useThemePreference from './useThemePreference'; import darkTheme from './default'; import lightTheme from './light'; +import ThemeContext from './ThemeContext'; +import useThemePreference from './useThemePreference'; const propTypes = { /** Rendered child component */ diff --git a/src/styles/themes/useThemePreference.ts b/src/styles/themes/useThemePreference.ts index 725ad72b2e7d..ac6ac02933c7 100644 --- a/src/styles/themes/useThemePreference.ts +++ b/src/styles/themes/useThemePreference.ts @@ -1,4 +1,4 @@ -import {useState, useEffect, useContext} from 'react'; +import {useContext, useEffect, useState} from 'react'; import {Appearance, ColorSchemeName} from 'react-native'; import {PreferredThemeContext} from '@components/OnyxProvider'; import CONST from '@src/CONST'; From d75501f485c7a8668f13eb09fca56e6281716335 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 6 Nov 2023 16:20:37 +0700 Subject: [PATCH 150/170] use usePrevious --- src/pages/home/report/ReportActionsView.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index b07a79d49635..28ddcd94dfb2 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -10,6 +10,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useInitialValue from '@hooks/useInitialValue'; +import usePrevious from '@hooks/usePrevious'; import compose from '@libs/compose'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; import Performance from '@libs/Performance'; @@ -89,7 +90,7 @@ function ReportActionsView(props) { const mostRecentIOUReportActionID = useInitialValue(() => ReportActionsUtils.getMostRecentIOURequestActionID(props.reportActions)); const prevNetworkRef = useRef(props.network); - const prevAuthTokenType = useRef(props.session.authTokenType); + const prevAuthTokenType = usePrevious(props.session.authTokenType); const prevIsSmallScreenWidthRef = useRef(props.isSmallScreenWidth); @@ -134,8 +135,7 @@ function ReportActionsView(props) { }, [props.network, props.report, isReportFullyVisible]); useEffect(() => { - const prevTokenType = prevAuthTokenType.current; - const wasLoginChangedDetected = prevTokenType === 'anonymousAccount' && !props.session.authTokenType; + const wasLoginChangedDetected = prevAuthTokenType === 'anonymousAccount' && !props.session.authTokenType; if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(props.report)) { if (isReportFullyVisible) { openReportIfNecessary(); @@ -143,7 +143,6 @@ function ReportActionsView(props) { Report.reconnect(reportID); } } - prevAuthTokenType.current = props.session.authTokenType; // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.session, props.report, isReportFullyVisible]); From 9983f8862899c86a89462b874690e856ee28f808 Mon Sep 17 00:00:00 2001 From: Pujan Date: Mon, 6 Nov 2023 18:33:13 +0530 Subject: [PATCH 151/170] removed unnecessary style --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3485bcbd7a60..0f9d566a43e1 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -573,7 +573,7 @@ function ReportActionItem(props) { content = ( <> - + Date: Mon, 6 Nov 2023 15:42:05 +0100 Subject: [PATCH 152/170] pass onyx data down --- .../LHNOptionsList/LHNOptionsList.js | 76 ++++++++++++++++--- .../LHNOptionsList/OptionRowLHNData.js | 17 ----- 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 5d63c176ea6f..1e6b1ddd438d 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -1,11 +1,15 @@ import PropTypes from 'prop-types'; -import React from 'react'; import {FlatList, View} from 'react-native'; +import React, {useCallback} from 'react'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import styles from '@styles/styles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import OptionRowLHNDataWithFocus from './OptionRowLHNDataWithFocus'; +import ONYXKEYS from '@src/ONYXKEYS'; +import OptionRowLHNDataWithFocus from '@src/components/LHNOptionsList/OptionRowLHNDataWithFocus'; +import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; +import reportPropTypes from '@pages/reportPropTypes'; const propTypes = { /** Wrapper style for the section list */ @@ -27,14 +31,37 @@ const propTypes = { /** Whether to allow option focus or not */ shouldDisableFocusOptions: PropTypes.bool, + + /** The policy which the user has access to and which the report could be tied to */ + policy: PropTypes.shape({ + /** The ID of the policy */ + id: PropTypes.string, + /** Name of the policy */ + name: PropTypes.string, + /** Avatar of the policy */ + avatar: PropTypes.string, + }), + + /** The actions from the parent report */ + parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + + /** All reports shared with the user */ + reports: PropTypes.objectOf(reportPropTypes), + + /** Array of report actions for this report */ + reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), }; const defaultProps = { style: styles.flex1, shouldDisableFocusOptions: false, + reportActions: [], + reports: {}, + parentReportActions: {}, + policy: {}, }; -function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optionMode, shouldDisableFocusOptions}) { +function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optionMode, shouldDisableFocusOptions, reports, reportActions, parentReportActions, policy}) { /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large @@ -62,13 +89,27 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio * * @return {Component} */ - const renderItem = ({item}) => ( - + const renderItem = useCallback( + ({item}) => { + const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${item}`]; + const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item}`]; + const itemParentReportActions = parentReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; + const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`]; + + return ( + + ); + }, + [onSelectRow, optionMode, parentReportActions, policy, reportActions, reports, shouldDisableFocusOptions], ); return ( @@ -96,4 +137,17 @@ LHNOptionsList.propTypes = propTypes; LHNOptionsList.defaultProps = defaultProps; LHNOptionsList.displayName = 'LHNOptionsList'; -export default LHNOptionsList; +export default withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + parentReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policy: { + key: ONYXKEYS.COLLECTION.POLICY, + }, +})(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index ebba2ffe0587..a61d7667462c 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -163,16 +163,6 @@ const personalDetailsSelector = (personalDetails) => export default React.memo( compose( withOnyx({ - comment: { - key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, - }, - fullReport: { - key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - }, - reportActions: { - key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, - canEvict: false, - }, personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, selector: personalDetailsSelector, @@ -183,13 +173,6 @@ export default React.memo( }), // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ - parentReportActions: { - key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`, - canEvict: false, - }, - policy: { - key: ({fullReport}) => `${ONYXKEYS.COLLECTION.POLICY}${fullReport.policyID}`, - }, // Ideally, we aim to access only the last transaction for the current report by listening to changes in reportActions. // In some scenarios, a transaction might be created after reportActions have been modified. // This can lead to situations where `lastTransaction` doesn't update and retains the previous value. From db18623e62119962e863e915f448454d2f6b77c7 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:05 +0100 Subject: [PATCH 153/170] remove onyx hoc from OptionRowLHNData --- .../LHNOptionsList/LHNOptionsList.js | 66 ++++++++++++++++++- .../LHNOptionsList/OptionRowLHNData.js | 61 ++--------------- 2 files changed, 71 insertions(+), 56 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 1e6b1ddd438d..5807b51cbab1 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -10,6 +10,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNDataWithFocus from '@src/components/LHNOptionsList/OptionRowLHNDataWithFocus'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; +import OptionRowLHNDataWithFocus from '@components/LHNOptionsList/OptionRowLHNDataWithFocus'; +import participantPropTypes from '@components/participantPropTypes'; +import * as UserUtils from '@libs/User'; const propTypes = { /** Wrapper style for the section list */ @@ -50,6 +53,20 @@ const propTypes = { /** Array of report actions for this report */ reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), + + /** Indicates which locale the user currently has selected */ + preferredLocale: PropTypes.string, + + /** List of users' personal details */ + personalDetails: PropTypes.objectOf(participantPropTypes), + + /** The transaction from the parent report action */ + transactions: PropTypes.arrayOf( + PropTypes.shape({ + /** The ID of the transaction */ + transactionID: PropTypes.string, + }), + ), }; const defaultProps = { @@ -59,9 +76,26 @@ const defaultProps = { reports: {}, parentReportActions: {}, policy: {}, + preferredLocale: CONST.LOCALES.DEFAULT, + personalDetails: {}, + transactions: [], }; -function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optionMode, shouldDisableFocusOptions, reports, reportActions, parentReportActions, policy}) { +function LHNOptionsList({ + style, + contentContainerStyles, + data, + onSelectRow, + optionMode, + shouldDisableFocusOptions, + reports, + reportActions, + parentReportActions, + policy, + preferredLocale, + personalDetails, + transactions, +}) { /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large @@ -95,6 +129,21 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item}`]; const itemParentReportActions = parentReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`]; + const itemTransaction = itemParentReportActions ? itemParentReportActions[itemFullReport.parentReportActionID].originalMessage.IOUTransactionID : undefined; + const itemPersonalDetails = _.reduce( + personalDetails, + (finalPersonalDetails, personalData, accountID) => { + // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails + // eslint-disable-next-line no-param-reassign + finalPersonalDetails[accountID] = { + ...personalData, + accountID: Number(accountID), + avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), + }; + return finalPersonalDetails; + }, + {}, + ); return ( ); }, - [onSelectRow, optionMode, parentReportActions, policy, reportActions, reports, shouldDisableFocusOptions], + [onSelectRow, optionMode, parentReportActions, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -150,4 +203,13 @@ export default withOnyx({ policy: { key: ONYXKEYS.COLLECTION.POLICY, }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, })(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index a61d7667462c..477a66da1832 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -2,14 +2,11 @@ import {deepEqual} from 'fast-equals'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef} from 'react'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; -import compose from '@libs/compose'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import * as UserUtils from '@libs/UserUtils'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; @@ -82,7 +79,6 @@ function OptionRowLHNData({ ...propsToForward }) { const reportID = propsToForward.reportID; - const parentReportAction = parentReportActions[fullReport.parentReportActionID]; const optionItemRef = useRef(); @@ -129,30 +125,6 @@ OptionRowLHNData.propTypes = propTypes; OptionRowLHNData.defaultProps = defaultProps; OptionRowLHNData.displayName = 'OptionRowLHNData'; -/** - * @param {Object} [personalDetails] - * @returns {Object|undefined} - */ -const personalDetailsSelector = (personalDetails) => - _.reduce( - personalDetails, - (finalPersonalDetails, personalData, accountID) => { - // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails - // eslint-disable-next-line no-param-reassign - finalPersonalDetails[accountID] = { - accountID: Number(accountID), - login: personalData.login, - displayName: personalData.displayName, - firstName: personalData.firstName, - status: personalData.status, - avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), - fallbackIcon: personalData.fallbackIcon, - }; - return finalPersonalDetails; - }, - {}, - ); - /** * This component is rendered in a list. * On scroll we want to avoid that a item re-renders @@ -161,30 +133,11 @@ const personalDetailsSelector = (personalDetails) => * use it to prevent re-renders from parent re-renders. */ export default React.memo( - compose( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - selector: personalDetailsSelector, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - // Ideally, we aim to access only the last transaction for the current report by listening to changes in reportActions. - // In some scenarios, a transaction might be created after reportActions have been modified. - // This can lead to situations where `lastTransaction` doesn't update and retains the previous value. - // However, performance overhead of this is minimized by using memos inside the component. - receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, - }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({fullReport, parentReportActions}) => - `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportActions, [fullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'], '')}`, - }, - }), - )(OptionRowLHNData), + withReportCommentDrafts({ + propName: 'comment', + transformValue: (drafts, props) => { + const draftKey = `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${props.reportID}`; + return lodashGet(drafts, draftKey, ''); + }, + })(OptionRowLHNData), ); From d1ef70e6ada2b06c9c04ba9c7c82f543d59eefb5 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:05 +0100 Subject: [PATCH 154/170] filter personal details list out of a single item --- .../LHNOptionsList/LHNOptionsList.js | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 5807b51cbab1..1a9af44b25e1 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -1,18 +1,18 @@ import PropTypes from 'prop-types'; import {FlatList, View} from 'react-native'; -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import styles from '@styles/styles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import OptionRowLHNDataWithFocus from '@src/components/LHNOptionsList/OptionRowLHNDataWithFocus'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; import OptionRowLHNDataWithFocus from '@components/LHNOptionsList/OptionRowLHNDataWithFocus'; import participantPropTypes from '@components/participantPropTypes'; -import * as UserUtils from '@libs/User'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as UserUtils from '@libs/UserUtils'; const propTypes = { /** Wrapper style for the section list */ @@ -96,6 +96,24 @@ function LHNOptionsList({ personalDetails, transactions, }) { + const itemPersonalDetails = useMemo( + () => + _.reduce( + personalDetails, + (finalPersonalDetails, personalData, accountID) => { + // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails + // eslint-disable-next-line no-param-reassign + finalPersonalDetails[accountID] = { + ...personalData, + accountID: Number(accountID), + avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), + }; + return finalPersonalDetails; + }, + {}, + ), + [personalDetails], + ); /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large @@ -130,21 +148,7 @@ function LHNOptionsList({ const itemParentReportActions = parentReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`]; const itemTransaction = itemParentReportActions ? itemParentReportActions[itemFullReport.parentReportActionID].originalMessage.IOUTransactionID : undefined; - const itemPersonalDetails = _.reduce( - personalDetails, - (finalPersonalDetails, personalData, accountID) => { - // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails - // eslint-disable-next-line no-param-reassign - finalPersonalDetails[accountID] = { - ...personalData, - accountID: Number(accountID), - avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), - }; - return finalPersonalDetails; - }, - {}, - ); - + const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, itemPersonalDetails)); return ( ); }, - [onSelectRow, optionMode, parentReportActions, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [itemPersonalDetails, onSelectRow, optionMode, parentReportActions, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( From ed2c4fcec6e73796598334a43b2593b4d4cab1cf Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:06 +0100 Subject: [PATCH 155/170] update types --- src/components/LHNOptionsList/LHNOptionsList.js | 2 +- src/components/LHNOptionsList/OptionRowLHNData.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 1a9af44b25e1..afad2c4da007 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -52,7 +52,7 @@ const propTypes = { reports: PropTypes.objectOf(reportPropTypes), /** Array of report actions for this report */ - reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), + reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), /** Indicates which locale the user currently has selected */ preferredLocale: PropTypes.string, diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 477a66da1832..bebdf94ef8d7 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -18,7 +18,7 @@ const propTypes = { isFocused: PropTypes.bool, /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), + personalDetails: PropTypes.arrayOf(participantPropTypes), /** The preferred language for the app */ preferredLocale: PropTypes.string, From d419ac309843af636ac6e59c1ecf8c3ace947ec8 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 6 Nov 2023 15:42:06 +0100 Subject: [PATCH 156/170] fix: use memoized flatlist properties --- .../LHNOptionsList/LHNOptionsList.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index afad2c4da007..4f1acbc2220b 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -81,6 +81,8 @@ const defaultProps = { transactions: [], }; +const keyExtractor = (item) => item; + function LHNOptionsList({ style, contentContainerStyles, @@ -124,14 +126,17 @@ function LHNOptionsList({ * * @returns {Object} */ - const getItemLayout = (itemData, index) => { - const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; - return { - length: optionHeight, - offset: index * optionHeight, - index, - }; - }; + const getItemLayout = useCallback( + (itemData, index) => { + const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; + return { + length: optionHeight, + offset: index * optionHeight, + index, + }; + }, + [optionMode], + ); /** * Function which renders a row in the list @@ -178,7 +183,7 @@ function LHNOptionsList({ showsVerticalScrollIndicator={false} data={data} testID="lhn-options-list" - keyExtractor={(item) => item} + keyExtractor={keyExtractor} stickySectionHeadersEnabled={false} renderItem={renderItem} getItemLayout={getItemLayout} From 4186a22713f94d30c4f57fbf6a0abdfca6c9e36e Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 6 Nov 2023 15:42:06 +0100 Subject: [PATCH 157/170] fix: invalid default prop value for reportActions --- src/components/LHNOptionsList/LHNOptionsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 4f1acbc2220b..c8fc11d88904 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -72,7 +72,7 @@ const propTypes = { const defaultProps = { style: styles.flex1, shouldDisableFocusOptions: false, - reportActions: [], + reportActions: {}, reports: {}, parentReportActions: {}, policy: {}, From 27e4e191a90890a9e99d15a14704033c8613f664 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 6 Nov 2023 15:42:07 +0100 Subject: [PATCH 158/170] fix: properties passed between list and list items --- .../LHNOptionsList/LHNOptionsList.js | 19 ++++++++----------- .../LHNOptionsList/OptionRowLHNData.js | 7 +++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index c8fc11d88904..2de44e9cae67 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -3,6 +3,7 @@ import {FlatList, View} from 'react-native'; import React, {useCallback, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import lodashGet from 'lodash/get'; import styles from '@styles/styles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -45,9 +46,6 @@ const propTypes = { avatar: PropTypes.string, }), - /** The actions from the parent report */ - parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), @@ -74,7 +72,6 @@ const defaultProps = { shouldDisableFocusOptions: false, reportActions: {}, reports: {}, - parentReportActions: {}, policy: {}, preferredLocale: CONST.LOCALES.DEFAULT, personalDetails: {}, @@ -92,7 +89,6 @@ function LHNOptionsList({ shouldDisableFocusOptions, reports, reportActions, - parentReportActions, policy, preferredLocale, personalDetails, @@ -150,9 +146,13 @@ function LHNOptionsList({ ({item}) => { const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${item}`]; const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item}`]; - const itemParentReportActions = parentReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; + const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`]; - const itemTransaction = itemParentReportActions ? itemParentReportActions[itemFullReport.parentReportActionID].originalMessage.IOUTransactionID : undefined; + const itemTransaction = `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet( + itemParentReportActions, + [itemFullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'], + '', + )}`; const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, itemPersonalDetails)); return ( ); }, - [itemPersonalDetails, onSelectRow, optionMode, parentReportActions, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [itemPersonalDetails, onSelectRow, optionMode, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -206,9 +206,6 @@ export default withOnyx({ reportActions: { key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, }, - parentReportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, policy: { key: ONYXKEYS.COLLECTION.POLICY, }, diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index bebdf94ef8d7..8e24e7915489 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -11,6 +11,7 @@ import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {withReportCommentDrafts} from '@components/OnyxProvider'; import OptionRowLHN, {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './OptionRowLHN'; const propTypes = { @@ -41,10 +42,8 @@ const propTypes = { parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), /** The transaction from the parent report action */ - transaction: PropTypes.shape({ - /** The ID of the transaction */ - transactionID: PropTypes.string, - }), + transaction: PropTypes.string, + ...basePropTypes, }; From 0702d7fafdff27704a390c33a004674b735c90bc Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 6 Nov 2023 15:42:07 +0100 Subject: [PATCH 159/170] fix: proptypes for transactions --- src/components/LHNOptionsList/LHNOptionsList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 2de44e9cae67..c78d1de67ddb 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -59,7 +59,7 @@ const propTypes = { personalDetails: PropTypes.objectOf(participantPropTypes), /** The transaction from the parent report action */ - transactions: PropTypes.arrayOf( + transactions: PropTypes.objectOf( PropTypes.shape({ /** The ID of the transaction */ transactionID: PropTypes.string, @@ -75,7 +75,7 @@ const defaultProps = { policy: {}, preferredLocale: CONST.LOCALES.DEFAULT, personalDetails: {}, - transactions: [], + transactions: {}, }; const keyExtractor = (item) => item; From 4d8617efeda996b7750868ef3e716b40b8f988f5 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:07 +0100 Subject: [PATCH 160/170] remove withReportCommentDrafts from OptionRowLHNData --- .../LHNOptionsList/LHNOptionsList.js | 35 ++++++++++++------- .../LHNOptionsList/OptionRowLHNData.js | 13 +------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index c78d1de67ddb..4bf28d9a8dd6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -1,19 +1,19 @@ +import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import {FlatList, View} from 'react-native'; import React, {useCallback, useMemo} from 'react'; +import {FlatList, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import lodashGet from 'lodash/get'; +import participantPropTypes from '@components/participantPropTypes'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as UserUtils from '@libs/UserUtils'; +import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; +import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; -import OptionRowLHNDataWithFocus from '@components/LHNOptionsList/OptionRowLHNDataWithFocus'; -import participantPropTypes from '@components/participantPropTypes'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as UserUtils from '@libs/UserUtils'; +import OptionRowLHNDataWithFocus from './OptionRowLHNDataWithFocus'; const propTypes = { /** Wrapper style for the section list */ @@ -65,6 +65,8 @@ const propTypes = { transactionID: PropTypes.string, }), ), + /** List of draft comments */ + comments: PropTypes.objectOf(PropTypes.string), }; const defaultProps = { @@ -76,6 +78,7 @@ const defaultProps = { preferredLocale: CONST.LOCALES.DEFAULT, personalDetails: {}, transactions: {}, + comments: {}, }; const keyExtractor = (item) => item; @@ -93,6 +96,7 @@ function LHNOptionsList({ preferredLocale, personalDetails, transactions, + comments, }) { const itemPersonalDetails = useMemo( () => @@ -143,9 +147,9 @@ function LHNOptionsList({ * @return {Component} */ const renderItem = useCallback( - ({item}) => { - const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${item}`]; - const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item}`]; + ({item: reportID}) => { + const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`]; const itemTransaction = `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet( @@ -153,10 +157,11 @@ function LHNOptionsList({ [itemFullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'], '', )}`; + const itemComment = comments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, itemPersonalDetails)); return ( ); }, - [itemPersonalDetails, onSelectRow, optionMode, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [comments, itemPersonalDetails, onSelectRow, optionMode, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -218,4 +224,7 @@ export default withOnyx({ transactions: { key: ONYXKEYS.COLLECTION.TRANSACTION, }, + comments: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + }, })(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 8e24e7915489..1fd7d4701a3e 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -1,5 +1,4 @@ import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef} from 'react'; import _ from 'underscore'; @@ -10,8 +9,6 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import {withReportCommentDrafts} from '@components/OnyxProvider'; import OptionRowLHN, {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './OptionRowLHN'; const propTypes = { @@ -131,12 +128,4 @@ OptionRowLHNData.displayName = 'OptionRowLHNData'; * Thats also why the React.memo is used on the outer component here, as we just * use it to prevent re-renders from parent re-renders. */ -export default React.memo( - withReportCommentDrafts({ - propName: 'comment', - transformValue: (drafts, props) => { - const draftKey = `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${props.reportID}`; - return lodashGet(drafts, draftKey, ''); - }, - })(OptionRowLHNData), -); +export default React.memo(OptionRowLHNData); From caf6556551aaa434646193bceb38e08de28e85b8 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:07 +0100 Subject: [PATCH 161/170] remove accountID reassignment --- src/components/LHNOptionsList/LHNOptionsList.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 4bf28d9a8dd6..71b96e454bf1 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -107,7 +107,6 @@ function LHNOptionsList({ // eslint-disable-next-line no-param-reassign finalPersonalDetails[accountID] = { ...personalData, - accountID: Number(accountID), avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), }; return finalPersonalDetails; From 92cbee89240cc3941df4e9490a7a5b585d41362f Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:08 +0100 Subject: [PATCH 162/170] remove unnecessary reduce fn --- .../LHNOptionsList/LHNOptionsList.js | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 71b96e454bf1..91f1e26e621d 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -1,12 +1,11 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback} from 'react'; import {FlatList, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as UserUtils from '@libs/UserUtils'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; import styles from '@styles/styles'; @@ -98,23 +97,6 @@ function LHNOptionsList({ transactions, comments, }) { - const itemPersonalDetails = useMemo( - () => - _.reduce( - personalDetails, - (finalPersonalDetails, personalData, accountID) => { - // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails - // eslint-disable-next-line no-param-reassign - finalPersonalDetails[accountID] = { - ...personalData, - avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), - }; - return finalPersonalDetails; - }, - {}, - ), - [personalDetails], - ); /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large @@ -157,7 +139,7 @@ function LHNOptionsList({ '', )}`; const itemComment = comments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; - const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, itemPersonalDetails)); + const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails)); return ( ); }, - [comments, itemPersonalDetails, onSelectRow, optionMode, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [comments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( From a105947edc1a04dbd98936b3095f10683ea45722 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:08 +0100 Subject: [PATCH 163/170] update namings of props --- src/components/LHNOptionsList/LHNOptionsList.js | 12 ++++++------ src/components/LHNOptionsList/OptionRowLHNData.js | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 91f1e26e621d..7cc3bab04fe7 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -65,7 +65,7 @@ const propTypes = { }), ), /** List of draft comments */ - comments: PropTypes.objectOf(PropTypes.string), + draftComments: PropTypes.objectOf(PropTypes.string), }; const defaultProps = { @@ -77,7 +77,7 @@ const defaultProps = { preferredLocale: CONST.LOCALES.DEFAULT, personalDetails: {}, transactions: {}, - comments: {}, + draftComments: {}, }; const keyExtractor = (item) => item; @@ -95,7 +95,7 @@ function LHNOptionsList({ preferredLocale, personalDetails, transactions, - comments, + draftComments, }) { /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization @@ -138,7 +138,7 @@ function LHNOptionsList({ [itemFullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'], '', )}`; - const itemComment = comments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; + const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails)); return ( ); }, - [comments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -205,7 +205,7 @@ export default withOnyx({ transactions: { key: ONYXKEYS.COLLECTION.TRANSACTION, }, - comments: { + draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, })(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 1fd7d4701a3e..4db32ed8bd2a 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -39,7 +39,7 @@ const propTypes = { parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), /** The transaction from the parent report action */ - transaction: PropTypes.string, + transactionID: PropTypes.string, ...basePropTypes, }; @@ -50,7 +50,7 @@ const defaultProps = { fullReport: {}, policy: {}, parentReportActions: {}, - transaction: {}, + transactionID: undefined, preferredLocale: CONST.LOCALES.DEFAULT, ...baseDefaultProps, }; @@ -71,7 +71,7 @@ function OptionRowLHNData({ policy, receiptTransactions, parentReportActions, - transaction, + transactionID, ...propsToForward }) { const reportID = propsToForward.reportID; @@ -97,7 +97,7 @@ function OptionRowLHNData({ // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); + }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transactionID]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { From 5210444eb0fafb4a0cc7b381474572eee8b0d359 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:08 +0100 Subject: [PATCH 164/170] remove OptionRowLHNDataWithFocus --- .../LHNOptionsList/LHNOptionsList.js | 62 +++++++++++-------- .../OptionRowLHNDataWithFocus.js | 39 ------------ 2 files changed, 35 insertions(+), 66 deletions(-) delete mode 100644 src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 7cc3bab04fe7..58de600720a5 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -5,6 +5,8 @@ import {FlatList, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; +import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; +import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; @@ -12,7 +14,7 @@ import styles from '@styles/styles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import OptionRowLHNDataWithFocus from './OptionRowLHNDataWithFocus'; +import OptionRowLHNData from './OptionRowLHNData'; const propTypes = { /** Wrapper style for the section list */ @@ -66,6 +68,7 @@ const propTypes = { ), /** List of draft comments */ draftComments: PropTypes.objectOf(PropTypes.string), + ...withCurrentReportIDPropTypes, }; const defaultProps = { @@ -78,6 +81,7 @@ const defaultProps = { personalDetails: {}, transactions: {}, draftComments: {}, + ...withCurrentReportIDDefaultProps, }; const keyExtractor = (item) => item; @@ -96,6 +100,7 @@ function LHNOptionsList({ personalDetails, transactions, draftComments, + currentReportID, }) { /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization @@ -141,7 +146,7 @@ function LHNOptionsList({ const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails)); return ( - ); }, - [draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], ); return ( @@ -186,26 +191,29 @@ LHNOptionsList.propTypes = propTypes; LHNOptionsList.defaultProps = defaultProps; LHNOptionsList.displayName = 'LHNOptionsList'; -export default withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policy: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - draftComments: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - }, -})(LHNOptionsList); +export default compose( + withCurrentReportID, + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + reportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + }, + policy: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, + draftComments: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + }, + }), +)(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js b/src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js deleted file mode 100644 index 67e90bcbb0e0..000000000000 --- a/src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js +++ /dev/null @@ -1,39 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; -import OptionRowLHNData from './OptionRowLHNData'; - -const propTypes = { - ...withCurrentReportIDPropTypes, - shouldDisableFocusOptions: PropTypes.bool, -}; - -const defaultProps = { - ...withCurrentReportIDDefaultProps, - shouldDisableFocusOptions: false, -}; - -/** - * Wrapper component for OptionRowLHNData that calculates isFocused prop based on currentReportID. - * This is extracted from OptionRowLHNData to prevent unnecessary re-renders when currentReportID changes. - * @returns {React.Component} OptionRowLHNData component with isFocused prop - */ -function OptionRowLHNDataWithFocus({currentReportID, shouldDisableFocusOptions, ...props}) { - // We only want to pass a boolean to the memoized component, - // instead of a changing number (so we prevent unnecessary re-renders). - const isFocused = !shouldDisableFocusOptions && currentReportID === props.reportID; - - return ( - - ); -} - -OptionRowLHNDataWithFocus.defaultProps = defaultProps; -OptionRowLHNDataWithFocus.propTypes = propTypes; -OptionRowLHNDataWithFocus.displayName = 'OptionRowLHNDataWithFocus'; - -export default withCurrentReportID(OptionRowLHNDataWithFocus); From c491848bfda2ae25daccf12ee55c904877cee967 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 6 Nov 2023 15:42:09 +0100 Subject: [PATCH 165/170] use usePersonalDetails --- src/components/LHNOptionsList/LHNOptionsList.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 58de600720a5..f7bb03373c8f 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -4,7 +4,7 @@ import React, {useCallback} from 'react'; import {FlatList, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import participantPropTypes from '@components/participantPropTypes'; +import {usePersonalDetails} from '@components/OnyxProvider'; import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -56,9 +56,6 @@ const propTypes = { /** Indicates which locale the user currently has selected */ preferredLocale: PropTypes.string, - /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), - /** The transaction from the parent report action */ transactions: PropTypes.objectOf( PropTypes.shape({ @@ -97,11 +94,11 @@ function LHNOptionsList({ reportActions, policy, preferredLocale, - personalDetails, transactions, draftComments, currentReportID, }) { + const personalDetails = usePersonalDetails(); /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large @@ -145,6 +142,7 @@ function LHNOptionsList({ )}`; const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails)); + return ( Date: Mon, 6 Nov 2023 15:42:09 +0100 Subject: [PATCH 166/170] Revert "use usePersonalDetails" This reverts commit f525c48e68416f30c6dc62cd5076ca9cb4544d61. --- src/components/LHNOptionsList/LHNOptionsList.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index f7bb03373c8f..58de600720a5 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -4,7 +4,7 @@ import React, {useCallback} from 'react'; import {FlatList, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import {usePersonalDetails} from '@components/OnyxProvider'; +import participantPropTypes from '@components/participantPropTypes'; import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -56,6 +56,9 @@ const propTypes = { /** Indicates which locale the user currently has selected */ preferredLocale: PropTypes.string, + /** List of users' personal details */ + personalDetails: PropTypes.objectOf(participantPropTypes), + /** The transaction from the parent report action */ transactions: PropTypes.objectOf( PropTypes.shape({ @@ -94,11 +97,11 @@ function LHNOptionsList({ reportActions, policy, preferredLocale, + personalDetails, transactions, draftComments, currentReportID, }) { - const personalDetails = usePersonalDetails(); /** * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large @@ -142,7 +145,6 @@ function LHNOptionsList({ )}`; const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails)); - return ( Date: Mon, 6 Nov 2023 15:42:09 +0100 Subject: [PATCH 167/170] fix tests --- src/components/LHNOptionsList/LHNOptionsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 58de600720a5..3986773aca87 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -134,7 +134,7 @@ function LHNOptionsList({ */ const renderItem = useCallback( ({item: reportID}) => { - const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] || {}; const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`]; const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`]; From c4e72a6544a46aecf2dce0b386a97b5b4e21d74a Mon Sep 17 00:00:00 2001 From: Matt Allen Date: Mon, 6 Nov 2023 08:12:14 -0800 Subject: [PATCH 168/170] Update CONTRIBUTING.md Removed part of line 151 > Depending on the hold length, our team will decide if a bonus will be applied to the job. --- contributingGuides/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 24e0d1878237..6e02cae677bb 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -148,7 +148,7 @@ Additionally if you want to discuss an idea with the open source community witho - If you have made a change to your pull request and are ready for another review, leave a comment that says "Updated" on the pull request itself. - Please keep the conversation in GitHub, and do not ping individual reviewers in Slack or Upwork to get their attention. - Pull Request reviews can sometimes take a few days. If your pull request has not been addressed after four days please let us know via the #expensify-open-source Slack channel. -- On occasion, our engineers will need to focus on a feature release and choose to place a hold on the review of your PR. Depending on the hold length, our team will decide if a bonus will be applied to the job. +- On occasion, our engineers will need to focus on a feature release and choose to place a hold on the review of your PR. #### Important note about JavaScript Style - Read our official [JavaScript and React style guide](https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md). Please refer to our Style Guide before asking for a review. From 0680f2745c581189d4a9cb6c1d2c185a773b68e0 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 6 Nov 2023 16:52:54 +0000 Subject: [PATCH 169/170] Update version to 1.3.95-6 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b07c66308609..aa329c3ae935 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001039505 - versionName "1.3.95-5" + versionCode 1001039506 + versionName "1.3.95-6" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 1966f3862d59..5cb86992bb4b 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.95.5 + 1.3.95.6 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 387687a2beaa..333b8d1eccff 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.95.5 + 1.3.95.6 diff --git a/package-lock.json b/package-lock.json index a80022853a24..07d602babc85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.95-5", + "version": "1.3.95-6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.95-5", + "version": "1.3.95-6", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 26fe03fa6e1c..13f262d3f8cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.95-5", + "version": "1.3.95-6", "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.", From 7ca83029bbf1a93ae257a963566f56eb6cc65951 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 6 Nov 2023 17:35:19 +0000 Subject: [PATCH 170/170] Update version to 1.3.95-7 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index aa329c3ae935..d3fe53bfc3e2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001039506 - versionName "1.3.95-6" + versionCode 1001039507 + versionName "1.3.95-7" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 5cb86992bb4b..3f4c403bc8f9 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.95.6 + 1.3.95.7 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 333b8d1eccff..55d75b28dc09 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.95.6 + 1.3.95.7 diff --git a/package-lock.json b/package-lock.json index 07d602babc85..b69224baeb1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.95-6", + "version": "1.3.95-7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.95-6", + "version": "1.3.95-7", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 13f262d3f8cc..9cfe724234a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.95-6", + "version": "1.3.95-7", "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.",