Permalink
Browse files

feat(Services): Improve user experience of service search

  • Loading branch information...
adlk committed Jan 3, 2018
1 parent 306c331 commit 7e784c699554dd85be3e9e219c59578995cadd38
@@ -16,6 +16,10 @@ const messages = defineMessages({
id: 'settings.recipes.headline',
defaultMessage: '!!!Available Services',
},
searchService: {
id: 'settings.searchService',
defaultMessage: '!!!Search service',
},
mostPopularRecipes: {
id: 'settings.recipes.mostPopular',
defaultMessage: '!!!Most popular',
@@ -81,13 +85,7 @@ export default class RecipesDashboard extends Component {
return (
<div className="settings__main">
<div className="settings__header">
<SearchInput
className="settings__search-header"
defaultValue={intl.formatMessage(messages.headline)}
onChange={e => searchRecipes(e)}
onReset={() => resetSearch()}
throttle
/>
<h1>{intl.formatMessage(messages.headline)}</h1>
</div>
<div className="settings__body recipes">
{serviceStatus.length > 0 && serviceStatus.includes('created') && (
@@ -101,7 +99,13 @@ export default class RecipesDashboard extends Component {
</Infobox>
</Appear>
)}
{/* {!searchNeedle && ( */}
<SearchInput
placeholder={intl.formatMessage(messages.searchService)}
onChange={e => searchRecipes(e)}
onReset={() => resetSearch()}
autoFocus
throttle
/>
<div className="recipes__navigation">
<Link
to="/settings/recipes"
@@ -15,10 +15,18 @@ const messages = defineMessages({
id: 'settings.services.headline',
defaultMessage: '!!!Your services',
},
searchService: {
id: 'settings.searchService',
defaultMessage: '!!!Search service',
},
noServicesAdded: {
id: 'settings.services.noServicesAdded',
defaultMessage: '!!!You haven\'t added any services yet.',
},
noServiceFound: {
id: 'settings.recipes.nothingFound',
defaultMessage: '!!!Sorry, but no service matched your search term.',
},
discoverServices: {
id: 'settings.services.discoverServices',
defaultMessage: '!!!Discover services',
@@ -53,7 +61,13 @@ export default class ServicesDashboard extends Component {
servicesRequestFailed: PropTypes.bool.isRequired,
retryServicesRequest: PropTypes.func.isRequired,
status: MobxPropTypes.arrayOrObservableArray.isRequired,
searchNeedle: PropTypes.string,
};
static defaultProps = {
searchNeedle: '',
}
static contextTypes = {
intl: intlShape,
};
@@ -69,20 +83,24 @@ export default class ServicesDashboard extends Component {
servicesRequestFailed,
retryServicesRequest,
status,
searchNeedle,
} = this.props;
const { intl } = this.context;
return (
<div className="settings__main">
<div className="settings__header">
<SearchInput
className="settings__search-header"
defaultValue={intl.formatMessage(messages.headline)}
onChange={needle => filterServices({ needle })}
onReset={() => resetFilter()}
/>
<h1>{intl.formatMessage(messages.headline)}</h1>
</div>
<div className="settings__body">
{!isLoading && (
<SearchInput
placeholder={intl.formatMessage(messages.searchService)}
onChange={needle => filterServices({ needle })}
onReset={() => resetFilter()}
autoFocus
/>
)}
{!isLoading && servicesRequestFailed && (
<div>
<Infobox
@@ -121,7 +139,7 @@ export default class ServicesDashboard extends Component {
</Appear>
)}
{!isLoading && services.length === 0 && (
{!isLoading && services.length === 0 && !searchNeedle && (
<div className="align-middle settings__empty-state">
<p className="settings__empty-text">
<span className="emoji">
@@ -132,6 +150,16 @@ export default class ServicesDashboard extends Component {
<Link to="/settings/recipes" className="button">{intl.formatMessage(messages.discoverServices)}</Link>
</div>
)}
{!isLoading && services.length === 0 && searchNeedle && (
<div className="align-middle settings__empty-state">
<p className="settings__empty-text">
<span className="emoji">
<img src="./assets/images/emoji/dontknow.png" alt="" />
</span>
{intl.formatMessage(messages.noServiceFound)}
</p>
</div>
)}
{isLoading ? (
<Loader />
) : (
@@ -9,36 +9,46 @@ import { debounce } from 'lodash';
export default class SearchInput extends Component {
static propTypes = {
value: PropTypes.string,
defaultValue: PropTypes.string,
placeholder: PropTypes.string,
className: PropTypes.string,
onChange: PropTypes.func,
onReset: PropTypes.func,
name: PropTypes.string,
throttle: PropTypes.bool,
throttleDelay: PropTypes.number,
autoFocus: PropTypes.bool,
};
static defaultProps = {
value: '',
defaultValue: '',
placeholder: '',
className: '',
name: uuidv1(),
throttle: false,
throttleDelay: 250,
onChange: () => null,
onReset: () => null,
autoFocus: false,
}
constructor(props) {
super(props);
this.state = {
value: props.value || props.defaultValue,
value: props.value,
};
this.throttledOnChange = debounce(this.throttledOnChange, this.props.throttleDelay);
}
componentDidMount() {
const { autoFocus } = this.props;
if (autoFocus) {
this.input.focus();
}
}
onChange(e) {
const { throttle, onChange } = this.props;
const { value } = e.target;
@@ -52,43 +62,23 @@ export default class SearchInput extends Component {
}
}
onClick() {
const { defaultValue } = this.props;
const { value } = this.state;
if (value === defaultValue) {
this.setState({ value: '' });
}
this.input.focus();
}
onBlur() {
const { defaultValue } = this.props;
const { value } = this.state;
if (value === '') {
this.setState({ value: defaultValue });
}
}
throttledOnChange(e) {
const { onChange } = this.props;
onChange(e);
}
reset() {
const { defaultValue, onReset } = this.props;
this.setState({ value: defaultValue });
const { onReset } = this.props;
this.setState({ value: '' });
onReset();
}
input = null;
render() {
const { className, name, defaultValue } = this.props;
const { className, name, placeholder } = this.props;
const { value } = this.state;
return (
@@ -101,18 +91,16 @@ export default class SearchInput extends Component {
<label
htmlFor={name}
className="mdi mdi-magnify"
onClick={() => this.onClick()}
/>
<input
name={name}
type="text"
placeholder={placeholder}
value={value}
onChange={e => this.onChange(e)}
onClick={() => this.onClick()}
onBlur={() => this.onBlur()}
ref={(ref) => { this.input = ref; }}
/>
{value !== defaultValue && value.length > 0 && (
{value.length > 0 && (
<span
className="mdi mdi-close-circle-outline"
onClick={() => this.reset()}
@@ -53,6 +53,7 @@ export default class ServicesScreen extends Component {
goTo={router.push}
servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
retryServicesRequest={() => services.allServicesRequest.reload()}
searchNeedle={services.filterNeedle}
/>
);
}
@@ -66,6 +66,7 @@
"sidebar.unmuteApp": "Enable notifications & audio",
"services.welcome": "Welcome to Franz",
"services.getStarted": "Get started",
"settings.searchService": "Search service",
"settings.account.headline": "Account",
"settings.account.headlineSubscription": "Your subscription",
"settings.account.headlineUpgrade": "Upgrade your account & support Franz",
@@ -1,4 +1,20 @@
.search-input {
width: 100%;
height: auto;
display: flex;
align-items: center;
padding: 0 10px;
border-radius: 30px;
background: $theme-gray-lightest;
padding: 5px 10px;
@extend %headline;
color: $theme-gray-light;
input {
padding-left: 10px;
background: none;
border: 0;
flex: 1;
color: $theme-gray-light;
}
}
@@ -129,26 +129,8 @@
}
}
.settings__search-header {
display: flex;
align-items: center;
padding: 0 10px;
border-radius: $theme-border-radius;
transition: background $theme-transition-time;
@extend %headline;
font-size: 22px;
&:hover {
background: darken($theme-gray-lighter, 5%);
}
input {
padding-left: 10px;
background: none;
border: 0;
flex: 1;
@extend %headline;
}
.search-input {
margin-bottom: 30px;
}
&__options {

0 comments on commit 7e784c6

Please sign in to comment.