Skip to content

Commit

Permalink
Merge pull request #3681 from Expensify/vit-settingsPageToConfigureWo…
Browse files Browse the repository at this point in the history
…rkspace

Add a Settings page to configure people in workspace
  • Loading branch information
marcaaron authored Jun 30, 2021
2 parents 55a8ec4 + 3eb5818 commit 72b8e1d
Show file tree
Hide file tree
Showing 19 changed files with 453 additions and 14 deletions.
6 changes: 4 additions & 2 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export default {
VALIDATE_LOGIN_WITH_VALIDATE_CODE: 'v/:accountID/:validateCode',
ENABLE_PAYMENTS: 'enable-payments',
WORKSPACE_NEW: 'workspace/new',
WORKSPACE_CARD: ':policyID/card',
WORKSPACE: 'workspace',
WORKSPACE_CARD: ':policyID/card',
WORKSPACE_PEOPLE: ':policyID/people',
getWorkspaceCardRoute: policyID => `workspace/${policyID}/card`,
WORKSPACE_INVITE: 'workspace/:policyID/invite',
getWorkspacePeopleRoute: policyID => `workspace/${policyID}/people`,
getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`,
WORKSPACE_INVITE: 'workspace/:policyID/invite',
REQUEST_CALL: 'request-call',

/**
Expand Down
10 changes: 9 additions & 1 deletion src/components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const propTypes = {
/** Whether we should use the success theme color */
success: PropTypes.bool,

/** Whether we should use the danger theme color */
danger: PropTypes.bool,

/** Optional content component to replace all inner contents of button */
ContentComponent: PropTypes.func,

Expand All @@ -55,6 +58,7 @@ const defaultProps = {
style: [],
textStyles: [],
success: false,
danger: false,
ContentComponent: undefined,
shouldRemoveRightBorderRadius: false,
shouldRemoveLeftBorderRadius: false,
Expand All @@ -79,6 +83,7 @@ const Button = (props) => {
styles.buttonText,
props.large && styles.buttonLargeText,
props.success && styles.buttonSuccessText,
props.danger && styles.buttonDangerText,
...props.textStyles,
]}
>
Expand All @@ -102,8 +107,11 @@ const Button = (props) => {
styles.button,
props.large ? styles.buttonLarge : undefined,
props.success ? styles.buttonSuccess : undefined,
props.isDisabled ? styles.buttonDisable : undefined,
props.danger ? styles.buttonDanger : undefined,
(props.isDisabled && props.danger) ? styles.buttonDangerDisabled : undefined,
(props.isDisabled && !props.danger) ? styles.buttonDisable : undefined,
(props.success && hovered) ? styles.buttonSuccessHovered : undefined,
(props.danger && hovered) ? styles.buttonDangerHovered : undefined,
props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
]}
Expand Down
5 changes: 5 additions & 0 deletions src/components/ConfirmModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const propTypes = {
/** Modal content text */
prompt: PropTypes.string,

/** Is the action destructive */
danger: PropTypes.bool,

...withLocalizePropTypes,

...windowDimensionsPropTypes,
Expand All @@ -41,6 +44,7 @@ const defaultProps = {
confirmText: '',
cancelText: '',
prompt: '',
danger: false,
};

const ConfirmModal = props => (
Expand All @@ -63,6 +67,7 @@ const ConfirmModal = props => (

<Button
success
danger={props.danger}
style={[styles.mt4]}
onPress={props.onConfirm}
text={props.confirmText || props.translate('common.yes')}
Expand Down
8 changes: 8 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export default {
whatThis: 'What\'s this?',
iAcceptThe: 'I accept the ',
passwordCannotBeBlank: 'Password cannot be blank',
remove: 'Remove',
admin: 'Admin',
dateFormat: 'YYYY-MM-DD',
send: 'Send',
notifications: 'Notifications',
Expand Down Expand Up @@ -434,6 +436,12 @@ export default {
getStarted: 'Get started!',
genericFailureMessage: 'An error occurred creating the workspace, please try again.',
},
people: {
assignee: 'Assignee',
genericFailureMessage: 'An error occurred removing a user from the workspace, please try again.',
removeMembersPrompt: 'Are you sure you want to remove the selected people from your workspace?',
removeMembersTitle: 'Remove Members',
},
card: {
addEmail: 'Add Email',
tagline: 'The Smartest corporate card in the room.',
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default {
whatThis: '¿Qué es esto?',
iAcceptThe: 'Acepto los ',
passwordCannotBeBlank: 'La contraseña no puede estar vacía',
remove: 'Eliminar',
admin: 'Administrador',
dateFormat: 'AAAA-MM-DD',
send: 'Enviar',
notifications: 'Notificaciones',
Expand Down Expand Up @@ -366,6 +368,12 @@ export default {
getStarted: '¡Empezar!',
genericFailureMessage: 'Se ha producido un error al intentar crear el Workspace. Por favor, inténtalo de nuevo.',
},
people: {
assignee: 'Persona asignada',
genericFailureMessage: 'Se ha producido un error al intentar eliminar a un usuario del espacio de trabajo. Por favor inténtalo más tarde.',
removeMembersPrompt: '¿Estás seguro que quieres eliminar a las personas seleccionadas de tu espacio de trabajo?',
removeMembersTitle: 'Eliminar miembros',
},
card: {
addEmail: 'Agregar correo electrónico',
tagline: 'La tarjeta corporativa más inteligente de la habitación.',
Expand Down
13 changes: 13 additions & 0 deletions src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,18 @@ function Policy_Create(parameters) {
return Network.post(commandName, parameters);
}

/**
* @param {Object} parameters
* @param {String} parameters.policyID
* @param {Array} parameters.emailList
* @returns {Promise}
*/
function Policy_Employees_Remove(parameters) {
const commandName = 'Policy_Employees_Remove';
requireParameters(['policyID', 'emailList'], parameters, commandName);
return Network.post(commandName, parameters);
}

/**
* @param {Object} parameters
* @param {String} parameters.taskID
Expand Down Expand Up @@ -1038,4 +1050,5 @@ export {
GetPreferredCurrency,
GetCurrencyList,
Policy_Create,
Policy_Employees_Remove,
};
1 change: 1 addition & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ class AuthScreens extends React.Component {
cardStyle: {...styles.fullscreenCard},
cardStyleInterpolator: props => modalCardStyleInterpolator(this.props.isSmallScreenWidth, true, props),
cardOverlayEnabled: false,
isFullScreenModal: true,
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
// Screens
import BaseDrawerNavigator from './BaseDrawerNavigator';
import WorkspaceCardPage from '../../../pages/workspace/WorkspaceCardPage';
import WorkspacePeoplePage from '../../../pages/workspace/WorkspacePeoplePage';
import WorkspaceSidebar from '../../../pages/workspace/WorkspaceSidebar';

const WorkspaceSettingsDrawerNavigator = () => (
Expand All @@ -15,6 +16,11 @@ const WorkspaceSettingsDrawerNavigator = () => (
component: WorkspaceCardPage,
initialParams: {},
},
{
name: 'WorkspacePeople',
component: WorkspacePeoplePage,
initialParams: {},
},
]}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const CustomRootStackNavigator = ({
const {state, navigation, descriptors} = useNavigationBuilder(StackRouter, {
children,
});
const isDisplayingModal = Boolean(_.find(descriptors, descriptor => descriptor.options.isModal));
const topScreen = _.last(_.values(descriptors));
const isDisplayingModal = Boolean(topScreen.options.isModal);
const isDisplayingFullScreenModal = Boolean(topScreen.options.isFullScreenModal);
return (
<>
<StackView
Expand All @@ -27,7 +29,7 @@ const CustomRootStackNavigator = ({
{/* We need to superimpose a clickaway handler when showing modals so that they can be dismissed. Capturing
press events on the cardOverlay element in react-navigation is not yet supported on web */}
<ClickAwayHandler
isDisplayingModal={isDisplayingModal}
isDisplayingModal={isDisplayingModal && !isDisplayingFullScreenModal}
/>
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export default {
path: ROUTES.WORKSPACE,
screens: {
WorkspaceCard: ROUTES.WORKSPACE_CARD,
WorkspacePeople: ROUTES.WORKSPACE_PEOPLE,
},
},

Expand Down
44 changes: 43 additions & 1 deletion src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import {
GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge, Policy_Create,
GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge, Policy_Create, Policy_Employees_Remove,
} from '../API';
import ONYXKEYS from '../../ONYXKEYS';
import {formatPersonalDetails} from './PersonalDetails';
Expand Down Expand Up @@ -91,6 +91,47 @@ function getPolicyList() {
});
}

/**
* Remove the passed members from the policy employeeList
*
* @param {Array} members
* @param {String} policyID
*/
function removeMembers(members, policyID) {
// In case user selects only themselves (admin), their email will be filtered out and the members
// array passed will be empty, prevent the funtion from proceeding in that case as there is noone to remove
if (members.length === 0) {
return;
}

const key = `${ONYXKEYS.COLLECTION.POLICY}${policyID}`;

// Make a shallow copy to preserve original data and remove the members
const policy = _.clone(allPolicies[key]);
policy.employeeList = _.without(policy.employeeList, ...members);

// Optimistically remove the members from the policy
Onyx.set(key, policy);

// Make the API call to merge the login into the policy
Policy_Employees_Remove({
emailList: members,
policyID,
})
.then((data) => {
if (data.jsonCode === 200) {
return;
}
const policyDataWithMembersRemoved = _.clone(allPolicies[key]);
policyDataWithMembersRemoved.employeeList = [...policyDataWithMembersRemoved.employeeList, ...members];
Onyx.set(key, policyDataWithMembersRemoved);

// Show the user feedback that the removal failed
console.error(data.message);
Growl.show(translateLocal('workspace.people.genericFailureMessage'), CONST.GROWL.ERROR, 5000);
});
}

/**
* Merges the passed in login into the specified policy
*
Expand Down Expand Up @@ -165,6 +206,7 @@ function create(name) {
export {
getPolicySummaries,
getPolicyList,
removeMembers,
invite,
create,
};
2 changes: 1 addition & 1 deletion src/pages/home/sidebar/optionPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const optionPropTypes = PropTypes.shape({
participantsList: PropTypes.arrayOf(participantPropTypes),

// The array URLs of the person's avatar
icon: PropTypes.arrayOf(PropTypes.string),
icons: PropTypes.arrayOf(PropTypes.string),

// Descriptive text to be displayed besides selection element
descriptiveText: PropTypes.string,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/settings/InitialPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const InitialSettingsPage = ({

// Add free policies (workspaces) to the list of menu items
const menuItems = _.chain(policies)
.filter(policy => policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN)
.filter(policy => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN)
.map(policy => ({
title: policy.name,
icon: Building,
Expand Down
Loading

0 comments on commit 72b8e1d

Please sign in to comment.