Skip to content

Commit

Permalink
Merge pull request #29541 from Expensify/vit-27836followup
Browse files Browse the repository at this point in the history
Allow Welcome Message on Room Creation
  • Loading branch information
Julesssss authored Oct 13, 2023
2 parents 749f17f + c56634a commit dc1ff84
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 17 deletions.
84 changes: 84 additions & 0 deletions src/components/ValuePicker/ValueSelectorModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, {useState, useEffect} from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import CONST from '../../CONST';
import HeaderWithBackButton from '../HeaderWithBackButton';
import SelectionList from '../SelectionList';
import Modal from '../Modal';
import ScreenWrapper from '../ScreenWrapper';
import styles from '../../styles/styles';

const propTypes = {
/** Whether the modal is visible */
isVisible: PropTypes.bool.isRequired,

/** Current value selected */
currentValue: PropTypes.string,

/** Items to pick from */
items: PropTypes.arrayOf(PropTypes.shape({value: PropTypes.string, label: PropTypes.string})),

/** The selected item */
selectedItem: PropTypes.shape({value: PropTypes.string, label: PropTypes.string}),

/** Label for values */
label: PropTypes.string,

/** Function to call when the user selects a item */
onItemSelected: PropTypes.func,

/** Function to call when the user closes the modal */
onClose: PropTypes.func,
};

const defaultProps = {
currentValue: '',
items: [],
selectedItem: {},
label: '',
onClose: () => {},
onItemSelected: () => {},
};

function ValueSelectorModal({currentValue, items, selectedItem, label, isVisible, onClose, onItemSelected}) {
const [sectionsData, setSectionsData] = useState([]);

useEffect(() => {
const itemsData = _.map(items, (item) => ({value: item.value, keyForList: item.value, text: item.label, isSelected: item === selectedItem}));
setSectionsData(itemsData);
}, [items, selectedItem]);

return (
<Modal
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible={isVisible}
onClose={onClose}
onModalHide={onClose}
hideModalContentWhileAnimating
useNativeDriver
>
<ScreenWrapper
style={[styles.pb0]}
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID="ValueSelectorModal"
>
<HeaderWithBackButton
title={label}
onBackButtonPress={onClose}
/>
<SelectionList
sections={[{data: sectionsData}]}
onSelectRow={onItemSelected}
initiallyFocusedOptionKey={currentValue}
/>
</ScreenWrapper>
</Modal>
);
}

ValueSelectorModal.propTypes = propTypes;
ValueSelectorModal.defaultProps = defaultProps;
ValueSelectorModal.displayName = 'ValueSelectorModal';

export default ValueSelectorModal;
102 changes: 102 additions & 0 deletions src/components/ValuePicker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'lodash';
import styles from '../../styles/styles';
import MenuItemWithTopDescription from '../MenuItemWithTopDescription';
import ValueSelectorModal from './ValueSelectorModal';
import FormHelpMessage from '../FormHelpMessage';
import refPropTypes from '../refPropTypes';

const propTypes = {
/** Form Error description */
errorText: PropTypes.string,

/** Item to display */
value: PropTypes.string,

/** A placeholder value to display */
placeholder: PropTypes.string,

/** Items to pick from */
items: PropTypes.arrayOf(PropTypes.shape({value: PropTypes.string, label: PropTypes.string})),

/** Label of picker */
label: PropTypes.string,

/** Callback to call when the input changes */
onInputChange: PropTypes.func,

/** A ref to forward to MenuItemWithTopDescription */
forwardedRef: refPropTypes,
};

const defaultProps = {
value: undefined,
label: undefined,
placeholder: '',
items: {},
forwardedRef: undefined,
errorText: '',
onInputChange: () => {},
};

function ValuePicker({value, label, items, placeholder, errorText, onInputChange, forwardedRef}) {
const [isPickerVisible, setIsPickerVisible] = useState(false);

const showPickerModal = () => {
setIsPickerVisible(true);
};

const hidePickerModal = () => {
setIsPickerVisible(false);
};

const updateInput = (item) => {
if (item.value !== value) {
onInputChange(item.value);
}
hidePickerModal();
};

const descStyle = value.length === 0 ? styles.textNormal : null;
const selectedItem = _.find(items, {value});
const selectedLabel = selectedItem ? selectedItem.label : '';

return (
<View>
<MenuItemWithTopDescription
ref={forwardedRef}
shouldShowRightIcon
title={selectedLabel || placeholder || ''}
descriptionTextStyle={descStyle}
description={label}
onPress={showPickerModal}
/>
<View style={styles.ml5}>
<FormHelpMessage message={errorText} />
</View>
<ValueSelectorModal
isVisible={isPickerVisible}
currentValue={selectedLabel || placeholder || ''}
label={label}
selectedItem={selectedItem}
items={items}
onClose={hidePickerModal}
onItemSelected={updateInput}
/>
</View>
);
}

ValuePicker.propTypes = propTypes;
ValuePicker.defaultProps = defaultProps;
ValuePicker.displayName = 'ValuePicker';

export default React.forwardRef((props, ref) => (
<ValuePicker
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
forwardedRef={ref}
/>
));
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@ export default {
},
welcomeMessagePage: {
welcomeMessage: 'Welcome message',
welcomeMessageOptional: 'Welcome message (optional)',
explainerText: 'Set a custom welcome message that will be sent to users when they join this room.',
},
languagePage: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ export default {
},
welcomeMessagePage: {
welcomeMessage: 'Mensaje de bienvenida',
welcomeMessageOptional: 'Mensaje de bienvenida (opcional)',
explainerText: 'Configura un mensaje de bienvenida privado y personalizado que se enviará cuando los usuarios se unan a esta sala de chat.',
},
languagePage: {
Expand Down
4 changes: 3 additions & 1 deletion src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2592,6 +2592,7 @@ function buildOptimisticTaskReportAction(taskReportID, actionName, message = '')
* @param {String} notificationPreference
* @param {String} parentReportActionID
* @param {String} parentReportID
* @param {String} welcomeMessage
* @returns {Object}
*/
function buildOptimisticChatReport(
Expand All @@ -2607,6 +2608,7 @@ function buildOptimisticChatReport(
notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
parentReportActionID = '',
parentReportID = '',
welcomeMessage = '',
) {
const currentTime = DateUtils.getDBTime();
return {
Expand All @@ -2633,7 +2635,7 @@ function buildOptimisticChatReport(
stateNum: 0,
statusNum: 0,
visibility,
welcomeMessage: '',
welcomeMessage,
writeCapability,
};
}
Expand Down
7 changes: 6 additions & 1 deletion src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -1430,8 +1430,9 @@ function navigateToConciergeChat(ignoreConciergeReportID = false) {
* @param {String} visibility
* @param {Array<Number>} policyMembersAccountIDs
* @param {String} writeCapability
* @param {String} welcomeMessage
*/
function addPolicyReport(policyID, reportName, visibility, policyMembersAccountIDs, writeCapability = CONST.REPORT.WRITE_CAPABILITIES.ALL) {
function addPolicyReport(policyID, reportName, visibility, policyMembersAccountIDs, writeCapability = CONST.REPORT.WRITE_CAPABILITIES.ALL, welcomeMessage = '') {
// The participants include the current user (admin), and for restricted rooms, the policy members. Participants must not be empty.
const members = visibility === CONST.REPORT.VISIBILITY.RESTRICTED ? policyMembersAccountIDs : [];
const participants = _.unique([currentUserAccountID, ...members]);
Expand All @@ -1448,6 +1449,9 @@ function addPolicyReport(policyID, reportName, visibility, policyMembersAccountI

// The room might contain all policy members so notifying always should be opt-in only.
CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY,
'',
'',
welcomeMessage,
);
const createdReportAction = ReportUtils.buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE);

Expand Down Expand Up @@ -1512,6 +1516,7 @@ function addPolicyReport(policyID, reportName, visibility, policyMembersAccountI
reportID: policyReport.reportID,
createdReportActionID: createdReportAction.reportActionID,
writeCapability,
welcomeMessage,
},
{optimisticData, successData, failureData},
);
Expand Down
53 changes: 38 additions & 15 deletions src/pages/workspace/WorkspaceNewRoomPage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useState, useCallback, useMemo, useRef} from 'react';
import React, {useState, useEffect, useCallback, useMemo, useRef} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
Expand All @@ -9,12 +9,12 @@ import * as App from '../../libs/actions/App';
import useLocalize from '../../hooks/useLocalize';
import styles from '../../styles/styles';
import RoomNameInput from '../../components/RoomNameInput';
import Picker from '../../components/Picker';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import ScreenWrapper from '../../components/ScreenWrapper';
import ONYXKEYS from '../../ONYXKEYS';
import CONST from '../../CONST';
import Text from '../../components/Text';
import TextInput from '../../components/TextInput';
import Permissions from '../../libs/Permissions';
import * as ErrorUtils from '../../libs/ErrorUtils';
import * as ValidationUtils from '../../libs/ValidationUtils';
Expand All @@ -26,6 +26,7 @@ import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoun
import compose from '../../libs/compose';
import variables from '../../styles/variables';
import useDelayedInputFocus from '../../hooks/useDelayedInputFocus';
import ValuePicker from '../../components/ValuePicker';

const propTypes = {
/** All reports shared with the user */
Expand Down Expand Up @@ -73,6 +74,7 @@ function WorkspaceNewRoomPage(props) {
const {translate} = useLocalize();
const [visibility, setVisibility] = useState(CONST.REPORT.VISIBILITY.RESTRICTED);
const [policyID, setPolicyID] = useState(null);
const [writeCapability, setWriteCapability] = useState(CONST.REPORT.WRITE_CAPABILITIES.ALL);
const visibilityDescription = useMemo(() => translate(`newRoomPage.${visibility}Description`), [translate, visibility]);
const isPolicyAdmin = useMemo(() => {
if (!policyID) {
Expand All @@ -87,9 +89,17 @@ function WorkspaceNewRoomPage(props) {
*/
const submit = (values) => {
const policyMembers = _.map(_.keys(props.allPolicyMembers[`${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${values.policyID}`]), (accountID) => Number(accountID));
Report.addPolicyReport(values.policyID, values.roomName, values.visibility, policyMembers, values.writeCapability);
Report.addPolicyReport(policyID, values.roomName, visibility, policyMembers, writeCapability, values.welcomeMessage);
};

useEffect(() => {
if (isPolicyAdmin) {
return;
}

setWriteCapability(CONST.REPORT.WRITE_CAPABILITIES.ALL);
}, [isPolicyAdmin]);

/**
* @param {Object} values - form input values passed by the Form component
* @returns {Boolean}
Expand Down Expand Up @@ -166,16 +176,15 @@ function WorkspaceNewRoomPage(props) {
>
{({insets}) => (
<KeyboardAvoidingView
style={{height: '100%'}}
behavior="height"
style={styles.h100}
behavior="padding"
// Offset is needed as KeyboardAvoidingView in nested inside of TabNavigator instead of wrapping whole screen.
// This is because when wrapping whole screen the screen was freezing when changing Tabs.
keyboardVerticalOffset={variables.contentHeaderHeight + variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding + insets.top}
>
<Form
formID={ONYXKEYS.FORMS.NEW_ROOM_FORM}
submitButtonText={translate('newRoomPage.createRoom')}
scrollContextEnabled
style={[styles.mh5, styles.flexGrow1]}
validate={validate}
onSubmit={submit}
Expand All @@ -190,32 +199,46 @@ function WorkspaceNewRoomPage(props) {
autoFocus
/>
</View>
<View style={styles.mb2}>
<Picker
<View style={styles.mb5}>
<TextInput
inputID="welcomeMessage"
label={translate('welcomeMessagePage.welcomeMessageOptional')}
accessibilityLabel={translate('welcomeMessagePage.welcomeMessageOptional')}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT}
autoGrowHeight
maxLength={CONST.MAX_COMMENT_LENGTH}
autoCapitalize="none"
textAlignVertical="top"
containerStyles={[styles.autoGrowHeightMultilineInput]}
/>
</View>
<View style={[styles.mhn5]}>
<ValuePicker
inputID="policyID"
label={translate('workspace.common.workspace')}
placeholder={{value: '', label: translate('newRoomPage.selectAWorkspace')}}
placeholder={translate('newRoomPage.selectAWorkspace')}
items={workspaceOptions}
onValueChange={setPolicyID}
/>
</View>
{isPolicyAdmin && (
<View style={styles.mb2}>
<Picker
<View style={styles.mhn5}>
<ValuePicker
inputID="writeCapability"
label={translate('writeCapabilityPage.label')}
items={writeCapabilityOptions}
defaultValue={CONST.REPORT.WRITE_CAPABILITIES.ALL}
value={writeCapability}
onValueChange={setWriteCapability}
/>
</View>
)}
<View style={styles.mb2}>
<Picker
<View style={[styles.mb1, styles.mhn5]}>
<ValuePicker
inputID="visibility"
label={translate('newRoomPage.visibility')}
items={visibilityOptions}
onValueChange={setVisibility}
defaultValue={CONST.REPORT.VISIBILITY.RESTRICTED}
value={visibility}
/>
</View>
<Text style={[styles.textLabel, styles.colorMuted]}>{visibilityDescription}</Text>
Expand Down

0 comments on commit dc1ff84

Please sign in to comment.