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

Implement Invite message page #15672

Merged
merged 28 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8958fed
Implementing Invite message page
abdulrahuman5196 Mar 24, 2023
8455cf4
Merge changes
abdulrahuman5196 Mar 24, 2023
b3f2a1f
Fixing issues
abdulrahuman5196 Mar 24, 2023
8eb8cf9
Fixing lint failures
abdulrahuman5196 Mar 24, 2023
c6926f1
Fixing regression
abdulrahuman5196 Mar 25, 2023
4f8fc62
Resolving calculation comment
abdulrahuman5196 Mar 25, 2023
cd795e1
Fixing future comments
abdulrahuman5196 Mar 25, 2023
b915748
adding JSDocs
abdulrahuman5196 Mar 25, 2023
da2352e
Fixing errors
abdulrahuman5196 Mar 25, 2023
df8e7c0
Fixing PR style comments
abdulrahuman5196 Mar 28, 2023
fabd9af
Reseting draft after submit
abdulrahuman5196 Apr 4, 2023
87e906b
Merge branch 'main' into inviteMessagePage
abdulrahuman5196 Apr 19, 2023
e9992b7
Fixing privacy link to move along with the submit button
abdulrahuman5196 Apr 19, 2023
4fa2bf0
Adding no members error message on submit
abdulrahuman5196 Apr 19, 2023
0a26961
Fixing lint
abdulrahuman5196 Apr 19, 2023
880bcdd
removing comment
abdulrahuman5196 Apr 19, 2023
d9fdfe6
adding translation
abdulrahuman5196 Apr 20, 2023
19e7b58
Merge branch 'main' into inviteMessagePage
abdulrahuman5196 Apr 20, 2023
2e08f11
Merge branch 'main' into inviteMessagePage
abdulrahuman5196 Apr 23, 2023
9e763ea
Fixing PR comments
abdulrahuman5196 Apr 23, 2023
d7dabdf
Removing inline styles
abdulrahuman5196 Apr 23, 2023
d5cf59c
Updating menu item right for multiple avatars
abdulrahuman5196 Apr 23, 2023
1cd96a2
Removing style
abdulrahuman5196 Apr 24, 2023
4357356
Merge branch 'main' into inviteMessagePage
abdulrahuman5196 Apr 25, 2023
a5e06c3
wrapping in page not found view
abdulrahuman5196 Apr 27, 2023
962209c
Fixing lint
abdulrahuman5196 Apr 27, 2023
2926e15
Fixing lint
abdulrahuman5196 Apr 27, 2023
781e6e3
Merge branch 'main' into inviteMessagePage
abdulrahuman5196 Apr 28, 2023
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/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export default {
DOWNLOAD: 'download_',
POLICY: 'policy_',
POLICY_MEMBER_LIST: 'policyMemberList_',
WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_',
REPORT: 'report_',
REPORT_ACTIONS: 'reportActions_',
REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_',
Expand Down Expand Up @@ -187,6 +188,7 @@ export default {
PROFILE_SETTINGS_FORM: 'profileSettingsForm',
DISPLAY_NAME_FORM: 'displayNameForm',
LEGAL_NAME_FORM: 'legalNameForm',
WORKSPACE_INVITE_MESSAGE_FORM: 'workspaceInviteMessageForm',
DATE_OF_BIRTH_FORM: 'dateOfBirthForm',
HOME_ADDRESS_FORM: 'homeAddressForm',
NEW_ROOM_FORM: 'newRoomForm',
Expand Down
2 changes: 2 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export default {
WORKSPACE_NEW: 'workspace/new',
WORKSPACE_INITIAL: 'workspace/:policyID',
WORKSPACE_INVITE: 'workspace/:policyID/invite',
WORKSPACE_INVITE_MESSAGE: 'workspace/:policyID/invite-message',
WORKSPACE_SETTINGS: 'workspace/:policyID/settings',
WORKSPACE_CARD: 'workspace/:policyID/card',
WORKSPACE_REIMBURSE: 'workspace/:policyID/reimburse',
Expand All @@ -132,6 +133,7 @@ export default {
WORKSPACE_NEW_ROOM: 'workspace/new-room',
getWorkspaceInitialRoute: policyID => `workspace/${policyID}`,
getWorkspaceInviteRoute: policyID => `workspace/${policyID}/invite`,
getWorkspaceInviteMessageRoute: policyID => `workspace/${policyID}/invite-message`,
getWorkspaceSettingsRoute: policyID => `workspace/${policyID}/settings`,
getWorkspaceCardRoute: policyID => `workspace/${policyID}/card`,
getWorkspaceReimburseRoute: policyID => `workspace/${policyID}/reimburse`,
Expand Down
5 changes: 5 additions & 0 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ const propTypes = {
/** Container styles */
style: stylePropTypes,

/** Custom content to display in the footer after submit button */
footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),

...withLocalizePropTypes,
};

Expand All @@ -84,6 +87,7 @@ const defaultProps = {
enabledWhenOffline: false,
isSubmitActionDangerous: false,
scrollContextEnabled: false,
footerContent: null,
style: [],
};

Expand Down Expand Up @@ -332,6 +336,7 @@ class Form extends React.Component {
isLoading={this.props.formState.isLoading}
message={_.isEmpty(this.props.formState.errorFields) ? this.getErrorMessage() : null}
onSubmit={this.submit}
footerContent={this.props.footerContent}
onFixTheErrorsLinkPressed={() => {
const errors = !_.isEmpty(this.state.errors) ? this.state.errors : this.props.formState.errorFields;
const focusKey = _.find(_.keys(this.inputRefs), key => _.keys(errors).includes(key));
Expand Down
48 changes: 29 additions & 19 deletions src/components/FormAlertWithSubmitButton.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import styles from '../styles/styles';
import Button from './Button';
import FormAlertWrapper from './FormAlertWrapper';
Expand Down Expand Up @@ -41,6 +42,9 @@ const propTypes = {

/** Whether the form submit action is dangerous */
isSubmitActionDangerous: PropTypes.bool,

/** Custom content to display in the footer after submit button */
footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
};

const defaultProps = {
Expand All @@ -53,6 +57,7 @@ const defaultProps = {
enabledWhenOffline: false,
disablePressOnEnter: false,
isSubmitActionDangerous: false,
footerContent: null,
};

const FormAlertWithSubmitButton = props => (
Expand All @@ -63,25 +68,30 @@ const FormAlertWithSubmitButton = props => (
message={props.message}
onFixTheErrorsLinkPressed={props.onFixTheErrorsLinkPressed}
>
{isOffline => ((isOffline && !props.enabledWhenOffline) ? (
<Button
success
isDisabled
text={props.buttonText}
style={[styles.mb3]}
danger={props.isSubmitActionDangerous}
/>
) : (
<Button
success
pressOnEnter={!props.disablePressOnEnter}
text={props.buttonText}
onPress={props.onSubmit}
isDisabled={props.isDisabled}
isLoading={props.isLoading}
danger={props.isSubmitActionDangerous}
/>
))}
{isOffline => (
<View>
{(isOffline && !props.enabledWhenOffline) ? (
<Button
success
isDisabled
text={props.buttonText}
style={[styles.mb3]}
danger={props.isSubmitActionDangerous}
/>
) : (
<Button
success
pressOnEnter={!props.disablePressOnEnter}
text={props.buttonText}
onPress={props.onSubmit}
isDisabled={props.isDisabled}
isLoading={props.isLoading}
danger={props.isSubmitActionDangerous}
/>
)}
{props.footerContent}
</View>
)}
</FormAlertWrapper>
);

Expand Down
7 changes: 5 additions & 2 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const defaultProps = {
brickRoadIndicator: '',
floatRightAvatars: [],
shouldStackHorizontally: false,
avatarSize: undefined,
shouldBlockSelection: false,
};

Expand All @@ -77,6 +78,8 @@ const MenuItem = (props) => {
props.title ? descriptionVerticalMargin : undefined,
]);

const fallbackAvatarSize = props.viewMode === CONST.OPTION_MODE.COMPACT ? CONST.AVATAR_SIZE.SMALL : CONST.AVATAR_SIZE.DEFAULT;

return (
<PressableWithSecondaryInteraction
onPress={(e) => {
Expand Down Expand Up @@ -189,12 +192,12 @@ const MenuItem = (props) => {
</View>
)}
{!_.isEmpty(props.floatRightAvatars) && (
<View style={[styles.justifyContentCenter, (props.brickRoadIndicator ? styles.mr4 : styles.mr3)]}>
<View style={[styles.justifyContentCenter, (props.brickRoadIndicator ? styles.mr2 : undefined)]}>
<MultipleAvatars
isHovered={hovered}
isPressed={pressed}
icons={props.floatRightAvatars}
size={props.viewMode === CONST.OPTION_MODE.COMPACT ? CONST.AVATAR_SIZE.SMALL : CONST.AVATAR_SIZE.DEFAULT}
size={props.avatarSize || fallbackAvatarSize}
fallbackIcon={defaultWorkspaceAvatars.WorkspaceBuilding}
shouldStackHorizontally={props.shouldStackHorizontally}
/>
Expand Down
62 changes: 51 additions & 11 deletions src/components/MultipleAvatars.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,12 @@ const defaultProps = {
};

const MultipleAvatars = (props) => {
const avatarContainerStyles = props.size === CONST.AVATAR_SIZE.SMALL ? styles.emptyAvatarSmall : styles.emptyAvatar;
let avatarContainerStyles = props.size === CONST.AVATAR_SIZE.SMALL ? styles.emptyAvatarSmall : styles.emptyAvatar;
const singleAvatarStyles = props.size === CONST.AVATAR_SIZE.SMALL ? styles.singleAvatarSmall : styles.singleAvatar;
const secondAvatarStyles = [
props.size === CONST.AVATAR_SIZE.SMALL ? styles.secondAvatarSmall : styles.secondAvatar,
...props.secondAvatarStyle,
];
const horizontalStyles = [styles.horizontalStackedAvatar4, styles.horizontalStackedAvatar3, styles.horizontalStackedAvatar2, styles.horizontalStackedAvatar1];

if (!props.icons.length) {
return null;
Expand All @@ -83,21 +82,49 @@ const MultipleAvatars = (props) => {
);
}

const oneAvatarSize = StyleUtils.getAvatarStyle(props.size);
const oneAvatarBorderWidth = StyleUtils.getAvatarBorderWidth(props.size);
const overlapSize = oneAvatarSize.width / 3;

if (props.shouldStackHorizontally) {
let width;

// Height of one avatar + border space
const height = oneAvatarSize.height + (2 * oneAvatarBorderWidth);
if (props.icons.length > 4) {
// Width of overlapping avatars + border space
width = (oneAvatarSize.width * 3) + (oneAvatarBorderWidth * 8);
} else {
// one avatar width + overlaping avatar sizes + border space
width = oneAvatarSize.width + (overlapSize * 2 * (props.icons.length - 1)) + (oneAvatarBorderWidth * (props.icons.length * 2));
}
avatarContainerStyles = StyleUtils.combineStyles([
styles.alignItemsCenter,
styles.flexRow,
StyleUtils.getHeight(height),
StyleUtils.getWidthStyle(width),
]);
}

return (
<View style={avatarContainerStyles}>
{props.shouldStackHorizontally ? (
<>
{
_.map([...props.icons].splice(0, 4).reverse(), (icon, index) => (
_.map([...props.icons].splice(0, 4), (icon, index) => (
<View
key={`stackedAvatars-${index}`}
style={[styles.horizontalStackedAvatar, StyleUtils.getHorizontalStackedAvatarBorderStyle(props.isHovered, props.isPressed), horizontalStyles[index],
StyleUtils.getAvatarBorderRadius(props.size, icon.type)]}
style={[styles.justifyContentCenter,
styles.alignItemsCenter,
StyleUtils.getHorizontalStackedAvatarBorderStyle(props.isHovered, props.isPressed),
StyleUtils.getHorizontalStackedAvatarStyle(index, overlapSize, oneAvatarBorderWidth, oneAvatarSize.width),
(icon.type === CONST.ICON_TYPE_WORKSPACE ? StyleUtils.getAvatarBorderRadius(props.size, icon.type) : {}),
]}
>
<Avatar
source={icon.source || props.fallbackIcon}
fill={themeColors.iconSuccessFill}
size={CONST.AVATAR_SIZE.SMALLER}
size={props.size}
name={icon.name}
type={icon.type}
/>
Expand All @@ -113,13 +140,26 @@ const MultipleAvatars = (props) => {

// Set overlay background color with RGBA value so that the text will not inherit opacity
StyleUtils.getBackgroundColorWithOpacityStyle(themeColors.overlay, variables.overlayOpacity),
styles.horizontalStackedAvatar4Overlay,
StyleUtils.getAvatarBorderRadius(props.size, props.icons[3].type),
StyleUtils.getHorizontalStackedOverlayAvatarStyle(oneAvatarSize, oneAvatarBorderWidth),
(props.icons[3].type === CONST.ICON_TYPE_WORKSPACE ? StyleUtils.getAvatarBorderRadius(props.size, props.icons[3].type) : {}),
]}
>
<Text style={styles.avatarInnerTextSmall}>
{`+${props.icons.length - 4}`}
</Text>
<View
style={[styles.justifyContentCenter,
styles.alignItemsCenter,
StyleUtils.getHeight(oneAvatarSize.height),
StyleUtils.getWidthStyle(oneAvatarSize.width),
]}
>
<Text
style={[
styles.avatarInnerTextSmall,
StyleUtils.getAvatarExtraFontSizeStyle(props.size),
]}
>
{`+${props.icons.length - 4}`}
</Text>
</View>
</View>
)}
</>
Expand Down
4 changes: 4 additions & 0 deletions src/components/menuItemPropTypes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
import _ from 'underscore';
import CONST from '../CONST';
import stylePropTypes from '../styles/stylePropTypes';
import avatarPropTypes from './avatarPropTypes';
Expand Down Expand Up @@ -89,6 +90,9 @@ const propTypes = {
/** Prop to identify if we should load avatars vertically instead of diagonally */
shouldStackHorizontally: PropTypes.bool,

/** Prop to represent the size of the avatar images to be shown */
avatarSize: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)),

/** The function that should be called when this component is LongPressed or right-clicked. */
onSecondaryInteraction: PropTypes.func,

Expand Down
10 changes: 8 additions & 2 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1055,11 +1055,17 @@ export default {
},
invite: {
invitePeople: 'Invite new members',
personalMessagePrompt: 'Add a personal message (optional)',
genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
welcomeNote: ({workspaceName}) => `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`,
pleaseEnterValidLogin: `Please ensure the email or phone number is valid (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`,
},
inviteMessage: {
inviteMessageTitle: 'Add message',
inviteMessagePrompt: 'Make your invitation extra special by adding a message below',
personalMessagePrompt: 'Message',
genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.',
inviteNoMembersError: 'Please select at least one member to invite',
welcomeNote: ({workspaceName}) => `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`,
},
editor: {
nameInputLabel: 'Name',
nameInputHelpText: 'This is the name you will see on your workspace.',
Expand Down
10 changes: 8 additions & 2 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1056,11 +1056,17 @@ export default {
},
invite: {
invitePeople: 'Invitar nuevos miembros',
personalMessagePrompt: 'Agregar un mensaje personal (Opcional)',
genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..',
welcomeNote: ({workspaceName}) => `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`,
pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`,
},
inviteMessage: {
inviteMessageTitle: 'Añadir un mensaje',
inviteMessagePrompt: 'Añadir un mensaje para hacer tu invitación destacar',
personalMessagePrompt: 'Mensaje',
inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar',
genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..',
welcomeNote: ({workspaceName}) => `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`,
},
editor: {
nameInputLabel: 'Nombre',
nameInputHelpText: 'Este es el nombre que verás en tu espacio de trabajo.',
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 @@ -476,6 +476,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Workspace_Invite',
},
{
getComponent: () => {
const WorkspaceInviteMessagePage = require('../../../pages/workspace/WorkspaceInviteMessagePage').default;
return WorkspaceInviteMessagePage;
},
name: 'Workspace_Invite_Message',
},
{
getComponent: () => {
const WorkspaceNewRoomPage = require('../../../pages/workspace/WorkspaceNewRoomPage').default;
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ export default {
Workspace_Invite: {
path: ROUTES.WORKSPACE_INVITE,
},
Workspace_Invite_Message: {
path: ROUTES.WORKSPACE_INVITE_MESSAGE,
},
Workspace_NewRoom: {
path: ROUTES.WORKSPACE_NEW_ROOM,
},
Expand Down
5 changes: 5 additions & 0 deletions src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,10 @@ function openWorkspaceInvitePage(policyID, clientMemberEmails) {
});
}

function setWorkspaceInviteMembersDraft(policyID, memberEmails) {
Onyx.set(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${policyID}`, memberEmails);
}

/**
*
* @param {String} reportID
Expand Down Expand Up @@ -1183,6 +1187,7 @@ export {
openWorkspaceMembersPage,
openWorkspaceInvitePage,
removeWorkspace,
setWorkspaceInviteMembersDraft,
isPolicyOwner,
leaveRoom,
};
2 changes: 2 additions & 0 deletions src/pages/settings/InitialSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class InitialSettingsPage extends React.Component {
action: () => { Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); },
floatRightAvatars: policiesAvatars,
shouldStackHorizontally: true,
avatarSize: CONST.AVATAR_SIZE.SMALLER,
brickRoadIndicator: policyBrickRoadIndicator,
},
{
Expand Down Expand Up @@ -246,6 +247,7 @@ class InitialSettingsPage extends React.Component {
brickRoadIndicator={item.brickRoadIndicator}
floatRightAvatars={item.floatRightAvatars}
shouldStackHorizontally={item.shouldStackHorizontally}
avatarSize={item.avatarSize}
ref={this.popoverAnchor}
shouldBlockSelection={Boolean(item.link)}
onSecondaryInteraction={!_.isEmpty(item.link) ? e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current) : undefined}
Expand Down
Loading