diff --git a/app/components/DropToImport.js b/app/components/DropToImport.js index bdb33dbd..5442ffb7 100644 --- a/app/components/DropToImport.js +++ b/app/components/DropToImport.js @@ -60,7 +60,9 @@ class DropToImport extends React.Component { } } } catch (err) { - this.props.ui.showToast(`Could not import file. ${err.message}`); + this.props.ui.showToast(`Could not import file. ${err.message}`, { + type: "error", + }); } finally { this.isImporting = false; importingLock = false; diff --git a/app/components/Sidebar/components/EditableTitle.js b/app/components/Sidebar/components/EditableTitle.js index a0af7f5a..057c908e 100644 --- a/app/components/Sidebar/components/EditableTitle.js +++ b/app/components/Sidebar/components/EditableTitle.js @@ -52,7 +52,9 @@ function EditableTitle({ title, onSubmit, canUpdate }: Props) { setOriginalValue(value); } catch (error) { setValue(originalValue); - ui.showToast(error.message); + ui.showToast(error.message, { + type: "error", + }); throw error; } } diff --git a/app/components/SocketProvider.js b/app/components/SocketProvider.js index 154b8b37..67541c78 100644 --- a/app/components/SocketProvider.js +++ b/app/components/SocketProvider.js @@ -110,7 +110,9 @@ class SocketProvider extends React.Component { this.socket.on("unauthorized", (err) => { this.socket.authenticated = false; - ui.showToast(err.message); + ui.showToast(err.message, { + type: "error", + }); throw err; }); diff --git a/app/components/Toasts/components/Toast.js b/app/components/Toasts/components/Toast.js index da1389f0..f8468556 100644 --- a/app/components/Toasts/components/Toast.js +++ b/app/components/Toasts/components/Toast.js @@ -1,4 +1,5 @@ // @flow +import { CheckboxIcon, InfoIcon, WarningIcon } from "outline-icons"; import { darken } from "polished"; import * as React from "react"; import styled, { css } from "styled-components"; @@ -14,7 +15,7 @@ type Props = { function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) { const timeout = React.useRef(); const [pulse, setPulse] = React.useState(false); - const { action, reoccurring } = toast; + const { action, type = "info", reoccurring } = toast; React.useEffect(() => { timeout.current = setTimeout(onRequestClose, toast.timeout || closeAfterMs); @@ -42,6 +43,10 @@ function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) { onClick={action ? undefined : onRequestClose} type={toast.type || "success"} > + {type === "info" && } + {type === "success" && } + {type === "warning" || + (type === "error" && )} {message} {action && ( @@ -78,10 +83,11 @@ const ListItem = styled.li` `; const Container = styled.div` - display: inline-block; + display: inline-flex; align-items: center; animation: ${fadeAndScaleIn} 100ms ease; margin: 8px 0; + padding: 0 12px; color: ${(props) => props.theme.toastText}; background: ${(props) => props.theme.toastBackground}; font-size: 15px; @@ -95,7 +101,8 @@ const Container = styled.div` const Message = styled.div` display: inline-block; - padding: 10px 12px; + font-weight: 500; + padding: 10px 4px; `; export default Toast; diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js index 0dc69b56..fae20725 100644 --- a/app/menus/CollectionMenu.js +++ b/app/menus/CollectionMenu.js @@ -66,7 +66,9 @@ class CollectionMenu extends React.Component { ); this.props.history.push(document.url); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { + type: "error", + }); } }; diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js index 31289f0b..3ab8aa6c 100644 --- a/app/menus/DocumentMenu.js +++ b/app/menus/DocumentMenu.js @@ -86,7 +86,7 @@ class DocumentMenu extends React.Component { // when duplicating, go straight to the duplicated document content this.redirectTo = duped.url; const { t } = this.props; - this.props.ui.showToast(t("Document duplicated")); + this.props.ui.showToast(t("Document duplicated"), { type: "success" }); }; handleOpenTemplateModal = () => { @@ -104,7 +104,7 @@ class DocumentMenu extends React.Component { handleArchive = async (ev: SyntheticEvent<>) => { await this.props.document.archive(); const { t } = this.props; - this.props.ui.showToast(t("Document archived")); + this.props.ui.showToast(t("Document archived"), { type: "success" }); }; handleRestore = async ( @@ -113,13 +113,13 @@ class DocumentMenu extends React.Component { ) => { await this.props.document.restore(options); const { t } = this.props; - this.props.ui.showToast(t("Document restored")); + this.props.ui.showToast(t("Document restored"), { type: "success" }); }; handleUnpublish = async (ev: SyntheticEvent<>) => { await this.props.document.unpublish(); const { t } = this.props; - this.props.ui.showToast(t("Document unpublished")); + this.props.ui.showToast(t("Document unpublished"), { type: "success" }); }; handlePin = (ev: SyntheticEvent<>) => { diff --git a/app/menus/RevisionMenu.js b/app/menus/RevisionMenu.js index 59311281..5a2ceae3 100644 --- a/app/menus/RevisionMenu.js +++ b/app/menus/RevisionMenu.js @@ -28,13 +28,13 @@ class RevisionMenu extends React.Component { ev.preventDefault(); await this.props.document.restore({ revisionId: this.props.revision.id }); const { t } = this.props; - this.props.ui.showToast(t("Document restored")); + this.props.ui.showToast(t("Document restored"), { type: "success" }); this.props.history.push(this.props.document.url); }; handleCopy = () => { const { t } = this.props; - this.props.ui.showToast(t("Link copied")); + this.props.ui.showToast(t("Link copied"), { type: "info" }); }; render() { diff --git a/app/menus/ShareMenu.js b/app/menus/ShareMenu.js index 6c3404a0..c4cbbb0b 100644 --- a/app/menus/ShareMenu.js +++ b/app/menus/ShareMenu.js @@ -39,15 +39,15 @@ class ShareMenu extends React.Component { try { await this.props.shares.revoke(this.props.share); const { t } = this.props; - this.props.ui.showToast(t("Share link revoked")); + this.props.ui.showToast(t("Share link revoked"), { type: "info" }); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } }; handleCopy = () => { const { t } = this.props; - this.props.ui.showToast(t("Share link copied")); + this.props.ui.showToast(t("Share link copied"), { type: "info" }); }; render() { diff --git a/app/scenes/CollectionDelete.js b/app/scenes/CollectionDelete.js index 44c26294..13e93ea3 100644 --- a/app/scenes/CollectionDelete.js +++ b/app/scenes/CollectionDelete.js @@ -32,7 +32,7 @@ class CollectionDelete extends React.Component { this.props.history.push(homeUrl()); this.props.onSubmit(); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isDeleting = false; } diff --git a/app/scenes/CollectionEdit.js b/app/scenes/CollectionEdit.js index dedf1b2c..5efa21f6 100644 --- a/app/scenes/CollectionEdit.js +++ b/app/scenes/CollectionEdit.js @@ -47,9 +47,11 @@ class CollectionEdit extends React.Component { sort: this.sort, }); this.props.onSubmit(); - this.props.ui.showToast(t("The collection was updated")); + this.props.ui.showToast(t("The collection was updated"), { + type: "success", + }); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } diff --git a/app/scenes/CollectionMembers/AddGroupsToCollection.js b/app/scenes/CollectionMembers/AddGroupsToCollection.js index 39225c12..5912a2d1 100644 --- a/app/scenes/CollectionMembers/AddGroupsToCollection.js +++ b/app/scenes/CollectionMembers/AddGroupsToCollection.js @@ -67,10 +67,11 @@ class AddGroupsToCollection extends React.Component { this.props.ui.showToast( t("{{ groupName }} was added to the collection", { groupName: group.name, - }) + }), + { type: "success" } ); } catch (err) { - this.props.ui.showToast(t("Could not add user")); + this.props.ui.showToast(t("Could not add user"), { type: "error" }); console.error(err); } }; diff --git a/app/scenes/CollectionMembers/AddPeopleToCollection.js b/app/scenes/CollectionMembers/AddPeopleToCollection.js index 8b30db4f..33543123 100644 --- a/app/scenes/CollectionMembers/AddPeopleToCollection.js +++ b/app/scenes/CollectionMembers/AddPeopleToCollection.js @@ -62,10 +62,13 @@ class AddPeopleToCollection extends React.Component { permission: "read_write", }); this.props.ui.showToast( - t("{{ userName }} was added to the collection", { userName: user.name }) + t("{{ userName }} was added to the collection", { + userName: user.name, + }), + { type: "success" } ); } catch (err) { - this.props.ui.showToast(t("Could not add user")); + this.props.ui.showToast(t("Could not add user"), { type: "error" }); } }; diff --git a/app/scenes/CollectionMembers/CollectionMembers.js b/app/scenes/CollectionMembers/CollectionMembers.js index 028a86a9..a8742f92 100644 --- a/app/scenes/CollectionMembers/CollectionMembers.js +++ b/app/scenes/CollectionMembers/CollectionMembers.js @@ -61,9 +61,11 @@ class CollectionMembers extends React.Component { collectionId: this.props.collection.id, userId: user.id, }); - this.props.ui.showToast(`${user.name} was removed from the collection`); + this.props.ui.showToast(`${user.name} was removed from the collection`, { + type: "success", + }); } catch (err) { - this.props.ui.showToast("Could not remove user"); + this.props.ui.showToast("Could not remove user", { type: "error" }); } }; @@ -74,9 +76,11 @@ class CollectionMembers extends React.Component { userId: user.id, permission, }); - this.props.ui.showToast(`${user.name} permissions were updated`); + this.props.ui.showToast(`${user.name} permissions were updated`, { + type: "success", + }); } catch (err) { - this.props.ui.showToast("Could not update user"); + this.props.ui.showToast("Could not update user", { type: "error" }); } }; @@ -86,9 +90,11 @@ class CollectionMembers extends React.Component { collectionId: this.props.collection.id, groupId: group.id, }); - this.props.ui.showToast(`${group.name} was removed from the collection`); + this.props.ui.showToast(`${group.name} was removed from the collection`, { + type: "success", + }); } catch (err) { - this.props.ui.showToast("Could not remove group"); + this.props.ui.showToast("Could not remove group", { type: "error" }); } }; @@ -99,9 +105,11 @@ class CollectionMembers extends React.Component { groupId: group.id, permission, }); - this.props.ui.showToast(`${group.name} permissions were updated`); + this.props.ui.showToast(`${group.name} permissions were updated`, { + type: "success", + }); } catch (err) { - this.props.ui.showToast("Could not update user"); + this.props.ui.showToast("Could not update user", { type: "error" }); } }; diff --git a/app/scenes/CollectionNew.js b/app/scenes/CollectionNew.js index 8833385b..acb2bc21 100644 --- a/app/scenes/CollectionNew.js +++ b/app/scenes/CollectionNew.js @@ -53,7 +53,7 @@ class CollectionNew extends React.Component { this.props.onSubmit(); this.props.history.push(collection.url); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } diff --git a/app/scenes/Document/components/Document.js b/app/scenes/Document/components/Document.js index 1d237644..23a5bac1 100644 --- a/app/scenes/Document/components/Document.js +++ b/app/scenes/Document/components/Document.js @@ -100,6 +100,7 @@ class DocumentScene extends React.Component { `Document updated by ${document.updatedBy.name}`, { timeout: 30 * 1000, + type: "warning", action: { text: "Reload", onClick: () => { @@ -239,7 +240,7 @@ class DocumentScene extends React.Component { this.props.ui.setActiveDocument(savedDocument); } } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; this.isPublishing = false; diff --git a/app/scenes/Document/components/DocumentMove.js b/app/scenes/Document/components/DocumentMove.js index dea5f081..c081a511 100644 --- a/app/scenes/Document/components/DocumentMove.js +++ b/app/scenes/Document/components/DocumentMove.js @@ -86,7 +86,7 @@ class DocumentMove extends React.Component { } handleSuccess = () => { - this.props.ui.showToast("Document moved"); + this.props.ui.showToast("Document moved", { type: "info" }); this.props.onRequestClose(); }; diff --git a/app/scenes/DocumentDelete.js b/app/scenes/DocumentDelete.js index e4beeed2..0f957128 100644 --- a/app/scenes/DocumentDelete.js +++ b/app/scenes/DocumentDelete.js @@ -48,7 +48,7 @@ class DocumentDelete extends React.Component { } this.props.onSubmit(); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isDeleting = false; } diff --git a/app/scenes/DocumentNew.js b/app/scenes/DocumentNew.js index 92feebb6..75645ba9 100644 --- a/app/scenes/DocumentNew.js +++ b/app/scenes/DocumentNew.js @@ -37,7 +37,9 @@ class DocumentNew extends React.Component { }); this.props.history.replace(editDocumentUrl(document)); } catch (err) { - this.props.ui.showToast("Couldn’t create the document, try again?"); + this.props.ui.showToast("Couldn’t create the document, try again?", { + type: "error", + }); this.props.history.goBack(); } } diff --git a/app/scenes/DocumentShare.js b/app/scenes/DocumentShare.js index 1e911bb7..6bc86102 100644 --- a/app/scenes/DocumentShare.js +++ b/app/scenes/DocumentShare.js @@ -45,7 +45,7 @@ class DocumentShare extends React.Component { try { await share.save({ published: event.target.checked }); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } diff --git a/app/scenes/DocumentTemplatize.js b/app/scenes/DocumentTemplatize.js index 66cb53d1..8a6f44f2 100644 --- a/app/scenes/DocumentTemplatize.js +++ b/app/scenes/DocumentTemplatize.js @@ -28,10 +28,12 @@ class DocumentTemplatize extends React.Component { try { const template = await this.props.document.templatize(); this.props.history.push(documentUrl(template)); - this.props.ui.showToast("Template created, go ahead and customize it"); + this.props.ui.showToast("Template created, go ahead and customize it", { + type: "info", + }); this.props.onSubmit(); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } diff --git a/app/scenes/GroupDelete.js b/app/scenes/GroupDelete.js index b3f1f0bf..7ae579de 100644 --- a/app/scenes/GroupDelete.js +++ b/app/scenes/GroupDelete.js @@ -30,7 +30,7 @@ class GroupDelete extends React.Component { this.props.history.push(groupSettings()); this.props.onSubmit(); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isDeleting = false; } diff --git a/app/scenes/GroupEdit.js b/app/scenes/GroupEdit.js index 0d15209f..5a28a417 100644 --- a/app/scenes/GroupEdit.js +++ b/app/scenes/GroupEdit.js @@ -30,7 +30,7 @@ class GroupEdit extends React.Component { await this.props.group.save({ name: this.name }); this.props.onSubmit(); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } diff --git a/app/scenes/GroupMembers/AddPeopleToGroup.js b/app/scenes/GroupMembers/AddPeopleToGroup.js index 5b1dea45..441543b6 100644 --- a/app/scenes/GroupMembers/AddPeopleToGroup.js +++ b/app/scenes/GroupMembers/AddPeopleToGroup.js @@ -62,10 +62,11 @@ class AddPeopleToGroup extends React.Component { userId: user.id, }); this.props.ui.showToast( - t(`{{userName}} was added to the group`, { userName: user.name }) + t(`{{userName}} was added to the group`, { userName: user.name }), + { type: "success" } ); } catch (err) { - this.props.ui.showToast(t("Could not add user")); + this.props.ui.showToast(t("Could not add user"), { type: "error" }); } }; diff --git a/app/scenes/GroupMembers/GroupMembers.js b/app/scenes/GroupMembers/GroupMembers.js index 0c11f43e..3a6cd480 100644 --- a/app/scenes/GroupMembers/GroupMembers.js +++ b/app/scenes/GroupMembers/GroupMembers.js @@ -52,10 +52,11 @@ class GroupMembers extends React.Component { userId: user.id, }); this.props.ui.showToast( - t(`{{userName}} was removed from the group`, { userName: user.name }) + t(`{{userName}} was removed from the group`, { userName: user.name }), + { type: "success" } ); } catch (err) { - this.props.ui.showToast(t("Could not remove user")); + this.props.ui.showToast(t("Could not remove user"), { type: "error" }); } }; diff --git a/app/scenes/GroupNew.js b/app/scenes/GroupNew.js index 20763196..f6131a98 100644 --- a/app/scenes/GroupNew.js +++ b/app/scenes/GroupNew.js @@ -39,7 +39,7 @@ class GroupNew extends React.Component { try { this.group = await group.save(); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } diff --git a/app/scenes/Invite.js b/app/scenes/Invite.js index dda540b1..92191cac 100644 --- a/app/scenes/Invite.js +++ b/app/scenes/Invite.js @@ -51,9 +51,9 @@ class Invite extends React.Component { try { await this.props.users.invite(this.invites); this.props.onSubmit(); - this.props.ui.showToast("We sent out your invites!"); + this.props.ui.showToast("We sent out your invites!", { type: "success" }); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } finally { this.isSaving = false; } @@ -73,7 +73,8 @@ class Invite extends React.Component { handleAdd = () => { if (this.invites.length >= MAX_INVITES) { this.props.ui.showToast( - `Sorry, you can only send ${MAX_INVITES} invites at a time` + `Sorry, you can only send ${MAX_INVITES} invites at a time`, + { type: "warning" } ); } @@ -88,7 +89,9 @@ class Invite extends React.Component { handleCopy = () => { this.linkCopied = true; - this.props.ui.showToast("A link was copied to your clipboard"); + this.props.ui.showToast("Share link copied", { + type: "success", + }); }; render() { diff --git a/app/scenes/Settings/Details.js b/app/scenes/Settings/Details.js index bc54c3a5..fa70e18b 100644 --- a/app/scenes/Settings/Details.js +++ b/app/scenes/Settings/Details.js @@ -52,9 +52,9 @@ class Details extends React.Component { avatarUrl: this.avatarUrl, subdomain: this.subdomain, }); - this.props.ui.showToast("Settings saved"); + this.props.ui.showToast("Settings saved", { type: "success" }); } catch (err) { - this.props.ui.showToast(err.message); + this.props.ui.showToast(err.message, { type: "error" }); } }; diff --git a/app/scenes/Settings/Export.js b/app/scenes/Settings/Export.js index eecfb4e5..b19b1e72 100644 --- a/app/scenes/Settings/Export.js +++ b/app/scenes/Settings/Export.js @@ -29,7 +29,7 @@ class Export extends React.Component { try { await this.props.collections.export(); this.isExporting = true; - this.props.ui.showToast("Export in progress…"); + this.props.ui.showToast("Export in progress…", { type: "info" }); } finally { this.isLoading = false; } diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js index 8a156261..36c5913f 100644 --- a/app/scenes/Settings/Notifications.js +++ b/app/scenes/Settings/Notifications.js @@ -75,7 +75,7 @@ class Notifications extends React.Component { }; showSuccessMessage = debounce(() => { - this.props.ui.showToast("Notifications saved"); + this.props.ui.showToast("Notifications saved", { type: "success" }); }, 500); render() { diff --git a/app/scenes/Settings/Profile.js b/app/scenes/Settings/Profile.js index 5062c45e..61ec678b 100644 --- a/app/scenes/Settings/Profile.js +++ b/app/scenes/Settings/Profile.js @@ -55,7 +55,7 @@ class Profile extends React.Component { language: this.language, }); - this.props.ui.showToast(t("Profile saved")); + this.props.ui.showToast(t("Profile saved"), { type: "success" }); }; handleNameChange = (ev: SyntheticInputEvent<*>) => { @@ -69,12 +69,15 @@ class Profile extends React.Component { await this.props.auth.updateUser({ avatarUrl: this.avatarUrl, }); - this.props.ui.showToast(t("Profile picture updated")); + this.props.ui.showToast(t("Profile picture updated"), { type: "success" }); }; handleAvatarError = (error: ?string) => { const { t } = this.props; - this.props.ui.showToast(error || t("Unable to upload new profile picture")); + this.props.ui.showToast( + error || t("Unable to upload new profile picture"), + { type: "error" } + ); }; handleLanguageChange = (ev: SyntheticInputEvent<*>) => { diff --git a/app/scenes/Settings/Security.js b/app/scenes/Settings/Security.js index ac0c8779..0e663825 100644 --- a/app/scenes/Settings/Security.js +++ b/app/scenes/Settings/Security.js @@ -56,7 +56,7 @@ class Security extends React.Component { }; showSuccessMessage = debounce(() => { - this.props.ui.showToast("Settings saved"); + this.props.ui.showToast("Settings saved", { type: "success" }); }, 500); render() { diff --git a/app/scenes/UserDelete.js b/app/scenes/UserDelete.js index 89ba2265..f04c3196 100644 --- a/app/scenes/UserDelete.js +++ b/app/scenes/UserDelete.js @@ -27,7 +27,7 @@ class UserDelete extends React.Component { await this.props.auth.deleteUser(); this.props.auth.logout(); } catch (error) { - this.props.ui.showToast(error.message); + this.props.ui.showToast(error.message, { type: "error" }); } finally { this.isDeleting = false; }