From 31581f9c234322f98b4274de43f15a85c924f260 Mon Sep 17 00:00:00 2001
From: Pedro Guerreiro <pedro.a.c.guerreiro@gmail.com>
Date: Thu, 18 Jan 2024 16:24:22 +0000
Subject: [PATCH 1/5] 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 (
         <SafeAreaConsumer>
-            {({insets, paddingTop, paddingBottom, safeAreaPaddingBottomStyle}) => {
+            {({
+                insets = {
+                    top: 0,
+                    bottom: 0,
+                    left: 0,
+                    right: 0,
+                },
+                paddingTop,
+                paddingBottom,
+                safeAreaPaddingBottomStyle,
+            }) => {
                 const paddingStyle: StyleProp<ViewStyle> = {};
 
                 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<string, string | [string, {isTranslated: boolean}]>;
  * @param errorList - An object containing current errors in the form
  * @param message - Message to assign to the inputID errors
  */
-function addErrorMessage<TKey extends TranslationPaths>(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<string, Report>, policyID: string): boolean {
-    return Object.values(reports).some((report) => report && report.policyID === policyID && report.reportName === roomName);
+function isExistingRoomName(roomName: string, reports: OnyxCollection<Report>, 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<typeof CONST.REPORT.WRITE_CAPABILITIES>;
+    visibility: ValueOf<typeof CONST.REPORT.VISIBILITY>;
 };
-const defaultProps = {
-    reports: {},
-    policies: {},
-    formState: {
-        isLoading: false,
-        errorFields: {},
-    },
-    session: {
-        accountID: 0,
-    },
-    activePolicyID: null,
+
+type WorkspaceNewRoomPageOnyxProps = {
+    policies: OnyxCollection<Policy>;
+    reports: OnyxCollection<ReportType>;
+    formState: OnyxEntry<Form>;
+    session: OnyxEntry<Session>;
+    activePolicyID: OnyxEntry<Required<Account>['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<FormValues['visibility']>(CONST.REPORT.VISIBILITY.RESTRICTED);
+    const [policyID, setPolicyID] = useState<FormValues['policyID']>(activePolicyID);
+    const [writeCapability, setWriteCapability] = useState<FormValues['writeCapability']>(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<string>();
 
     /**
-     * @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<string, string> = {};
 
             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. */}
                         <FormProvider
                             formID={ONYXKEYS.FORMS.NEW_ROOM_FORM}
                             submitButtonText={translate('newRoomPage.createRoom')}
@@ -280,16 +239,18 @@ function WorkspaceNewRoomPage(props) {
                         >
                             <View style={styles.mb5}>
                                 <InputWrapper
+                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={RoomNameInput}
                                     ref={inputCallbackRef}
                                     inputID="roomName"
-                                    isFocused={props.isFocused}
+                                    isFocused={isFocused}
                                     shouldDelayFocus
                                     autoFocus
                                 />
                             </View>
                             <View style={styles.mb5}>
                                 <InputWrapper
+                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={TextInput}
                                     inputID="welcomeMessage"
                                     label={translate('welcomeMessagePage.welcomeMessageOptional')}
@@ -303,6 +264,7 @@ function WorkspaceNewRoomPage(props) {
                             </View>
                             <View style={[styles.mhn5]}>
                                 <InputWrapper
+                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={ValuePicker}
                                     inputID="policyID"
                                     label={translate('workspace.common.workspace')}
@@ -314,6 +276,7 @@ function WorkspaceNewRoomPage(props) {
                             {isPolicyAdmin && (
                                 <View style={styles.mhn5}>
                                     <InputWrapper
+                                        // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                         InputComponent={ValuePicker}
                                         inputID="writeCapability"
                                         label={translate('writeCapabilityPage.label')}
@@ -325,6 +288,7 @@ function WorkspaceNewRoomPage(props) {
                             )}
                             <View style={[styles.mb1, styles.mhn5]}>
                                 <InputWrapper
+                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={ValuePicker}
                                     inputID="visibility"
                                     label={translate('newRoomPage.visibility')}
@@ -344,32 +308,24 @@ function WorkspaceNewRoomPage(props) {
     );
 }
 
-WorkspaceNewRoomPage.propTypes = propTypes;
-WorkspaceNewRoomPage.defaultProps = defaultProps;
 WorkspaceNewRoomPage.displayName = 'WorkspaceNewRoomPage';
 
-export default compose(
-    withNavigationFocus,
-    withOnyx({
-        betas: {
-            key: ONYXKEYS.BETAS,
-        },
-        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 && account.activePolicyID) || null,
-            initialValue: null,
-        },
-    }),
-)(WorkspaceNewRoomPage);
+export default withOnyx<WorkspaceNewRoomPageProps, WorkspaceNewRoomPageOnyxProps>({
+    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 <pedro.a.c.guerreiro@gmail.com>
Date: Fri, 19 Jan 2024 17:17:18 +0000
Subject: [PATCH 2/5] 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<Policy>;
+
+    /** All reports shared with the user */
     reports: OnyxCollection<ReportType>;
+
+    /** Form state for NEW_ROOM_FORM */
     formState: OnyxEntry<Form>;
+
+    /** Session details for the user */
     session: OnyxEntry<Session>;
+
+    /** policyID for main workspace */
     activePolicyID: OnyxEntry<Required<Account>['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<string, string> = {};
+        (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 4542a8f47e37ef419e94ba7fe33c8b04f960dda6 Mon Sep 17 00:00:00 2001
From: Pedro Guerreiro <pedro.a.c.guerreiro@gmail.com>
Date: Fri, 26 Jan 2024 18:07:56 +0000
Subject: [PATCH 3/5] refactor: apply pull request feedback

---
 src/pages/workspace/WorkspaceNewRoomPage.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx
index 27063d01fe33..323594b1703a 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.tsx
+++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx
@@ -86,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?.accountID ? [session.accountID] : [];
+        const participants = [session?.accountID ?? 0];
         const parsedWelcomeMessage = ReportUtils.getParsedComment(values.welcomeMessage);
         const policyReport = ReportUtils.buildOptimisticChatReport(
             participants,

From da4ffd7d3bec3c3f67c1738ac1e47f3b3ba083e0 Mon Sep 17 00:00:00 2001
From: Pedro Guerreiro <pedro.a.c.guerreiro@gmail.com>
Date: Mon, 29 Jan 2024 22:15:07 +0000
Subject: [PATCH 4/5] refactor(typescript): fix types

---
 src/pages/workspace/WorkspaceNewRoomPage.tsx | 63 +++++++++-----------
 1 file changed, 27 insertions(+), 36 deletions(-)

diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx
index 59606d9bbcfd..b156261c7e77 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.tsx
+++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx
@@ -8,6 +8,7 @@ import BlockingView from '@components/BlockingViews/BlockingView';
 import Button from '@components/Button';
 import FormProvider from '@components/Form/FormProvider';
 import InputWrapper from '@components/Form/InputWrapper';
+import type {OnyxFormValuesFields} from '@components/Form/types';
 import * as Illustrations from '@components/Icon/Illustrations';
 import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
 import OfflineIndicator from '@components/OfflineIndicator';
@@ -31,18 +32,10 @@ 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 type {Account, NewRoomForm, 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 = {
-    welcomeMessage: string;
-    roomName: string;
-    policyID: string | null;
-    writeCapability: ValueOf<typeof CONST.REPORT.WRITE_CAPABILITIES>;
-    visibility: ValueOf<typeof CONST.REPORT.VISIBILITY>;
-};
-
 type WorkspaceNewRoomPageOnyxProps = {
     /** The list of policies the user has access to. */
     policies: OnyxCollection<Policy>;
@@ -51,7 +44,7 @@ type WorkspaceNewRoomPageOnyxProps = {
     reports: OnyxCollection<ReportType>;
 
     /** Form state for NEW_ROOM_FORM */
-    formState: OnyxEntry<Form>;
+    formState: OnyxEntry<NewRoomForm>;
 
     /** Session details for the user */
     session: OnyxEntry<Session>;
@@ -68,23 +61,23 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
     const {translate} = useLocalize();
     const {isOffline} = useNetwork();
     const {isSmallScreenWidth} = useWindowDimensions();
-    const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED);
-    const [writeCapability, setWriteCapability] = useState(CONST.REPORT.WRITE_CAPABILITIES.ALL);
-    const wasLoading = usePrevious(props.formState.isLoading);
+    const [visibility, setVisibility] = useState<ValueOf<typeof CONST.REPORT.VISIBILITY>>(CONST.REPORT.VISIBILITY.RESTRICTED);
+    const [writeCapability, setWriteCapability] = useState<ValueOf<typeof CONST.REPORT.WRITE_CAPABILITIES>>(CONST.REPORT.WRITE_CAPABILITIES.ALL);
+    const wasLoading = usePrevious(formState?.isLoading);
     const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]);
 
     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 [policyID, setPolicyID] = useState(() => {
-        if (_.some(workspaceOptions, (option) => option.value === props.activePolicyID)) {
-            return props.activePolicyID;
+    const [policyID, setPolicyID] = useState<string>(() => {
+        if (!!activePolicyID && workspaceOptions.some((option) => option.value === activePolicyID)) {
+            return activePolicyID;
         }
         return '';
     });
@@ -100,9 +93,9 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
     /**
      * @param values - form input values passed by the Form component
      */
-    const submit = (values: FormValues) => {
+    const submit = (values: OnyxFormValuesFields<'newRoomForm'>) => {
         const participants = [session?.accountID ?? 0];
-        const parsedWelcomeMessage = ReportUtils.getParsedComment(values.welcomeMessage);
+        const parsedWelcomeMessage = ReportUtils.getParsedComment(values.welcomeMessage ?? '');
         const policyReport = ReportUtils.buildOptimisticChatReport(
             participants,
             values.roomName,
@@ -128,17 +121,17 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
 
     useEffect(() => {
         if (policyID) {
-            if (!_.some(workspaceOptions, (opt) => opt.value === policyID)) {
+            if (!workspaceOptions.some((opt) => opt.value === policyID)) {
                 setPolicyID('');
             }
             return;
         }
-        if (_.some(workspaceOptions, (opt) => opt.value === props.activePolicyID)) {
-            setPolicyID(props.activePolicyID);
+        if (!!activePolicyID && workspaceOptions.some((opt) => opt.value === activePolicyID)) {
+            setPolicyID(activePolicyID);
         } else {
             setPolicyID('');
         }
-    }, [props.activePolicyID, policyID, workspaceOptions]);
+    }, [activePolicyID, policyID, workspaceOptions]);
 
     useEffect(() => {
         if (!(((wasLoading && !formState?.isLoading) || (isOffline && formState?.isLoading)) && isEmptyObject(formState?.errorFields))) {
@@ -161,7 +154,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
      * @returns an object containing validation errors, if any were found during validation
      */
     const validate = useCallback(
-        (values: FormValues): OnyxCommon.Errors => {
+        (values: OnyxFormValuesFields<'newRoomForm'>): OnyxCommon.Errors => {
             const errors: OnyxCommon.Errors = {};
 
             if (!values.roomName || values.roomName === CONST.POLICY.ROOM_PREFIX) {
@@ -250,7 +243,6 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
                         // 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. */}
                         <FormProvider
                             formID={ONYXKEYS.FORMS.NEW_ROOM_FORM}
                             submitButtonText={translate('newRoomPage.createRoom')}
@@ -261,23 +253,22 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
                         >
                             <View style={styles.mb5}>
                                 <InputWrapper
-                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={RoomNameInput}
                                     ref={inputCallbackRef}
                                     inputID="roomName"
                                     isFocused={isFocused}
+                                    // @ts-expect-error TODO: Remove this once RoomNameInput (https://github.com/Expensify/App/issues/25090) is migrated to TypeScript.
                                     shouldDelayFocus
                                     autoFocus
                                 />
                             </View>
                             <View style={styles.mb5}>
                                 <InputWrapper
-                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={TextInput}
                                     inputID="welcomeMessage"
                                     label={translate('welcomeMessagePage.welcomeMessageOptional')}
                                     accessibilityLabel={translate('welcomeMessagePage.welcomeMessageOptional')}
-                                    role={CONST.ACCESSIBILITY_ROLE.TEXT}
+                                    role={CONST.ROLE.PRESENTATION}
                                     autoGrowHeight
                                     maxLength={CONST.MAX_COMMENT_LENGTH}
                                     autoCapitalize="none"
@@ -286,36 +277,36 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
                             </View>
                             <View style={[styles.mhn5]}>
                                 <InputWrapper
-                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={ValuePicker}
                                     inputID="policyID"
+                                    // @ts-expect-error TODO: Remove this once ValuePicker (https://github.com/Expensify/App/issues/31965) is migrated to TypeScript.
                                     label={translate('workspace.common.workspace')}
                                     items={workspaceOptions}
                                     value={policyID}
-                                    onValueChange={setPolicyID}
+                                    onValueChange={(value) => setPolicyID(value as typeof policyID)}
                                 />
                             </View>
                             {isPolicyAdmin && (
                                 <View style={styles.mhn5}>
                                     <InputWrapper
-                                        // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                         InputComponent={ValuePicker}
                                         inputID="writeCapability"
+                                        // @ts-expect-error TODO: Remove this once ValuePicker (https://github.com/Expensify/App/issues/31965) is migrated to TypeScript.
                                         label={translate('writeCapabilityPage.label')}
                                         items={writeCapabilityOptions}
                                         value={writeCapability}
-                                        onValueChange={setWriteCapability}
+                                        onValueChange={(value) => setWriteCapability(value as typeof writeCapability)}
                                     />
                                 </View>
                             )}
                             <View style={[styles.mb1, styles.mhn5]}>
                                 <InputWrapper
-                                    // @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
                                     InputComponent={ValuePicker}
                                     inputID="visibility"
+                                    // @ts-expect-error TODO: Remove this once ValuePicker (https://github.com/Expensify/App/issues/31965) is migrated to TypeScript.
                                     label={translate('newRoomPage.visibility')}
                                     items={visibilityOptions}
-                                    onValueChange={setVisibility}
+                                    onValueChange={(value) => setVisibility(value as typeof visibility)}
                                     value={visibility}
                                     furtherDetails={visibilityDescription}
                                     shouldShowTooltips={false}

From c8fa7935a786b13d30899458e3028412c3db3ccc Mon Sep 17 00:00:00 2001
From: Pedro Guerreiro <pedro.a.c.guerreiro@gmail.com>
Date: Mon, 29 Jan 2024 22:57:50 +0000
Subject: [PATCH 5/5] chore: resolve linter issues

---
 src/CONST.ts                                 | 1 +
 src/hooks/useNetwork.ts                      | 5 +++--
 src/pages/workspace/WorkspaceNewRoomPage.tsx | 9 +++++----
 src/setup/index.js                           | 2 +-
 src/types/onyx/Network.ts                    | 2 +-
 5 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/CONST.ts b/src/CONST.ts
index b1a84aef3ca0..19f1642f0533 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -848,6 +848,7 @@ const CONST = {
     DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
     DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false},
     DEFAULT_CLOSE_ACCOUNT_DATA: {errors: null, success: '', isLoading: false},
+    DEFAULT_NETWORK_DATA: {isOffline: false},
     FORMS: {
         LOGIN_FORM: 'LoginForm',
         VALIDATE_CODE_FORM: 'ValidateCodeForm',
diff --git a/src/hooks/useNetwork.ts b/src/hooks/useNetwork.ts
index f9e1a627c5f5..1e4a6d4cf2ca 100644
--- a/src/hooks/useNetwork.ts
+++ b/src/hooks/useNetwork.ts
@@ -1,17 +1,18 @@
 import {useContext, useEffect, useRef} from 'react';
 import {NetworkContext} from '@components/OnyxProvider';
+import CONST from '@src/CONST';
 
 type UseNetworkProps = {
     onReconnect?: () => void;
 };
 
-type UseNetwork = {isOffline?: boolean};
+type UseNetwork = {isOffline: boolean};
 
 export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = {}): UseNetwork {
     const callback = useRef(onReconnect);
     callback.current = onReconnect;
 
-    const {isOffline} = useContext(NetworkContext) ?? {};
+    const {isOffline} = useContext(NetworkContext) ?? CONST.DEFAULT_NETWORK_DATA;
     const prevOfflineStatusRef = useRef(isOffline);
     useEffect(() => {
         // If we were offline before and now we are not offline then we just reconnected
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx
index b156261c7e77..092d4bd2c4b6 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.tsx
+++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx
@@ -63,8 +63,9 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
     const {isSmallScreenWidth} = useWindowDimensions();
     const [visibility, setVisibility] = useState<ValueOf<typeof CONST.REPORT.VISIBILITY>>(CONST.REPORT.VISIBILITY.RESTRICTED);
     const [writeCapability, setWriteCapability] = useState<ValueOf<typeof CONST.REPORT.WRITE_CAPABILITIES>>(CONST.REPORT.WRITE_CAPABILITIES.ALL);
-    const wasLoading = usePrevious(formState?.isLoading);
+    const wasLoading = usePrevious<boolean>(!!formState?.isLoading);
     const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]);
+    const {isLoading = false, errorFields = {}} = formState ?? {};
 
     const workspaceOptions = useMemo(
         () =>
@@ -100,7 +101,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
             participants,
             values.roomName,
             CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
-            policyID ?? undefined,
+            policyID,
             CONST.REPORT.OWNER_ACCOUNT_ID_FAKE,
             false,
             '',
@@ -134,12 +135,12 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
     }, [activePolicyID, policyID, workspaceOptions]);
 
     useEffect(() => {
-        if (!(((wasLoading && !formState?.isLoading) || (isOffline && formState?.isLoading)) && isEmptyObject(formState?.errorFields))) {
+        if (!(((wasLoading && !isLoading) || (isOffline && isLoading)) && isEmptyObject(errorFields))) {
             return;
         }
         Navigation.dismissModal(newRoomReportID);
         // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to update on changing the form State
-    }, [formState]);
+    }, [isLoading, errorFields]);
 
     useEffect(() => {
         if (isPolicyAdmin) {
diff --git a/src/setup/index.js b/src/setup/index.js
index aac6f1e4e732..5955c6c218f2 100644
--- a/src/setup/index.js
+++ b/src/setup/index.js
@@ -33,7 +33,7 @@ export default function () {
             // Clear any loading and error messages so they do not appear on app startup
             [ONYXKEYS.SESSION]: {loading: false},
             [ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA,
-            [ONYXKEYS.NETWORK]: {isOffline: false},
+            [ONYXKEYS.NETWORK]: CONST.DEFAULT_NETWORK_DATA,
             [ONYXKEYS.IS_SIDEBAR_LOADED]: false,
             [ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: true,
             [ONYXKEYS.MODAL]: {
diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts
index 32b084bbf2f7..173ca486b53c 100644
--- a/src/types/onyx/Network.ts
+++ b/src/types/onyx/Network.ts
@@ -1,6 +1,6 @@
 type Network = {
     /** Is the network currently offline or not */
-    isOffline?: boolean;
+    isOffline: boolean;
 
     /** Should the network be forced offline */
     shouldForceOffline?: boolean;
