AA-305: Update Masquerade Widget UI (#188)
* AA-305: Update Masquerade Widget UI This will start showing the user being masqueraded as in the Toolbar and will show any error messages next to the Toolbar as well. * Further masquerade widget fixes Co-authored-by: Dillon Dumesnil <ddumesnil@edx.org>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { ALERT_TYPES } from '../generic/user-messages';
|
||||
import Alert from '../generic/user-messages/Alert';
|
||||
import MasqueradeWidget from './masquerade-widget';
|
||||
|
||||
function getInsightsUrl(courseId) {
|
||||
@@ -48,28 +50,47 @@ export default function InstructorToolbar(props) {
|
||||
return activeUnit ? activeUnit.lmsWebUrl : undefined;
|
||||
});
|
||||
const urlStudio = getStudioUrl(courseId, unitId);
|
||||
const [masqueradeErrorMessage, showMasqueradeError] = useState(null);
|
||||
return (
|
||||
<div className="bg-primary text-light">
|
||||
<div className="container-fluid py-3 d-md-flex justify-content-end align-items-center">
|
||||
<div className="align-items-center flex-grow-1 d-md-flex mx-1 my-1">
|
||||
<MasqueradeWidget courseId={courseId} />
|
||||
<div>
|
||||
<div className="bg-primary text-light">
|
||||
<div className="container-fluid py-3 d-md-flex justify-content-end align-items-start">
|
||||
<div className="align-items-center flex-grow-1 d-md-flex mx-1 my-1">
|
||||
<MasqueradeWidget courseId={courseId} onError={showMasqueradeError} />
|
||||
</div>
|
||||
{(urlLms || urlStudio || urlInsights) && (
|
||||
<>
|
||||
<hr className="border-light" />
|
||||
<span className="mr-2 mt-1 col-form-label">View course in:</span>
|
||||
</>
|
||||
)}
|
||||
{urlLms && (
|
||||
<span className="mx-1 my-1">
|
||||
<a className="btn btn-outline-light" href={urlLms}>Existing experience</a>
|
||||
</span>
|
||||
)}
|
||||
{urlStudio && (
|
||||
<span className="mx-1 my-1">
|
||||
<a className="btn btn-outline-light" href={urlStudio}>Studio</a>
|
||||
</span>
|
||||
)}
|
||||
{urlInsights && (
|
||||
<span className="mx-1 my-1">
|
||||
<a className="btn btn-outline-light" href={urlInsights}>Insights</a>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{urlLms && (
|
||||
<div className="flex-shrink-0 mx-1 my-1">
|
||||
<a className="btn d-block btn-outline-light" href={urlLms}>View in the existing experience</a>
|
||||
</div>
|
||||
)}
|
||||
{urlStudio && (
|
||||
<div className="flex-shrink-0 mx-1 my-1">
|
||||
<a className="btn d-block btn-outline-light" href={urlStudio}>View in Studio</a>
|
||||
</div>
|
||||
)}
|
||||
{urlInsights && (
|
||||
<div className="flex-shrink-0 mx-1 my-1">
|
||||
<a className="btn d-block btn-outline-light" href={urlInsights}>View in Insights</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{masqueradeErrorMessage && (
|
||||
<div className="container-fluid mt-3">
|
||||
<Alert
|
||||
type={ALERT_TYPES.ERROR}
|
||||
dismissible={false}
|
||||
>
|
||||
{masqueradeErrorMessage}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,22 +32,25 @@ class MasqueradeUserNameInput extends Component {
|
||||
this.onError(error);
|
||||
}
|
||||
}).catch(() => {
|
||||
const message = this.props.intl.formatMessage(messages['userName.error.generic']);
|
||||
const message = this.props.intl.formatMessage(messages.genericError);
|
||||
this.onError(message);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
onError,
|
||||
onSubmit,
|
||||
...rest
|
||||
} = this.props;
|
||||
return (
|
||||
<Input
|
||||
autoFocus
|
||||
className="flex-shrink-1"
|
||||
defaultValue=""
|
||||
label={this.props.intl.formatMessage(messages['userName.input.label'])}
|
||||
label={intl.formatMessage(messages.userNameLabel)}
|
||||
onKeyPress={(event) => this.onKeyPress(event)}
|
||||
placeholder={this.props.intl.formatMessage(messages['userName.input.placeholder'])}
|
||||
type="text"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import {
|
||||
ALERT_TYPES,
|
||||
UserMessagesContext,
|
||||
} from '../../generic/user-messages';
|
||||
import { UserMessagesContext } from '../../generic/user-messages';
|
||||
|
||||
import MasqueradeUserNameInput from './MasqueradeUserNameInput';
|
||||
import MasqueradeWidgetOption from './MasqueradeWidgetOption';
|
||||
@@ -23,8 +20,11 @@ class MasqueradeWidget extends Component {
|
||||
super(props);
|
||||
this.courseId = props.courseId;
|
||||
this.state = {
|
||||
autoFocus: false,
|
||||
masquerade: 'Staff',
|
||||
options: [],
|
||||
shouldShowUserNameInput: false,
|
||||
masqueradeUsername: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,18 +48,11 @@ class MasqueradeWidget extends Component {
|
||||
}
|
||||
|
||||
onError(message) {
|
||||
if (message) {
|
||||
this.errorAlertId = this.context.add({
|
||||
text: message,
|
||||
topic: 'course',
|
||||
type: ALERT_TYPES.ERROR,
|
||||
dismissible: false,
|
||||
});
|
||||
}
|
||||
this.props.onError(message);
|
||||
}
|
||||
|
||||
async onSubmit(payload) {
|
||||
this.context.remove(this.errorAlertId);
|
||||
this.clearError();
|
||||
const options = await postMasqueradeOptions(this.courseId, payload);
|
||||
return options;
|
||||
}
|
||||
@@ -69,47 +62,18 @@ class MasqueradeWidget extends Component {
|
||||
this.setState({
|
||||
options,
|
||||
});
|
||||
const active = data.active || {};
|
||||
const message = this.getStatusMessage(active);
|
||||
if (message) {
|
||||
this.context.add({
|
||||
text: message,
|
||||
topic: 'course',
|
||||
type: ALERT_TYPES.INFO,
|
||||
dismissible: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusMessage(active) {
|
||||
const {
|
||||
groupName,
|
||||
} = active;
|
||||
let message = '';
|
||||
if (active.userName) {
|
||||
message = this.props.intl.formatMessage(messages['status.userName'], {
|
||||
userName: active.userName,
|
||||
});
|
||||
} else if (groupName) {
|
||||
message = this.props.intl.formatMessage(messages['status.groupName'], {
|
||||
groupName,
|
||||
});
|
||||
} else if (active.role === 'student') {
|
||||
message = this.props.intl.formatMessage(messages['status.learner']);
|
||||
}
|
||||
return message;
|
||||
clearError() {
|
||||
this.props.onError('');
|
||||
}
|
||||
|
||||
toggle(show) {
|
||||
let shouldShow;
|
||||
if (show === undefined) {
|
||||
shouldShow = !this.state.shouldShowUserNameInput;
|
||||
} else {
|
||||
shouldShow = show;
|
||||
}
|
||||
this.setState({
|
||||
shouldShowUserNameInput: shouldShow,
|
||||
});
|
||||
this.setState(prevState => ({
|
||||
autoFocus: true,
|
||||
masquerade: 'Specific Student...',
|
||||
shouldShowUserNameInput: show === undefined ? !prevState.shouldShowUserNameInput : show,
|
||||
}));
|
||||
}
|
||||
|
||||
parseAvailableOptions(postData) {
|
||||
@@ -125,47 +89,68 @@ class MasqueradeWidget extends Component {
|
||||
selected={active}
|
||||
userName={group.userName}
|
||||
userPartitionId={group.userPartitionId}
|
||||
userNameInput={this.userNameInput}
|
||||
userNameInputToggle={(...args) => this.toggle(...args)}
|
||||
onSubmit={(payload) => this.onSubmit(payload)}
|
||||
/>
|
||||
));
|
||||
if (active.userName) {
|
||||
this.setState({
|
||||
autoFocus: false,
|
||||
masquerade: 'Specific Student...',
|
||||
masqueradeUsername: active.userName,
|
||||
shouldShowUserNameInput: true,
|
||||
});
|
||||
} else if (active.groupName) {
|
||||
this.setState({ masquerade: active.groupName });
|
||||
} else if (active.role === 'student') {
|
||||
this.setState({ masquerade: 'Learner' });
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
autoFocus,
|
||||
masquerade,
|
||||
options,
|
||||
shouldShowUserNameInput,
|
||||
masqueradeUsername,
|
||||
} = this.state;
|
||||
const specificLearnerInputText = this.props.intl.formatMessage(messages.placeholder);
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
className="flex-shrink-1 mx-1 my-1"
|
||||
style={{ textAlign: 'center' }}
|
||||
>
|
||||
<Dropdown.Toggle variant="light">
|
||||
View this course as
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{options}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
{this.state.shouldShowUserNameInput && (
|
||||
<MasqueradeUserNameInput
|
||||
className="flex-shrink-0 mx-1 my-1"
|
||||
label="test"
|
||||
onError={(errorMessage) => this.onError(errorMessage)}
|
||||
onSubmit={(payload) => this.onSubmit(payload)}
|
||||
ref={(input) => { this.userNameInput = input; }}
|
||||
/>
|
||||
<div className="flex-grow-1">
|
||||
<div className="row">
|
||||
<span className="col-auto col-form-label pl-3">View this course as:</span>
|
||||
<Dropdown className="flex-shrink-1 mx-1">
|
||||
<Dropdown.Toggle variant="light">
|
||||
{masquerade}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{options}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{shouldShowUserNameInput && (
|
||||
<div className="row mt-2">
|
||||
<span className="col-auto col-form-label pl-3">{`${specificLearnerInputText}:`}</span>
|
||||
<MasqueradeUserNameInput
|
||||
id="masquerade-search"
|
||||
className="col-4 form-control"
|
||||
autoFocus={autoFocus}
|
||||
defaultValue={masqueradeUsername}
|
||||
onError={(errorMessage) => this.onError(errorMessage)}
|
||||
onSubmit={(payload) => this.onSubmit(payload)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
MasqueradeWidget.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
onError: PropTypes.func.isRequired,
|
||||
};
|
||||
MasqueradeWidget.contextType = UserMessagesContext;
|
||||
export default injectIntl(MasqueradeWidget);
|
||||
|
||||
@@ -1,36 +1,21 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'status.groupName': {
|
||||
id: 'masquerade-widget.status.groupName',
|
||||
defaultMessage: 'You are masquerading as a learner in the {groupName} group.',
|
||||
description: 'Message when masquerading as a generic user in a specific track',
|
||||
},
|
||||
'status.learner': {
|
||||
id: 'masquerade-widget.status.learner',
|
||||
defaultMessage: 'You are masquerading as a learner.',
|
||||
description: 'Message when masquerading as a specific user',
|
||||
},
|
||||
'status.userName': {
|
||||
id: 'masquerade-widget.status.userName',
|
||||
defaultMessage: 'You are masquerading as the following user: {userName}',
|
||||
description: 'Message when masquerading as a specific user',
|
||||
},
|
||||
'userName.input.label': {
|
||||
id: 'masquerade-widget.userName.input.label',
|
||||
defaultMessage: 'Masquerade as this user',
|
||||
description: 'Label for the masquerade user input',
|
||||
},
|
||||
'userName.error.generic': {
|
||||
genericError: {
|
||||
id: 'masquerade-widget.userName.error.generic',
|
||||
defaultMessage: 'An error has occurred; please try again.',
|
||||
description: 'Message shown after a general error when attempting to masquerade',
|
||||
},
|
||||
'userName.input.placeholder': {
|
||||
placeholder: {
|
||||
id: 'masquerade-widget.userName.input.placeholder',
|
||||
defaultMessage: 'username or email',
|
||||
defaultMessage: 'Username or email',
|
||||
description: 'Placeholder text to prompt for a user to masquerade as',
|
||||
},
|
||||
userNameLabel: {
|
||||
id: 'masquerade-widget.userName.input.label',
|
||||
defaultMessage: 'Masquerade as this user',
|
||||
description: 'Label for the masquerade user input',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
Reference in New Issue
Block a user