Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS Migration] Migrate WorkspaceProfilePage and WorkspaceProfileCurrencyPage #34897

Merged
merged 22 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8ee9a05
[TS Migration] Migrate WorkspaceSettingsPage and WorkspaceSettingsCur…
ruben-rebelo Jan 22, 2024
c8b7d3b
[TS migration] TS error fix WorkspaceSettingsPage
ruben-rebelo Jan 22, 2024
456e38c
[TS migration] Added default values on currencyList
ruben-rebelo Jan 22, 2024
63f2976
[TS migration] WorkspaceSettings code improvements
ruben-rebelo Jan 22, 2024
fb683a9
[TS Migration] WorkspaceSettingsPage ts error fix
ruben-rebelo Jan 22, 2024
cd50da3
[TS migration] WorkspaceSettings prettified
ruben-rebelo Jan 22, 2024
4ed1693
Merge branch 'main' into ts-migration/WorkspaceSettings
ruben-rebelo Jan 30, 2024
4f20d01
[TS migration][WorkspaceSettings] Removed unused todo
ruben-rebelo Jan 25, 2024
ae6f7f6
Merge branch 'main' into ts-migration/WorkspaceSettings
ruben-rebelo Jan 31, 2024
9e900e0
[TS migration][WorkspaceSettings] Updated typings based on main merge
ruben-rebelo Jan 31, 2024
a42cef2
Merge branch 'main' into ts-migration/WorkspaceSettings
ruben-rebelo Feb 7, 2024
b345087
[TS migration] Lint and ts fixes
ruben-rebelo Feb 7, 2024
37c12e2
[TS migration][WorkspaceSettings] Fixed wrongly resolved conflicts
ruben-rebelo Feb 7, 2024
b2e0b3b
[TS migration][WorkscpaceSettings] Lint fixes
ruben-rebelo Feb 7, 2024
4c61ce3
[TS migration][WorkspaceSettings] Minor ts issues fixed
ruben-rebelo Feb 7, 2024
e014172
Merge branch 'main' into ts-migration/WorkspaceSettings
ruben-rebelo Feb 14, 2024
4545cc9
[TS migration] Fixed migration of WorkspaceSettings based on the rena…
ruben-rebelo Feb 14, 2024
0983db3
Merge branch 'main' into ts-migration/WorkspaceSettings
ruben-rebelo Feb 14, 2024
9d451c8
[TS migration][WorkspaceSettings] Fixed ts issues
ruben-rebelo Feb 14, 2024
6331bdd
[TS migration][WorkspaceSettings] Feedback addressed
ruben-rebelo Feb 22, 2024
8fab1ec
Merge branch 'main' into ts-migration/WorkspaceSettings
ruben-rebelo Feb 22, 2024
5d06edb
[TS migration][WorkspaceSettings] Prettier run after merge
ruben-rebelo Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,7 @@ const CONST = {
WORKSPACE_TRAVEL: 'WorkspaceBookTravel',
WORKSPACE_MEMBERS: 'WorkspaceManageMembers',
WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount',
WORKSPACE_SETTINGS: 'WorkspaceSettings',
},
get EXPENSIFY_EMAILS() {
return [
Expand Down
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, OnyxTypes.Currency>;
[ONYXKEYS.CURRENCY_LIST]: OnyxTypes.CurrencyList;
[ONYXKEYS.UPDATE_AVAILABLE]: boolean;
[ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest;
[ONYXKEYS.COUNTRY_CODE]: number;
Expand Down
2 changes: 1 addition & 1 deletion src/libs/UserUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CurrencyList>;
};

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,
};
Expand All @@ -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 (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={WorkspaceSettingsCurrencyPage.displayName}
testID={WorkspaceProfileCurrencyPage.displayName}
>
<FullPageNotFoundView
onBackButtonPress={() => 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'}
>
<HeaderWithBackButton
title={translate('workspace.editor.currencyInputLabel')}
Expand All @@ -105,13 +102,10 @@ function WorkspaceSettingsCurrencyPage({currencyList, policy, isLoadingReportDat
);
}

WorkspaceSettingsCurrencyPage.displayName = 'WorkspaceSettingsCurrencyPage';
WorkspaceSettingsCurrencyPage.propTypes = propTypes;
WorkspaceSettingsCurrencyPage.defaultProps = defaultProps;
WorkspaceProfileCurrencyPage.displayName = 'WorkspaceProfileCurrencyPage';

export default compose(
withPolicyAndFullscreenLoading,
withOnyx({
export default withPolicyAndFullscreenLoading(
withOnyx<WorkspaceProfileCurrentPageProps, WorkspaceProfileCurrentPageOnyxProps>({
currencyList: {key: ONYXKEYS.CURRENCY_LIST},
}),
)(WorkspaceSettingsCurrencyPage);
})(WorkspaceProfileCurrencyPage),
);
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<CurrencyList>;
};

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<ImageStyle> = isSmallScreenWidth ? [styles.mhv12, styles.mhn5] : [styles.mhv8, styles.mhn8];

return (
<WorkspacePageWithSections
Expand All @@ -81,26 +67,29 @@ function WorkspaceProfilePage({policy, currencyList, route}) {
shouldShowNonAdmin
icon={Illustrations.House}
>
{(hasVBA) => (
{(hasVBA?: boolean) => (
<ScrollView>
<View style={[styles.flex1, isSmallScreenWidth ? styles.workspaceSectionMobile : styles.workspaceSection]}>
<Section isCentralPane>
<Section
isCentralPane
title=""
>
<Image
style={StyleSheet.flatten([styles.br4, styles.wAuto, styles.h68, imageStyle])}
source={WorkspaceProfile}
resizeMode="cover"
/>
<AvatarWithImagePicker
onViewPhotoPress={() => 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
DefaultAvatar={() => (
<Avatar
containerStyles={styles.avatarXLarge}
imageStyles={[styles.avatarXLarge, styles.alignSelfCenter]}
source={policy.avatar ? policy.avatar : ReportUtils.getDefaultWorkspaceAvatar(policyName)}
source={policy?.avatar ? policy?.avatar : ReportUtils.getDefaultWorkspaceAvatar(policyName)}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
size={CONST.AVATAR_SIZE.XLARGE}
name={policyName}
Expand All @@ -110,20 +99,21 @@ function WorkspaceProfilePage({policy, currencyList, route}) {
type={CONST.ICON_TYPE_WORKSPACE}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
style={[styles.mb3, isSmallScreenWidth ? styles.mtn17 : styles.mtn20, styles.alignItemsStart, styles.sectionMenuItemTopDescription]}
isUsingDefaultAvatar={!lodashGet(policy, 'avatar', null)}
onImageSelected={(file) => 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}
/>
<OfflineWithFeedback pendingAction={lodashGet(policy, 'pendingFields.generalSettings')}>
<OfflineWithFeedback pendingAction={policy?.pendingFields?.generalSettings as OnyxCommon.PendingAction}>
<MenuItemWithTopDescription
title={policyName}
titleStyle={styles.workspaceTitleStyle}
Expand All @@ -136,8 +126,8 @@ function WorkspaceProfilePage({policy, currencyList, route}) {
shouldUseDefaultCursorWhenDisabled
/>
</OfflineWithFeedback>
{(!_.isEmpty(policy.description) || !readOnly) && (
<OfflineWithFeedback pendingAction={lodashGet(policy, 'pendingFields.description')}>
{(!StringUtils.isEmptyString(policy?.description ?? '') || !readOnly) && (
<OfflineWithFeedback pendingAction={policy?.pendingFields?.description as OnyxCommon.PendingAction}>
<MenuItemWithTopDescription
title={policyDescription}
description={translate('workspace.editor.descriptionInputLabel')}
Expand All @@ -151,13 +141,13 @@ function WorkspaceProfilePage({policy, currencyList, route}) {
/>
</OfflineWithFeedback>
)}
<OfflineWithFeedback pendingAction={lodashGet(policy, 'pendingFields.generalSettings')}>
<OfflineWithFeedback pendingAction={policy?.pendingFields?.generalSettings as OnyxCommon.PendingAction}>
<View>
<MenuItemWithTopDescription
title={formattedCurrency}
description={translate('workspace.editor.currencyInputLabel')}
shouldShowRightIcon={!readOnly}
disabled={hasVBA || readOnly}
disabled={hasVBA ?? readOnly}
wrapperStyle={styles.sectionMenuItemTopDescription}
Copy link
Contributor

@situchan situchan Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused deploy blocker - #37288
During TS migration, please be careful of using nullish coalescing.

onPress={onPressCurrency}
shouldGreyOutWhenDisabled={false}
Expand All @@ -176,13 +166,10 @@ function WorkspaceProfilePage({policy, currencyList, route}) {
);
}

WorkspaceProfilePage.propTypes = propTypes;
WorkspaceProfilePage.defaultProps = defaultProps;
WorkspaceProfilePage.displayName = 'WorkspaceProfilePage';

export default compose(
withPolicy,
withOnyx({
export default withPolicy(
withOnyx<WorkSpaceProfilePageProps, WorkSpaceProfilePageOnyxProps>({
currencyList: {key: ONYXKEYS.CURRENCY_LIST},
}),
)(WorkspaceProfilePage);
})(WorkspaceProfilePage),
);
3 changes: 3 additions & 0 deletions src/types/onyx/Currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ type Currency = {
cacheBurst?: number;
};

type CurrencyList = Record<string, Currency | null>;

export default Currency;
export type {CurrencyList};
Loading
Loading