chore: Improve toasts

This commit is contained in:
Tom Moor
2021-01-02 21:47:02 -08:00
parent bb81aa0065
commit 9df02d6fd4
32 changed files with 101 additions and 59 deletions

View File

@ -60,7 +60,9 @@ class DropToImport extends React.Component<Props> {
} }
} }
} catch (err) { } 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 { } finally {
this.isImporting = false; this.isImporting = false;
importingLock = false; importingLock = false;

View File

@ -52,7 +52,9 @@ function EditableTitle({ title, onSubmit, canUpdate }: Props) {
setOriginalValue(value); setOriginalValue(value);
} catch (error) { } catch (error) {
setValue(originalValue); setValue(originalValue);
ui.showToast(error.message); ui.showToast(error.message, {
type: "error",
});
throw error; throw error;
} }
} }

View File

@ -110,7 +110,9 @@ class SocketProvider extends React.Component<Props> {
this.socket.on("unauthorized", (err) => { this.socket.on("unauthorized", (err) => {
this.socket.authenticated = false; this.socket.authenticated = false;
ui.showToast(err.message); ui.showToast(err.message, {
type: "error",
});
throw err; throw err;
}); });

View File

@ -1,4 +1,5 @@
// @flow // @flow
import { CheckboxIcon, InfoIcon, WarningIcon } from "outline-icons";
import { darken } from "polished"; import { darken } from "polished";
import * as React from "react"; import * as React from "react";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
@ -14,7 +15,7 @@ type Props = {
function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) { function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
const timeout = React.useRef(); const timeout = React.useRef();
const [pulse, setPulse] = React.useState(false); const [pulse, setPulse] = React.useState(false);
const { action, reoccurring } = toast; const { action, type = "info", reoccurring } = toast;
React.useEffect(() => { React.useEffect(() => {
timeout.current = setTimeout(onRequestClose, toast.timeout || closeAfterMs); timeout.current = setTimeout(onRequestClose, toast.timeout || closeAfterMs);
@ -42,6 +43,10 @@ function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
onClick={action ? undefined : onRequestClose} onClick={action ? undefined : onRequestClose}
type={toast.type || "success"} type={toast.type || "success"}
> >
{type === "info" && <InfoIcon color="currentColor" />}
{type === "success" && <CheckboxIcon checked color="currentColor" />}
{type === "warning" ||
(type === "error" && <WarningIcon color="currentColor" />)}
<Message>{message}</Message> <Message>{message}</Message>
{action && ( {action && (
<Action type={toast.type || "success"} onClick={action.onClick}> <Action type={toast.type || "success"} onClick={action.onClick}>
@ -78,10 +83,11 @@ const ListItem = styled.li`
`; `;
const Container = styled.div` const Container = styled.div`
display: inline-block; display: inline-flex;
align-items: center; align-items: center;
animation: ${fadeAndScaleIn} 100ms ease; animation: ${fadeAndScaleIn} 100ms ease;
margin: 8px 0; margin: 8px 0;
padding: 0 12px;
color: ${(props) => props.theme.toastText}; color: ${(props) => props.theme.toastText};
background: ${(props) => props.theme.toastBackground}; background: ${(props) => props.theme.toastBackground};
font-size: 15px; font-size: 15px;
@ -95,7 +101,8 @@ const Container = styled.div`
const Message = styled.div` const Message = styled.div`
display: inline-block; display: inline-block;
padding: 10px 12px; font-weight: 500;
padding: 10px 4px;
`; `;
export default Toast; export default Toast;

View File

@ -66,7 +66,9 @@ class CollectionMenu extends React.Component<Props> {
); );
this.props.history.push(document.url); this.props.history.push(document.url);
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, {
type: "error",
});
} }
}; };

View File

@ -86,7 +86,7 @@ class DocumentMenu extends React.Component<Props> {
// when duplicating, go straight to the duplicated document content // when duplicating, go straight to the duplicated document content
this.redirectTo = duped.url; this.redirectTo = duped.url;
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Document duplicated")); this.props.ui.showToast(t("Document duplicated"), { type: "success" });
}; };
handleOpenTemplateModal = () => { handleOpenTemplateModal = () => {
@ -104,7 +104,7 @@ class DocumentMenu extends React.Component<Props> {
handleArchive = async (ev: SyntheticEvent<>) => { handleArchive = async (ev: SyntheticEvent<>) => {
await this.props.document.archive(); await this.props.document.archive();
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Document archived")); this.props.ui.showToast(t("Document archived"), { type: "success" });
}; };
handleRestore = async ( handleRestore = async (
@ -113,13 +113,13 @@ class DocumentMenu extends React.Component<Props> {
) => { ) => {
await this.props.document.restore(options); await this.props.document.restore(options);
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Document restored")); this.props.ui.showToast(t("Document restored"), { type: "success" });
}; };
handleUnpublish = async (ev: SyntheticEvent<>) => { handleUnpublish = async (ev: SyntheticEvent<>) => {
await this.props.document.unpublish(); await this.props.document.unpublish();
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Document unpublished")); this.props.ui.showToast(t("Document unpublished"), { type: "success" });
}; };
handlePin = (ev: SyntheticEvent<>) => { handlePin = (ev: SyntheticEvent<>) => {

View File

@ -28,13 +28,13 @@ class RevisionMenu extends React.Component<Props> {
ev.preventDefault(); ev.preventDefault();
await this.props.document.restore({ revisionId: this.props.revision.id }); await this.props.document.restore({ revisionId: this.props.revision.id });
const { t } = this.props; 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); this.props.history.push(this.props.document.url);
}; };
handleCopy = () => { handleCopy = () => {
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Link copied")); this.props.ui.showToast(t("Link copied"), { type: "info" });
}; };
render() { render() {

View File

@ -39,15 +39,15 @@ class ShareMenu extends React.Component<Props> {
try { try {
await this.props.shares.revoke(this.props.share); await this.props.shares.revoke(this.props.share);
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Share link revoked")); this.props.ui.showToast(t("Share link revoked"), { type: "info" });
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} }
}; };
handleCopy = () => { handleCopy = () => {
const { t } = this.props; const { t } = this.props;
this.props.ui.showToast(t("Share link copied")); this.props.ui.showToast(t("Share link copied"), { type: "info" });
}; };
render() { render() {

View File

@ -32,7 +32,7 @@ class CollectionDelete extends React.Component<Props> {
this.props.history.push(homeUrl()); this.props.history.push(homeUrl());
this.props.onSubmit(); this.props.onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isDeleting = false; this.isDeleting = false;
} }

View File

@ -47,9 +47,11 @@ class CollectionEdit extends React.Component<Props> {
sort: this.sort, sort: this.sort,
}); });
this.props.onSubmit(); 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) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@ -67,10 +67,11 @@ class AddGroupsToCollection extends React.Component<Props> {
this.props.ui.showToast( this.props.ui.showToast(
t("{{ groupName }} was added to the collection", { t("{{ groupName }} was added to the collection", {
groupName: group.name, groupName: group.name,
}) }),
{ type: "success" }
); );
} catch (err) { } 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); console.error(err);
} }
}; };

View File

@ -62,10 +62,13 @@ class AddPeopleToCollection extends React.Component<Props> {
permission: "read_write", permission: "read_write",
}); });
this.props.ui.showToast( 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) { } catch (err) {
this.props.ui.showToast(t("Could not add user")); this.props.ui.showToast(t("Could not add user"), { type: "error" });
} }
}; };

View File

@ -61,9 +61,11 @@ class CollectionMembers extends React.Component<Props> {
collectionId: this.props.collection.id, collectionId: this.props.collection.id,
userId: user.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) { } 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<Props> {
userId: user.id, userId: user.id,
permission, permission,
}); });
this.props.ui.showToast(`${user.name} permissions were updated`); this.props.ui.showToast(`${user.name} permissions were updated`, {
type: "success",
});
} catch (err) { } 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<Props> {
collectionId: this.props.collection.id, collectionId: this.props.collection.id,
groupId: group.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) { } 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<Props> {
groupId: group.id, groupId: group.id,
permission, permission,
}); });
this.props.ui.showToast(`${group.name} permissions were updated`); this.props.ui.showToast(`${group.name} permissions were updated`, {
type: "success",
});
} catch (err) { } catch (err) {
this.props.ui.showToast("Could not update user"); this.props.ui.showToast("Could not update user", { type: "error" });
} }
}; };

View File

@ -53,7 +53,7 @@ class CollectionNew extends React.Component<Props> {
this.props.onSubmit(); this.props.onSubmit();
this.props.history.push(collection.url); this.props.history.push(collection.url);
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@ -100,6 +100,7 @@ class DocumentScene extends React.Component<Props> {
`Document updated by ${document.updatedBy.name}`, `Document updated by ${document.updatedBy.name}`,
{ {
timeout: 30 * 1000, timeout: 30 * 1000,
type: "warning",
action: { action: {
text: "Reload", text: "Reload",
onClick: () => { onClick: () => {
@ -239,7 +240,7 @@ class DocumentScene extends React.Component<Props> {
this.props.ui.setActiveDocument(savedDocument); this.props.ui.setActiveDocument(savedDocument);
} }
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
this.isPublishing = false; this.isPublishing = false;

View File

@ -86,7 +86,7 @@ class DocumentMove extends React.Component<Props> {
} }
handleSuccess = () => { handleSuccess = () => {
this.props.ui.showToast("Document moved"); this.props.ui.showToast("Document moved", { type: "info" });
this.props.onRequestClose(); this.props.onRequestClose();
}; };

View File

@ -48,7 +48,7 @@ class DocumentDelete extends React.Component<Props> {
} }
this.props.onSubmit(); this.props.onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isDeleting = false; this.isDeleting = false;
} }

View File

@ -37,7 +37,9 @@ class DocumentNew extends React.Component<Props> {
}); });
this.props.history.replace(editDocumentUrl(document)); this.props.history.replace(editDocumentUrl(document));
} catch (err) { } catch (err) {
this.props.ui.showToast("Couldnt create the document, try again?"); this.props.ui.showToast("Couldnt create the document, try again?", {
type: "error",
});
this.props.history.goBack(); this.props.history.goBack();
} }
} }

View File

@ -45,7 +45,7 @@ class DocumentShare extends React.Component<Props> {
try { try {
await share.save({ published: event.target.checked }); await share.save({ published: event.target.checked });
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@ -28,10 +28,12 @@ class DocumentTemplatize extends React.Component<Props> {
try { try {
const template = await this.props.document.templatize(); const template = await this.props.document.templatize();
this.props.history.push(documentUrl(template)); 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(); this.props.onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@ -30,7 +30,7 @@ class GroupDelete extends React.Component<Props> {
this.props.history.push(groupSettings()); this.props.history.push(groupSettings());
this.props.onSubmit(); this.props.onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isDeleting = false; this.isDeleting = false;
} }

View File

@ -30,7 +30,7 @@ class GroupEdit extends React.Component<Props> {
await this.props.group.save({ name: this.name }); await this.props.group.save({ name: this.name });
this.props.onSubmit(); this.props.onSubmit();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@ -62,10 +62,11 @@ class AddPeopleToGroup extends React.Component<Props> {
userId: user.id, userId: user.id,
}); });
this.props.ui.showToast( 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) { } catch (err) {
this.props.ui.showToast(t("Could not add user")); this.props.ui.showToast(t("Could not add user"), { type: "error" });
} }
}; };

View File

@ -52,10 +52,11 @@ class GroupMembers extends React.Component<Props> {
userId: user.id, userId: user.id,
}); });
this.props.ui.showToast( 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) { } catch (err) {
this.props.ui.showToast(t("Could not remove user")); this.props.ui.showToast(t("Could not remove user"), { type: "error" });
} }
}; };

View File

@ -39,7 +39,7 @@ class GroupNew extends React.Component<Props> {
try { try {
this.group = await group.save(); this.group = await group.save();
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@ -51,9 +51,9 @@ class Invite extends React.Component<Props> {
try { try {
await this.props.users.invite(this.invites); await this.props.users.invite(this.invites);
this.props.onSubmit(); 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) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }
@ -73,7 +73,8 @@ class Invite extends React.Component<Props> {
handleAdd = () => { handleAdd = () => {
if (this.invites.length >= MAX_INVITES) { if (this.invites.length >= MAX_INVITES) {
this.props.ui.showToast( 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<Props> {
handleCopy = () => { handleCopy = () => {
this.linkCopied = true; this.linkCopied = true;
this.props.ui.showToast("A link was copied to your clipboard"); this.props.ui.showToast("Share link copied", {
type: "success",
});
}; };
render() { render() {

View File

@ -52,9 +52,9 @@ class Details extends React.Component<Props> {
avatarUrl: this.avatarUrl, avatarUrl: this.avatarUrl,
subdomain: this.subdomain, subdomain: this.subdomain,
}); });
this.props.ui.showToast("Settings saved"); this.props.ui.showToast("Settings saved", { type: "success" });
} catch (err) { } catch (err) {
this.props.ui.showToast(err.message); this.props.ui.showToast(err.message, { type: "error" });
} }
}; };

View File

@ -29,7 +29,7 @@ class Export extends React.Component<Props> {
try { try {
await this.props.collections.export(); await this.props.collections.export();
this.isExporting = true; this.isExporting = true;
this.props.ui.showToast("Export in progress…"); this.props.ui.showToast("Export in progress…", { type: "info" });
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }

View File

@ -75,7 +75,7 @@ class Notifications extends React.Component<Props> {
}; };
showSuccessMessage = debounce(() => { showSuccessMessage = debounce(() => {
this.props.ui.showToast("Notifications saved"); this.props.ui.showToast("Notifications saved", { type: "success" });
}, 500); }, 500);
render() { render() {

View File

@ -55,7 +55,7 @@ class Profile extends React.Component<Props> {
language: this.language, language: this.language,
}); });
this.props.ui.showToast(t("Profile saved")); this.props.ui.showToast(t("Profile saved"), { type: "success" });
}; };
handleNameChange = (ev: SyntheticInputEvent<*>) => { handleNameChange = (ev: SyntheticInputEvent<*>) => {
@ -69,12 +69,15 @@ class Profile extends React.Component<Props> {
await this.props.auth.updateUser({ await this.props.auth.updateUser({
avatarUrl: this.avatarUrl, avatarUrl: this.avatarUrl,
}); });
this.props.ui.showToast(t("Profile picture updated")); this.props.ui.showToast(t("Profile picture updated"), { type: "success" });
}; };
handleAvatarError = (error: ?string) => { handleAvatarError = (error: ?string) => {
const { t } = this.props; 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<*>) => { handleLanguageChange = (ev: SyntheticInputEvent<*>) => {

View File

@ -56,7 +56,7 @@ class Security extends React.Component<Props> {
}; };
showSuccessMessage = debounce(() => { showSuccessMessage = debounce(() => {
this.props.ui.showToast("Settings saved"); this.props.ui.showToast("Settings saved", { type: "success" });
}, 500); }, 500);
render() { render() {

View File

@ -27,7 +27,7 @@ class UserDelete extends React.Component<Props> {
await this.props.auth.deleteUser(); await this.props.auth.deleteUser();
this.props.auth.logout(); this.props.auth.logout();
} catch (error) { } catch (error) {
this.props.ui.showToast(error.message); this.props.ui.showToast(error.message, { type: "error" });
} finally { } finally {
this.isDeleting = false; this.isDeleting = false;
} }