From 08337c3bc03cea5a74ee0aa22818826fb3f4b964 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 20 Sep 2023 15:31:39 +0700 Subject: [PATCH 001/358] leave room when has no comment --- src/libs/actions/Report.js | 11 +++++++---- src/pages/home/ReportScreen.js | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2a34c839a94e..759f245ac0a5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1777,8 +1777,9 @@ function getCurrentUserAccountID() { * Leave a report by setting the state to submitted and closed * * @param {String} reportID + * @param {Boolean} shouldNavigate should navigate after leaving room or not */ -function leaveRoom(reportID) { +function leaveRoom(reportID, shouldNavigate = true) { const report = lodashGet(allReports, [reportID], {}); const reportKeys = _.keys(report); API.write( @@ -1819,10 +1820,12 @@ function leaveRoom(reportID) { }, ); Navigation.dismissModal(); - if (Navigation.getTopmostReportId() === reportID) { - Navigation.goBack(); + if (shouldNavigate) { + if (Navigation.getTopmostReportId() === reportID) { + Navigation.goBack(); + } + navigateToConciergeChat(); } - navigateToConciergeChat(); } /** diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 8528b8f213a9..13e77923ad5c 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -1,6 +1,6 @@ import React, {useRef, useState, useEffect, useMemo, useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; -import {useFocusEffect} from '@react-navigation/native'; +import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import lodashGet from 'lodash/get'; @@ -151,6 +151,7 @@ function ReportScreen({ const flatListRef = useRef(); const reactionListRef = useRef(); const prevReport = usePrevious(report); + const isFocused = useIsFocused(); const [skeletonViewContainerHeight, setSkeletonViewContainerHeight] = useState(0); const [isBannerVisible, setIsBannerVisible] = useState(true); @@ -312,6 +313,18 @@ function ReportScreen({ [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete], ); + useEffect(() => { + if (isFocused) { + return; + } + + if (ReportUtils.isThread(report) && report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + Report.leaveRoom(report.reportID, false); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isFocused]); + return ( Date: Tue, 7 Nov 2023 16:24:43 +0700 Subject: [PATCH 002/358] fix: attachment modal is not closed when click notification --- ...bscribeToReportCommentPushNotifications.js | 23 +++++++++++-------- src/libs/actions/Report.js | 11 ++++++++- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js index 04fd34bf6075..84d6c4ef6514 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js @@ -2,6 +2,7 @@ import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import Visibility from '@libs/Visibility'; +import * as Modal from '@userActions/Modal'; import ROUTES from '@src/ROUTES'; import backgroundRefresh from './backgroundRefresh'; import PushNotification from './index'; @@ -24,17 +25,19 @@ export default function subscribeToReportCommentPushNotifications() { Log.info('[PushNotification] onSelected() - called', false, {reportID, reportActionID}); Navigation.isNavigationReady().then(() => { - try { - // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back - if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) { - Navigation.goBack(ROUTES.HOME); - } + Modal.close(() => { + try { + // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back + if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) { + Navigation.goBack(ROUTES.HOME); + } - Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); - } catch (error) { - Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: error.message}); - } + Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + } catch (error) { + Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: error.message}); + } + }); }); }); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 1de15c1184cb..9d6688882486 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -22,6 +22,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import Visibility from '@libs/Visibility'; +import * as Modal from '@userActions/Modal'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -1815,7 +1816,15 @@ function showReportActionNotification(reportID, reportAction) { const notificationParams = { report, reportAction, - onClick: () => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)), + onClick: () => { + Modal.close(() => { + const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID); + if (Navigation.isActiveRoute(reportRoute)) { + return; + } + Navigation.navigate(reportRoute); + }); + }, }; if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { LocalNotification.showModifiedExpenseNotification(notificationParams); From e2cc597f44a726885355ec8ebd5923739d2408fc Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 Nov 2023 17:04:57 +0700 Subject: [PATCH 003/358] fix lint --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 9d6688882486..672ff1d97cd7 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -22,11 +22,11 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import Visibility from '@libs/Visibility'; -import * as Modal from '@userActions/Modal'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import * as Modal from './Modal'; import * as Session from './Session'; import * as Welcome from './Welcome'; From f56e0e36ff9a11bbe9dd55a7379d36522b7d8098 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 8 Nov 2023 17:12:29 +0700 Subject: [PATCH 004/358] fix conflict --- src/pages/home/ReportScreen.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index fd741f051618..7abf395644f8 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -1,11 +1,3 @@ -<<<<<<< HEAD -import React, {useRef, useState, useEffect, useMemo, useCallback} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import {useFocusEffect, useIsFocused} from '@react-navigation/native'; -import PropTypes from 'prop-types'; -import {View} from 'react-native'; -======= ->>>>>>> main import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; From 0c63dac9d928589456f66e18d529cdf6660d768d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 8 Nov 2023 17:26:10 +0700 Subject: [PATCH 005/358] fix lint --- src/components/AttachmentModal.js | 2 ++ src/libs/Navigation/AppNavigator/AuthScreens.js | 2 +- src/pages/ReportAvatar.js | 2 +- src/pages/settings/Profile/ProfileAvatar.js | 4 ++-- src/pages/workspace/WorkspaceAvatar.js | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index c541b47ea9c4..098723f941e6 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -41,6 +41,8 @@ import * as Illustrations from './Icon/Illustrations'; import Modal from './Modal'; import SafeAreaConsumer from './SafeAreaConsumer'; import transactionPropTypes from './transactionPropTypes'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; /** * Modal render prop component that exposes modal launching triggers that can be used diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 20971041a0d0..719bad9f2c06 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -326,7 +326,7 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio getComponent={loadReportAttachments} listeners={modalScreenListeners} /> - `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, }), -)(ReportAvatar); \ No newline at end of file +)(ReportAvatar); diff --git a/src/pages/settings/Profile/ProfileAvatar.js b/src/pages/settings/Profile/ProfileAvatar.js index 3faff62231f1..948636c4a02f 100644 --- a/src/pages/settings/Profile/ProfileAvatar.js +++ b/src/pages/settings/Profile/ProfileAvatar.js @@ -2,12 +2,12 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import AttachmentModal from '@components/AttachmentModal'; import participantPropTypes from '@components/participantPropTypes'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import _ from 'underscore'; const propTypes = { /** React Navigation route */ @@ -60,4 +60,4 @@ export default withOnyx({ isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, }, -})(ProfileAvatar); \ No newline at end of file +})(ProfileAvatar); diff --git a/src/pages/workspace/WorkspaceAvatar.js b/src/pages/workspace/WorkspaceAvatar.js index da6f4891b2a5..d3e2f700a353 100644 --- a/src/pages/workspace/WorkspaceAvatar.js +++ b/src/pages/workspace/WorkspaceAvatar.js @@ -78,4 +78,4 @@ export default withOnyx({ isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, }, -})(WorkspaceAvatar); \ No newline at end of file +})(WorkspaceAvatar); From 30f7f4b450129cbd6936c72ff69dd154dfdf9a19 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 15 Nov 2023 21:36:28 +0700 Subject: [PATCH 006/358] get full size avatar --- src/pages/settings/Profile/ProfileAvatar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/ProfileAvatar.js b/src/pages/settings/Profile/ProfileAvatar.js index 948636c4a02f..6fe6ba9c0baa 100644 --- a/src/pages/settings/Profile/ProfileAvatar.js +++ b/src/pages/settings/Profile/ProfileAvatar.js @@ -40,7 +40,7 @@ function ProfileAvatar(props) { { Navigation.goBack(); }} From c781431011d147a1ed27a8a158bfa700e8066b00 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 15 Nov 2023 21:59:51 +0700 Subject: [PATCH 007/358] add get full size for all --- src/pages/ReportAvatar.js | 4 +++- src/pages/workspace/WorkspaceAvatar.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportAvatar.js b/src/pages/ReportAvatar.js index b3d14f169853..a40edb2226d9 100644 --- a/src/pages/ReportAvatar.js +++ b/src/pages/ReportAvatar.js @@ -7,6 +7,7 @@ import AttachmentModal from '@components/AttachmentModal'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import reportPropTypes from './reportPropTypes'; @@ -60,11 +61,12 @@ const defaultProps = { function ReportAvatar(props) { const isArchivedRoom = ReportUtils.isArchivedRoom(props.report); const policyName = isArchivedRoom ? props.report.oldPolicyName : lodashGet(props.policy, 'name', ''); + const avatarURL = lodashGet(props.policy, 'avatar', '') || ReportUtils.getDefaultWorkspaceAvatar(policyName); return ( { Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.report.reportID)); }} diff --git a/src/pages/workspace/WorkspaceAvatar.js b/src/pages/workspace/WorkspaceAvatar.js index d3e2f700a353..8495decb842e 100644 --- a/src/pages/workspace/WorkspaceAvatar.js +++ b/src/pages/workspace/WorkspaceAvatar.js @@ -6,6 +6,7 @@ import _ from 'underscore'; import AttachmentModal from '@components/AttachmentModal'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as UserUtils from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -51,11 +52,12 @@ const defaultProps = { }; function WorkspaceAvatar(props) { + const avatarURL = lodashGet(props.policy, 'avatar', '') || ReportUtils.getDefaultWorkspaceAvatar(lodashGet(props.policy, 'name', '')); return ( { Navigation.goBack(ROUTES.WORKSPACE_SETTINGS.getRoute(getPolicyIDFromRoute(props.route))); }} From 3e5c8b094bdec621983d5d075719ba2903bc031f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 20 Nov 2023 17:17:26 +0100 Subject: [PATCH 008/358] ref: started migrating LHNOptionsList module --- .../{LHNOptionsList.js => LHNOptionsList.tsx} | 20 ++- .../{OptionRowLHN.js => OptionRowLHN.tsx} | 117 +++++++----------- ...tionRowLHNData.js => OptionRowLHNData.tsx} | 0 3 files changed, 60 insertions(+), 77 deletions(-) rename src/components/LHNOptionsList/{LHNOptionsList.js => LHNOptionsList.tsx} (90%) rename src/components/LHNOptionsList/{OptionRowLHN.js => OptionRowLHN.tsx} (76%) rename src/components/LHNOptionsList/{OptionRowLHNData.js => OptionRowLHNData.tsx} (100%) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.tsx similarity index 90% rename from src/components/LHNOptionsList/LHNOptionsList.js rename to src/components/LHNOptionsList/LHNOptionsList.tsx index 0d300c5e2179..5def414f9010 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -2,8 +2,9 @@ import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -17,6 +18,7 @@ import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; const propTypes = { @@ -82,6 +84,20 @@ const defaultProps = { const keyExtractor = (item) => `report_${item}`; +type LHNOptionsListProps = { + style?: StyleProp; + contentContainerStyles: StyleProp; + data: string[]; + onSelectRow: (reportID: string) => void; + optionMode: ValueOf; + shouldDisableFocusOptions?: boolean; + policy: OnyxEntry; + reports: OnyxEntry>; + reportActions: OnyxEntry; + preferredLocale: OnyxEntry>; + personalDetails: OnyxEntry>; +}; + function LHNOptionsList({ style, contentContainerStyles, diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.tsx similarity index 76% rename from src/components/LHNOptionsList/OptionRowLHN.js rename to src/components/LHNOptionsList/OptionRowLHN.tsx index 8420f3db7a1e..89133fe27d72 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,8 +1,10 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import React, {RefObject, useCallback, useRef, useState} from 'react'; +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import _ from 'underscore'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; @@ -30,54 +32,26 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {Beta} from '@src/types/onyx'; -const propTypes = { - /** Style for hovered state */ - // eslint-disable-next-line react/forbid-prop-types - hoverStyle: PropTypes.object, - - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - - /** The ID of the report that the option is for */ - reportID: PropTypes.string.isRequired, - - /** Whether this option is currently in focus so we can modify its style */ - isFocused: PropTypes.bool, - - /** A function that is called when an option is selected. Selected option is passed as a param */ - onSelectRow: PropTypes.func, - - /** Toggle between compact and default view */ - viewMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)), - - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - - /** The item that should be rendered */ - // eslint-disable-next-line react/forbid-prop-types - optionItem: PropTypes.object, -}; - -const defaultProps = { - hoverStyle: undefined, - viewMode: 'default', - onSelectRow: () => {}, - style: null, - optionItem: null, - isFocused: false, - betas: [], +type OptionRowLHNProps = { + hoverStyle?: StyleProp; + betas?: Beta[]; + reportID: string; + isFocused?: boolean; + onSelectRow?: (optionItem: unknown, popoverAnchor: RefObject) => void; + viewMode?: ValueOf; + style?: StyleProp; + optionItem?: unknown; }; - -function OptionRowLHN(props) { +function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const isFocusedRef = useRef(true); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - - const optionItem = props.optionItem; const [isContextMenuActive, setIsContextMenuActive] = useState(false); useFocusEffect( @@ -94,30 +68,28 @@ function OptionRowLHN(props) { } const isHidden = optionItem.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; - if (isHidden && !props.isFocused && !optionItem.isPinned) { + if (isHidden && !isFocused && !optionItem.isPinned) { return null; } - const textStyle = props.isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; + const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], props.style); + const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style); const alternateTextStyle = StyleUtils.combineStyles( - props.viewMode === CONST.OPTION_MODE.COMPACT + viewMode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - props.style, + style, ); const contentContainerStyles = - props.viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( - props.viewMode === CONST.OPTION_MODE.COMPACT + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], ); const hoveredBackgroundColor = - (props.hoverStyle || styles.sidebarLinkHover) && (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - ? (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - : theme.sidebar; + (!!hoverStyle || styles.sidebarLinkHover) && (hoverStyle || styles.sidebarLinkHover).backgroundColor ? (hoverStyle || styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -139,9 +111,9 @@ function OptionRowLHN(props) { event, '', popoverAnchor, - props.reportID, + reportID, '0', - props.reportID, + reportID, '', () => {}, () => setIsContextMenuActive(false), @@ -152,15 +124,14 @@ function OptionRowLHN(props) { ); }; - const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); - const statusText = lodashGet(optionItem, 'status.text', ''); - const statusClearAfterDate = lodashGet(optionItem, 'status.clearAfter', ''); + const emojiCode = optionItem.status.emojiCode ?? ''; + const statusText = optionItem.status.text ?? ''; + const statusClearAfterDate = optionItem.status.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); + const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); - const isGroupChat = - optionItem.type === CONST.REPORT.TYPE.CHAT && _.isEmpty(optionItem.chatType) && !optionItem.isThread && lodashGet(optionItem, 'displayNamesWithTooltips.length', 0) > 2; + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips.length ?? 0) > 2; const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text; return ( @@ -180,7 +151,7 @@ function OptionRowLHN(props) { } // Enable Composer to focus on clicking the same chat after opening the context menu. ReportActionComposeFocusManager.focus(); - props.onSelectRow(optionItem, popoverAnchor); + onSelectRow(optionItem, popoverAnchor); }} onMouseDown={(e) => { // Allow composer blur on right click @@ -196,7 +167,7 @@ function OptionRowLHN(props) { showPopover(e); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - DomUtils.getActiveElement().blur(); + DomUtils.getActiveElement()?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -208,32 +179,32 @@ function OptionRowLHN(props) { styles.sidebarLink, styles.sidebarLinkInner, StyleUtils.getBackgroundColorStyle(theme.sidebar), - props.isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !props.isFocused ? props.hoverStyle || styles.sidebarLinkHover : null, + isFocused ? styles.sidebarLinkActive : null, + (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : null, ]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} - needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2} + needsOffscreenAlphaCompositing={optionItem.icons.length >= 2} > {!_.isEmpty(optionItem.icons) && (optionItem.shouldShowSubscript ? ( ) : ( @@ -321,10 +292,6 @@ function OptionRowLHN(props) { ); } -OptionRowLHN.propTypes = propTypes; -OptionRowLHN.defaultProps = defaultProps; OptionRowLHN.displayName = 'OptionRowLHN'; export default React.memo(OptionRowLHN); - -export {propTypes, defaultProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.tsx similarity index 100% rename from src/components/LHNOptionsList/OptionRowLHNData.js rename to src/components/LHNOptionsList/OptionRowLHNData.tsx From e6d20349facf35a8244f347083741c380d806468 Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Tue, 21 Nov 2023 10:04:19 +0300 Subject: [PATCH 009/358] fix wrong cursor style based on accessibility role --- .../Pressable/GenericPressable/BaseGenericPressable.tsx | 2 +- src/components/Pressable/GenericPressable/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx b/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx index 1576fe18da54..e10f9088d653 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx @@ -132,7 +132,7 @@ function GenericPressable( onPressIn={!isDisabled ? onPressIn : undefined} onPressOut={!isDisabled ? onPressOut : undefined} style={(state) => [ - getCursorStyle(shouldUseDisabledCursor, [rest.accessibilityRole, rest.role].includes('text')), + getCursorStyle(shouldUseDisabledCursor, [rest.accessibilityRole, rest.role].includes('presentation')), StyleUtils.parseStyleFromFunction(style, state), isScreenReaderActive && StyleUtils.parseStyleFromFunction(screenReaderActiveStyle, state), state.focused && StyleUtils.parseStyleFromFunction(focusStyle, state), diff --git a/src/components/Pressable/GenericPressable/index.tsx b/src/components/Pressable/GenericPressable/index.tsx index e0436c26c8fe..523db81863f4 100644 --- a/src/components/Pressable/GenericPressable/index.tsx +++ b/src/components/Pressable/GenericPressable/index.tsx @@ -14,7 +14,7 @@ function WebGenericPressable({focusable = true, ...props}: PressableProps, ref: // change native accessibility props to web accessibility props focusable={focusable} tabIndex={!accessible || !focusable ? -1 : 0} - role={props.accessibilityRole as Role} + role={props.role as Role} id={props.nativeID} aria-label={props.accessibilityLabel} aria-labelledby={props.accessibilityLabelledBy} From f80cf828a546d6d2083bedad905be1f78538964c Mon Sep 17 00:00:00 2001 From: Getabalew Tesfaye Date: Tue, 21 Nov 2023 10:29:12 +0300 Subject: [PATCH 010/358] fix lint --- src/components/Pressable/GenericPressable/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Pressable/GenericPressable/index.tsx b/src/components/Pressable/GenericPressable/index.tsx index 523db81863f4..b3b1ff13f5f9 100644 --- a/src/components/Pressable/GenericPressable/index.tsx +++ b/src/components/Pressable/GenericPressable/index.tsx @@ -1,5 +1,5 @@ import React, {ForwardedRef, forwardRef} from 'react'; -import {Role, View} from 'react-native'; +import {View} from 'react-native'; import GenericPressable from './BaseGenericPressable'; import PressableProps from './types'; @@ -14,7 +14,7 @@ function WebGenericPressable({focusable = true, ...props}: PressableProps, ref: // change native accessibility props to web accessibility props focusable={focusable} tabIndex={!accessible || !focusable ? -1 : 0} - role={props.role as Role} + role={props.role} id={props.nativeID} aria-label={props.accessibilityLabel} aria-labelledby={props.accessibilityLabelledBy} From 2418cd131d94790de2b152edfac532ef0d344c9b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 21 Nov 2023 16:16:51 +0100 Subject: [PATCH 011/358] ref: working on migration to typescript --- .../LHNOptionsList/LHNOptionsList.tsx | 135 ++++++------------ .../LHNOptionsList/OptionRowLHN.tsx | 12 +- .../LHNOptionsList/OptionRowLHNData.tsx | 103 +++++++------ 3 files changed, 112 insertions(+), 138 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 5def414f9010..160f1330cfa8 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,102 +1,60 @@ -import {FlashList} from '@shopify/flash-list'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import {ContentStyle, FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; -import _ from 'underscore'; -import participantPropTypes from '@components/participantPropTypes'; -import transactionPropTypes from '@components/transactionPropTypes'; -import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; +import withCurrentReportID, {CurrentReportIDContextValue} 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'; -import stylePropTypes from '@styles/stylePropTypes'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; -const propTypes = { +const keyExtractor = (item) => `report_${item}`; + +type LHNOptionsListProps = { /** Wrapper style for the section list */ - style: stylePropTypes, + style?: StyleProp; /** Extra styles for the section list container */ - contentContainerStyles: stylePropTypes.isRequired, + contentContainerStyles?: ContentStyle; /** Sections for the section list */ - data: PropTypes.arrayOf(PropTypes.string).isRequired, + data: string[]; /** Callback to fire when a row is selected */ - onSelectRow: PropTypes.func.isRequired, + onSelectRow: (reportID: string) => void; /** Toggle between compact and default view of the option */ - optionMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)).isRequired, + optionMode: ValueOf; /** Whether to allow option focus or not */ - shouldDisableFocusOptions: PropTypes.bool, + shouldDisableFocusOptions?: boolean; /** 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, - }), + policy: OnyxEntry>; /** All reports shared with the user */ - reports: PropTypes.objectOf(reportPropTypes), + reports: OnyxEntry>; /** Array of report actions for this report */ - reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + reportActions: OnyxEntry>; /** Indicates which locale the user currently has selected */ - preferredLocale: PropTypes.string, + preferredLocale: OnyxEntry>; /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), + personalDetails: OnyxEntry>; /** The transaction from the parent report action */ - transactions: PropTypes.objectOf(transactionPropTypes), - /** List of draft comments */ - draftComments: PropTypes.objectOf(PropTypes.string), - ...withCurrentReportIDPropTypes, -}; - -const defaultProps = { - style: undefined, - shouldDisableFocusOptions: false, - reportActions: {}, - reports: {}, - policy: {}, - preferredLocale: CONST.LOCALES.DEFAULT, - personalDetails: {}, - transactions: {}, - draftComments: {}, - ...withCurrentReportIDDefaultProps, -}; + transactions: OnyxEntry>; -const keyExtractor = (item) => `report_${item}`; - -type LHNOptionsListProps = { - style?: StyleProp; - contentContainerStyles: StyleProp; - data: string[]; - onSelectRow: (reportID: string) => void; - optionMode: ValueOf; - shouldDisableFocusOptions?: boolean; - policy: OnyxEntry; - reports: OnyxEntry>; - reportActions: OnyxEntry; - preferredLocale: OnyxEntry>; - personalDetails: OnyxEntry>; -}; + /** List of draft comments */ + draftComments: OnyxEntry>; +} & CurrentReportIDContextValue; function LHNOptionsList({ style, @@ -104,36 +62,31 @@ function LHNOptionsList({ data, onSelectRow, optionMode, - shouldDisableFocusOptions, - reports, - reportActions, - policy, - preferredLocale, - personalDetails, - transactions, - draftComments, - currentReportID, -}) { + shouldDisableFocusOptions = false, + reports = {}, + reportActions = {}, + policy = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + personalDetails = {}, + transactions = {}, + draftComments = {}, + currentReportID = '', +}: LHNOptionsListProps) { const styles = useThemeStyles(); /** * Function which renders a row in the list - * - * @param {Object} params - * @param {Object} params.item - * - * @return {Component} */ const renderItem = useCallback( - ({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 itemParentReportAction = itemParentReportActions[itemFullReport.parentReportActionID] || {}; - const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`] || {}; - const transactionID = lodashGet(itemParentReportAction, ['originalMessage', 'IOUTransactionID'], ''); - const itemTransaction = transactionID ? transactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; - const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails); + ({item: reportID}: {item: string}) => { + const itemFullReport: Report | undefined = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID : ''; + const itemTransaction = transactionID ? transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); return ( + ; /** The preferred language for the app */ - preferredLocale: PropTypes.string, + preferredLocale: string; /** The full data of the report */ - // eslint-disable-next-line react/forbid-prop-types - fullReport: PropTypes.object, + fullReport: Report; /** 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, - }), + policy: Policy; /** The action from the parent report */ - parentReportAction: PropTypes.shape(reportActionPropTypes), + parentReportAction: ReportAction; /** The transaction from the parent report action */ - transaction: transactionPropTypes, - - ...basePropTypes, -}; - -const defaultProps = { - isFocused: false, - personalDetails: {}, - fullReport: {}, - policy: {}, - parentReportAction: {}, - transaction: {}, - preferredLocale: CONST.LOCALES.DEFAULT, - ...baseDefaultProps, -}; + transaction: Transaction; + + comment: string; + + receiptTransactions: Transaction[]; +} & LHNOptionsListProps; /* * This component gets the data from onyx for the actual @@ -74,7 +97,7 @@ function OptionRowLHNData({ parentReportAction, transaction, ...propsToForward -}) { +}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; const optionItemRef = useRef(); @@ -116,8 +139,6 @@ function OptionRowLHNData({ ); } -OptionRowLHNData.propTypes = propTypes; -OptionRowLHNData.defaultProps = defaultProps; OptionRowLHNData.displayName = 'OptionRowLHNData'; /** From 2e6f52a91aa81c4c8ab11703f8f20fc29770f0e9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 21 Nov 2023 20:21:41 +0100 Subject: [PATCH 012/358] ref: contuniue migration --- .../LHNOptionsList/LHNOptionsList.tsx | 44 +-------- .../LHNOptionsList/OptionRowLHN.tsx | 29 ++---- .../LHNOptionsList/OptionRowLHNData.tsx | 46 ++------- src/components/LHNOptionsList/types.ts | 95 +++++++++++++++++++ src/components/SubscriptAvatar.tsx | 32 +++---- src/libs/SidebarUtils.ts | 2 + 6 files changed, 127 insertions(+), 121 deletions(-) create mode 100644 src/components/LHNOptionsList/types.ts diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 160f1330cfa8..64acf390e6a6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -12,50 +12,10 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; +import {LHNOptionsListProps} from './types'; const keyExtractor = (item) => `report_${item}`; -type LHNOptionsListProps = { - /** Wrapper style for the section list */ - style?: StyleProp; - - /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; - - /** Sections for the section list */ - data: string[]; - - /** Callback to fire when a row is selected */ - onSelectRow: (reportID: string) => void; - - /** Toggle between compact and default view of the option */ - optionMode: ValueOf; - - /** Whether to allow option focus or not */ - shouldDisableFocusOptions?: boolean; - - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry>; - - /** All reports shared with the user */ - reports: OnyxEntry>; - - /** Array of report actions for this report */ - reportActions: OnyxEntry>; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry>; - - /** List of users' personal details */ - personalDetails: OnyxEntry>; - - /** The transaction from the parent report action */ - transactions: OnyxEntry>; - - /** List of draft comments */ - draftComments: OnyxEntry>; -} & CurrentReportIDContextValue; - function LHNOptionsList({ style, contentContainerStyles, @@ -69,8 +29,8 @@ function LHNOptionsList({ preferredLocale = CONST.LOCALES.DEFAULT, personalDetails = {}, transactions = {}, - draftComments = {}, currentReportID = '', + draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); /** diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 72add9bc8ae5..24d006ae7555 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,11 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {RefObject, useCallback, useRef, useState} from 'react'; -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import _ from 'underscore'; +import React, {useCallback, useRef, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -32,18 +27,8 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import {Beta} from '@src/types/onyx'; +import {OptionRowLHNProps} from './types'; -type OptionRowLHNProps = { - hoverStyle?: StyleProp; - betas?: Beta[]; - reportID: string; - isFocused?: boolean; - onSelectRow?: (optionItem: unknown, popoverAnchor: RefObject) => void; - viewMode?: ValueOf; - style?: StyleProp; - optionItem?: unknown; -}; function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -184,7 +169,7 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe ]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} - needsOffscreenAlphaCompositing={optionItem.icons.length >= 2} + needsOffscreenAlphaCompositing={(optionItem?.icons?.length ?? 0) >= 2} > @@ -192,13 +177,13 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe (optionItem.shouldShowSubscript ? ( ) : ( ; - - /** The preferred language for the app */ - preferredLocale: string; - - /** The full data of the report */ - fullReport: Report; - - /** The policy which the user has access to and which the report could be tied to */ - policy: Policy; - - /** The action from the parent report */ - parentReportAction: ReportAction; - - /** The transaction from the parent report action */ - transaction: Transaction; - - comment: string; - - receiptTransactions: Transaction[]; -} & LHNOptionsListProps; - /* * This component gets the data from onyx for the actual * OptionRowLHN component. @@ -100,10 +72,10 @@ function OptionRowLHNData({ }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(); + const optionItemRef = useRef(); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const lastReportAction = _.first(sortedReportActions); + const lastReportAction = sortedReportActions[0]; return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions, reportActions]); @@ -114,7 +86,9 @@ function OptionRowLHNData({ if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } - optionItemRef.current = item; + if (item) { + optionItemRef.current = item; + } return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed @@ -122,10 +96,10 @@ function OptionRowLHNData({ }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); useEffect(() => { - if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { + if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; } - Report.setReportWithDraft(reportID, true); + ReportLib.setReportWithDraft(reportID, true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts new file mode 100644 index 000000000000..2f7bfc1005c0 --- /dev/null +++ b/src/components/LHNOptionsList/types.ts @@ -0,0 +1,95 @@ +import {ContentStyle} from '@shopify/flash-list'; +import {Transaction} from 'electron'; +import {RefObject} from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; +import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import {OptionData} from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions} from '@src/types/onyx'; + +type CustomLHNOptionsListProps = { + /** Wrapper style for the section list */ + style?: StyleProp; + + /** Extra styles for the section list container */ + contentContainerStyles?: ContentStyle; + + /** Sections for the section list */ + data: string[]; + + /** Callback to fire when a row is selected */ + onSelectRow: (reportID: string) => void; + + /** Toggle between compact and default view of the option */ + optionMode: ValueOf; + + /** Whether to allow option focus or not */ + shouldDisableFocusOptions?: boolean; + + /** The policy which the user has access to and which the report could be tied to */ + policy: OnyxEntry>; + + /** All reports shared with the user */ + reports: OnyxEntry>; + + /** Array of report actions for this report */ + reportActions: OnyxEntry>; + + /** Indicates which locale the user currently has selected */ + preferredLocale: OnyxEntry>; + + /** List of users' personal details */ + personalDetails: OnyxEntry>; + + /** The transaction from the parent report action */ + transactions: OnyxEntry>; + + /** List of draft comments */ + draftComments: OnyxEntry>; +}; + +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; + +type OptionRowLHNDataProps = { + /** Whether row should be focused */ + isFocused: boolean; + + /** List of users' personal details */ + personalDetails: Record; + + /** The preferred language for the app */ + preferredLocale: string; + + /** The full data of the report */ + fullReport: Report; + + /** The policy which the user has access to and which the report could be tied to */ + policy: Policy; + + /** The action from the parent report */ + parentReportAction: ReportAction; + + /** The transaction from the parent report action */ + transaction: Transaction; + + comment: string; + + receiptTransactions: Transaction[]; + + reportID: string; +} & CustomLHNOptionsListProps; + +type OptionRowLHNProps = { + hoverStyle?: StyleProp; + betas?: Beta[]; + reportID: string; + isFocused?: boolean; + onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + viewMode?: ValueOf; + style?: StyleProp; + optionItem?: OptionData; +}; + +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps}; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index ab9f0dec8e57..8b77565e25ff 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -1,37 +1,20 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import {ValueOf} from 'type-fest'; -import type {AvatarSource} from '@libs/UserUtils'; import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {Icon} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; -type SubAvatar = { - /** Avatar source to display */ - source?: AvatarSource; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; - - /** Avatar id */ - id?: number | string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: AvatarSource; -}; - type SubscriptAvatarProps = { /** Avatar URL or icon */ - mainAvatar?: SubAvatar; + mainAvatar?: Icon; /** Subscript avatar URL or icon */ - secondaryAvatar?: SubAvatar; + secondaryAvatar?: Icon; /** Set the size of avatars */ size?: ValueOf; @@ -46,7 +29,14 @@ type SubscriptAvatarProps = { showTooltip?: boolean; }; -function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) { +function SubscriptAvatar({ + mainAvatar = {} as Icon, + secondaryAvatar = {} as Icon, + size = CONST.AVATAR_SIZE.DEFAULT, + backgroundColor, + noMargin = false, + showTooltip = true, +}: SubscriptAvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); const isSmall = size === CONST.AVATAR_SIZE.SMALL; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 58c4a124335d..8aa01543ddae 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -527,3 +527,5 @@ export default { isSidebarLoadedReady, resetIsSidebarLoadedReadyPromise, }; + +export type {OptionData}; From 6c72cd965b0620907d4a75e8fe42ae874ce069d5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 22 Nov 2023 13:09:20 +0100 Subject: [PATCH 013/358] fix: add few type fixes --- .../LHNOptionsList/LHNOptionsList.tsx | 63 ++++++++++++------- .../LHNOptionsList/OptionRowLHNData.tsx | 50 +++------------ src/components/LHNOptionsList/types.ts | 62 +++++++++--------- src/libs/SidebarUtils.ts | 22 ++++--- 4 files changed, 96 insertions(+), 101 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 64acf390e6a6..967525b0ff6a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,20 +1,17 @@ -import {ContentStyle, FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import withCurrentReportID, {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import {FlatList, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListProps} from './types'; +import {LHNOptionsListOnyxProps, LHNOptionsListProps} from './types'; -const keyExtractor = (item) => `report_${item}`; +const keyExtractor = (item: string) => `report_${item}`; function LHNOptionsList({ style, @@ -33,21 +30,41 @@ function LHNOptionsList({ draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + + /** + * 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 + * lists. + * + * @param itemData - This is the same as the data we pass into the component + * @param index the current item's index in the set of data + */ + const getItemLayout = useCallback( + // eslint-disable-next-line @typescript-eslint/naming-convention + (_, index: number) => { + 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 */ const renderItem = useCallback( ({item: reportID}: {item: string}) => { - const itemFullReport: Report | undefined = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; - const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID : ''; - const itemTransaction = transactionID ? transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; + const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; + const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); - return ( - ); @@ -90,8 +109,7 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; export default compose( - withCurrentReportID, - withOnyx({ + withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, }, @@ -114,6 +132,7 @@ export default compose( key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, }), + withCurrentReportID, )(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index de2e3742a817..4abc69828791 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -4,42 +4,10 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils, {OptionData} from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as ReportLib from '@userActions/Report'; +import CONST from '@src/CONST'; import OptionRowLHN from './OptionRowLHN'; import {OptionRowLHNDataProps} from './types'; -// const propTypes = { -// /** Whether row should be focused */ -// isFocused: PropTypes.bool, - -// /** List of users' personal details */ -// personalDetails: PropTypes.objectOf(participantPropTypes), - -// /** The preferred language for the app */ -// preferredLocale: PropTypes.string, - -// /** The full data of the report */ -// // eslint-disable-next-line react/forbid-prop-types -// fullReport: PropTypes.object, - -// /** 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 action from the parent report */ -// parentReportAction: PropTypes.shape(reportActionPropTypes), - -// /** The transaction from the parent report action */ -// transaction: transactionPropTypes, - -// ...basePropTypes, -// }; - // const defaultProps = { // isFocused: false, // personalDetails: {}, @@ -58,16 +26,16 @@ import {OptionRowLHNDataProps} from './types'; * re-render if the data really changed. */ function OptionRowLHNData({ - isFocused, - fullReport, + isFocused = false, + fullReport = null, reportActions, - personalDetails, - preferredLocale, + personalDetails = {}, + preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy, + policy = null, receiptTransactions, - parentReportAction, - transaction, + parentReportAction = null, + transaction = null, ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; @@ -78,7 +46,7 @@ function OptionRowLHNData({ const lastReportAction = sortedReportActions[0]; return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport.reportID, receiptTransactions, reportActions]); + }, [fullReport?.reportID, receiptTransactions, reportActions]); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 2f7bfc1005c0..5a736e57721d 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,5 +1,4 @@ import {ContentStyle} from '@shopify/flash-list'; -import {Transaction} from 'electron'; import {RefObject} from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import {OnyxEntry} from 'react-native-onyx'; @@ -7,27 +6,9 @@ import {ValueOf} from 'type-fest'; import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import {OptionData} from '@libs/SidebarUtils'; import CONST from '@src/CONST'; -import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions} from '@src/types/onyx'; - -type CustomLHNOptionsListProps = { - /** Wrapper style for the section list */ - style?: StyleProp; - - /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; - - /** Sections for the section list */ - data: string[]; - - /** Callback to fire when a row is selected */ - onSelectRow: (reportID: string) => void; - - /** Toggle between compact and default view of the option */ - optionMode: ValueOf; - - /** Whether to allow option focus or not */ - shouldDisableFocusOptions?: boolean; +import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxEntry>; @@ -49,8 +30,27 @@ type CustomLHNOptionsListProps = { /** List of draft comments */ draftComments: OnyxEntry>; }; +type CustomLHNOptionsListProps = { + /** Wrapper style for the section list */ + style?: StyleProp; + + /** Extra styles for the section list container */ + contentContainerStyles?: ContentStyle; + + /** Sections for the section list */ + data: string[]; + + /** Callback to fire when a row is selected */ + onSelectRow: (reportID: string) => void; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; + /** Toggle between compact and default view of the option */ + optionMode: ValueOf; + + /** Whether to allow option focus or not */ + shouldDisableFocusOptions?: boolean; +}; + +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ @@ -60,26 +60,28 @@ type OptionRowLHNDataProps = { personalDetails: Record; /** The preferred language for the app */ - preferredLocale: string; + preferredLocale: OnyxEntry>; /** The full data of the report */ - fullReport: Report; + fullReport: OnyxEntry; /** The policy which the user has access to and which the report could be tied to */ - policy: Policy; + policy: OnyxEntry; /** The action from the parent report */ - parentReportAction: ReportAction; + parentReportAction: OnyxEntry; /** The transaction from the parent report action */ - transaction: Transaction; + transaction: OnyxEntry; comment: string; - receiptTransactions: Transaction[]; + receiptTransactions: OnyxEntry>; reportID: string; -} & CustomLHNOptionsListProps; + + reportActions: OnyxEntry; +}; type OptionRowLHNProps = { hoverStyle?: StyleProp; @@ -92,4 +94,4 @@ type OptionRowLHNProps = { optionItem?: OptionData; }; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index d3bc3e609486..824e397376ab 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -219,6 +219,12 @@ function getOrderedReportIDs( return LHNReports; } +type Status = { + text: string; + emojiCode: string; + clearAfter: string; +}; + type OptionData = { text?: string | null; alternateText?: string | null; @@ -235,7 +241,7 @@ type OptionData = { managerID?: number | null; reportID?: string | null; policyID?: string | null; - status?: string | null; + status?: Status | null; type?: string | null; stateNum?: ValueOf | null; statusNum?: ValueOf | null; @@ -292,12 +298,12 @@ type Icon = { * Gets all the data necessary for rendering an OptionRowLHN component */ function getOptionData( - report: Report, - reportActions: Record, - personalDetails: Record, - preferredLocale: ValueOf, - policy: Policy, - parentReportAction: ReportAction, + report: OnyxEntry, + reportActions: OnyxEntry>, + personalDetails: OnyxEntry>, + preferredLocale: OnyxEntry>, + policy: OnyxEntry, + parentReportAction: OnyxEntry, ): OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do From 2bf40c8f31db5342889c0b2845ec63f87c7a95ec Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 22 Nov 2023 19:37:24 +0100 Subject: [PATCH 014/358] fix: types --- src/components/DisplayNames/types.ts | 2 +- .../LHNOptionsList/OptionRowLHN.tsx | 43 +++++++++++-------- .../LHNOptionsList/OptionRowLHNData.tsx | 11 ----- src/components/LHNOptionsList/types.ts | 2 +- src/libs/SidebarUtils.ts | 12 ++---- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/components/DisplayNames/types.ts b/src/components/DisplayNames/types.ts index 94e4fc7c39c6..307c28bd2df3 100644 --- a/src/components/DisplayNames/types.ts +++ b/src/components/DisplayNames/types.ts @@ -12,7 +12,7 @@ type DisplayNameWithTooltip = { login?: string; /** The avatar for the tooltip fallback */ - avatar: AvatarSource; + avatar?: AvatarSource; }; type DisplayNamesProps = { diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 24d006ae7555..e2db0cba498a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleProp, StyleSheet, TextStyle, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -59,22 +59,24 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem?.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style); + const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style ?? {}); const alternateTextStyle = StyleUtils.combineStyles( viewMode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - style, + style ?? {}, ); const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; - const sidebarInnerRowStyle = StyleSheet.flatten( - viewMode === CONST.OPTION_MODE.COMPACT - ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] - : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], - ); + const sidebarInnerRowStyle = StyleSheet.flatten([ + styles.chatLinkRowPressable, + styles.flexGrow1, + styles.optionItemAvatarNameWrapper, + viewMode === CONST.OPTION_MODE.COMPACT ? styles.optionRowCompact : styles.optionRow, + styles.justifyContentCenter, + ]); const hoveredBackgroundColor = - (!!hoverStyle || styles.sidebarLinkHover) && (hoverStyle || styles.sidebarLinkHover).backgroundColor ? (hoverStyle || styles.sidebarLinkHover).backgroundColor : theme.sidebar; + !!hoverStyle && 'backgroundColor' in hoverStyle && 'backgroundColor' in styles.sidebarLinkHover ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -84,7 +86,7 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe /** * Show the ReportActionContextMenu modal popover. * - * @param {Object} [event] - A press event. + * @param [event] - A press event. */ const showPopover = (event) => { if (!isFocusedRef.current && isSmallScreenWidth) { @@ -114,11 +116,10 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); - - const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips.length ?? 0) > 2; - const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text; + const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem?.reportID ?? '')); + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem?.displayNamesWithTooltips?.length ?? 0) > 2; + const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem?.reportID ?? '')) : optionItem.text; return ( {isStatusVisible && ( @@ -226,9 +231,9 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe ) : null} - {optionItem.descriptiveText ? ( + {optionItem?.descriptiveText ? ( - {optionItem.descriptiveText} + {optionItem?.descriptiveText} ) : null} {hasBrickError && ( diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 4abc69828791..b48454fb7ec9 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -8,17 +8,6 @@ import CONST from '@src/CONST'; import OptionRowLHN from './OptionRowLHN'; import {OptionRowLHNDataProps} from './types'; -// const defaultProps = { -// isFocused: false, -// personalDetails: {}, -// fullReport: {}, -// policy: {}, -// parentReportAction: {}, -// transaction: {}, -// preferredLocale: CONST.LOCALES.DEFAULT, -// ...baseDefaultProps, -// }; - /* * This component gets the data from onyx for the actual * OptionRowLHN component. diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 5a736e57721d..fa0e48a413c4 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -90,7 +90,7 @@ type OptionRowLHNProps = { isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; - style?: StyleProp; + style?: ViewStyle | ViewStyle[]; optionItem?: OptionData; }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 824e397376ab..5432662dcc94 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -231,7 +231,7 @@ type OptionData = { pendingAction?: OnyxCommon.PendingAction | null; allReportErrors?: OnyxCommon.Errors | null; brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null; - icons?: Icon[] | null; + icons?: OnyxCommon.Icon[] | null; tooltipText?: string | null; ownerAccountID?: number | null; subtitle?: string | null; @@ -272,11 +272,12 @@ type OptionData = { notificationPreference?: string | number | null; displayNamesWithTooltips?: DisplayNamesWithTooltip[] | null; chatType?: ValueOf | null; + descriptiveText?: string; }; type DisplayNamesWithTooltip = { displayName?: string; - avatar?: string; + avatar?: UserUtils.AvatarSource; login?: string; accountID?: number; pronouns?: string; @@ -287,13 +288,6 @@ type ActorDetails = { accountID?: number; }; -type Icon = { - source?: string; - id?: number; - type?: string; - name?: string; -}; - /** * Gets all the data necessary for rendering an OptionRowLHN component */ From e205c9d9d416107e2fe8d8e1a3ca500c35d61b40 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 24 Nov 2023 12:48:21 +0100 Subject: [PATCH 015/358] fix: few type issues --- .../LHNOptionsList/LHNOptionsList.tsx | 33 ++++--------------- src/components/LHNOptionsList/types.ts | 3 +- src/styles/StyleUtils.ts | 4 +-- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 967525b0ff6a..d71fe1db535d 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,6 @@ +import {FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; -import {FlatList, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; @@ -31,26 +32,6 @@ function LHNOptionsList({ }: LHNOptionsListProps) { const styles = useThemeStyles(); - /** - * 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 - * lists. - * - * @param itemData - This is the same as the data we pass into the component - * @param index the current item's index in the set of data - */ - const getItemLayout = useCallback( - // eslint-disable-next-line @typescript-eslint/naming-convention - (_, index: number) => { - 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 */ @@ -88,19 +69,17 @@ function LHNOptionsList({ return ( - ); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index fa0e48a413c4..bd6fb9a1a0a0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -85,13 +85,12 @@ type OptionRowLHNDataProps = { type OptionRowLHNProps = { hoverStyle?: StyleProp; - betas?: Beta[]; reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; style?: ViewStyle | ViewStyle[]; - optionItem?: OptionData; + optionItem?: OptionData | null; }; export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 4b998f940244..695570f2669f 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -425,7 +425,7 @@ function getAutoGrowHeightInputStyle(textInputHeight: number, maxHeight: number) /** * Returns a style with backgroundColor and borderColor set to the same color */ -function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { +function getBackgroundAndBorderStyle(backgroundColor: ColorValue): ViewStyle { return { backgroundColor, borderColor: backgroundColor, @@ -435,7 +435,7 @@ function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { /** * Returns a style with the specified backgroundColor */ -function getBackgroundColorStyle(backgroundColor: string): ViewStyle { +function getBackgroundColorStyle(backgroundColor: ColorValue): ViewStyle { return { backgroundColor, }; From 0d1e9df22c910fc394a8669a2f951c1bc09edf26 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 24 Nov 2023 11:36:09 -1000 Subject: [PATCH 016/358] Upgrade required idea --- src/CONST.ts | 2 + src/Expensify.js | 13 +++ src/ONYXKEYS.ts | 4 + .../ErrorBoundary/BaseErrorBoundary.tsx | 10 ++- src/components/MenuItem.js | 20 +++-- src/libs/HttpUtils.ts | 5 ++ src/libs/actions/AppUpdate.ts | 6 +- src/pages/ErrorPage/GenericErrorPage.js | 79 +++++++++++-------- src/pages/settings/AppDownloadLinks.js | 68 ++-------------- src/styles/styles.ts | 2 +- 10 files changed, 98 insertions(+), 111 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index f1364ebbb5bf..223de7283530 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -731,6 +731,7 @@ const CONST = { EXP_ERROR: 666, MANY_WRITES_ERROR: 665, UNABLE_TO_RETRY: 'unableToRetry', + UPGRADE_REQUIRED: 426, }, HTTP_STATUS: { // When Cloudflare throttles @@ -761,6 +762,7 @@ const CONST = { GATEWAY_TIMEOUT: 'Gateway Timeout', EXPENSIFY_SERVICE_INTERRUPTED: 'Expensify service interrupted', DUPLICATE_RECORD: 'A record already exists with this ID', + UPGRADE_REQUIRED: 'Upgrade Required', }, ERROR_TYPE: { SOCKET: 'Expensify\\Auth\\Error\\Socket', diff --git a/src/Expensify.js b/src/Expensify.js index 1b692f86a197..8050ed99665a 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -36,6 +36,7 @@ import Visibility from './libs/Visibility'; import ONYXKEYS from './ONYXKEYS'; import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/PopoverReportActionContextMenu'; import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from './CONST'; Onyx.registerLogger(({level, message}) => { if (level === 'alert') { @@ -76,6 +77,9 @@ const propTypes = { /** Whether the app is waiting for the server's response to determine if a room is public */ isCheckingPublicRoom: PropTypes.bool, + /** True when the user must update to the latest minimum version of the app */ + upgradeRequired: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -88,6 +92,7 @@ const defaultProps = { isSidebarLoaded: false, screenShareRequest: null, isCheckingPublicRoom: true, + upgradeRequired: false, }; const SplashScreenHiddenContext = React.createContext({}); @@ -201,6 +206,10 @@ function Expensify(props) { return null; } + if (props.upgradeRequired) { + throw new Error(CONST.ERROR.UPGRADE_REQUIRED); + } + return ( {shouldInit && ( @@ -261,6 +270,10 @@ export default compose( screenShareRequest: { key: ONYXKEYS.SCREEN_SHARE_REQUEST, }, + upgradeRequired: { + key: ONYXKEYS.UPGRADE_REQUIRED, + initWithStoredValues: false, + } }), )(Expensify); diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 75c284fb9546..b30a2808a24b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -234,6 +234,9 @@ const ONYXKEYS = { // Max width supported for HTML element MAX_CANVAS_WIDTH: 'maxCanvasWidth', + /** Indicates whether an forced upgrade is required */ + UPGRADE_REQUIRED: 'upgradeRequired', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -417,6 +420,7 @@ type OnyxValues = { [ONYXKEYS.MAX_CANVAS_AREA]: number; [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; + [ONYXKEYS.UPGRADE_REQUIRED]: boolean; // Collections [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; diff --git a/src/components/ErrorBoundary/BaseErrorBoundary.tsx b/src/components/ErrorBoundary/BaseErrorBoundary.tsx index 2a6524d5a993..2ba78c5f8863 100644 --- a/src/components/ErrorBoundary/BaseErrorBoundary.tsx +++ b/src/components/ErrorBoundary/BaseErrorBoundary.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import {ErrorBoundary} from 'react-error-boundary'; import BootSplash from '@libs/BootSplash'; import GenericErrorPage from '@pages/ErrorPage/GenericErrorPage'; @@ -11,15 +11,17 @@ import {BaseErrorBoundaryProps, LogError} from './types'; */ function BaseErrorBoundary({logError = () => {}, errorMessage, children}: BaseErrorBoundaryProps) { - const catchError = (error: Error, errorInfo: React.ErrorInfo) => { - logError(errorMessage, error, JSON.stringify(errorInfo)); + const [error, setError] = useState(() => new Error()); + const catchError = (errorObject: Error, errorInfo: React.ErrorInfo) => { + logError(errorMessage, errorObject, JSON.stringify(errorInfo)); // We hide the splash screen since the error might happened during app init BootSplash.hide(); + setError(errorObject); }; return ( } + fallback={} onError={catchError} > {children} diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 9883672976e8..56461f52cc2a 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -175,14 +175,18 @@ const MenuItem = React.forwardRef((props, ref) => { onPressIn={() => props.shouldBlockSelection && isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={ControlSelection.unblock} onSecondaryInteraction={props.onSecondaryInteraction} - style={({pressed}) => [ - props.style, - !props.interactive && styles.cursorDefault, - StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || isHovered, pressed, props.success, props.disabled, props.interactive), true), - (isHovered || pressed) && props.hoverAndPressStyle, - ...(_.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle]), - props.shouldGreyOutWhenDisabled && props.disabled && styles.buttonOpacityDisabled, - ]} + style={({pressed}) => { + const s = [ + props.style, + !props.interactive && styles.cursorDefault, + StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || isHovered, pressed, props.success, props.disabled, props.interactive), true), + (isHovered || pressed) && props.hoverAndPressStyle, + ...(_.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle]), + props.shouldGreyOutWhenDisabled && props.disabled && styles.buttonOpacityDisabled, + ]; + console.log({s, style: props.style, ws: props.wrapperStyle}); + return s; + }} disabled={props.disabled} ref={ref} role={CONST.ACCESSIBILITY_ROLE.MENUITEM} diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index 859c8624833c..d51fb38e0cee 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -7,6 +7,7 @@ import {RequestType} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import * as ApiUtils from './ApiUtils'; import HttpsError from './Errors/HttpsError'; +import * as AppUpdate from './actions/AppUpdate'; let shouldFailAllRequests = false; let shouldForceOffline = false; @@ -103,6 +104,10 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form alert('Too many auth writes', message); } } + if (response.jsonCode === CONST.JSON_CODE.UPGRADE_REQUIRED) { + // Trigger a modal and disable the app as the user needs to upgrade to the latest minimum version to continue + AppUpdate.triggerUpgradeRequired(); + } return response as Promise; }); } diff --git a/src/libs/actions/AppUpdate.ts b/src/libs/actions/AppUpdate.ts index 29ee2a4547ab..8f2ce3ead102 100644 --- a/src/libs/actions/AppUpdate.ts +++ b/src/libs/actions/AppUpdate.ts @@ -9,4 +9,8 @@ function setIsAppInBeta(isBeta: boolean) { Onyx.set(ONYXKEYS.IS_BETA, isBeta); } -export {triggerUpdateAvailable, setIsAppInBeta}; +function triggerUpgradeRequired() { + Onyx.set(ONYXKEYS.UPGRADE_REQUIRED, true); +} + +export {triggerUpdateAvailable, setIsAppInBeta, triggerUpgradeRequired}; diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.js index 7b627a8e18d5..cdf2ff1cea63 100644 --- a/src/pages/ErrorPage/GenericErrorPage.js +++ b/src/pages/ErrorPage/GenericErrorPage.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import {useErrorBoundary} from 'react-error-boundary'; import {View} from 'react-native'; import LogoWordmark from '@assets/images/expensify-wordmark.svg'; @@ -15,17 +16,21 @@ import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; +import AppDownloadLinksView from '@pages/settings/AppDownloadLinksView'; import ErrorBodyText from './ErrorBodyText'; const propTypes = { + /** Error object handled by the boundary */ + error: PropTypes.instanceOf(Error).isRequired, + ...withLocalizePropTypes, }; -function GenericErrorPage({translate}) { +function GenericErrorPage({translate, error}) { const theme = useTheme(); const styles = useThemeStyles(); const {resetBoundary} = useErrorBoundary(); - + const upgradeRequired = error.message === CONST.ERROR.UPGRADE_REQUIRED; return ( {({paddingBottom}) => ( @@ -34,46 +39,50 @@ function GenericErrorPage({translate}) { - {translate('genericErrorPage.title')} - - - - - {`${translate('genericErrorPage.body.helpTextConcierge')} `} - - {CONST.EMAIL.CONCIERGE} - - - - - -