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

[NHUB-93] Improve UX for Topic subscriptions #115

Merged
merged 1 commit into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion assets/components/EditPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import {gettext} from '../utils';
import {isEmpty, get, pickBy, isEqual} from 'lodash';
import {isEmpty, get, pickBy, isEqual, every} from 'lodash';
import CheckboxInput from 'components/CheckboxInput';

class EditPanel extends React.Component {
constructor(props) {
super(props);
this.onItemChange = this.onItemChange.bind(this);
this.toggleSelectAll = this.toggleSelectAll.bind(this);
this.saveItems = this.saveItems.bind(this);
this.initItems = this.initItems.bind(this);

Expand All @@ -18,6 +19,22 @@ class EditPanel extends React.Component {
const item = event.target.name;
const items = Object.assign({}, this.state.items);
items[item] = !items[item];
this.updateItems(items);
}

toggleSelectAll(availableItems, allActive) {
let items = {};

if (!allActive) {
availableItems.forEach((item) => {
items[item._id] = true;
});
}

this.updateItems(items);
}

updateItems(items) {
this.setState({items});
if (this.props.onChange) {
this.props.onChange({
Expand Down Expand Up @@ -63,8 +80,22 @@ class EditPanel extends React.Component {
}

renderList(items) {
const allActive = every(items, (item) => {
return !!this.state.items[item._id];
});

return (
<ul className="list-unstyled">
{!this.props.includeSelectAll ? null : (
<li style={{borderBottom: '1px dotted #cacaca'}}>
<CheckboxInput
name="select_all"
label={allActive ? gettext('Deselect All') : gettext('Select All')}
onChange={() => this.toggleSelectAll(items, allActive)}
value={allActive}
/>
</li>
)}
{items.map((item) => (
<li key={item._id}>
<CheckboxInput
Expand All @@ -83,6 +114,9 @@ class EditPanel extends React.Component {
<div className='tab-pane active' id='navigations'>
<form onSubmit={this.saveItems}>
<div className="list-item__preview-form">
{!this.props.title ? null : (
<div>{this.props.title}</div>
)}
{!isEmpty(this.props.groups) && this.props.groups.map((group) => (
<div className="form-group" key={group._id}>
<label>{group.name}</label>
Expand Down Expand Up @@ -130,6 +164,8 @@ EditPanel.propTypes = {
cancelDisabled: PropTypes.bool,
saveDisabled: PropTypes.bool,
onCancel: PropTypes.func,
includeSelectAll: PropTypes.bool,
title: PropTypes.string,
};

export default EditPanel;
30 changes: 20 additions & 10 deletions assets/search/components/TopicEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {loadMyWireTopic} from 'wire/actions';
import {loadMyAgendaTopic} from 'agenda/actions';
import EditPanel from 'components/EditPanel';
import AuditInformation from 'components/AuditInformation';
import {ToolTip} from 'ui/components/ToolTip';

class TopicEditor extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -73,8 +74,8 @@ class TopicEditor extends React.Component {
return (!topic._id || !topic.is_global || !this.props.isAdmin) ?
[] :
[
{label: gettext('Company Topic'), name: 'topic'},
{label: gettext('Subscribers'), name: 'subscribers'},
{label: gettext('Company Topic'), name: 'topic', tooltip: gettext('Edit Metadata')},
{label: gettext('Subscribers'), name: 'subscribers', tooltip: gettext('Email Notifications')},
];
}

Expand Down Expand Up @@ -116,9 +117,11 @@ class TopicEditor extends React.Component {
if (this.state.topic.notifications) {
unsubscribeToTopic(this.state.topic);
topic.notifications = false;
topic.subscribers = topic.subscribers.filter((userId) => userId !== this.props.userId);
} else {
subscribeToTopic(this.state.topic);
topic.notifications = true;
topic.subscribers.push(this.props.userId);
}

this.setState({topic});
Expand Down Expand Up @@ -245,13 +248,19 @@ class TopicEditor extends React.Component {
{!showTabs ? null : (
<ul className='nav nav-tabs'>
{this.state.tabs.map((tab) => (
<li key={tab.name} className='nav-item'>
<a
name={tab.name}
className={`nav-link ${this.state.activeTab === tab.name && 'active'}`}
href='#'
onClick={this.handleTabClick}>{tab.label}
</a>
<li
key={tab.name}
className='nav-item'
>
<ToolTip placement="bottom">
<a
name={tab.name}
className={`nav-link ${this.state.activeTab === tab.name && 'active'}`}
href='#'
title={tab.tooltip}
onClick={this.handleTabClick}>{tab.label}
</a>
</ToolTip>
</li>
))}
</ul>
Expand All @@ -268,7 +277,6 @@ class TopicEditor extends React.Component {
topic={updatedTopic}
save={this.saveTopic}
onChange={this.onChangeHandler}
showSubscribeButton={!showTabs && originalTopic.is_global}
onSubscribeChanged={this.onSubscribeChanged}
readOnly={isReadOnly}
/>
Expand All @@ -291,6 +299,8 @@ class TopicEditor extends React.Component {
onCancel={this.props.closeEditor}
saveDisabled={this.state.saving || !this.state.valid}
cancelDisabled={this.state.saving}
includeSelectAll={true}
title={gettext('Send notifications to:')}
/>
)}
</div>
Expand Down
36 changes: 25 additions & 11 deletions assets/search/components/TopicForm.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import TextInput from 'components/TextInput';
import CheckboxInput from 'components/CheckboxInput';
import {ToolTip} from 'ui/components/ToolTip';

import {gettext} from 'utils';

const TOPIC_NAME_MAXLENGTH = 30;

const TopicForm = ({original, topic, save, onChange, globalTopicsEnabled, onSubscribeChanged, readOnly, showSubscribeButton}) => (
const TopicForm = ({original, topic, save, onChange, globalTopicsEnabled, onSubscribeChanged, readOnly}) => (
<div>
<form onSubmit={save}>
{showSubscribeButton && (
{original._id == null ? null : (
<div className="form-group">
<input
name="notifications"
type="button"
className="btn btn-outline-primary"
value={(topic.notifications || false) ? gettext('Unsubscribe') : gettext('Subscribe')}
onClick={onSubscribeChanged}
/>
<label htmlFor="notifications">{gettext('Email Notifications:')}</label>
<div className="field">
<ToolTip>
<button
type="button"
className={classNames(
'btn',
{
'btn-primary': topic.notifications,
'btn-outline-primary': !topic.notifications
}
)}
title={gettext('Toggle email notifications')}
name="notifications"
onClick={onSubscribeChanged}
>
{(topic.notifications || false) ? gettext('Unsubscribe') : gettext('Subscribe')}
</button>
</ToolTip>
</div>
</div>
)}
<TextInput
Expand All @@ -31,7 +46,7 @@ const TopicForm = ({original, topic, save, onChange, globalTopicsEnabled, onSubs
autoFocus={true}
readOnly={readOnly}
/>
{!(original._id == null || !original.is_global) ? null : (
{original._id != null ? null : (
<CheckboxInput
label={gettext('Send me notifications')}
value={topic.notifications || false}
Expand Down Expand Up @@ -62,7 +77,6 @@ TopicForm.propTypes = {
onSubscribeChanged: PropTypes.func.isRequired,
save: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
showSubscribeButton: PropTypes.bool,
};

export default TopicForm;
30 changes: 18 additions & 12 deletions assets/ui/components/ToolTip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,33 @@ import {isTouchDevice} from 'utils';

export class ToolTip extends React.PureComponent {
getFirstChild() {
return !isTouchDevice() && this.elem && this.elem.firstChild && this.elem.firstChild ?
return this.elem && this.elem.firstChild && this.elem.firstChild ?
this.elem.firstChild :
null;
}

componentDidMount() {
const child = this.getFirstChild();

if (!child) {
console.error('No child supplied to <ToolTip>!');
} else if (!child.getAttribute('title')) {
console.error('Child of <ToolTip> must have a "title" attribute!');
} else {
$(child).tooltip({trigger: 'hover'});
if (!isTouchDevice()) {
const child = this.getFirstChild();

if (!child) {
console.error('No child supplied to <ToolTip>!');
} else if (!child.getAttribute('title')) {
console.error('Child of <ToolTip> must have a "title" attribute!');
} else {
$(child).tooltip({trigger: 'hover', placement: this.props.placement || 'top'});
}
}

}

componentWillUnmount() {
const child = this.getFirstChild();
if (!isTouchDevice()) {
const child = this.getFirstChild();

if (child) {
$(child).tooltip('dispose'); // make sure it's gone
if (child) {
$(child).tooltip('dispose'); // make sure it's gone
}
}
}

Expand All @@ -43,4 +48,5 @@ export class ToolTip extends React.PureComponent {

ToolTip.propTypes = {
children: PropTypes.node.isRequired,
placement: PropTypes.string,
};
2 changes: 1 addition & 1 deletion assets/user-profile/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ export const globalTopicsEnabledSelector = (state, context) => context === 'moni
state,
`uiConfigs.${context}.enable_global_topics`,
DEFAULT_ENABLE_GLOBAL_TOPICS
) === true && get(state, 'user.company.length');
) === true && get(state, 'user.company.length') > 0;
2 changes: 1 addition & 1 deletion assets/wire/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const actions = PropTypes.arrayOf(PropTypes.shape({
const topic = PropTypes.shape({
_id: PropTypes.string,
_created: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
name: PropTypes.string,
label: PropTypes.string.isRequired,
description: PropTypes.string,
is_global: PropTypes.bool,
Expand Down