From dde6aca441a7aa3f99ddcea54826f1de16a8e26a Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 18 Dec 2023 17:11:25 +0100 Subject: [PATCH 001/438] chore: move IOUWaypoint Pages to TS --- src/ROUTES.ts | 2 +- src/libs/Navigation/types.ts | 4 +- src/libs/actions/Transaction.ts | 20 ++- src/pages/iou/MoneyRequestEditWaypointPage.js | 32 ----- .../iou/MoneyRequestEditWaypointPage.tsx | 15 ++ .../NewDistanceRequestWaypointEditorPage.js | 55 -------- .../NewDistanceRequestWaypointEditorPage.tsx | 38 +++++ .../{WaypointEditor.js => WaypointEditor.tsx} | 132 ++++++++---------- src/types/onyx/Transaction.ts | 20 ++- 9 files changed, 145 insertions(+), 173 deletions(-) delete mode 100644 src/pages/iou/MoneyRequestEditWaypointPage.js create mode 100644 src/pages/iou/MoneyRequestEditWaypointPage.tsx delete mode 100644 src/pages/iou/NewDistanceRequestWaypointEditorPage.js create mode 100644 src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx rename src/pages/iou/{WaypointEditor.js => WaypointEditor.tsx} (73%) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ca1fe9f0e81a..1a205c7827b1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -300,7 +300,7 @@ const ROUTES = { }, MONEY_REQUEST_EDIT_WAYPOINT: { route: 'r/:threadReportID/edit/distance/:transactionID/waypoint/:waypointIndex', - getRoute: (threadReportID: number, transactionID: string, waypointIndex: number) => `r/${threadReportID}/edit/distance/${transactionID}/waypoint/${waypointIndex}` as const, + getRoute: (threadReportID: string, transactionID: string, waypointIndex: number) => `r/${threadReportID}/edit/distance/${transactionID}/waypoint/${waypointIndex}` as const, }, MONEY_REQUEST_DISTANCE_TAB: { route: ':iouType/new/:reportID?/distance', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 94a07ddc6b73..bae2ccaf1cc0 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -227,13 +227,13 @@ type MoneyRequestNavigatorParamList = { iouType: string; transactionID: string; waypointIndex: string; - threadReportID: number; + threadReportID: string; }; [SCREENS.MONEY_REQUEST.EDIT_WAYPOINT]: { iouType: string; transactionID: string; waypointIndex: string; - threadReportID: number; + threadReportID: string; }; [SCREENS.MONEY_REQUEST.DISTANCE]: { iouType: ValueOf; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 86bd1b31714d..75a202b59986 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1,7 +1,7 @@ import {isEqual} from 'lodash'; import lodashClone from 'lodash/clone'; import lodashHas from 'lodash/has'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import * as API from '@libs/API'; import * as CollectionUtils from '@libs/CollectionUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -102,7 +102,7 @@ function saveWaypoint(transactionID: string, index: string, waypoint: RecentWayp } } -function removeWaypoint(transaction: Transaction, currentIndex: string, isDraft: boolean) { +function removeWaypoint(transaction: OnyxEntry, currentIndex: string, isDraft?: boolean) { // Index comes from the route params and is a string const index = Number(currentIndex); const existingWaypoints = transaction?.comment?.waypoints ?? {}; @@ -131,8 +131,18 @@ function removeWaypoint(transaction: Transaction, currentIndex: string, isDraft: // Doing a deep clone of the transaction to avoid mutating the original object and running into a cache issue when using Onyx.set let newTransaction: Transaction = { ...transaction, + amount: transaction?.amount ?? 0, + billable: transaction?.billable ?? false, + category: transaction?.category ?? '', + created: transaction?.created ?? '', + currency: transaction?.currency ?? '', + pendingAction: transaction?.pendingAction ?? null, + merchant: transaction?.merchant ?? '', + reportID: transaction?.reportID ?? '', + transactionID: transaction?.transactionID ?? '', + tag: transaction?.tag ?? '', comment: { - ...transaction.comment, + ...transaction?.comment, waypoints: reIndexedWaypoints, }, }; @@ -156,10 +166,10 @@ function removeWaypoint(transaction: Transaction, currentIndex: string, isDraft: }; } if (isDraft) { - Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction.transactionID}`, newTransaction); + Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction?.transactionID}`, newTransaction); return; } - Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, newTransaction); + Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction?.transactionID}`, newTransaction); } function getOnyxDataForRouteRequest(transactionID: string, isDraft = false): OnyxData { diff --git a/src/pages/iou/MoneyRequestEditWaypointPage.js b/src/pages/iou/MoneyRequestEditWaypointPage.js deleted file mode 100644 index fc777891109e..000000000000 --- a/src/pages/iou/MoneyRequestEditWaypointPage.js +++ /dev/null @@ -1,32 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import WaypointEditor from './WaypointEditor'; - -const propTypes = { - /** Route params */ - route: PropTypes.shape({ - params: PropTypes.shape({ - /** Thread reportID */ - threadReportID: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - - /** ID of the transaction being edited */ - transactionID: PropTypes.string, - - /** Index of the waypoint being edited */ - waypointIndex: PropTypes.string, - }), - }), -}; - -const defaultProps = { - route: {}, -}; - -function MoneyRequestEditWaypointPage({route}) { - return ; -} - -MoneyRequestEditWaypointPage.displayName = 'MoneyRequestEditWaypointPage'; -MoneyRequestEditWaypointPage.propTypes = propTypes; -MoneyRequestEditWaypointPage.defaultProps = defaultProps; -export default MoneyRequestEditWaypointPage; diff --git a/src/pages/iou/MoneyRequestEditWaypointPage.tsx b/src/pages/iou/MoneyRequestEditWaypointPage.tsx new file mode 100644 index 000000000000..49319b2c0e3e --- /dev/null +++ b/src/pages/iou/MoneyRequestEditWaypointPage.tsx @@ -0,0 +1,15 @@ +import {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import SCREENS from '@src/SCREENS'; +import WaypointEditor from './WaypointEditor'; + +type MoneyRequestEditWaypointPageProps = StackScreenProps; + +function MoneyRequestEditWaypointPage({route}: MoneyRequestEditWaypointPageProps) { + return ; +} + +MoneyRequestEditWaypointPage.displayName = 'MoneyRequestEditWaypointPage'; + +export default MoneyRequestEditWaypointPage; diff --git a/src/pages/iou/NewDistanceRequestWaypointEditorPage.js b/src/pages/iou/NewDistanceRequestWaypointEditorPage.js deleted file mode 100644 index 269cde577040..000000000000 --- a/src/pages/iou/NewDistanceRequestWaypointEditorPage.js +++ /dev/null @@ -1,55 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import WaypointEditor from './WaypointEditor'; - -const propTypes = { - /** The transactionID of this request */ - transactionID: PropTypes.string, - - /** Route params */ - route: PropTypes.shape({ - params: PropTypes.shape({ - /** IOU type */ - iouType: PropTypes.string, - - /** Index of the waypoint being edited */ - waypointIndex: PropTypes.string, - }), - }), -}; - -const defaultProps = { - transactionID: '', - route: { - params: { - iouType: '', - waypointIndex: '', - }, - }, -}; - -// This component is responsible for grabbing the transactionID from the IOU key -// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that WaypointEditor can subscribe to the transaction. -function NewDistanceRequestWaypointEditorPage({transactionID, route}) { - return ( - - ); -} - -NewDistanceRequestWaypointEditorPage.displayName = 'NewDistanceRequestWaypointEditorPage'; -NewDistanceRequestWaypointEditorPage.propTypes = propTypes; -NewDistanceRequestWaypointEditorPage.defaultProps = defaultProps; -export default withOnyx({ - transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou && iou.transactionID}, -})(NewDistanceRequestWaypointEditorPage); diff --git a/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx b/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx new file mode 100644 index 000000000000..2480c8cc097c --- /dev/null +++ b/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx @@ -0,0 +1,38 @@ +import {RouteProp} from '@react-navigation/native'; +import {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; +import WaypointEditor from './WaypointEditor'; + +type NewDistanceRequestWaypointEditorPageOnyxProps = { + transactionID: string | undefined; +}; +type NewDistanceRequestWaypointEditorPageProps = StackScreenProps & NewDistanceRequestWaypointEditorPageOnyxProps; + +// This component is responsible for grabbing the transactionID from the IOU key +// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that WaypointEditor can subscribe to the transaction. +function NewDistanceRequestWaypointEditorPage({transactionID, route}: NewDistanceRequestWaypointEditorPageProps) { + return ( + + } + /> + ); +} + +NewDistanceRequestWaypointEditorPage.displayName = 'NewDistanceRequestWaypointEditorPage'; + +export default withOnyx({ + transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou?.transactionID}, +})(NewDistanceRequestWaypointEditorPage); diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.tsx similarity index 73% rename from src/pages/iou/WaypointEditor.js rename to src/pages/iou/WaypointEditor.tsx index e8d3c8520ca8..d72a3001094f 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.tsx @@ -1,9 +1,7 @@ -import {useNavigation} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import {RouteProp, useNavigation} from '@react-navigation/native'; +import {StackScreenProps} from '@react-navigation/stack'; import React, {useMemo, useRef, useState} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import AddressSearch from '@components/AddressSearch'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; @@ -12,71 +10,51 @@ import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; +import * as OnyxTypes from '@src/types/onyx'; +import {Waypoint} from '@src/types/onyx/Transaction'; +import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; + +type MappedWaypoint = { + name: string | undefined; + description: string; + geometry: { + location: { + lat: number; + lng: number; + }; + }; +}; -const propTypes = { - /** Route params */ - route: PropTypes.shape({ - params: PropTypes.shape({ - /** IOU type */ - iouType: PropTypes.string, - - /** Thread reportID */ - threadReportID: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - - /** ID of the transaction being edited */ - transactionID: PropTypes.string, - - /** Index of the waypoint being edited */ - waypointIndex: PropTypes.string, - }), - }), - - recentWaypoints: PropTypes.arrayOf( - PropTypes.shape({ - /** The name of the location */ - name: PropTypes.string, - - /** A description of the location (usually the address) */ - description: PropTypes.string, - - /** Data required by the google auto complete plugin to know where to put the markers on the map */ - geometry: PropTypes.shape({ - /** Data about the location */ - location: PropTypes.shape({ - /** Latitude of the location */ - lat: PropTypes.number, - - /** Longitude of the location */ - lng: PropTypes.number, - }), - }), - }), - ), - - /* Onyx props */ +type WaypointEditorOnyxProps = { /** The optimistic transaction for this request */ - transaction: transactionPropTypes, -}; + transaction: OnyxEntry; -const defaultProps = { - route: {}, - recentWaypoints: [], - transaction: {}, + /** List of recent waypoints */ + recentWaypoints: OnyxEntry; }; -function WaypointEditor({route: {params: {iouType = '', transactionID = '', waypointIndex = '', threadReportID = 0}} = {}, transaction, recentWaypoints}) { +type WaypointEditorProps = {route: RouteProp} & WaypointEditorOnyxProps; + +function WaypointEditor({ + route: { + params: {iouType = '', transactionID = '', waypointIndex = '', threadReportID = ''}, + }, + transaction, + recentWaypoints = [], +}: WaypointEditorProps) { const styles = useThemeStyles(); const {windowWidth} = useWindowDimensions(); const [isDeleteStopModalOpen, setIsDeleteStopModalOpen] = useState(false); @@ -86,11 +64,11 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp const {isOffline} = useNetwork(); const textInput = useRef(null); const parsedWaypointIndex = parseInt(waypointIndex, 10); - const allWaypoints = lodashGet(transaction, 'comment.waypoints', {}); - const currentWaypoint = lodashGet(allWaypoints, `waypoint${waypointIndex}`, {}); + const allWaypoints = transaction?.comment.waypoints ?? {}; + const currentWaypoint = allWaypoints[`waypoint${waypointIndex}`] ?? {}; - const waypointCount = _.size(allWaypoints); - const filledWaypointCount = _.size(_.filter(allWaypoints, (waypoint) => !_.isEmpty(waypoint))); + const waypointCount = Object.keys(allWaypoints).length; + const filledWaypointCount = Object.values(allWaypoints).filter((waypoint) => isNotEmptyObject(waypoint)).length; const waypointDescriptionKey = useMemo(() => { switch (parsedWaypointIndex) { @@ -103,15 +81,15 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp } }, [parsedWaypointIndex, waypointCount]); - const waypointAddress = lodashGet(currentWaypoint, 'address', ''); - const isEditingWaypoint = Boolean(threadReportID); + const waypointAddress = currentWaypoint.address ?? ''; + const isEditingWaypoint = !!threadReportID; // Hide the menu when there is only start and finish waypoint const shouldShowThreeDotsButton = waypointCount > 2; const shouldDisableEditor = isFocused && (Number.isNaN(parsedWaypointIndex) || parsedWaypointIndex < 0 || parsedWaypointIndex > waypointCount || (filledWaypointCount < 2 && parsedWaypointIndex >= waypointCount)); - const validate = (values) => { + const validate = (values: Record) => { const errors = {}; const waypointValue = values[`waypoint${waypointIndex}`] || ''; if (isOffline && waypointValue !== '' && !ValidationUtils.isValidAddress(waypointValue)) { @@ -127,9 +105,9 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp return errors; }; - const saveWaypoint = (waypoint) => Transaction.saveWaypoint(transactionID, waypointIndex, waypoint, isEditingWaypoint); + const saveWaypoint = (waypoint: OnyxTypes.RecentWaypoint) => Transaction.saveWaypoint(transactionID, waypointIndex, waypoint, isEditingWaypoint); - const submit = (values) => { + const submit = (values: Record) => { const waypointValue = values[`waypoint${waypointIndex}`] || ''; // Allows letting you set a waypoint to an empty value @@ -141,10 +119,10 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp // Therefore, we're going to save the waypoint as just the address, and the lat/long will be filled in on the backend if (isOffline && waypointValue) { const waypoint = { - lat: null, - lng: null, + lat: -1, + lng: -1, address: waypointValue, - name: null, + name: undefined, }; saveWaypoint(waypoint); } @@ -159,12 +137,13 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); }; - const selectWaypoint = (values) => { + const selectWaypoint = (values: Waypoint) => { const waypoint = { - lat: values.lat, - lng: values.lng, - address: values.address, - name: values.name || null, + lat: values.lat ?? 0, + lng: values.lng ?? 0, + address: values.address ?? '', + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + name: values.name, }; saveWaypoint(waypoint); @@ -178,7 +157,7 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp return ( textInput.current && textInput.current.focus()} + onEntryTransitionEnd={() => textInput.current?.focus()} shouldEnableMaxHeight testID={WaypointEditor.displayName} > @@ -251,11 +230,10 @@ function WaypointEditor({route: {params: {iouType = '', transactionID = '', wayp } WaypointEditor.displayName = 'WaypointEditor'; -WaypointEditor.propTypes = propTypes; -WaypointEditor.defaultProps = defaultProps; -export default withOnyx({ + +export default withOnyx({ transaction: { - key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(route, 'params.transactionID')}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${route.params.transactionID}`, }, recentWaypoints: { key: ONYXKEYS.NVP_RECENT_WAYPOINTS, @@ -263,7 +241,7 @@ export default withOnyx({ // Only grab the most recent 5 waypoints because that's all that is shown in the UI. This also puts them into the format of data // that the google autocomplete component expects for it's "predefined places" feature. selector: (waypoints) => - _.map(waypoints ? waypoints.slice(0, 5) : [], (waypoint) => ({ + (waypoints ? waypoints.slice(0, 5) : []).map((waypoint) => ({ name: waypoint.name, description: waypoint.address, geometry: { diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 53bfc36a4e47..5ca701cf1f8e 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -15,6 +15,24 @@ type Waypoint = { /** The longitude of the waypoint */ lng?: number; + + /** Address city */ + city?: string; + + /** Address state */ + state?: string; + + /** Address zip code */ + zipCode?: string; + + /** Address country */ + country?: string; + + /** Address street line 1 */ + street?: string; + + /** Address street line 2 */ + street2?: string; }; type WaypointCollection = Record; @@ -70,7 +88,7 @@ type Transaction = { modifiedWaypoints?: WaypointCollection; // Used during the creation flow before the transaction is saved to the server and helps dictate where the user is navigated to when pressing the back button on the confirmation step participantsAutoAssigned?: boolean; - pendingAction: OnyxCommon.PendingAction; + pendingAction: OnyxCommon.PendingAction | null; receipt?: Receipt; reportID: string; routes?: Routes; From 36c73edf7f23439187fdf5de45800ff036a62d28 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 18 Dec 2023 17:27:00 +0100 Subject: [PATCH 002/438] ref: seperate types --- src/pages/iou/WaypointEditor.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/WaypointEditor.tsx b/src/pages/iou/WaypointEditor.tsx index d72a3001094f..2d7fd5b3abcd 100644 --- a/src/pages/iou/WaypointEditor.tsx +++ b/src/pages/iou/WaypointEditor.tsx @@ -1,5 +1,4 @@ import {RouteProp, useNavigation} from '@react-navigation/native'; -import {StackScreenProps} from '@react-navigation/stack'; import React, {useMemo, useRef, useState} from 'react'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import AddressSearch from '@components/AddressSearch'; @@ -27,15 +26,19 @@ import * as OnyxTypes from '@src/types/onyx'; import {Waypoint} from '@src/types/onyx/Transaction'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; +type Location = { + lat: number; + lng: number; +}; + +type Geometry = { + location: Location; +}; + type MappedWaypoint = { name: string | undefined; description: string; - geometry: { - location: { - lat: number; - lng: number; - }; - }; + geometry: Geometry; }; type WaypointEditorOnyxProps = { From a32e718532f0f1029dbad23fee180331677c00ef Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 08:32:45 +0100 Subject: [PATCH 003/438] chore: added todos --- src/pages/iou/WaypointEditor.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/WaypointEditor.tsx b/src/pages/iou/WaypointEditor.tsx index 2d7fd5b3abcd..43ade2c0b559 100644 --- a/src/pages/iou/WaypointEditor.tsx +++ b/src/pages/iou/WaypointEditor.tsx @@ -1,5 +1,6 @@ import {RouteProp, useNavigation} from '@react-navigation/native'; import React, {useMemo, useRef, useState} from 'react'; +import {TextInput} from 'react-native'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import AddressSearch from '@components/AddressSearch'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -65,7 +66,7 @@ function WaypointEditor({ const isFocused = navigation.isFocused(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const textInput = useRef(null); + const textInput = useRef(null); const parsedWaypointIndex = parseInt(waypointIndex, 10); const allWaypoints = transaction?.comment.waypoints ?? {}; const currentWaypoint = allWaypoints[`waypoint${waypointIndex}`] ?? {}; @@ -158,9 +159,12 @@ function WaypointEditor({ }; return ( + /* @ts-expect-error TODO: Remove this once ScreenWrapper (https://github.com/Expensify/App/issues/25109) is migrated to TypeScript. */ textInput.current?.focus()} + onEntryTransitionEnd={() => { + textInput.current?.focus(); + }} shouldEnableMaxHeight testID={WaypointEditor.displayName} > @@ -191,6 +195,7 @@ function WaypointEditor({ cancelText={translate('common.cancel')} danger /> + {/* @ts-expect-error TODO: Remove this once Form (https://github.com/Expensify/App/issues/25109) is migrated to TypeScript. */} (textInput.current = e)} + ref={(e) => { + textInput.current = e; + }} hint={!isOffline ? 'distance.errors.selectSuggestedAddress' : ''} containerStyles={[styles.mt3]} label={translate('distance.address')} From 049e7adb17efdbbede6f908ea39acbd64223f91f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 11:45:09 +0100 Subject: [PATCH 004/438] fix: address comments --- src/libs/actions/Transaction.ts | 1 - src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx | 4 ++-- src/pages/iou/WaypointEditor.tsx | 6 +++--- src/types/onyx/Transaction.ts | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index cdb4fad6c14f..ee749b4fab03 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -133,7 +133,6 @@ function removeWaypoint(transaction: OnyxEntry, currentIndex: strin category: transaction?.category ?? '', created: transaction?.created ?? '', currency: transaction?.currency ?? '', - pendingAction: transaction?.pendingAction ?? null, merchant: transaction?.merchant ?? '', reportID: transaction?.reportID ?? '', transactionID: transaction?.transactionID ?? '', diff --git a/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx b/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx index 2480c8cc097c..de811c5ad9b5 100644 --- a/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx +++ b/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx @@ -14,7 +14,7 @@ type NewDistanceRequestWaypointEditorPageProps = StackScreenProps } diff --git a/src/pages/iou/WaypointEditor.tsx b/src/pages/iou/WaypointEditor.tsx index 43ade2c0b559..d2d175624905 100644 --- a/src/pages/iou/WaypointEditor.tsx +++ b/src/pages/iou/WaypointEditor.tsx @@ -37,7 +37,7 @@ type Geometry = { }; type MappedWaypoint = { - name: string | undefined; + name?: string; description: string; geometry: Geometry; }; @@ -143,8 +143,8 @@ function WaypointEditor({ const selectWaypoint = (values: Waypoint) => { const waypoint = { - lat: values.lat ?? 0, - lng: values.lng ?? 0, + lat: values.lat ?? -1, + lng: values.lng ?? -1, address: values.address ?? '', // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing name: values.name, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 5ca701cf1f8e..f3fec51bf362 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -88,7 +88,7 @@ type Transaction = { modifiedWaypoints?: WaypointCollection; // Used during the creation flow before the transaction is saved to the server and helps dictate where the user is navigated to when pressing the back button on the confirmation step participantsAutoAssigned?: boolean; - pendingAction: OnyxCommon.PendingAction | null; + pendingAction?: OnyxCommon.PendingAction; receipt?: Receipt; reportID: string; routes?: Routes; From 2a751759ef8e01ee5c8d60fffcbeebb3f2151063 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Dec 2023 15:11:14 +0100 Subject: [PATCH 005/438] fix: adjust recentwaypoint type --- src/pages/iou/WaypointEditor.tsx | 11 ++++------- src/types/onyx/RecentWaypoint.ts | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/pages/iou/WaypointEditor.tsx b/src/pages/iou/WaypointEditor.tsx index d2d175624905..c28b1b8f86dc 100644 --- a/src/pages/iou/WaypointEditor.tsx +++ b/src/pages/iou/WaypointEditor.tsx @@ -28,8 +28,8 @@ import {Waypoint} from '@src/types/onyx/Transaction'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; type Location = { - lat: number; - lng: number; + lat?: number; + lng?: number; }; type Geometry = { @@ -123,10 +123,7 @@ function WaypointEditor({ // Therefore, we're going to save the waypoint as just the address, and the lat/long will be filled in on the backend if (isOffline && waypointValue) { const waypoint = { - lat: -1, - lng: -1, address: waypointValue, - name: undefined, }; saveWaypoint(waypoint); } @@ -143,8 +140,8 @@ function WaypointEditor({ const selectWaypoint = (values: Waypoint) => { const waypoint = { - lat: values.lat ?? -1, - lng: values.lng ?? -1, + lat: values.lat, + lng: values.lng, address: values.address ?? '', // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing name: values.name, diff --git a/src/types/onyx/RecentWaypoint.ts b/src/types/onyx/RecentWaypoint.ts index 097aed3be916..4028e59846ce 100644 --- a/src/types/onyx/RecentWaypoint.ts +++ b/src/types/onyx/RecentWaypoint.ts @@ -6,10 +6,10 @@ type RecentWaypoint = { address: string; /** The lattitude of the waypoint */ - lat: number; + lat?: number; /** The longitude of the waypoint */ - lng: number; + lng?: number; }; export default RecentWaypoint; From 9b36c85dfd9639ace0b6f8ffa2df6150eb2d5daa Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 2 Jan 2024 14:41:39 +0100 Subject: [PATCH 006/438] fix: type problems --- .../BlockingViews/FullPageNotFoundView.tsx | 4 ++-- src/libs/ErrorUtils.ts | 1 + src/libs/actions/Transaction.ts | 12 ++---------- src/pages/iou/WaypointEditor.tsx | 12 +++++++++--- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index 6d7f838bf6c2..0427b75aa036 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -33,10 +33,10 @@ type FullPageNotFoundViewProps = { linkKey?: TranslationPaths; /** Method to trigger when pressing the back button of the header */ - onBackButtonPress: () => void; + onBackButtonPress?: () => void; /** Function to call when pressing the navigation link */ - onLinkPress: () => void; + onLinkPress?: () => void; }; // eslint-disable-next-line rulesdir/no-negated-variables diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 46bdd510f5c4..ab589ea73fbf 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -120,3 +120,4 @@ function addErrorMessage(errors: ErrorsList, inpu } export {getAuthenticateErrorMessage, getMicroSecondOnyxError, getMicroSecondOnyxErrorObject, getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, addErrorMessage}; +export type {ErrorsList}; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index ee749b4fab03..ee09fa6966e4 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -127,16 +127,8 @@ function removeWaypoint(transaction: OnyxEntry, currentIndex: strin // to remove nested keys while also preserving other object keys // Doing a deep clone of the transaction to avoid mutating the original object and running into a cache issue when using Onyx.set let newTransaction: Transaction = { - ...transaction, - amount: transaction?.amount ?? 0, - billable: transaction?.billable ?? false, - category: transaction?.category ?? '', - created: transaction?.created ?? '', - currency: transaction?.currency ?? '', - merchant: transaction?.merchant ?? '', - reportID: transaction?.reportID ?? '', - transactionID: transaction?.transactionID ?? '', - tag: transaction?.tag ?? '', + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style + ...(transaction as Transaction), comment: { ...transaction?.comment, waypoints: reIndexedWaypoints, diff --git a/src/pages/iou/WaypointEditor.tsx b/src/pages/iou/WaypointEditor.tsx index c28b1b8f86dc..80491b3b4a91 100644 --- a/src/pages/iou/WaypointEditor.tsx +++ b/src/pages/iou/WaypointEditor.tsx @@ -36,9 +36,16 @@ type Geometry = { location: Location; }; +type Values = Record; + type MappedWaypoint = { + /* Waypoint name */ name?: string; + + /* Waypoint description */ description: string; + + /* Waypoint geometry object cointaing coordinates */ geometry: Geometry; }; @@ -93,7 +100,7 @@ function WaypointEditor({ isFocused && (Number.isNaN(parsedWaypointIndex) || parsedWaypointIndex < 0 || parsedWaypointIndex > waypointCount || (filledWaypointCount < 2 && parsedWaypointIndex >= waypointCount)); - const validate = (values: Record) => { + const validate = (values: Values): ErrorUtils.ErrorsList => { const errors = {}; const waypointValue = values[`waypoint${waypointIndex}`] || ''; if (isOffline && waypointValue !== '' && !ValidationUtils.isValidAddress(waypointValue)) { @@ -111,7 +118,7 @@ function WaypointEditor({ const saveWaypoint = (waypoint: OnyxTypes.RecentWaypoint) => Transaction.saveWaypoint(transactionID, waypointIndex, waypoint, isEditingWaypoint); - const submit = (values: Record) => { + const submit = (values: Values) => { const waypointValue = values[`waypoint${waypointIndex}`] || ''; // Allows letting you set a waypoint to an empty value @@ -143,7 +150,6 @@ function WaypointEditor({ lat: values.lat, lng: values.lng, address: values.address ?? '', - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing name: values.name, }; saveWaypoint(waypoint); From 7f463df300e29633d97ab2756736fc030e4b5b1a Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 3 Jan 2024 14:20:47 +0100 Subject: [PATCH 007/438] fix: resolve comments --- src/pages/iou/WaypointEditor.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/WaypointEditor.tsx b/src/pages/iou/WaypointEditor.tsx index 80491b3b4a91..e2367502652e 100644 --- a/src/pages/iou/WaypointEditor.tsx +++ b/src/pages/iou/WaypointEditor.tsx @@ -36,16 +36,16 @@ type Geometry = { location: Location; }; -type Values = Record; +type WaypointValues = Record; type MappedWaypoint = { - /* Waypoint name */ + /** Waypoint name */ name?: string; - /* Waypoint description */ + /** Waypoint description */ description: string; - /* Waypoint geometry object cointaing coordinates */ + /** Waypoint geometry object cointaing coordinates */ geometry: Geometry; }; @@ -100,7 +100,7 @@ function WaypointEditor({ isFocused && (Number.isNaN(parsedWaypointIndex) || parsedWaypointIndex < 0 || parsedWaypointIndex > waypointCount || (filledWaypointCount < 2 && parsedWaypointIndex >= waypointCount)); - const validate = (values: Values): ErrorUtils.ErrorsList => { + const validate = (values: WaypointValues): ErrorUtils.ErrorsList => { const errors = {}; const waypointValue = values[`waypoint${waypointIndex}`] || ''; if (isOffline && waypointValue !== '' && !ValidationUtils.isValidAddress(waypointValue)) { @@ -118,7 +118,7 @@ function WaypointEditor({ const saveWaypoint = (waypoint: OnyxTypes.RecentWaypoint) => Transaction.saveWaypoint(transactionID, waypointIndex, waypoint, isEditingWaypoint); - const submit = (values: Values) => { + const submit = (values: WaypointValues) => { const waypointValue = values[`waypoint${waypointIndex}`] || ''; // Allows letting you set a waypoint to an empty value From 02dbc360a4068ba132d3f2af9bd6badd594c0105 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 13:08:51 +0100 Subject: [PATCH 008/438] fix: move changes to new file --- src/ROUTES.ts | 2 +- src/libs/Navigation/types.ts | 8 + src/pages/iou/MoneyRequestWaypointPage.tsx | 36 ++++ .../NewDistanceRequestWaypointEditorPage.tsx | 52 ----- ...Waypoint.js => IOURequestStepWaypoint.tsx} | 181 +++++++++--------- 5 files changed, 132 insertions(+), 147 deletions(-) create mode 100644 src/pages/iou/MoneyRequestWaypointPage.tsx delete mode 100644 src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx rename src/pages/iou/request/step/{IOURequestStepWaypoint.js => IOURequestStepWaypoint.tsx} (67%) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 37003a09a0cd..4fda2e3ef3da 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -494,4 +494,4 @@ type RouteIsPlainString = IsEqual; */ type Route = RouteIsPlainString extends true ? never : AllRoutes; -export type {Route}; +export type {Route, AllRoutes}; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8563db7db172..e044f0b16f33 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -3,6 +3,7 @@ import type {CommonActions, NavigationContainerRefWithCurrent, NavigationHelpers import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type NAVIGATORS from '@src/NAVIGATORS'; +import {AllRoutes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type NavigationRef = NavigationContainerRefWithCurrent; @@ -226,6 +227,13 @@ type MoneyRequestNavigatorParamList = { reportID: string; backTo: string; }; + [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: { + iouType: ValueOf; + reportID: string; + backTo: AllRoutes | undefined; + action: ValueOf; + pageIndex: string; + }; [SCREENS.MONEY_REQUEST.MERCHANT]: { iouType: string; reportID: string; diff --git a/src/pages/iou/MoneyRequestWaypointPage.tsx b/src/pages/iou/MoneyRequestWaypointPage.tsx new file mode 100644 index 000000000000..ce31e58514f5 --- /dev/null +++ b/src/pages/iou/MoneyRequestWaypointPage.tsx @@ -0,0 +1,36 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import IOURequestStepWaypoint from './request/step/IOURequestStepWaypoint'; + +type MoneyRequestWaypointPageOnyxProps = { + transactionID: string | undefined; +}; +type MoneyRequestWaypointPageProps = StackScreenProps & MoneyRequestWaypointPageOnyxProps; + +// This component is responsible for grabbing the transactionID from the IOU key +// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that WaypointEditor can subscribe to the transaction. +function MoneyRequestWaypointPage({transactionID = '', route}: MoneyRequestWaypointPageProps) { + return ( + // @ts-expect-error TODO: Remove this once withFullTransactionOrNotFound is migrated to TypeScript. + + ); +} + +MoneyRequestWaypointPage.displayName = 'MoneyRequestWaypointPage'; + +export default withOnyx({ + transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou?.transactionID}, +})(MoneyRequestWaypointPage); diff --git a/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx b/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx deleted file mode 100644 index 8456135c6101..000000000000 --- a/src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; -import ONYXKEYS from '@src/ONYXKEYS'; -<<<<<<<< HEAD:src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx -import type SCREENS from '@src/SCREENS'; -import WaypointEditor from './WaypointEditor'; -======== -import IOURequestStepWaypoint from './request/step/IOURequestStepWaypoint'; ->>>>>>>> f268c39c331305c49dbb80a0d0e59984289ecb25:src/pages/iou/MoneyRequestWaypointPage.js - -type NewDistanceRequestWaypointEditorPageOnyxProps = { - transactionID: string | undefined; -}; -type NewDistanceRequestWaypointEditorPageProps = StackScreenProps & NewDistanceRequestWaypointEditorPageOnyxProps; - -// This component is responsible for grabbing the transactionID from the IOU key -// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that WaypointEditor can subscribe to the transaction. -<<<<<<<< HEAD:src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx -function NewDistanceRequestWaypointEditorPage({transactionID = '', route}: NewDistanceRequestWaypointEditorPageProps) { -======== -function MoneyRequestWaypointPage({transactionID, route}) { ->>>>>>>> f268c39c331305c49dbb80a0d0e59984289ecb25:src/pages/iou/MoneyRequestWaypointPage.js - return ( - - ); -} - -<<<<<<<< HEAD:src/pages/iou/NewDistanceRequestWaypointEditorPage.tsx -NewDistanceRequestWaypointEditorPage.displayName = 'NewDistanceRequestWaypointEditorPage'; - -export default withOnyx({ - transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou?.transactionID}, -})(NewDistanceRequestWaypointEditorPage); -======== -MoneyRequestWaypointPage.displayName = 'MoneyRequestWaypointPage'; -MoneyRequestWaypointPage.propTypes = propTypes; -MoneyRequestWaypointPage.defaultProps = defaultProps; -export default withOnyx({ - transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou && iou.transactionID}, -})(MoneyRequestWaypointPage); ->>>>>>>> f268c39c331305c49dbb80a0d0e59984289ecb25:src/pages/iou/MoneyRequestWaypointPage.js diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.js b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx similarity index 67% rename from src/pages/iou/request/step/IOURequestStepWaypoint.js rename to src/pages/iou/request/step/IOURequestStepWaypoint.tsx index 1087018eeed9..f03d6299b873 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.js +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx @@ -1,10 +1,10 @@ import {useNavigation} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; +import type {TextInput} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import AddressSearch from '@components/AddressSearch'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmModal from '@components/ConfirmModal'; @@ -13,79 +13,76 @@ import InputWrapperWithRef from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useLocationBias from '@hooks/useLocationBias'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {AllRoutes} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Waypoint} from '@src/types/onyx/Transaction'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, +type Location = { + lat?: number; + lng?: number; +}; - /* Onyx props */ - /** The optimistic transaction for this request */ - transaction: transactionPropTypes, +type Geometry = { + location: Location; +}; - /* Current location coordinates of the user */ - userLocation: PropTypes.shape({ - /** Latitude of the location */ - latitude: PropTypes.number, +type WaypointValues = Record; - /** Longitude of the location */ - longitude: PropTypes.number, - }), +type MappedWaypoint = { + /** Waypoint name */ + name?: string; - /** Recent waypoints that the user has selected */ - recentWaypoints: PropTypes.arrayOf( - PropTypes.shape({ - /** The name of the location */ - name: PropTypes.string, + /** Waypoint description */ + description: string; - /** A description of the location (usually the address) */ - description: PropTypes.string, + /** Waypoint geometry object cointaing coordinates */ + geometry: Geometry; +}; - /** Data required by the google auto complete plugin to know where to put the markers on the map */ - geometry: PropTypes.shape({ - /** Data about the location */ - location: PropTypes.shape({ - /** Latitude of the location */ - lat: PropTypes.number, +type IOURequestStepWaypointOnyxProps = { + /** List of recent waypoints */ + recentWaypoints: OnyxEntry; - /** Longitude of the location */ - lng: PropTypes.number, - }), - }), - }), - ), + userLocation: OnyxEntry; }; -const defaultProps = { - recentWaypoints: [], - transaction: {}, - userLocation: undefined, -}; +type IOURequestStepWaypointProps = { + route: { + params: { + iouType: ValueOf; + transactionID: string; + reportID: string; + backTo: AllRoutes | undefined; + action: ValueOf; + pageIndex: string; + }; + }; + transaction: OnyxEntry; +} & IOURequestStepWaypointOnyxProps; function IOURequestStepWaypoint({ - recentWaypoints, route: { params: {action, backTo, iouType, pageIndex, reportID, transactionID}, }, transaction, + recentWaypoints = [], userLocation, -}) { +}: IOURequestStepWaypointProps) { const styles = useThemeStyles(); const {windowWidth} = useWindowDimensions(); const [isDeleteStopModalOpen, setIsDeleteStopModalOpen] = useState(false); @@ -93,12 +90,12 @@ function IOURequestStepWaypoint({ const isFocused = navigation.isFocused(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const textInput = useRef(null); + const textInput = useRef(null); const parsedWaypointIndex = parseInt(pageIndex, 10); - const allWaypoints = lodashGet(transaction, 'comment.waypoints', {}); - const currentWaypoint = lodashGet(allWaypoints, `waypoint${pageIndex}`, {}); - const waypointCount = _.size(allWaypoints); - const filledWaypointCount = _.size(_.filter(allWaypoints, (waypoint) => !_.isEmpty(waypoint))); + const allWaypoints = transaction?.comment.waypoints ?? {}; + const currentWaypoint = allWaypoints[`waypoint${pageIndex}`] ?? {}; + const waypointCount = Object.keys(allWaypoints).length; + const filledWaypointCount = Object.values(allWaypoints).filter((waypoint) => !isEmptyObject(waypoint)).length; const waypointDescriptionKey = useMemo(() => { switch (parsedWaypointIndex) { @@ -112,14 +109,14 @@ function IOURequestStepWaypoint({ }, [parsedWaypointIndex, waypointCount]); const locationBias = useLocationBias(allWaypoints, userLocation); - const waypointAddress = lodashGet(currentWaypoint, 'address', ''); + const waypointAddress = currentWaypoint.address ?? ''; // Hide the menu when there is only start and finish waypoint const shouldShowThreeDotsButton = waypointCount > 2; const shouldDisableEditor = isFocused && (Number.isNaN(parsedWaypointIndex) || parsedWaypointIndex < 0 || parsedWaypointIndex > waypointCount || (filledWaypointCount < 2 && parsedWaypointIndex >= waypointCount)); - const validate = (values) => { + const validate = (values: WaypointValues): ErrorUtils.ErrorsList => { const errors = {}; const waypointValue = values[`waypoint${pageIndex}`] || ''; if (isOffline && waypointValue !== '' && !ValidationUtils.isValidAddress(waypointValue)) { @@ -135,9 +132,9 @@ function IOURequestStepWaypoint({ return errors; }; - const saveWaypoint = (waypoint) => Transaction.saveWaypoint(transactionID, pageIndex, waypoint, action === CONST.IOU.ACTION.CREATE); + const saveWaypoint = (waypoint: OnyxTypes.RecentWaypoint) => Transaction.saveWaypoint(transactionID, pageIndex, waypoint, action === CONST.IOU.ACTION.CREATE); - const submit = (values) => { + const submit = (values: WaypointValues) => { const waypointValue = values[`waypoint${pageIndex}`] || ''; // Allows letting you set a waypoint to an empty value @@ -149,10 +146,8 @@ function IOURequestStepWaypoint({ // Therefore, we're going to save the waypoint as just the address, and the lat/long will be filled in on the backend if (isOffline && waypointValue) { const waypoint = { - lat: null, - lng: null, address: waypointValue, - name: values.name || null, + name: values.name, }; saveWaypoint(waypoint); } @@ -167,18 +162,12 @@ function IOURequestStepWaypoint({ Navigation.goBack(ROUTES.MONEY_REQUEST_DISTANCE_TAB.getRoute(iouType)); }; - /** - * @param {Object} values - * @param {String} values.lat - * @param {String} values.lng - * @param {String} values.address - */ - const selectWaypoint = (values) => { + const selectWaypoint = (values: Waypoint) => { const waypoint = { lat: values.lat, lng: values.lng, - address: values.address, - name: values.name || null, + address: values.address ?? '', + name: values.name, }; Transaction.saveWaypoint(transactionID, pageIndex, waypoint, action === CONST.IOU.ACTION.CREATE); if (backTo) { @@ -191,7 +180,7 @@ function IOURequestStepWaypoint({ return ( textInput.current && textInput.current.focus()} + onEntryTransitionEnd={() => textInput.current?.focus()} shouldEnableMaxHeight testID={IOURequestStepWaypoint.displayName} > @@ -222,6 +211,7 @@ function IOURequestStepWaypoint({ cancelText={translate('common.cancel')} danger /> + {/* @ts-expect-error TODO: Remove this once Form (https://github.com/Expensify/App/issues/25109) is migrated to TypeScript. */} (textInput.current = e)} + ref={(e) => { + textInput.current = e; + }} hint={!isOffline ? 'distance.errors.selectSuggestedAddress' : ''} containerStyles={[styles.mt4]} label={translate('distance.address')} @@ -267,32 +260,32 @@ function IOURequestStepWaypoint({ } IOURequestStepWaypoint.displayName = 'IOURequestStepWaypoint'; -IOURequestStepWaypoint.propTypes = propTypes; -IOURequestStepWaypoint.defaultProps = defaultProps; -export default compose( - withWritableReportOrNotFound, - withFullTransactionOrNotFound, - withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, - recentWaypoints: { - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepWaypointWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepWaypoint); +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepWaypointWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepWaypointWithWritableReportOrNotFound); - // Only grab the most recent 5 waypoints because that's all that is shown in the UI. This also puts them into the format of data - // that the google autocomplete component expects for it's "predefined places" feature. - selector: (waypoints) => - _.map(waypoints ? waypoints.slice(0, 5) : [], (waypoint) => ({ - name: waypoint.name, - description: waypoint.address, - geometry: { - location: { - lat: waypoint.lat, - lng: waypoint.lng, - }, +export default withOnyx({ + userLocation: { + key: ONYXKEYS.USER_LOCATION, + }, + recentWaypoints: { + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + + // Only grab the most recent 5 waypoints because that's all that is shown in the UI. This also puts them into the format of data + // that the google autocomplete component expects for it's "predefined places" feature. + selector: (waypoints) => + (waypoints ? waypoints.slice(0, 5) : []).map((waypoint) => ({ + name: waypoint.name, + description: waypoint.address, + geometry: { + location: { + lat: waypoint.lat, + lng: waypoint.lng, }, - })), - }, - }), -)(IOURequestStepWaypoint); + }, + })), + }, + // @ts-expect-error TODO: Remove this once withFullTransactionOrNotFound and IOURequestStepWaypointWithWritableReportOrNotFound is migrated to TypeScript. +})(IOURequestStepWaypointWithFullTransactionOrNotFound); From e6164a2c0561f9cd1497289e558a3cc13acc56ce Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 13:30:46 +0100 Subject: [PATCH 009/438] fix: type errors --- src/hooks/useLocationBias.ts | 2 +- src/libs/Navigation/types.ts | 2 +- src/libs/actions/Transaction.ts | 5 ++++- src/pages/iou/MoneyRequestEditWaypointPage.tsx | 15 --------------- .../iou/request/step/IOURequestStepWaypoint.tsx | 2 ++ 5 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 src/pages/iou/MoneyRequestEditWaypointPage.tsx diff --git a/src/hooks/useLocationBias.ts b/src/hooks/useLocationBias.ts index 607b1bfbc0d2..9efd13298bbb 100644 --- a/src/hooks/useLocationBias.ts +++ b/src/hooks/useLocationBias.ts @@ -1,6 +1,6 @@ import {useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import type {Location} from '@pages/iou/WaypointEditor'; +import type {Location} from '@pages/iou/request/step/IOURequestStepWaypoint'; import type {UserLocation} from '@src/types/onyx'; /** diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index e044f0b16f33..24cf74562047 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -3,7 +3,7 @@ import type {CommonActions, NavigationContainerRefWithCurrent, NavigationHelpers import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type NAVIGATORS from '@src/NAVIGATORS'; -import {AllRoutes} from '@src/ROUTES'; +import type {AllRoutes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type NavigationRef = NavigationContainerRefWithCurrent; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 5f2e1186ec32..90d557188238 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -102,6 +102,9 @@ function saveWaypoint(transactionID: string, index: string, waypoint: RecentWayp } function removeWaypoint(transaction: OnyxEntry, currentIndex: string, isDraft?: boolean) { + if (!transaction) { + return; + } // Index comes from the route params and is a string const index = Number(currentIndex); const existingWaypoints = transaction?.comment?.waypoints ?? {}; @@ -130,7 +133,7 @@ function removeWaypoint(transaction: OnyxEntry, currentIndex: strin // Doing a deep clone of the transaction to avoid mutating the original object and running into a cache issue when using Onyx.set let newTransaction: Transaction = { // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - ...(transaction as Transaction), + ...transaction, comment: { ...transaction?.comment, waypoints: reIndexedWaypoints, diff --git a/src/pages/iou/MoneyRequestEditWaypointPage.tsx b/src/pages/iou/MoneyRequestEditWaypointPage.tsx deleted file mode 100644 index 478b076e1991..000000000000 --- a/src/pages/iou/MoneyRequestEditWaypointPage.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; -import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; -import type SCREENS from '@src/SCREENS'; -import WaypointEditor from './WaypointEditor'; - -type MoneyRequestEditWaypointPageProps = StackScreenProps; - -function MoneyRequestEditWaypointPage({route}: MoneyRequestEditWaypointPageProps) { - return ; -} - -MoneyRequestEditWaypointPage.displayName = 'MoneyRequestEditWaypointPage'; - -export default MoneyRequestEditWaypointPage; diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx index f03d6299b873..ff6ec5d5f25c 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx @@ -289,3 +289,5 @@ export default withOnyx Date: Mon, 15 Jan 2024 17:41:10 +0100 Subject: [PATCH 010/438] fix: types --- src/pages/iou/request/step/IOURequestStepWaypoint.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx index ff6ec5d5f25c..675b2f0b2c68 100644 --- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx +++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx @@ -24,7 +24,7 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {AllRoutes} from '@src/ROUTES'; +import type {Route as Routes} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Waypoint} from '@src/types/onyx/Transaction'; @@ -67,7 +67,7 @@ type IOURequestStepWaypointProps = { iouType: ValueOf; transactionID: string; reportID: string; - backTo: AllRoutes | undefined; + backTo: Routes | undefined; action: ValueOf; pageIndex: string; }; From 31581f9c234322f98b4274de43f15a85c924f260 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 18 Jan 2024 16:24:22 +0000 Subject: [PATCH 011/438] refactor(typescript): migrate workspacenewroompage --- src/components/ScreenWrapper.tsx | 14 +- src/libs/ErrorUtils.ts | 5 +- src/libs/ValidationUtils.ts | 8 +- ...ewRoomPage.js => WorkspaceNewRoomPage.tsx} | 194 +++++++----------- 4 files changed, 96 insertions(+), 125 deletions(-) rename src/pages/workspace/{WorkspaceNewRoomPage.js => WorkspaceNewRoomPage.tsx} (73%) diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index 0653e2ff8577..b2815d02dcd6 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -24,7 +24,7 @@ import SafeAreaConsumer from './SafeAreaConsumer'; import TestToolsModal from './TestToolsModal'; type ChildrenProps = { - insets?: EdgeInsets; + insets: EdgeInsets; safeAreaPaddingBottomStyle?: { paddingBottom?: DimensionValue; }; @@ -190,7 +190,17 @@ function ScreenWrapper( return ( - {({insets, paddingTop, paddingBottom, safeAreaPaddingBottomStyle}) => { + {({ + insets = { + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + paddingTop, + paddingBottom, + safeAreaPaddingBottomStyle, + }) => { const paddingStyle: StyleProp = {}; if (includePaddingTop) { diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 159a5817189b..68bfbe706ac6 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,9 +1,10 @@ import CONST from '@src/CONST'; -import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; +import type {TranslationFlatObject} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; +import type {MaybePhraseKey} from './Localize'; function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { switch (response.jsonCode) { @@ -101,7 +102,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?: TKey) { +function addErrorMessage(errors: ErrorsList, inputID?: string, message?: MaybePhraseKey) { if (!message || !inputID) { return; } diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 9ba11fb16d6a..4a98a2e99f06 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -3,6 +3,7 @@ import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url'; import isDate from 'lodash/isDate'; import isEmpty from 'lodash/isEmpty'; import isObject from 'lodash/isObject'; +import type {OnyxCollection} from 'react-native-onyx'; import CONST from '@src/CONST'; import type {Report} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; @@ -354,8 +355,11 @@ function isReservedRoomName(roomName: string): boolean { /** * Checks if the room name already exists. */ -function isExistingRoomName(roomName: string, reports: Record, policyID: string): boolean { - return Object.values(reports).some((report) => report && report.policyID === policyID && report.reportName === roomName); +function isExistingRoomName(roomName: string, reports: OnyxCollection, policyID: string): boolean { + if (!reports) { + return false; + } + return Object.values(reports).some((report) => report?.policyID === policyID && report?.reportName === roomName); } /** diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.tsx similarity index 73% rename from src/pages/workspace/WorkspaceNewRoomPage.js rename to src/pages/workspace/WorkspaceNewRoomPage.tsx index 35fab36e5d41..318c3a4d744b 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -1,8 +1,9 @@ -import PropTypes from 'prop-types'; +import {useIsFocused} from '@react-navigation/core'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import BlockingView from '@components/BlockingViews/BlockingView'; import Button from '@components/Button'; import FormProvider from '@components/Form/FormProvider'; @@ -14,14 +15,12 @@ import RoomNameInput from '@components/RoomNameInput'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import ValuePicker from '@components/ValuePicker'; -import withNavigationFocus from '@components/withNavigationFocus'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -32,98 +31,58 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Account, Form, Policy, Report as ReportType, Session} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; -const propTypes = { - /** All reports shared with the user */ - reports: PropTypes.shape({ - /** The report name */ - reportName: PropTypes.string, - - /** The report type */ - type: PropTypes.string, - - /** ID of the policy */ - policyID: PropTypes.string, - }), - - /** The list of policies the user has access to. */ - policies: PropTypes.objectOf( - PropTypes.shape({ - /** The policy type */ - type: PropTypes.oneOf(_.values(CONST.POLICY.TYPE)), - - /** The name of the policy */ - name: PropTypes.string, - - /** The ID of the policy */ - id: PropTypes.string, - }), - ), - - /** Whether navigation is focused */ - isFocused: PropTypes.bool.isRequired, - - /** Form state for NEW_ROOM_FORM */ - formState: PropTypes.shape({ - /** Loading state for the form */ - isLoading: PropTypes.bool, - - /** Field errors in the form */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), - }), - - /** Session details for the user */ - session: PropTypes.shape({ - /** accountID of current user */ - accountID: PropTypes.number, - }), - - /** policyID for main workspace */ - activePolicyID: PropTypes.string, +type FormValues = { + welcomeMessage: string; + roomName: string; + policyID: string | null; + writeCapability: ValueOf; + visibility: ValueOf; }; -const defaultProps = { - reports: {}, - policies: {}, - formState: { - isLoading: false, - errorFields: {}, - }, - session: { - accountID: 0, - }, - activePolicyID: null, + +type WorkspaceNewRoomPageOnyxProps = { + policies: OnyxCollection; + reports: OnyxCollection; + formState: OnyxEntry
; + session: OnyxEntry; + activePolicyID: OnyxEntry['activePolicyID']>; }; -function WorkspaceNewRoomPage(props) { +type WorkspaceNewRoomPageProps = WorkspaceNewRoomPageOnyxProps; + +function WorkspaceNewRoomPage({policies, reports, formState, session, activePolicyID}: WorkspaceNewRoomPageProps) { const styles = useThemeStyles(); + const isFocused = useIsFocused(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); const {isSmallScreenWidth} = useWindowDimensions(); - const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED); - const [policyID, setPolicyID] = useState(props.activePolicyID); - const [writeCapability, setWriteCapability] = useState(CONST.REPORT.WRITE_CAPABILITIES.ALL); - const wasLoading = usePrevious(props.formState.isLoading); + const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED); + const [policyID, setPolicyID] = useState(activePolicyID); + const [writeCapability, setWriteCapability] = useState(CONST.REPORT.WRITE_CAPABILITIES.ALL); + const wasLoading = usePrevious(!!formState?.isLoading); const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]); const isPolicyAdmin = useMemo(() => { if (!policyID) { return false; } - return ReportUtils.isPolicyAdmin(policyID, props.policies); - }, [policyID, props.policies]); - const [newRoomReportID, setNewRoomReportID] = useState(undefined); + return ReportUtils.isPolicyAdmin(policyID, policies); + }, [policyID, policies]); + const [newRoomReportID, setNewRoomReportID] = useState(); /** - * @param {Object} values - form input values passed by the Form component + * @param values - form input values passed by the Form component */ - const submit = (values) => { - const participants = [props.session.accountID]; + const submit = (values: FormValues) => { + const participants = session ? [session.accountID ?? -1] : []; const parsedWelcomeMessage = ReportUtils.getParsedComment(values.welcomeMessage); const policyReport = ReportUtils.buildOptimisticChatReport( participants, values.roomName, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - policyID, + policyID ?? undefined, CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, false, '', @@ -146,16 +105,16 @@ function WorkspaceNewRoomPage(props) { if (policyID) { return; } - setPolicyID(props.activePolicyID); - }, [props.activePolicyID, policyID]); + setPolicyID(activePolicyID); + }, [activePolicyID, policyID]); useEffect(() => { - if (!(((wasLoading && !props.formState.isLoading) || (isOffline && props.formState.isLoading)) && _.isEmpty(props.formState.errorFields))) { + if (!(((wasLoading && !formState?.isLoading) || (isOffline && formState?.isLoading)) && isEmptyObject(formState?.errorFields))) { return; } Navigation.dismissModal(newRoomReportID); // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to update on changing the form State - }, [props.formState]); + }, [formState]); useEffect(() => { if (isPolicyAdmin) { @@ -170,8 +129,8 @@ function WorkspaceNewRoomPage(props) { * @returns {Boolean} */ const validate = useCallback( - (values) => { - const errors = {}; + (values: FormValues) => { + const errors: Record = {}; if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { // We error if the user doesn't enter a room name or left blank @@ -182,7 +141,7 @@ function WorkspaceNewRoomPage(props) { } else if (ValidationUtils.isReservedRoomName(values.roomName)) { // Certain names are reserved for default rooms and should not be used for policy rooms. ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]); - } else if (ValidationUtils.isExistingRoomName(values.roomName, props.reports, values.policyID)) { + } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, values.policyID ?? '')) { // Certain names are reserved for default rooms and should not be used for policy rooms. ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError'); } @@ -193,22 +152,22 @@ function WorkspaceNewRoomPage(props) { return errors; }, - [props.reports], + [reports], ); const workspaceOptions = useMemo( () => - _.map(PolicyUtils.getActivePolicies(props.policies), (policy) => ({ + PolicyUtils.getActivePolicies(policies)?.map((policy) => ({ label: policy.name, key: policy.id, value: policy.id, - })), - [props.policies], + })) ?? [], + [policies], ); const writeCapabilityOptions = useMemo( () => - _.map(CONST.REPORT.WRITE_CAPABILITIES, (value) => ({ + Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({ value, label: translate(`writeCapabilityPage.writeCapability.${value}`), })), @@ -217,14 +176,13 @@ function WorkspaceNewRoomPage(props) { const visibilityOptions = useMemo( () => - _.map( - _.filter(_.values(CONST.REPORT.VISIBILITY), (visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE), - (visibilityOption) => ({ + Object.values(CONST.REPORT.VISIBILITY) + .filter((visibilityOption) => visibilityOption !== CONST.REPORT.VISIBILITY.PUBLIC_ANNOUNCE) + .map((visibilityOption) => ({ label: translate(`newRoomPage.visibilityOptions.${visibilityOption}`), value: visibilityOption, description: translate(`newRoomPage.${visibilityOption}Description`), - }), - ), + })), [translate], ); @@ -270,6 +228,7 @@ function WorkspaceNewRoomPage(props) { // This is because when wrapping whole screen the screen was freezing when changing Tabs. keyboardVerticalOffset={variables.contentHeaderHeight + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding + insets.top} > + {/** @ts-expect-error TODO: Remove this once FormProvider (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript. */} (account && account.activePolicyID) || null, - initialValue: null, - }, - }), -)(WorkspaceNewRoomPage); +export default withOnyx({ + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + formState: { + key: ONYXKEYS.FORMS.NEW_ROOM_FORM, + }, + session: { + key: ONYXKEYS.SESSION, + }, + activePolicyID: { + key: ONYXKEYS.ACCOUNT, + selector: (account) => account?.activePolicyID ?? null, + initialValue: null, + }, +})(WorkspaceNewRoomPage); From d9fb612f3a622ce1e205c915e05fec790ec62d17 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 19 Jan 2024 17:17:18 +0000 Subject: [PATCH 012/438] refactor(typescript): apply pull request feedback --- src/pages/workspace/WorkspaceNewRoomPage.tsx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 318c3a4d744b..27063d01fe33 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -32,6 +32,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Account, Form, Policy, Report as ReportType, Session} from '@src/types/onyx'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type FormValues = { @@ -43,10 +44,19 @@ type FormValues = { }; type WorkspaceNewRoomPageOnyxProps = { + /** The list of policies the user has access to. */ policies: OnyxCollection; + + /** All reports shared with the user */ reports: OnyxCollection; + + /** Form state for NEW_ROOM_FORM */ formState: OnyxEntry; + + /** Session details for the user */ session: OnyxEntry; + + /** policyID for main workspace */ activePolicyID: OnyxEntry['activePolicyID']>; }; @@ -76,7 +86,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli * @param values - form input values passed by the Form component */ const submit = (values: FormValues) => { - const participants = session ? [session.accountID ?? -1] : []; + const participants = session?.accountID ? [session.accountID] : []; const parsedWelcomeMessage = ReportUtils.getParsedComment(values.welcomeMessage); const policyReport = ReportUtils.buildOptimisticChatReport( participants, @@ -125,12 +135,12 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli }, [isPolicyAdmin]); /** - * @param {Object} values - form input values passed by the Form component - * @returns {Boolean} + * @param values - form input values passed by the Form component + * @returns an object containing validation errors, if any were found during validation */ const validate = useCallback( - (values: FormValues) => { - const errors: Record = {}; + (values: FormValues): OnyxCommon.Errors => { + const errors: OnyxCommon.Errors = {}; if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) { // We error if the user doesn't enter a room name or left blank From 8f4e247fca310cf7b9548d65ea8ffd9a62b3b898 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 22 Jan 2024 11:12:18 -0800 Subject: [PATCH 013/438] Remove unnecessary directory --- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 2 +- src/pages/settings/{AboutPage => }/AboutPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/pages/settings/{AboutPage => }/AboutPage.js (99%) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index b0f33af0ce2e..89365d1e0224 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -203,7 +203,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/Preferences/ThemePage').default as React.ComponentType, [SCREENS.SETTINGS.CLOSE]: () => require('../../../pages/settings/Security/CloseAccountPage').default as React.ComponentType, [SCREENS.SETTINGS.SECURITY]: () => require('../../../pages/settings/Security/SecuritySettingsPage').default as React.ComponentType, - [SCREENS.SETTINGS.ABOUT]: () => require('../../../pages/settings/AboutPage/AboutPage').default as React.ComponentType, + [SCREENS.SETTINGS.ABOUT]: () => require('../../../pages/settings/AboutPage').default as React.ComponentType, [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS]: () => require('../../../pages/settings/AppDownloadLinks').default as React.ComponentType, [SCREENS.SETTINGS.LOUNGE_ACCESS]: () => require('../../../pages/settings/Profile/LoungeAccessPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.ROOT]: () => require('../../../pages/settings/Wallet/WalletPage').default as React.ComponentType, diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage.js similarity index 99% rename from src/pages/settings/AboutPage/AboutPage.js rename to src/pages/settings/AboutPage.js index a460c95cdfe6..1ec6c119a5a6 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage.js @@ -22,7 +22,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import pkg from '../../../../package.json'; +import pkg from '../../../package.json'; const propTypes = { ...withLocalizePropTypes, From d9d97caa2e3ceff968449684c43938eafe7fafec Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 22 Jan 2024 11:55:15 -0800 Subject: [PATCH 014/438] Set up pages and navigation --- src/ROUTES.ts | 13 ++++++++ src/SCREENS.ts | 6 ++++ .../AppNavigator/ModalStackNavigators.tsx | 3 ++ src/libs/Navigation/linkingConfig.ts | 9 ++++++ .../ExitSurvey/ExitSurveyConfirmPage.tsx | 30 +++++++++++++++++++ .../ExitSurvey/ExitSurveyReasonPage.tsx | 27 +++++++++++++++++ .../ExitSurvey/ExitSurveyResponsePage.tsx | 27 +++++++++++++++++ src/pages/settings/InitialSettingsPage.js | 5 +--- 8 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx create mode 100644 src/pages/settings/ExitSurvey/ExitSurveyReasonPage.tsx create mode 100644 src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 37003a09a0cd..b59348e5edf9 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -144,6 +144,19 @@ const ROUTES = { SETTINGS_STATUS_CLEAR_AFTER_DATE: 'settings/profile/status/clear-after/date', SETTINGS_STATUS_CLEAR_AFTER_TIME: 'settings/profile/status/clear-after/time', + SETTINGS_EXIT_SURVEY_REASON: { + route: 'settings/exit-survey/reason', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/exit-survey/reason', backTo), + }, + SETTINGS_EXIT_SURVEY_RESPONSE: { + route: 'settings/exit-survey/response', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/exit-survey/response', backTo), + }, + SETTINGS_EXIT_SURVEY_CONFIRM: { + route: 'settings/exit-survey/confirm', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/exit-survey/confirm', backTo), + }, + KEYBOARD_SHORTCUTS: 'keyboard-shortcuts', NEW: 'new', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 703cb309d641..2b630aab62c8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -77,6 +77,12 @@ const SCREENS = { REPORT_VIRTUAL_CARD_FRAUD: 'Settings_Wallet_ReportVirtualCardFraud', CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', }, + + EXIT_SURVEY: { + REASON: 'Settings_ExitSurvey_Reason', + RESPONSE: 'Settings_ExitSurvey_Response', + CONFIRM: 'Settings_ExitSurvey_Confirm', + }, }, SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 89365d1e0224..286ccc07a615 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -241,6 +241,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, [SCREENS.SETTINGS.REPORT_CARD_LOST_OR_DAMAGED]: () => require('../../../pages/settings/Wallet/ReportCardLostPage').default as React.ComponentType, [SCREENS.KEYBOARD_SHORTCUTS]: () => require('../../../pages/KeyboardShortcutsPage').default as React.ComponentType, + [SCREENS.SETTINGS.EXIT_SURVEY.REASON]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyReasonPage').default as React.ComponentType, + [SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyResponsePage').default as React.ComponentType, + [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: () => require('../../../pages/settings/ExitSurvey/ExitSurveyConfirmPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 1a495e92eb80..28955156da05 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -274,6 +274,15 @@ const linkingConfig: LinkingOptions = { [SCREENS.KEYBOARD_SHORTCUTS]: { path: ROUTES.KEYBOARD_SHORTCUTS, }, + [SCREENS.SETTINGS.EXIT_SURVEY.REASON]: { + path: ROUTES.SETTINGS_EXIT_SURVEY_REASON.route, + }, + [SCREENS.SETTINGS.EXIT_SURVEY.RESPONSE]: { + path: ROUTES.SETTINGS_EXIT_SURVEY_RESPONSE.route, + }, + [SCREENS.SETTINGS.EXIT_SURVEY.CONFIRM]: { + path: ROUTES.SETTINGS_EXIT_SURVEY_CONFIRM.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx new file mode 100644 index 000000000000..ff4e39113581 --- /dev/null +++ b/src/pages/settings/ExitSurvey/ExitSurveyConfirmPage.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import Button from '@components/Button'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import Navigation from '@navigation/Navigation'; +import * as Link from '@userActions/Link'; +import CONST from '@src/CONST'; + +function ExitSurveyReasonPage() { + return ( + + Navigation.goBack()} + /> + Confirm page +