diff --git a/src/CONST.ts b/src/CONST.ts index 6a57738d06ec..51664a22c362 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1555,6 +1555,7 @@ const CONST = { WORKSPACE_TRAVEL: 'WorkspaceBookTravel', WORKSPACE_MEMBERS: 'WorkspaceManageMembers', WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount', + WORKSPACE_SETTINGS: 'WorkspaceSettings', }, get EXPENSIFY_EMAILS() { return [ diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8fcbb106268a..ee8bceeab44a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -491,7 +491,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PRIVATE_PERSONAL_DETAILS]: OnyxTypes.PrivatePersonalDetails; [ONYXKEYS.TASK]: OnyxTypes.Task; [ONYXKEYS.WORKSPACE_RATE_AND_UNIT]: OnyxTypes.WorkspaceRateAndUnit; - [ONYXKEYS.CURRENCY_LIST]: Record; + [ONYXKEYS.CURRENCY_LIST]: OnyxTypes.CurrencyList; [ONYXKEYS.UPDATE_AVAILABLE]: boolean; [ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest; [ONYXKEYS.COUNTRY_CODE]: number; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index e3a667fd5a44..12b52524f113 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -173,7 +173,7 @@ function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number) * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. * This removes that part of the URL so the full version of the image can load. */ -function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID: number): AvatarSource { +function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number): AvatarSource { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; diff --git a/src/pages/workspace/WorkspaceProfileCurrencyPage.js b/src/pages/workspace/WorkspaceProfileCurrencyPage.tsx similarity index 53% rename from src/pages/workspace/WorkspaceProfileCurrencyPage.js rename to src/pages/workspace/WorkspaceProfileCurrencyPage.tsx index bd13ce4687f5..e5824ef8a9f9 100644 --- a/src/pages/workspace/WorkspaceProfileCurrencyPage.js +++ b/src/pages/workspace/WorkspaceProfileCurrencyPage.tsx @@ -1,65 +1,62 @@ -import PropTypes from 'prop-types'; import React, {useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {policyDefaultProps, policyPropTypes} from './withPolicy'; +import type {CurrencyList} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; -const propTypes = { +type WorkspaceProfileCurrentPageOnyxProps = { /** Constant, list of available currencies */ - currencyList: PropTypes.objectOf( - PropTypes.shape({ - /** Symbol of the currency */ - symbol: PropTypes.string.isRequired, - }), - ), - isLoadingReportData: PropTypes.bool, - ...policyPropTypes, + currencyList: OnyxEntry; }; -const defaultProps = { - currencyList: {}, - isLoadingReportData: true, - ...policyDefaultProps, +type WorkspaceProfileCurrentPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceProfileCurrentPageOnyxProps; + +type WorkspaceProfileCurrencyPageSectionItem = { + text: string; + keyForList: string; + isSelected: boolean; }; -const getDisplayText = (currencyCode, currencySymbol) => `${currencyCode} - ${currencySymbol}`; +const getDisplayText = (currencyCode: string, currencySymbol: string) => `${currencyCode} - ${currencySymbol}`; -function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportData}) { +function WorkspaceProfileCurrencyPage({currencyList = {}, policy, isLoadingReportData = true}: WorkspaceProfileCurrentPageProps) { const {translate} = useLocalize(); const [searchText, setSearchText] = useState(''); const trimmedText = searchText.trim().toLowerCase(); - const currencyListKeys = _.keys(currencyList); + const currencyListKeys = Object.keys(currencyList ?? {}); - const filteredItems = _.filter(currencyListKeys, (currencyCode) => { - const currency = currencyList[currencyCode]; - return getDisplayText(currencyCode, currency.symbol).toLowerCase().includes(trimmedText); + const filteredItems = currencyListKeys.filter((currencyCode: string) => { + const currency = currencyList?.[currencyCode]; + return getDisplayText(currencyCode, currency?.symbol ?? '') + .toLowerCase() + .includes(trimmedText); }); let initiallyFocusedOptionKey; - const currencyItems = _.map(filteredItems, (currencyCode) => { - const currency = currencyList[currencyCode]; - const isSelected = policy.outputCurrency === currencyCode; + const currencyItems: WorkspaceProfileCurrencyPageSectionItem[] = filteredItems.map((currencyCode: string) => { + const currency = currencyList?.[currencyCode]; + const isSelected = policy?.outputCurrency === currencyCode; if (isSelected) { initiallyFocusedOptionKey = currencyCode; } return { - text: getDisplayText(currencyCode, currency.symbol), + text: getDisplayText(currencyCode, currency?.symbol ?? ''), keyForList: currencyCode, isSelected, }; @@ -69,20 +66,20 @@ function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportDat const headerMessage = searchText.trim() && !currencyItems.length ? translate('common.noResultsFound') : ''; - const onSelectCurrency = (item) => { - Policy.updateGeneralSettings(policy.id, policy.name, item.keyForList); + const onSelectCurrency = (item: WorkspaceProfileCurrencyPageSectionItem) => { + Policy.updateGeneralSettings(policy?.id ?? '', policy?.name ?? '', item.keyForList); Navigation.goBack(); }; return ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} - shouldShow={(_.isEmpty(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy)} - subtitleKey={_.isEmpty(policy) ? undefined : 'workspace.common.notAuthorized'} + shouldShow={(isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy)} + subtitleKey={isEmptyObject(policy) ? undefined : 'workspace.common.notAuthorized'} > ({ currencyList: {key: ONYXKEYS.CURRENCY_LIST}, - }), -)(WorkspaceSettingsCurrencyPage); + })(WorkspaceProfileCurrencyPage), +); diff --git a/src/pages/workspace/WorkspaceProfilePage.js b/src/pages/workspace/WorkspaceProfilePage.tsx similarity index 72% rename from src/pages/workspace/WorkspaceProfilePage.js rename to src/pages/workspace/WorkspaceProfilePage.tsx index c91f7ed8fb44..014445ee6e56 100644 --- a/src/pages/workspace/WorkspaceProfilePage.js +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -1,9 +1,8 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; +import type {ImageStyle, StyleProp} from 'react-native'; import {Image, ScrollView, StyleSheet, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import WorkspaceProfile from '@assets/images/workspace-profile.png'; import Avatar from '@components/Avatar'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; @@ -16,59 +15,46 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import StringUtils from '@libs/StringUtils'; import * as UserUtils from '@libs/UserUtils'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import withPolicy, {policyDefaultProps, policyPropTypes} from './withPolicy'; +import type {CurrencyList} from '@src/types/onyx'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import withPolicy from './withPolicy'; +import type {WithPolicyProps} from './withPolicy'; import WorkspacePageWithSections from './WorkspacePageWithSections'; -const propTypes = { +type WorkSpaceProfilePageOnyxProps = { /** Constant, list of available currencies */ - currencyList: PropTypes.objectOf( - PropTypes.shape({ - /** Symbol of the currency */ - symbol: PropTypes.string.isRequired, - }), - ), - - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - - ...policyPropTypes, + currencyList: OnyxEntry; }; -const defaultProps = { - currencyList: {}, - ...policyDefaultProps, -}; +type WorkSpaceProfilePageProps = WithPolicyProps & WorkSpaceProfilePageOnyxProps; -function WorkspaceProfilePage({policy, currencyList, route}) { +function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfilePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); - const formattedCurrency = !_.isEmpty(policy) && !_.isEmpty(currencyList) && !!policy.outputCurrency ? `${policy.outputCurrency} - ${currencyList[policy.outputCurrency].symbol}` : ''; + const outputCurrency = policy?.outputCurrency ?? ''; + const currencySymbol = currencyList?.[outputCurrency]?.symbol ?? ''; + const formattedCurrency = !isEmptyObject(policy) && !isEmptyObject(currencyList) ? `${outputCurrency} - ${currencySymbol}` : ''; - const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_CURRENCY.getRoute(policy.id)), [policy.id]); - const onPressName = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_NAME.getRoute(policy.id)), [policy.id]); - const onPressDescription = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(policy.id)), [policy.id]); + const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_CURRENCY.getRoute(policy?.id ?? '')), [policy?.id]); + const onPressName = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_NAME.getRoute(policy?.id ?? '')), [policy?.id]); + const onPressDescription = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(policy?.id ?? '')), [policy?.id]); - const policyName = lodashGet(policy, 'name', ''); - const policyDescription = lodashGet(policy, 'description', ''); + const policyName = policy?.name ?? ''; + const policyDescription = policy?.description ?? ''; const readOnly = !PolicyUtils.isPolicyAdmin(policy); - const imageStyle = isSmallScreenWidth ? [styles.mhv12, styles.mhn5] : [styles.mhv8, styles.mhn8]; + const imageStyle: StyleProp = isSmallScreenWidth ? [styles.mhv12, styles.mhn5] : [styles.mhv8, styles.mhn8]; return ( - {(hasVBA) => ( + {(hasVBA?: boolean) => ( -
+
Navigation.navigate(ROUTES.WORKSPACE_AVATAR.getRoute(policy.id))} - source={lodashGet(policy, 'avatar')} + onViewPhotoPress={() => Navigation.navigate(ROUTES.WORKSPACE_AVATAR.getRoute(policy?.id ?? ''))} + source={policy?.avatar ?? ''} size={CONST.AVATAR_SIZE.XLARGE} avatarStyle={styles.avatarXLarge} enablePreview @@ -100,7 +89,7 @@ function WorkspaceProfilePage({policy, currencyList, route}) { Policy.updateWorkspaceAvatar(lodashGet(policy, 'id', ''), file)} - onImageRemoved={() => Policy.deleteWorkspaceAvatar(lodashGet(policy, 'id', ''))} + isUsingDefaultAvatar={!policy?.avatar ?? null} + onImageSelected={(file: File) => Policy.updateWorkspaceAvatar(policy?.id ?? '', file)} + onImageRemoved={() => Policy.deleteWorkspaceAvatar(policy?.id ?? '')} editorMaskImage={Expensicons.ImageCropSquareMask} - pendingAction={lodashGet(policy, 'pendingFields.avatar', null)} - errors={lodashGet(policy, 'errorFields.avatar', null)} - onErrorClose={() => Policy.clearAvatarErrors(policy.id)} - previewSource={UserUtils.getFullSizeAvatar(policy.avatar, '')} + pendingAction={policy?.pendingFields?.avatar ?? null} + errors={policy?.errorFields?.avatar ?? null} + onErrorClose={() => Policy.clearAvatarErrors(policy?.id ?? '')} + previewSource={UserUtils.getFullSizeAvatar(policy?.avatar ?? '')} headerTitle={translate('workspace.common.workspaceAvatar')} - originalFileName={policy.originalFileName} + originalFileName={policy?.originalFileName} disabled={readOnly} disabledStyle={styles.cursorDefault} + errorRowStyles={undefined} /> - + - {(!_.isEmpty(policy.description) || !readOnly) && ( - + {(!StringUtils.isEmptyString(policy?.description ?? '') || !readOnly) && ( + )} - + ({ currencyList: {key: ONYXKEYS.CURRENCY_LIST}, - }), -)(WorkspaceProfilePage); + })(WorkspaceProfilePage), +); diff --git a/src/types/onyx/Currency.ts b/src/types/onyx/Currency.ts index a4767403381f..b8d6f8dda88b 100644 --- a/src/types/onyx/Currency.ts +++ b/src/types/onyx/Currency.ts @@ -21,4 +21,7 @@ type Currency = { cacheBurst?: number; }; +type CurrencyList = Record; + export default Currency; +export type {CurrencyList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 1b2ecdbdce12..c24e0871e0ed 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -9,6 +9,7 @@ import type {CardList} from './Card'; import type Log from './Console'; import type Credentials from './Credentials'; import type Currency from './Currency'; +import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; @@ -81,6 +82,7 @@ export type { CardList, Credentials, Currency, + CurrencyList, CustomStatusDraft, Download, FrequentlyUsedEmoji,