Skip to content

Commit

Permalink
Merge pull request #7028 from Expensify/amal-policy-rooms-details-page
Browse files Browse the repository at this point in the history
Report Details and Settings for User Created Policy Rooms
  • Loading branch information
yuwenmemon committed Jan 12, 2022
2 parents e44706a + 6ddfd43 commit afcbb13
Show file tree
Hide file tree
Showing 16 changed files with 449 additions and 109 deletions.
File renamed without changes
2 changes: 2 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export default {
) => `r/${reportID}/participants/details?login=${encodeURIComponent(login)}`,
REPORT_WITH_ID_DETAILS: 'r/:reportID/details',
getReportDetailsRoute: reportID => `r/${reportID}/details`,
REPORT_SETTINGS: 'r/:reportID/settings',
getReportSettingsRoute: reportID => `r/${reportID}/settings`,
LOGIN_WITH_SHORT_LIVED_TOKEN: 'transition',
VALIDATE_LOGIN: 'v/:accountID/:validateCode',
GET_ASSISTANCE: 'get-assistance/:taskID',
Expand Down
7 changes: 6 additions & 1 deletion src/components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ const propTypes = {
/** Call the onPress function when Enter key is pressed */
pressOnEnter: PropTypes.bool,

/** Additional styles to add after local styles */
/** Additional styles to add after local styles. Applied to Pressable portion of button */
style: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.object),
PropTypes.object,
]),

/** Additional button styles. Specific to the OpacityView of button */
innerStyles: PropTypes.arrayOf(PropTypes.object),

/** Additional text styles */
textStyles: PropTypes.arrayOf(PropTypes.object),

Expand Down Expand Up @@ -82,6 +85,7 @@ const defaultProps = {
onPressOut: () => {},
pressOnEnter: false,
style: [],
innerStyles: [],
textStyles: [],
success: false,
danger: false,
Expand Down Expand Up @@ -195,6 +199,7 @@ class Button extends Component {
(this.props.danger && hovered) ? styles.buttonDangerHovered : undefined,
this.props.shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined,
this.props.shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined,
...this.props.innerStyles,
]}
>
{this.renderContent()}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Icon/Expensicons.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import DownArrow from '../../../assets/images/down.svg';
import Download from '../../../assets/images/download.svg';
import Emoji from '../../../assets/images/emoji.svg';
import Exclamation from '../../../assets/images/exclamation.svg';
import Exit from '../../../assets/images/exit.svg';
import Eye from '../../../assets/images/eye.svg';
import EyeDisabled from '../../../assets/images/eye-disabled.svg';
import ExpensifyCard from '../../../assets/images/expensifycard.svg';
Expand Down Expand Up @@ -58,7 +59,6 @@ import Receipt from '../../../assets/images/receipt.svg';
import ReceiptSearch from '../../../assets/images/receipt-search.svg';
import RotateLeft from '../../../assets/images/rotate-left.svg';
import Send from '../../../assets/images/send.svg';
import SignOut from '../../../assets/images/sign-out.svg';
import Sync from '../../../assets/images/sync.svg';
import Transfer from '../../../assets/images/transfer.svg';
import ThreeDots from '../../../assets/images/three-dots.svg';
Expand Down Expand Up @@ -95,6 +95,7 @@ export {
Download,
Emoji,
Exclamation,
Exit,
Eye,
EyeDisabled,
ExpensifyCard,
Expand Down Expand Up @@ -131,7 +132,6 @@ export {
ReceiptSearch,
RotateLeft,
Send,
SignOut,
Sync,
Transfer,
ThreeDots,
Expand Down
151 changes: 151 additions & 0 deletions src/components/RoomNameInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
import CONST from '../CONST';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
import compose from '../libs/compose';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import withFullPolicy, {fullPolicyDefaultProps, fullPolicyPropTypes} from '../pages/workspace/withFullPolicy';

import TextInputWithPrefix from './TextInputWithPrefix';

const propTypes = {
/** Callback to execute when the text input is modified correctly */
onChangeText: PropTypes.func,

/** Callback to execute when an error gets found/cleared/modified */
onChangeError: PropTypes.func,

/** Initial room name to show in input field. This should include the '#' already prefixed to the name */
initialValue: PropTypes.string,

/** Whether we should show the input as disabled */
disabled: PropTypes.bool,

/** ID of policy whose room names we should be checking for duplicates */
policyID: PropTypes.string,

...withLocalizePropTypes,
...fullPolicyPropTypes,

/* Onyx Props */

/** All reports shared with the user */
reports: PropTypes.shape({
/** The report name */
reportName: PropTypes.string,

/** ID of the report */
reportID: PropTypes.number,
}).isRequired,

/** The policies which the user has access to and which the report could be tied to */
policies: PropTypes.shape({
/** The policy name */
name: PropTypes.string,

/** ID of the policy */
id: PropTypes.string,
}).isRequired,
};

const defaultProps = {
onChangeText: () => {},
onChangeError: () => {},
initialValue: '',
disabled: false,
policyID: '',
...fullPolicyDefaultProps,
};

class RoomNameInput extends Component {
constructor(props) {
super(props);
this.state = {
roomName: props.initialValue,
error: '',
};

this.originalRoomName = props.initialValue;

this.checkAndModifyRoomName = this.checkAndModifyRoomName.bind(this);
}

componentDidUpdate(prevProps, prevState) {
// As we are modifying the text input, we'll bubble up any changes/errors so the parent component can see it
if (prevState.roomName !== this.state.roomName) {
this.props.onChangeText(this.state.roomName);
}
if (prevState.error !== this.state.error) {
this.props.onChangeError(this.state.error);
}
}

/**
* Modifies the room name to follow our conventions:
* - Max length 80 characters
* - Cannot not include space or special characters, and we automatically apply an underscore for spaces
* - Must be lowercase
* Also checks to see if this room name already exists, and displays an error message if so.
* @param {String} roomName
*
*/
checkAndModifyRoomName(roomName) {
const modifiedRoomNameWithoutHash = roomName.substring(1)
.replace(/ /g, '_')
.replace(/[^a-zA-Z\d_]/g, '')
.substring(0, CONST.REPORT.MAX_ROOM_NAME_LENGTH)
.toLowerCase();
const finalRoomName = `#${modifiedRoomNameWithoutHash}`;

const isExistingRoomName = _.some(
_.values(this.props.reports),
report => report && report.policyID === this.props.policyID && report.reportName === finalRoomName,
);

// We error if the room name already exists. We don't care if it matches the original name provided in this
// component because then we are not changing the room's name.
const error = isExistingRoomName && finalRoomName !== this.originalRoomName
? this.props.translate('newRoomPage.roomAlreadyExists')
: '';

this.setState({
roomName: finalRoomName,
error,
});
}

render() {
return (
<TextInputWithPrefix
disabled={this.props.disabled}
label={this.props.translate('newRoomPage.roomName')}
prefixCharacter="#"
placeholder={this.props.translate('newRoomPage.social')}
containerStyles={[styles.mb5]}
onChangeText={roomName => this.checkAndModifyRoomName(roomName)}
value={this.state.roomName.substring(1)}
errorText={this.state.error}
autoCapitalize="none"
/>
);
}
}

RoomNameInput.propTypes = propTypes;
RoomNameInput.defaultProps = defaultProps;

export default compose(
withLocalize,
withFullPolicy,
withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
}),
)(RoomNameInput);
6 changes: 4 additions & 2 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default {
transferBalance: 'Transfer Balance',
cantFindAddress: 'Can\'t find your address? ',
enterManually: 'Enter it manually',
leaveRoom: 'Leave room',
},
attachmentPicker: {
cameraPermissionRequired: 'Camera permission required',
Expand Down Expand Up @@ -213,8 +214,9 @@ export default {
other: 'Unexpected error, please try again later',
},
},
reportDetailsPage: {
notificationPreferencesDescription: 'Notify me about new messages',
notificationPreferences: {
description: 'How often should we notify you when there are new messages to catch up on in this room?',
label: 'Notify me about new messages',
always: 'Always',
daily: 'Daily',
mute: 'Mute',
Expand Down
6 changes: 4 additions & 2 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default {
transferBalance: 'Transferencia de saldo',
cantFindAddress: '¿No encuentras tu dirección? ',
enterManually: 'Ingresar manualmente',
leaveRoom: 'Salir de la sala de chat',
},
attachmentPicker: {
cameraPermissionRequired: 'Se necesita permiso para usar la cámara',
Expand Down Expand Up @@ -213,8 +214,9 @@ export default {
other: 'Error inesperado, por favor inténtalo más tarde',
},
},
reportDetailsPage: {
notificationPreferencesDescription: 'Avisar sobre nuevos mensajes',
notificationPreferences: {
description: '¿Con qué frecuencia podemos notificarle si haya nuevos mensajes en esta sala de chat?',
label: 'Avisar sobre nuevos mensajes',
always: 'Siempre',
daily: 'Cada día',
mute: 'Nunca',
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ class AuthScreens extends React.Component {
component={ModalStackNavigators.ReportDetailsModalStackNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name="Report_Settings"
options={modalScreenOptions}
component={ModalStackNavigators.ReportSettingsModalStackNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name="Participants"
options={modalScreenOptions}
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 @@ -44,6 +44,7 @@ import CONST from '../../../CONST';
import AddDebitCardPage from '../../../pages/settings/Payments/AddDebitCardPage';
import TransferBalancePage from '../../../pages/settings/Payments/TransferBalancePage';
import ChooseTransferAccountPage from '../../../pages/settings/Payments/ChooseTransferAccountPage';
import ReportSettingsPage from '../../../pages/ReportSettingsPage';

const defaultSubRouteOptions = {
cardStyle: styles.navigationScreenCardStyle,
Expand Down Expand Up @@ -141,6 +142,11 @@ const ReportDetailsModalStackNavigator = createModalStackNavigator([{
name: 'Report_Details_Root',
}]);

const ReportSettingsModalStackNavigator = createModalStackNavigator([{
Component: ReportSettingsPage,
name: 'Report_Settings_Root',
}]);

const ReportParticipantsModalStackNavigator = createModalStackNavigator([
{
Component: ReportParticipantsPage,
Expand Down Expand Up @@ -310,6 +316,7 @@ export {
IOUDetailsModalStackNavigator,
DetailsModalStackNavigator,
ReportDetailsModalStackNavigator,
ReportSettingsModalStackNavigator,
ReportParticipantsModalStackNavigator,
SearchModalStackNavigator,
NewGroupModalStackNavigator,
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ export default {
Report_Details_Root: ROUTES.REPORT_WITH_ID_DETAILS,
},
},
Report_Settings: {
screens: {
Report_Settings_Root: ROUTES.REPORT_SETTINGS,
},
},
NewGroup: {
screens: {
NewGroup_Root: ROUTES.NEW_GROUP,
Expand Down
4 changes: 4 additions & 0 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ function getSimplifiedReportObject(report) {
// Used for archived rooms, will store the policy name that the room used to belong to.
const oldPolicyName = lodashGet(report, ['reportNameValuePairs', 'oldPolicyName'], '');

// Used for User Created Policy Rooms, will denote how access to a chat room is given among workspace members
const visibility = lodashGet(report, ['reportNameValuePairs', 'visibility']);

return {
reportID: report.reportID,
reportName,
Expand All @@ -217,6 +220,7 @@ function getSimplifiedReportObject(report) {
stateNum: report.state,
statusNum: report.status,
oldPolicyName,
visibility,
};
}

Expand Down
Loading

0 comments on commit afcbb13

Please sign in to comment.