diff --git a/app/components/Toasts/Toasts.js b/app/components/Toasts/Toasts.js index 66989637d..65c185595 100644 --- a/app/components/Toasts/Toasts.js +++ b/app/components/Toasts/Toasts.js @@ -10,20 +10,16 @@ type Props = { }; @observer class Toasts extends React.Component { - handleClose = (index: number) => { - this.props.ui.removeToast(index); - }; - render() { const { ui } = this.props; return ( - {ui.toasts.map((toast, index) => ( + {ui.orderedToasts.map(toast => ( ui.removeToast(toast.id)} /> ))} diff --git a/app/components/Toasts/components/Toast.js b/app/components/Toasts/components/Toast.js index 9bc3ab7d5..b0d717609 100644 --- a/app/components/Toasts/components/Toast.js +++ b/app/components/Toasts/components/Toast.js @@ -15,7 +15,7 @@ class Toast extends React.Component { timeout: TimeoutID; static defaultProps = { - closeAfterMs: 3000, + closeAfterMs: 30000, }; componentDidMount() { @@ -37,15 +37,17 @@ class Toast extends React.Component { : toast.message.toString(); return ( - - {message} - +
  • + + {message} + +
  • ); } } -const Container = styled.li` - display: flex; +const Container = styled.div` + display: inline-block; align-items: center; animation: ${fadeAndScaleIn} 100ms ease; margin: 8px 0; diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js index 69dbd7790..694151f20 100644 --- a/app/scenes/Settings/Notifications.js +++ b/app/scenes/Settings/Notifications.js @@ -71,7 +71,7 @@ class Notifications extends React.Component { }; showSuccessMessage = debounce(() => { - this.props.ui.showToast('Notifications updated'); + this.props.ui.showToast('Notifications saved'); }, 500); render() { diff --git a/app/scenes/Settings/Security.js b/app/scenes/Settings/Security.js index d1a96f6c9..9daa6e826 100644 --- a/app/scenes/Settings/Security.js +++ b/app/scenes/Settings/Security.js @@ -2,11 +2,11 @@ import * as React from 'react'; import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; +import { debounce } from 'lodash'; import AuthStore from 'stores/AuthStore'; import UiStore from 'stores/UiStore'; import Checkbox from 'components/Checkbox'; -import Button from 'components/Button'; import CenteredContent from 'components/CenteredContent'; import PageTitle from 'components/PageTitle'; import HelpText from 'components/HelpText'; @@ -31,33 +31,29 @@ class Security extends React.Component { } } - handleSubmit = async (ev: SyntheticEvent<*>) => { - ev.preventDefault(); + handleChange = async (ev: SyntheticInputEvent<*>) => { + switch (ev.target.name) { + case 'sharing': + this.sharing = ev.target.checked; + break; + case 'documentEmbeds': + this.documentEmbeds = ev.target.checked; + break; + default: + } await this.props.auth.updateTeam({ sharing: this.sharing, documentEmbeds: this.documentEmbeds, }); - this.props.ui.showToast('Settings saved', 'success'); + this.showSuccessMessage(); }; - handleChange = (ev: SyntheticInputEvent<*>) => { - switch (ev.target.name) { - case 'sharing': - return (this.sharing = ev.target.checked); - case 'documentEmbeds': - return (this.documentEmbeds = ev.target.checked); - default: - } - }; - - get isValid() { - return this.form && this.form.checkValidity(); - } + showSuccessMessage = debounce(() => { + this.props.ui.showToast('Settings saved'); + }, 500); render() { - const { isSaving } = this.props.auth; - return ( @@ -67,25 +63,20 @@ class Security extends React.Component { knowledgebase. -
    (this.form = ref)}> - - - - + +
    ); } diff --git a/app/stores/UiStore.js b/app/stores/UiStore.js index e3c34c475..01f9bf4ab 100644 --- a/app/stores/UiStore.js +++ b/app/stores/UiStore.js @@ -1,5 +1,7 @@ // @flow -import { observable, action } from 'mobx'; +import { v4 } from 'uuid'; +import { orderBy } from 'lodash'; +import { observable, action, computed } from 'mobx'; import Document from 'models/Document'; import Collection from 'models/Collection'; import type { Toast } from '../types'; @@ -12,7 +14,7 @@ class UiStore { @observable progressBarVisible: boolean = false; @observable editMode: boolean = false; @observable mobileSidebarVisible: boolean = false; - @observable toasts: Toast[] = []; + @observable toasts: Map = new Map(); @action setActiveModal = (name: string, props: ?Object): void => { @@ -85,14 +87,23 @@ class UiStore { showToast = ( message: string, type?: 'warning' | 'error' | 'info' | 'success' = 'success' - ): void => { - this.toasts.push({ message, type }); + ) => { + const id = v4(); + const createdAt = new Date().toISOString(); + this.toasts.set(id, { message, type, createdAt, id }); + return id; }; @action - removeToast = (index: number): void => { - this.toasts.splice(index, 1); + removeToast = (id: string) => { + this.toasts.delete(id); }; + + @computed + get orderedToasts(): Toast[] { + // $FlowIssue + return orderBy(Array.from(this.toasts.values()), 'createdAt', 'desc'); + } } export default UiStore; diff --git a/app/types/index.js b/app/types/index.js index 0eb8a207b..7053d8185 100644 --- a/app/types/index.js +++ b/app/types/index.js @@ -2,6 +2,8 @@ import Document from 'models/Document'; export type Toast = { + id: string, + createdAt: string, message: string, type: 'warning' | 'error' | 'info' | 'success', };