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

Add modal for inviting users to a workspace #3463

Merged
merged 38 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6e72ad1
update routes
Jag96 Jun 9, 2021
2a60ac7
update linkingConfig
Jag96 Jun 9, 2021
e01059d
add workspaceInvite page
Jag96 Jun 9, 2021
c255c9e
add placeholder values for spanish translations
Jag96 Jun 9, 2021
ba6e9ce
import withLocalize
Jag96 Jun 9, 2021
b87c128
add new screen and navigator
Jag96 Jun 9, 2021
89dd12f
update text, fix broken state
Jag96 Jun 9, 2021
41d45d3
style welcome message box
Jag96 Jun 9, 2021
39d1b51
get policy name showing up in welcome text
Jag96 Jun 9, 2021
0bde5fc
add Policy_Employees_Merge api command
Jag96 Jun 9, 2021
8be51d8
style
Jag96 Jun 9, 2021
44f095a
add policyID to params
Jag96 Jun 11, 2021
04382bb
add API call for policyList
Jag96 Jun 11, 2021
b92ebaf
fix empty employeeList
Jag96 Jun 11, 2021
5604e30
pass data to invite, optimisitcally add user
Jag96 Jun 11, 2021
4023ff8
insert personal details into onyx
Jag96 Jun 11, 2021
686db89
test mergeCollection bug
Jag96 Jun 11, 2021
6836fe0
merge main
Jag96 Jun 11, 2021
d8e8120
get optimistic addition and fallback remove working
Jag96 Jun 11, 2021
2901551
insert formatted personalDetails
Jag96 Jun 11, 2021
25d16b8
pass default welcome if none is filled in
Jag96 Jun 11, 2021
77cc488
add privacy policy link
Jag96 Jun 11, 2021
7cee97a
style
Jag96 Jun 11, 2021
04f28fe
merge main
Jag96 Jun 15, 2021
cc62bd2
add welcomeNote label
Jag96 Jun 15, 2021
a6d715b
match styles from mockup
Jag96 Jun 15, 2021
7e5937f
move page into workspace folder
Jag96 Jun 15, 2021
59ab488
show error on invite failure
Jag96 Jun 15, 2021
ff4f095
more style updates
Jag96 Jun 15, 2021
f34c715
fix textInput spacing on mobile
Jag96 Jun 15, 2021
409f66a
simplify privacy link
Jag96 Jun 15, 2021
466a9bc
improve error messages
Jag96 Jun 15, 2021
c787e01
fix style
Jag96 Jun 15, 2021
4fe596a
translate to spanish
Jag96 Jun 15, 2021
5be04c0
address PR review comments
Jag96 Jun 15, 2021
ae3fc4a
remove commented out line
Jag96 Jun 15, 2021
2b0e575
don't modify original data
Jag96 Jun 15, 2021
cdbc397
merge main
Jag96 Jun 15, 2021
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
2 changes: 2 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export default {
VALIDATE_LOGIN: 'v',
VALIDATE_LOGIN_WITH_VALIDATE_CODE: 'v/:accountID/:validateCode',
ENABLE_PAYMENTS: 'enable-payments',
WORKSPACE_INVITE: 'workspace/:policyID/invite',
getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`,

/**
* @param {String} route
Expand Down
7 changes: 7 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default {
recents: 'Recents',
close: 'Close',
settings: 'Settings',
invite: 'Invite',
},
attachmentPicker: {
cameraPermissionRequired: 'Camera Permission Required',
Expand Down Expand Up @@ -318,4 +319,10 @@ export default {
session: {
offlineMessage: 'Looks like you\'re not connected to internet. Can you check your connection and try again?',
},
workspaceInvitePage: {
invitePeople: 'Invite People',
invitePeoplePrompt: 'Invite a colleague to your workspace.',
enterEmailOrPhone: 'Email or Phone',
welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
},
};
7 changes: 7 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default {
contacts: 'Contactos',
recents: 'Recientes',
settings: 'Configuración',
invite: 'Invitar',
},
attachmentPicker: {
cameraPermissionRequired: 'Se necesita permiso para usar la cámara',
Expand Down Expand Up @@ -275,4 +276,10 @@ export default {
session: {
offlineMessage: 'Parece que no estás conectado a internet. Comprueba tu conexión e inténtalo de nuevo.',
},
workspaceInvitePage: {
invitePeople: 'Invite People',
invitePeoplePrompt: 'Invite a colleague to your workspace.',
enterEmailOrPhone: 'Email or Phone',
welcomeNote: ({workspaceName}) => `You have been invited to the ${workspaceName} Workspace! Download the Expensify mobile App to start tracking your expenses.`,
},
};
28 changes: 28 additions & 0 deletions src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,17 @@ function GetIOUReport(parameters) {
return Network.post(commandName, parameters);
}

/**
* @returns {Promise}
*/
function GetPolicyList() {
const commandName = 'Get';
const parameters = {
returnValueList: 'policyList',
};
return Network.post(commandName, parameters);
}

/**
* @returns {Promise}
*/
Expand Down Expand Up @@ -783,6 +794,21 @@ function BankAccount_Get(parameters) {
return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
}

/**
* @param {Object} parameters
* @param {Object[]} parameters.employees
* @param {String} parameters.welcomeNote
* @param {String} parameters.policyID
* @returns {Promise}
*/
function Policy_Employees_Merge(parameters) {
const commandName = 'Policy_Employees_Merge';
requireParameters(['employees', 'welcomeNote', 'policyID'], parameters, commandName);

// Always include returnPersonalDetails to ensure we get the employee's personal details in the response
return Network.post(commandName, {...parameters, returnPersonalDetails: true});
}

/**
* @param {Object} parameters
* @param {String} parameters.accountNumber
Expand Down Expand Up @@ -857,6 +883,7 @@ export {
Get,
GetAccountStatus,
GetIOUReport,
GetPolicyList,
GetPolicySummaryList,
GetRequestCountryCode,
Graphite_Timer,
Expand All @@ -866,6 +893,7 @@ export {
PersonalDetails_GetForEmails,
PersonalDetails_Update,
Plaid_GetLinkToken,
Policy_Employees_Merge,
Push_Authenticate,
RejectTransaction,
Report_AddComment,
Expand Down
10 changes: 9 additions & 1 deletion src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Navigation from '../Navigation';
import * as User from '../../actions/User';
import {setModalVisibility} from '../../actions/Modal';
import NameValuePair from '../../actions/NameValuePair';
import {getPolicySummaries} from '../../actions/Policy';
import {getPolicySummaries, getPolicyList} from '../../actions/Policy';
import modalCardStyleInterpolator from './modalCardStyleInterpolator';
import createCustomModalStackNavigator from './createCustomModalStackNavigator';

Expand All @@ -50,6 +50,7 @@ import {
SettingsModalStackNavigator,
EnablePaymentsStackNavigator,
AddPersonalBankAccountModalStackNavigator,
WorkspaceInviteModalStackNavigator,
} from './ModalStackNavigators';
import SCREENS from '../../../SCREENS';
import Timers from '../../Timers';
Expand Down Expand Up @@ -125,6 +126,7 @@ class AuthScreens extends React.Component {
fetchCountryCodeByRequestIP();
UnreadIndicatorUpdater.listenForReportChanges();
getPolicySummaries();
getPolicyList();

// Refresh the personal details, timezone and betas every 30 minutes
// There is no pusher event that sends updated personal details data yet
Expand Down Expand Up @@ -275,6 +277,12 @@ class AuthScreens extends React.Component {
component={AddPersonalBankAccountModalStackNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name="WorkspaceInvite"
options={modalScreenOptions}
component={WorkspaceInviteModalStackNavigator}
listeners={modalScreenListeners}
/>
</RootStack.Navigator>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import IOUCurrencySelection from '../../../pages/iou/IOUCurrencySelection';
import ReportParticipantsPage from '../../../pages/ReportParticipantsPage';
import EnablePaymentsPage from '../../../pages/EnablePayments';
import AddPersonalBankAccountPage from '../../../pages/AddPersonalBankAccountPage';
import WorkspaceInvitePage from '../../../pages/WorkspaceInvitePage';

const defaultSubRouteOptions = {
cardStyle: styles.navigationScreenCardStyle,
Expand Down Expand Up @@ -152,6 +153,11 @@ const AddPersonalBankAccountModalStackNavigator = createModalStackNavigator([{
name: 'AddPersonalBankAccount_Root',
}]);

const WorkspaceInviteModalStackNavigator = createModalStackNavigator([{
Component: WorkspaceInvitePage,
name: 'WorkspaceInvite_Root',
}]);

export {
IOUBillStackNavigator,
IOURequestModalStackNavigator,
Expand All @@ -164,4 +170,5 @@ export {
SettingsModalStackNavigator,
EnablePaymentsStackNavigator,
AddPersonalBankAccountModalStackNavigator,
WorkspaceInviteModalStackNavigator,
};
5 changes: 5 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ export default {
EnablePayments_Root: ROUTES.ENABLE_PAYMENTS,
},
},
WorkspaceInvite: {
screens: {
WorkspaceInvite_Root: ROUTES.WORKSPACE_INVITE,
},
},
},
},
};
100 changes: 99 additions & 1 deletion src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import {GetPolicySummaryList} from '../API';
import {GetPolicySummaryList, GetPolicyList, Policy_Employees_Merge} from '../API';
import ONYXKEYS from '../../ONYXKEYS';

const allPolicies = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (val && key) {
allPolicies[key] = val;
}
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB but maybe a good follow up. I noticed that we are doing this in every file that needs to use translate(). Perhaps we should just subscribe to the preferred locale in '../translate.js' and then we could export the method that already knows what the locale is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That sounds like a good follow-up to me 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created a follow-up PR here: #3716


/**
* Takes a full policy summary that is returned from the policySummaryList and simplifies it so we are only storing
* the pieces of data that we need to in Onyx
Expand All @@ -17,6 +27,27 @@ function getSimplifiedPolicyObject(fullPolicy) {
};
}

/**
* Simplifies the policyList response into an object containing an array of emails
*
* @param {Object} fullPolicy
* @returns {Object}
*/
function getSimplifiedMemberList(fullPolicy) {
const employeeListEmails = _.chain(fullPolicy.value.employeeList)
.pluck('email')
.flatten()
.unique()
.value();

return {
employeeList: employeeListEmails,
};
}

/**
* Fetches the policySummaryList from the API and saves a simplified version in Onyx
*/
function getPolicySummaries() {
GetPolicySummaryList()
.then((data) => {
Expand All @@ -30,7 +61,74 @@ function getPolicySummaries() {
});
}

/**
* Fetches the policyList from the API and saves a simplified version in Onyx
*/
function getPolicyList() {
GetPolicyList()
.then((data) => {
if (data.jsonCode === 200) {
const policyDataToStore = _.reduce(data.policyList, (memo, policy) => ({
...memo,
[`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedMemberList(policy),
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB, I wonder if it would be more clear that this is doing a merge on an object if we instead did this...

[`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: {employeeList: getSimplifiedMemberList(policy)},

also seems like getSimplifiedMemberList() would return a list.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated the name of the function to getSimplifiedEmployeeListObject, let me know if that's more clear or if you think it'd still be better to return just the array from that function instead.

}), {});
Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataToStore);
}
});
}

/**
* Merges the passed in login into the specified policy
*
* @param {String} login
* @param {String} welcomeNote
* @param {String} policyID
*/
function invite(login, welcomeNote, policyID) {
// Optimistically add the user to the policy
const key = `${ONYXKEYS.COLLECTION.POLICY}${policyID}`;
const dataToStore = {};
dataToStore[key] = {
employeeList: allPolicies[key].employeeList.concat([login]),
};

Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, dataToStore)
.then(() => {
const policyDataWithoutLogin = {};
policyDataWithoutLogin[key] = {
employeeList: _.without(allPolicies[key].employeeList, login),
};
Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataWithoutLogin)
.then(() => {
console.debug('done');
});
});
Jag96 marked this conversation as resolved.
Show resolved Hide resolved

// Policy_Employees_Merge({
// employees: JSON.stringify([{email: login}]),
// welcomeNote,
// policyID,
// })
// .then((data) => {
// // Save data.personalDetails
// if (data.jsonCode !== 200) {
// // TODO: need to match personalDetails to data in PersonalDetails.formatPersonalDetails
// Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {[login]: data.personalDetails[login]});
// return;
// }
//
// // If the operation failed, undo the optimistic addition
// const policyDataWithoutLogin = {};
// policyDataWithoutLogin[key] = {
// employeeList: _.without(allPolicies[key].employeeList, login),
// };
// Onyx.mergeCollection(ONYXKEYS.COLLECTION.POLICY, policyDataWithoutLogin);
// });
}

export {
// eslint-disable-next-line import/prefer-default-export
getPolicySummaries,
getPolicyList,
invite,
};
Loading