Skip to content

Commit

Permalink
Merge pull request #10 from software-mansion-labs/ideal-nav/workspace…
Browse files Browse the repository at this point in the history
…-settings

Improve workspace settings on Main Pane
  • Loading branch information
adamgrzybowski committed Dec 19, 2023
2 parents 9f506be + 1a20dc7 commit a725b49
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 71 deletions.
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,10 @@ const ROUTES = {
route: 'workspace/:policyID/members',
getRoute: (policyID: string) => `workspace/${policyID}/members` as const,
},
WORKSPACE_NAME: {
route: 'workspace/:policyID/name',
getRoute: (policyID: string) => `workspace/${policyID}/name` as const,
},
// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ const SCREENS = {
INVITE: 'Workspace_Invite',
INVITE_MESSAGE: 'Workspace_Invite_Message',
CURRENCY: 'Workspace_Overview_Currency',
NAME: 'Workspace_Name',
},

EDIT_REQUEST: {
Expand Down
2 changes: 2 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage').default as React.ComponentType,
[SCREENS.WORKSPACE.INVITE]: () => require('../../../pages/workspace/WorkspaceInvitePage').default as React.ComponentType,
[SCREENS.WORKSPACE.INVITE_MESSAGE]: () => require('../../../pages/workspace/WorkspaceInviteMessagePage').default as React.ComponentType,
[SCREENS.WORKSPACE.NAME]: () => require('../../../pages/workspace/WorkspaceNamePage').default as React.ComponentType,
[SCREENS.WORKSPACE.CURRENCY]: () => require('../../../pages/workspace/WorkspaceOverviewCurrencyPage').default as React.ComponentType,
[SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType,
[SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType,
[SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ const linkingConfig: LinkingOptions<RootStackParamList> = {
[SCREENS.KEYBOARD_SHORTCUTS]: {
path: ROUTES.KEYBOARD_SHORTCUTS,
},
[SCREENS.WORKSPACE.NAME]: ROUTES.WORKSPACE_NAME.route,
},
},
[SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: {
Expand Down
7 changes: 4 additions & 3 deletions src/pages/workspace/WorkspaceMembersPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import compose from '@libs/compose';
import Log from '@libs/Log';
Expand Down Expand Up @@ -86,6 +87,7 @@ function WorkspaceMembersPage(props) {
const textInputRef = useRef(null);
const isOfflineAndNoMemberDataAvailable = _.isEmpty(props.policyMembers) && props.network.isOffline;
const prevPersonalDetails = usePrevious(props.personalDetails);
const {isSmallScreenWidth} = useWindowDimensions();

const isFocusedScreen = useIsFocused();

Expand Down Expand Up @@ -301,7 +303,6 @@ function WorkspaceMembersPage(props) {
const policyOwner = lodashGet(props.policy, 'owner');
const currentUserLogin = lodashGet(props.currentUserPersonalDetails, 'login');
const policyID = lodashGet(props.route, 'params.policyID');
const policyName = lodashGet(props.policy, 'name');
const invitedPrimaryToSecondaryLogins = _.invert(props.policy.primaryLoginsInvited);

const getMemberOptions = () => {
Expand Down Expand Up @@ -427,13 +428,13 @@ function WorkspaceMembersPage(props) {
>
<HeaderWithBackButton
title={props.translate('workspace.common.members')}
subtitle={policyName}
onBackButtonPress={() => {
setSearchValue('');
Navigation.goBack(ROUTES.WORKSPACE_INITIAL.getRoute(policyID));
}}
shouldShowGetAssistanceButton
shouldShowBackButton={isSmallScreenWidth}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
shouldShowBorderBottom
/>
<ConfirmModal
danger
Expand Down
113 changes: 113 additions & 0 deletions src/pages/workspace/WorkspaceNamePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, {useCallback} from 'react';
import {Keyboard, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {withNetwork} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import TextInput from '@components/TextInput';
import {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
import {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions from '@components/withWindowDimensions';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as ValidationUtils from '@libs/ValidationUtils';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import withPolicy from './withPolicy';

const propTypes = {
...withLocalizePropTypes,
...withCurrentUserPersonalDetailsPropTypes,
};

const defaultProps = {
...withCurrentUserPersonalDetailsDefaultProps,
isLoadingApp: true,
};

function WorkspaceNamePage({policy}) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const submit = useCallback(
(values) => {
if (policy.isPolicyUpdating) {
return;
}

Policy.updateGeneralSettings(policy.id, values.name.trim(), policy.outputCurrency);
Keyboard.dismiss();
Navigation.goBack(ROUTES.WORKSPACE_OVERVIEW.getRoute(policy.id));
},
[policy.id, policy.isPolicyUpdating, policy.outputCurrency],
);

const validate = useCallback((values) => {
const errors = {};
const name = values.name.trim();

if (!ValidationUtils.isRequiredFulfilled(name)) {
errors.name = 'workspace.editor.nameIsRequiredError';
} else if ([...name].length > CONST.WORKSPACE_NAME_CHARACTER_LIMIT) {
// Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16
// code units.
errors.name = 'workspace.editor.nameIsTooLongError';
}

return errors;
}, []);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={WorkspaceNamePage.displayName}
>
<HeaderWithBackButton
title={translate('workspace.editor.nameInputLabel')}
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_OVERVIEW.getRoute(policy.id))}
/>
<FormProvider
formID={ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM}
submitButtonText={translate('workspace.editor.save')}
style={[styles.flexGrow1, styles.ph5]}
scrollContextEnabled
validate={validate}
onSubmit={submit}
enabledWhenOffline
>
<View style={styles.mb4}>
<InputWrapper
InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
inputID="name"
label={translate('workspace.editor.nameInputLabel')}
accessibilityLabel={translate('workspace.editor.nameInputLabel')}
defaultValue={policy.name}
maxLength={CONST.WORKSPACE_NAME_CHARACTER_LIMIT}
spellCheck={false}
/>
</View>
</FormProvider>
</ScreenWrapper>
);
}

WorkspaceNamePage.propTypes = propTypes;
WorkspaceNamePage.defaultProps = defaultProps;
WorkspaceNamePage.displayName = 'WorkspaceNamePage';

export default compose(
withPolicy,
withWindowDimensions,
withOnyx({
currencyList: {key: ONYXKEYS.CURRENCY_LIST},
}),
withNetwork(),
)(WorkspaceNamePage);
68 changes: 13 additions & 55 deletions src/pages/workspace/WorkspaceOverviewPage.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback} from 'react';
import {Keyboard, View} from 'react-native';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import Avatar from '@components/Avatar';
import AvatarWithImagePicker from '@components/AvatarWithImagePicker';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {withNetwork} from '@components/OnyxProvider';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import * as UserUtils from '@libs/UserUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -62,35 +58,8 @@ function WorkspaceOverviewPage({policy, currencyList, windowWidth, route}) {

const formattedCurrency = !_.isEmpty(policy) && !_.isEmpty(currencyList) ? `${policy.outputCurrency} - ${currencyList[policy.outputCurrency].symbol}` : '';

const submit = useCallback(
(values) => {
if (policy.isPolicyUpdating) {
return;
}

Policy.updateGeneralSettings(policy.id, values.name.trim(), policy.outputCurrency);
Keyboard.dismiss();
Navigation.goBack(ROUTES.WORKSPACE_INITIAL.getRoute(policy.id));
},
[policy.id, policy.isPolicyUpdating, policy.outputCurrency],
);

const validate = useCallback((values) => {
const errors = {};
const name = values.name.trim();

if (!ValidationUtils.isRequiredFulfilled(name)) {
errors.name = 'workspace.editor.nameIsRequiredError';
} else if ([...name].length > CONST.WORKSPACE_NAME_CHARACTER_LIMIT) {
// Uses the spread syntax to count the number of Unicode code points instead of the number of UTF-16
// code units.
errors.name = 'workspace.editor.nameIsTooLongError';
}

return errors;
}, []);

const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_OVERVIEW_CURRENCY.getRoute(policy.id)), [policy.id]);
const onPressName = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_NAME.getRoute(policy.id)), [policy.id]);

const policyName = lodashGet(policy, 'name', '');

Expand All @@ -101,15 +70,7 @@ function WorkspaceOverviewPage({policy, currencyList, windowWidth, route}) {
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_OVERVIEW}
>
{(hasVBA) => (
<FormProvider
formID={ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM}
submitButtonText={translate('workspace.editor.save')}
style={[styles.flexGrow1, styles.ph5]}
scrollContextEnabled
validate={validate}
onSubmit={submit}
enabledWhenOffline
>
<>
<AvatarWithImagePicker
source={lodashGet(policy, 'avatar')}
size={CONST.AVATAR_SIZE.LARGE}
Expand All @@ -127,7 +88,7 @@ function WorkspaceOverviewPage({policy, currencyList, windowWidth, route}) {
)}
type={CONST.ICON_TYPE_WORKSPACE}
fallbackIcon={Expensicons.FallbackWorkspaceAvatar}
style={[styles.mb3]}
style={[styles.mb3, styles.mt5]}
anchorPosition={styles.createMenuPositionProfile(windowWidth)}
anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP}}
isUsingDefaultAvatar={!lodashGet(policy, 'avatar', null)}
Expand All @@ -142,18 +103,15 @@ function WorkspaceOverviewPage({policy, currencyList, windowWidth, route}) {
originalFileName={policy.originalFileName}
/>
<OfflineWithFeedback pendingAction={lodashGet(policy, 'pendingFields.generalSettings')}>
<InputWrapper
InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
inputID="name"
label={translate('workspace.editor.nameInputLabel')}
accessibilityLabel={translate('workspace.editor.nameInputLabel')}
defaultValue={policy.name}
maxLength={CONST.WORKSPACE_NAME_CHARACTER_LIMIT}
containerStyles={[styles.mt4]}
spellCheck={false}
<MenuItemWithTopDescription
title={policy.name}
description={translate('workspace.editor.nameInputLabel')}
shouldShowRightIcon
disabled={hasVBA}
onPress={onPressName}
/>
<View style={[styles.mt4, styles.mhn5]}>

<View>
<MenuItemWithTopDescription
title={formattedCurrency}
description={translate('workspace.editor.currencyInputLabel')}
Expand All @@ -166,7 +124,7 @@ function WorkspaceOverviewPage({policy, currencyList, windowWidth, route}) {
</Text>
</View>
</OfflineWithFeedback>
</FormProvider>
</>
)}
</WorkspacePageWithSections>
);
Expand Down
7 changes: 4 additions & 3 deletions src/pages/workspace/WorkspacePageWithSections.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import ScrollViewWithContext from '@components/ScrollViewWithContext';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import compose from '@libs/compose';
import BankAccount from '@libs/models/BankAccount';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -91,8 +92,8 @@ function WorkspacePageWithSections({backButtonRoute, children, footer, guidesCal
const hasVBA = achState === BankAccount.STATE.OPEN;
const isUsingECard = lodashGet(user, 'isUsingExpensifyCard', false);
const policyID = lodashGet(route, 'params.policyID');
const policyName = lodashGet(policy, 'name');
const content = children(hasVBA, policyID, isUsingECard);
const {isSmallScreenWidth} = useWindowDimensions();

useEffect(() => {
fetchData(shouldSkipVBBACall);
Expand All @@ -112,10 +113,10 @@ function WorkspacePageWithSections({backButtonRoute, children, footer, guidesCal
>
<HeaderWithBackButton
title={headerText}
subtitle={policyName}
shouldShowGetAssistanceButton
guidesCallTaskID={guidesCallTaskID}
shouldShowBackButton={isSmallScreenWidth}
onBackButtonPress={() => Navigation.goBack(backButtonRoute || ROUTES.WORKSPACE_INITIAL.getRoute(policyID))}
shouldShowBorderBottom
/>
{shouldUseScrollView ? (
<ScrollViewWithContext
Expand Down
7 changes: 5 additions & 2 deletions src/pages/workspace/bills/WorkspaceBillsPage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
import CONST from '@src/CONST';
import WorkspaceBillsNoVBAView from './WorkspaceBillsNoVBAView';
Expand All @@ -20,6 +22,7 @@ const propTypes = {
};

function WorkspaceBillsPage(props) {
const styles = useThemeStyles();
return (
<WorkspacePageWithSections
shouldUseScrollView
Expand All @@ -28,10 +31,10 @@ function WorkspaceBillsPage(props) {
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BILLS}
>
{(hasVBA, policyID) => (
<>
<View style={[styles.workspaceSection, styles.mt6]}>
{!hasVBA && <WorkspaceBillsNoVBAView policyID={policyID} />}
{hasVBA && <WorkspaceBillsVBAView policyID={policyID} />}
</>
</View>
)}
</WorkspacePageWithSections>
);
Expand Down
Loading

0 comments on commit a725b49

Please sign in to comment.