Move toasts to sonner (#6053)

This commit is contained in:
Tom Moor
2023-10-22 17:30:24 -04:00
committed by GitHub
parent 389297a337
commit ef76405bd6
92 changed files with 363 additions and 1015 deletions

View File

@@ -1,6 +1,6 @@
import { ToolsIcon, TrashIcon, UserIcon } from "outline-icons";
import * as React from "react";
import stores from "~/stores";
import { toast } from "sonner";
import { createAction } from "~/actions";
import { DeveloperSection } from "~/actions/sections";
import env from "~/env";
@@ -15,7 +15,7 @@ export const clearIndexedDB = createAction({
section: DeveloperSection,
perform: async ({ t }) => {
await deleteAllDatabases();
stores.toasts.showToast(t("IndexedDB cache deleted"));
toast.message(t("IndexedDB cache deleted"));
},
});
@@ -29,9 +29,9 @@ export const createTestUsers = createAction({
try {
await client.post("/developer.create_test_users", { count });
stores.toasts.showToast(`${count} test users created`);
toast.message(`${count} test users created`);
} catch (err) {
stores.toasts.showToast(err.message, { type: "error" });
toast.error(err.message);
}
},
});
@@ -41,15 +41,8 @@ export const createToast = createAction({
section: DeveloperSection,
visible: () => env.ENVIRONMENT === "development",
perform: async () => {
stores.toasts.showToast("Hello world", {
type: "info",
timeout: 30000,
action: {
text: "Click me",
onClick: () => {
stores.toasts.showToast("Clicked!");
},
},
toast.message("Hello world", {
duration: 30000,
});
},
});
@@ -60,7 +53,7 @@ export const toggleDebugLogging = createAction({
section: DeveloperSection,
perform: async ({ t }) => {
Logger.debugLoggingEnabled = !Logger.debugLoggingEnabled;
stores.toasts.showToast(
toast.message(
Logger.debugLoggingEnabled
? t("Debug logging enabled")
: t("Debug logging disabled")

View File

@@ -25,6 +25,7 @@ import {
CommentIcon,
} from "outline-icons";
import * as React from "react";
import { toast } from "sonner";
import { ExportContentType, TeamPreference } from "@shared/types";
import { getEventFiles } from "@shared/utils/files";
import DocumentDelete from "~/scenes/DocumentDelete";
@@ -209,13 +210,10 @@ export const publishDocument = createAction({
await document.save(undefined, {
publish: true,
});
stores.toasts.showToast(
toast.success(
t("Published {{ documentName }}", {
documentName: document.noun,
}),
{
type: "success",
}
})
);
} else if (document) {
stores.dialogs.openModal({
@@ -250,13 +248,10 @@ export const unpublishDocument = createAction({
await document.unpublish();
stores.toasts.showToast(
toast.message(
t("Unpublished {{ documentName }}", {
documentName: document.noun,
}),
{
type: "success",
}
})
);
},
});
@@ -287,9 +282,7 @@ export const subscribeDocument = createAction({
await document?.subscribe();
stores.toasts.showToast(t("Subscribed to document notifications"), {
type: "success",
});
toast.success(t("Subscribed to document notifications"));
},
});
@@ -319,9 +312,7 @@ export const unsubscribeDocument = createAction({
await document?.unsubscribe(currentUserId);
stores.toasts.showToast(t("Unsubscribed from document notifications"), {
type: "success",
});
toast.success(t("Unsubscribed from document notifications"));
},
});
@@ -360,15 +351,11 @@ export const downloadDocumentAsPDF = createAction({
return;
}
const id = stores.toasts.showToast(`${t("Exporting")}`, {
type: "loading",
timeout: 30 * 1000,
});
const id = toast.loading(`${t("Exporting")}`);
const document = stores.documents.get(activeDocumentId);
document
?.download(ExportContentType.Pdf)
.finally(() => id && stores.toasts.hideToast(id));
.finally(() => id && toast.dismiss(id));
},
});
@@ -479,12 +466,10 @@ export const pinDocumentToCollection = createAction({
const collection = stores.collections.get(activeCollectionId);
if (!collection || !location.pathname.startsWith(collection?.url)) {
stores.toasts.showToast(t("Pinned to collection"));
toast.success(t("Pinned to collection"));
}
} catch (err) {
stores.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
});
@@ -521,12 +506,10 @@ export const pinDocumentToHome = createAction({
await document?.pin();
if (location.pathname !== homePath()) {
stores.toasts.showToast(t("Pinned to home"));
toast.success(t("Pinned to home"));
}
} catch (err) {
stores.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
});
@@ -569,7 +552,7 @@ export const importDocument = createAction({
return false;
},
perform: ({ activeCollectionId, activeDocumentId, stores }) => {
const { documents, toasts } = stores;
const { documents } = stores;
const input = document.createElement("input");
input.type = "file";
input.accept = documents.importFileTypes.join(", ");
@@ -589,9 +572,7 @@ export const importDocument = createAction({
);
history.push(document.url);
} catch (err) {
toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
throw err;
}
};
@@ -712,9 +693,7 @@ export const archiveDocument = createAction({
}
await document.archive();
stores.toasts.showToast(t("Document archived"), {
type: "success",
});
toast.success(t("Document archived"));
}
},
});

View File

@@ -2,6 +2,7 @@ import copy from "copy-to-clipboard";
import { LinkIcon, RestoreIcon } from "outline-icons";
import * as React from "react";
import { matchPath } from "react-router-dom";
import { toast } from "sonner";
import stores from "~/stores";
import { createAction } from "~/actions";
import { RevisionSection } from "~/actions/sections";
@@ -68,9 +69,7 @@ export const copyLinkToRevision = createAction({
copy(url, {
format: "text/plain",
onCopy: () => {
stores.toasts.showToast(t("Link copied"), {
type: "info",
});
toast.message(t("Link copied"));
},
});
},

View File

@@ -1,5 +1,6 @@
import flattenDeep from "lodash/flattenDeep";
import * as React from "react";
import { toast } from "sonner";
import { Optional } from "utility-types";
import { v4 as uuidv4 } from "uuid";
import {
@@ -77,9 +78,7 @@ export function actionToMenuItem(
try {
action.perform?.(context);
} catch (err) {
context.stores.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
selected: action.selected?.(context),

View File

@@ -5,6 +5,7 @@ import Initials from "./Initials";
export enum AvatarSize {
Small = 16,
Toast = 18,
Medium = 24,
Large = 32,
XLarge = 48,

View File

@@ -2,12 +2,12 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import Collection from "~/models/Collection";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { homePath } from "~/utils/routeHelpers";
type Props = {
@@ -18,7 +18,6 @@ type Props = {
function CollectionDeleteDialog({ collection, onSubmit }: Props) {
const team = useCurrentTeam();
const { ui } = useStores();
const { showToast } = useToasts();
const history = useHistory();
const { t } = useTranslation();
@@ -31,7 +30,7 @@ function CollectionDeleteDialog({ collection, onSubmit }: Props) {
await collection.delete();
onSubmit();
showToast(t("Collection deleted"), { type: "success" });
toast.success(t("Collection deleted"));
};
return (

View File

@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import { transparentize } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
import Collection from "~/models/Collection";
@@ -13,7 +14,6 @@ import LoadingIndicator from "~/components/LoadingIndicator";
import NudeButton from "~/components/NudeButton";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
collection: Collection;
@@ -21,7 +21,6 @@ type Props = {
function CollectionDescription({ collection }: Props) {
const { collections } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const [isExpanded, setExpanded] = React.useState(false);
const [isEditing, setEditing] = React.useState(false);
@@ -59,15 +58,11 @@ function CollectionDescription({ collection }: Props) {
});
setDirty(false);
} catch (err) {
showToast(
t("Sorry, an error occurred saving the collection", {
type: "error",
})
);
toast.error(t("Sorry, an error occurred saving the collection"));
throw err;
}
}, 1000),
[collection, showToast, t]
[collection, t]
);
const handleChange = React.useCallback(

View File

@@ -1,11 +1,11 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Comment from "~/models/Comment";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
comment: Comment;
@@ -14,7 +14,6 @@ type Props = {
function CommentDeleteDialog({ comment, onSubmit }: Props) {
const { comments } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const hasChildComments = comments.inThread(comment.id).length > 1;
@@ -23,7 +22,7 @@ function CommentDeleteDialog({ comment, onSubmit }: Props) {
await comment.delete();
onSubmit?.();
} catch (err) {
showToast(err.message, { type: "error" });
toast.error(err.message);
}
};

View File

@@ -1,10 +1,10 @@
import { observer } from "mobx-react";
import * as React from "react";
import { toast } from "sonner";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
/** Callback when the dialog is submitted */
@@ -30,7 +30,6 @@ const ConfirmationDialog: React.FC<Props> = ({
}: Props) => {
const [isSaving, setIsSaving] = React.useState(false);
const { dialogs } = useStores();
const { showToast } = useToasts();
const handleSubmit = React.useCallback(
async (ev: React.SyntheticEvent) => {
@@ -40,14 +39,12 @@ const ConfirmationDialog: React.FC<Props> = ({
await onSubmit();
dialogs.closeAllModals();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[onSubmit, dialogs, showToast]
[onSubmit, dialogs]
);
return (

View File

@@ -1,13 +1,13 @@
import { HomeIcon } from "outline-icons";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Optional } from "utility-types";
import Flex from "~/components/Flex";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import InputSelect from "~/components/InputSelect";
import { IconWrapper } from "~/components/Sidebar/components/SidebarLink";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type DefaultCollectionInputSelectProps = Optional<
React.ComponentProps<typeof InputSelect>
@@ -25,7 +25,6 @@ const DefaultCollectionInputSelect = ({
const { collections } = useStores();
const [fetching, setFetching] = useState(false);
const [fetchError, setFetchError] = useState();
const { showToast } = useToasts();
React.useEffect(() => {
async function fetchData() {
@@ -36,11 +35,8 @@ const DefaultCollectionInputSelect = ({
limit: 100,
});
} catch (error) {
showToast(
t("Collections could not be loaded, please reload the app"),
{
type: "error",
}
toast.error(
t("Collections could not be loaded, please reload the app")
);
setFetchError(error);
} finally {
@@ -49,7 +45,7 @@ const DefaultCollectionInputSelect = ({
}
}
void fetchData();
}, [showToast, fetchError, t, fetching, collections]);
}, [fetchError, t, fetching, collections]);
const options = React.useMemo(
() =>

View File

@@ -1,10 +1,10 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { useDesktopTitlebar } from "~/hooks/useDesktopTitlebar";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import Desktop from "~/utils/Desktop";
export default function DesktopEventHandler() {
@@ -12,7 +12,6 @@ export default function DesktopEventHandler() {
const { t } = useTranslation();
const history = useHistory();
const { dialogs } = useStores();
const { showToast } = useToasts();
React.useEffect(() => {
Desktop.bridge?.redirect((path: string, replace = false) => {
@@ -24,11 +23,11 @@ export default function DesktopEventHandler() {
});
Desktop.bridge?.updateDownloaded(() => {
showToast("An update is ready to install.", {
type: "info",
timeout: Infinity,
toast.message("An update is ready to install.", {
duration: Infinity,
dismissible: true,
action: {
text: "Install now",
label: t("Install now"),
onClick: () => {
void Desktop.bridge?.restartAndInstall();
},
@@ -50,7 +49,7 @@ export default function DesktopEventHandler() {
content: <KeyboardShortcuts />,
});
});
}, [t, history, dialogs, showToast]);
}, [t, history, dialogs]);
return null;
}

View File

@@ -3,9 +3,9 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { documentPath } from "~/utils/routeHelpers";
type Props = {
@@ -14,7 +14,6 @@ type Props = {
function DocumentTemplatizeDialog({ documentId }: Props) {
const history = useHistory();
const { showToast } = useToasts();
const { t } = useTranslation();
const { documents } = useStores();
const document = documents.get(documentId);
@@ -24,11 +23,9 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
const template = await document?.templatize();
if (template) {
history.push(documentPath(template));
showToast(t("Template created, go ahead and customize it"), {
type: "info",
});
toast.message(t("Template created, go ahead and customize it"));
}
}, [document, showToast, history, t]);
}, [document, history, t]);
return (
<ConfirmationDialog

View File

@@ -24,7 +24,6 @@ import type { Props as EditorProps, Editor as SharedEditor } from "~/editor";
import useDictionary from "~/hooks/useDictionary";
import useEmbeds from "~/hooks/useEmbeds";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import useUserLocale from "~/hooks/useUserLocale";
import { NotFoundError } from "~/utils/errors";
import { uploadFile } from "~/utils/files";
@@ -43,7 +42,6 @@ export type Props = Optional<
| "onClickLink"
| "embeds"
| "dictionary"
| "onShowToast"
| "extensions"
> & {
shareId?: string | undefined;
@@ -68,7 +66,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const userLocale = useUserLocale();
const locale = dateLocale(userLocale);
const { auth, comments, documents } = useStores();
const { showToast } = useToasts();
const dictionary = useDictionary();
const embeds = useEmbeds(!shareId);
const history = useHistory();
@@ -241,7 +238,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
uploadFile: handleUploadFile,
onFileUploadStart: props.onFileUploadStart,
onFileUploadStop: props.onFileUploadStop,
onShowToast: showToast,
dictionary,
isAttachment,
});
@@ -252,7 +248,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
props.onFileUploadStop,
dictionary,
handleUploadFile,
showToast,
]
);
@@ -336,7 +331,6 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
<LazyLoadedEditor
ref={mergeRefs([ref, localRef, handleRefChanged])}
uploadFile={handleUploadFile}
onShowToast={showToast}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}

View File

@@ -1,6 +1,7 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { FileOperationFormat, NotificationEventType } from "@shared/types";
import Collection from "~/models/Collection";
@@ -10,7 +11,6 @@ import Text from "~/components/Text";
import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import history from "~/utils/history";
import { settingsPath } from "~/utils/routeHelpers";
@@ -26,7 +26,6 @@ function ExportDialog({ collection, onSubmit }: Props) {
const [includeAttachments, setIncludeAttachments] =
React.useState<boolean>(true);
const user = useCurrentUser();
const { showToast } = useToasts();
const { collections } = useStores();
const { t } = useTranslation();
const appName = env.APP_NAME;
@@ -48,23 +47,20 @@ function ExportDialog({ collection, onSubmit }: Props) {
const handleSubmit = async () => {
if (collection) {
await collection.export(format, includeAttachments);
showToast(
t(`Your file will be available in {{ location }} soon`, {
toast.success(t("Export started"), {
description: t(`Your file will be available in {{ location }} soon`, {
location: `"${t("Settings")} > ${t("Export")}"`,
}),
{
type: "success",
action: {
text: t("Go to exports"),
onClick: () => {
history.push(settingsPath("export"));
},
action: {
label: t("View"),
onClick: () => {
history.push(settingsPath("export"));
},
}
);
},
});
} else {
await collections.export(format, includeAttachments);
showToast(t("Export started"), { type: "success" });
toast.success(t("Export started"));
}
onSubmit();
};

View File

@@ -3,24 +3,21 @@ import { ArchiveIcon } from "outline-icons";
import * as React from "react";
import { useDrop } from "react-dnd";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { archivePath } from "~/utils/routeHelpers";
import SidebarLink, { DragObject } from "./SidebarLink";
function ArchiveLink() {
const { policies, documents } = useStores();
const { t } = useTranslation();
const { showToast } = useToasts();
const [{ isDocumentDropping }, dropToArchiveDocument] = useDrop({
accept: "document",
drop: async (item: DragObject) => {
const document = documents.get(item.id);
await document?.archive();
showToast(t("Document archived"), {
type: "success",
});
toast.success(t("Document archived"));
},
canDrop: (item) => policies.abilities(item.id).archive,
collect: (monitor) => ({

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useDrop } from "react-dnd";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import Collection from "~/models/Collection";
import Document from "~/models/Document";
@@ -9,7 +10,6 @@ import DocumentsLoader from "~/components/DocumentsLoader";
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import DocumentLink from "./DocumentLink";
import DropCursor from "./DropCursor";
import EmptyCollectionPlaceholder from "./EmptyCollectionPlaceholder";
@@ -30,7 +30,6 @@ function CollectionLinkChildren({
prefetchDocument,
}: Props) {
const can = usePolicy(collection);
const { showToast } = useToasts();
const manualSort = collection.sort.field === "index";
const { documents } = useStores();
const { t } = useTranslation();
@@ -42,14 +41,10 @@ function CollectionLinkChildren({
accept: "document",
drop: (item: DragObject) => {
if (!manualSort && item.collectionId === collection?.id) {
showToast(
toast.message(
t(
"You can't reorder documents in an alphabetically sorted collection"
),
{
type: "info",
timeout: 5000,
}
)
);
return;
}

View File

@@ -6,6 +6,7 @@ import { useDrag, useDrop } from "react-dnd";
import { getEmptyImage } from "react-dnd-html5-backend";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import styled from "styled-components";
import { NavigationNode } from "@shared/types";
import { sortNavigationNodes } from "@shared/utils/collections";
@@ -18,7 +19,6 @@ import Tooltip from "~/components/Tooltip";
import useBoolean from "~/hooks/useBoolean";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import DocumentMenu from "~/menus/DocumentMenu";
import { newDocumentPath } from "~/utils/routeHelpers";
import DropCursor from "./DropCursor";
@@ -53,7 +53,6 @@ function InnerDocumentLink(
}: Props,
ref: React.RefObject<HTMLAnchorElement>
) {
const { showToast } = useToasts();
const { documents, policies } = useStores();
const { t } = useTranslation();
const canUpdate = usePolicy(node.id).update;
@@ -222,14 +221,10 @@ function InnerDocumentLink(
accept: "document",
drop: (item: DragObject) => {
if (!manualSort) {
showToast(
toast.message(
t(
"You can't reorder documents in an alphabetically sorted collection"
),
{
type: "info",
timeout: 5000,
}
)
);
return;
}

View File

@@ -3,12 +3,12 @@ import { observer } from "mobx-react";
import * as React from "react";
import Dropzone from "react-dropzone";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled, { css } from "styled-components";
import LoadingIndicator from "~/components/LoadingIndicator";
import useImportDocument from "~/hooks/useImportDocument";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
children: JSX.Element;
@@ -21,7 +21,6 @@ type Props = {
function DropToImport({ disabled, children, collectionId, documentId }: Props) {
const { t } = useTranslation();
const { documents } = useStores();
const { showToast } = useToasts();
const { handleFiles, isImporting } = useImportDocument(
collectionId,
documentId
@@ -35,13 +34,10 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
const canDocument = usePolicy(documentId);
const handleRejection = React.useCallback(() => {
showToast(
t("Document not supported try Markdown, Plain text, HTML, or Word"),
{
type: "error",
}
toast.error(
t("Document not supported try Markdown, Plain text, HTML, or Word")
);
}, [t, showToast]);
}, [t]);
if (
disabled ||

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
import useToasts from "~/hooks/useToasts";
type Props = {
onSubmit: (title: string) => Promise<void>;
@@ -22,7 +22,6 @@ function EditableTitle(
const [isEditing, setIsEditing] = React.useState(false);
const [originalValue, setOriginalValue] = React.useState(title);
const [value, setValue] = React.useState(title);
const { showToast } = useToasts();
React.useImperativeHandle(ref, () => ({
setIsEditing,
@@ -78,14 +77,12 @@ function EditableTitle(
setOriginalValue(trimmedValue);
} catch (error) {
setValue(originalValue);
showToast(error.message, {
type: "error",
});
toast.error(error.message);
throw error;
}
}
},
[originalValue, showToast, value, onSubmit]
[originalValue, value, onSubmit]
);
React.useEffect(() => {

View File

@@ -1,120 +0,0 @@
import { CheckboxIcon, InfoIcon, WarningIcon } from "outline-icons";
import { darken } from "polished";
import * as React from "react";
import styled, { css } from "styled-components";
import { s } from "@shared/styles";
import { fadeAndScaleIn, pulse } from "~/styles/animations";
import { Toast as TToast } from "~/types";
import Spinner from "./Spinner";
type Props = {
onRequestClose: () => void;
closeAfterMs?: number;
toast: TToast;
};
function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
const [pulse, setPulse] = React.useState(false);
const { action, type = "info", reoccurring } = toast;
React.useEffect(() => {
if (toast.timeout !== 0) {
timeout.current = setTimeout(
onRequestClose,
toast.timeout || closeAfterMs
);
}
return () => timeout.current && clearTimeout(timeout.current);
}, [onRequestClose, toast, closeAfterMs]);
React.useEffect(() => {
if (reoccurring) {
setPulse(!!reoccurring);
// must match animation time in css below vvv
setTimeout(() => setPulse(false), 250);
}
}, [reoccurring]);
const handlePause = React.useCallback(() => {
if (timeout.current) {
clearTimeout(timeout.current);
}
}, []);
const handleResume = React.useCallback(() => {
if (timeout.current && toast.timeout !== 0) {
timeout.current = setTimeout(
onRequestClose,
toast.timeout || closeAfterMs
);
}
}, [onRequestClose, toast, closeAfterMs]);
return (
<ListItem
$pulse={pulse}
onMouseEnter={handlePause}
onMouseLeave={handleResume}
>
<Container onClick={action ? undefined : onRequestClose}>
{type === "loading" && <Spinner />}
{type === "info" && <InfoIcon />}
{type === "success" && <CheckboxIcon checked />}
{(type === "warning" || type === "error") && <WarningIcon />}
<Message>{toast.message}</Message>
{action && <Action onClick={action.onClick}>{action.text}</Action>}
</Container>
</ListItem>
);
}
const Action = styled.span`
display: inline-block;
padding: 4px 8px;
color: ${s("toastText")};
background: ${(props) => darken(0.05, props.theme.toastBackground)};
border-radius: 4px;
margin-left: 8px;
margin-right: -4px;
font-weight: 500;
user-select: none;
&:hover {
background: ${(props) => darken(0.1, props.theme.toastBackground)};
}
`;
const ListItem = styled.li<{ $pulse?: boolean }>`
${(props) =>
props.$pulse &&
css`
animation: ${pulse} 250ms;
`}
`;
const Container = styled.div`
display: inline-flex;
align-items: center;
animation: ${fadeAndScaleIn} 100ms ease;
margin: 8px 0;
padding: 0 12px;
color: ${s("toastText")};
background: ${s("toastBackground")};
font-size: 15px;
border-radius: 5px;
cursor: default;
&:hover {
background: ${(props) => darken(0.05, props.theme.toastBackground)};
}
`;
const Message = styled.div`
display: inline-block;
font-weight: 500;
padding: 10px 4px;
user-select: none;
`;
export default Toast;

View File

@@ -1,34 +1,28 @@
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import { depths } from "@shared/styles";
import Toast from "~/components/Toast";
import { Toaster } from "sonner";
import { useTheme } from "styled-components";
import useStores from "~/hooks/useStores";
import { Toast as TToast } from "~/types";
function Toasts() {
const { toasts } = useStores();
const { ui } = useStores();
const theme = useTheme();
return (
<List>
{toasts.orderedData.map((toast: TToast) => (
<Toast
key={toast.id}
toast={toast}
onRequestClose={() => toasts.hideToast(toast.id)}
/>
))}
</List>
<Toaster
theme={ui.resolvedTheme}
toastOptions={{
duration: 5000,
style: {
color: theme.toastText,
background: theme.toastBackground,
border: `1px solid ${theme.divider}`,
fontFamily: theme.fontFamily,
fontSize: "14px",
},
}}
/>
);
}
const List = styled.ol`
position: fixed;
left: 16px;
bottom: 16px;
list-style: none;
margin: 0;
padding: 0;
z-index: ${depths.toasts};
`;
export default observer(Toasts);

View File

@@ -4,6 +4,7 @@ import { action, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { io, Socket } from "socket.io-client";
import { toast } from "sonner";
import RootStore from "~/stores/RootStore";
import Collection from "~/models/Collection";
import Comment from "~/models/Comment";
@@ -77,7 +78,6 @@ class WebsocketProvider extends React.Component<Props> {
this.socket.authenticated = false;
const {
auth,
toasts,
documents,
collections,
groups,
@@ -111,9 +111,7 @@ class WebsocketProvider extends React.Component<Props> {
if (this.socket) {
this.socket.authenticated = false;
}
toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
throw err;
});

View File

@@ -9,6 +9,7 @@ import { Mark } from "prosemirror-model";
import { Selection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as React from "react";
import { toast } from "sonner";
import styled from "styled-components";
import { s, hideScrollbars } from "@shared/styles";
import { isInternalUrl, sanitizeUrl } from "@shared/utils/urls";
@@ -16,7 +17,6 @@ import Flex from "~/components/Flex";
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
import Scrollable from "~/components/Scrollable";
import { Dictionary } from "~/hooks/useDictionary";
import { ToastOptions } from "~/types";
import Logger from "~/utils/Logger";
import Input from "./Input";
import LinkSearchResult from "./LinkSearchResult";
@@ -47,7 +47,6 @@ type Props = {
href: string,
event: React.MouseEvent<HTMLButtonElement>
) => void;
onShowToast: (message: string, options?: ToastOptions) => void;
view: EditorView;
};
@@ -240,7 +239,7 @@ class LinkEditor extends React.Component<Props, State> {
try {
this.props.onClickLink(this.href, event);
} catch (err) {
this.props.onShowToast(this.props.dictionary.openLinkError);
toast.error(this.props.dictionary.openLinkError);
}
};

View File

@@ -4,7 +4,6 @@ import createAndInsertLink from "@shared/editor/commands/createAndInsertLink";
import { creatingUrlPrefix } from "@shared/utils/urls";
import useDictionary from "~/hooks/useDictionary";
import useEventListener from "~/hooks/useEventListener";
import useToasts from "~/hooks/useToasts";
import { useEditor } from "./EditorContext";
import FloatingToolbar from "./FloatingToolbar";
import LinkEditor, { SearchResult } from "./LinkEditor";
@@ -39,7 +38,6 @@ export default function LinkToolbar({
}: Props) {
const dictionary = useDictionary();
const { view } = useEditor();
const { showToast } = useToasts();
const menuRef = React.useRef<HTMLDivElement>(null);
useEventListener("mousedown", (event: Event) => {
@@ -84,11 +82,10 @@ export default function LinkToolbar({
return createAndInsertLink(view, title, href, {
onCreateLink,
onShowToast: showToast,
dictionary,
});
},
[onCreateLink, onClose, view, dictionary, showToast]
[onCreateLink, onClose, view, dictionary]
);
const handleOnSelectLink = React.useCallback(
@@ -137,7 +134,6 @@ export default function LinkToolbar({
onCreateLink={onCreateLink ? handleOnCreateLink : undefined}
onSelectLink={handleOnSelectLink}
onRemoveLink={onClose}
onShowToast={showToast}
onClickLink={onClickLink}
onSearchLink={onSearchLink}
dictionary={dictionary}

View File

@@ -15,7 +15,6 @@ import useDictionary from "~/hooks/useDictionary";
import useEventListener from "~/hooks/useEventListener";
import useMobile from "~/hooks/useMobile";
import usePrevious from "~/hooks/usePrevious";
import useToasts from "~/hooks/useToasts";
import getCodeMenuItems from "../menus/code";
import getDividerMenuItems from "../menus/divider";
import getFormattingMenuItems from "../menus/formatting";
@@ -97,7 +96,6 @@ function useIsDragging() {
export default function SelectionToolbar(props: Props) {
const { onClose, readOnly, onOpen } = props;
const { view, commands } = useEditor();
const { showToast: onShowToast } = useToasts();
const dictionary = useDictionary();
const menuRef = React.useRef<HTMLDivElement | null>(null);
const isActive = useIsActive(view.state);
@@ -175,7 +173,6 @@ export default function SelectionToolbar(props: Props) {
return createAndInsertLink(view, title, href, {
onCreateLink,
onShowToast,
dictionary,
});
};
@@ -271,7 +268,6 @@ export default function SelectionToolbar(props: Props) {
mark={range.mark}
from={range.from}
to={range.to}
onShowToast={onShowToast}
onClickLink={props.onClickLink}
onSearchLink={props.onSearchLink}
onCreateLink={onCreateLink ? handleOnCreateLink : undefined}

View File

@@ -3,6 +3,7 @@ import capitalize from "lodash/capitalize";
import * as React from "react";
import { Trans } from "react-i18next";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import { toast } from "sonner";
import styled from "styled-components";
import insertFiles from "@shared/editor/commands/insertFiles";
import { EmbedDescriptor } from "@shared/editor/embeds";
@@ -15,7 +16,6 @@ import { AttachmentValidation } from "@shared/validations";
import { Portal } from "~/components/Portal";
import Scrollable from "~/components/Scrollable";
import useDictionary from "~/hooks/useDictionary";
import useToasts from "~/hooks/useToasts";
import Logger from "~/utils/Logger";
import { useEditor } from "./EditorContext";
import Input from "./Input";
@@ -77,7 +77,6 @@ export type Props<T extends MenuItem = MenuItem> = {
function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
const { view, commands } = useEditor();
const { showToast: onShowToast } = useToasts();
const dictionary = useDictionary();
const hasActivated = React.useRef(false);
const menuRef = React.useRef<HTMLDivElement>(null);
@@ -292,7 +291,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
const matches = "matcher" in insertItem && insertItem.matcher(href);
if (!matches) {
onShowToast(dictionary.embedInvalidLink);
toast.error(dictionary.embedInvalidLink);
return;
}
@@ -365,7 +364,6 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
uploadFile,
onFileUploadStart,
onFileUploadStop,
onShowToast,
dictionary,
isAttachment: inputRef.current?.accept === "*",
});

View File

@@ -133,8 +133,6 @@ export type Props = {
userPreferences?: UserPreferences | null;
/** Whether embeds should be rendered without an iframe */
embedsDisabled?: boolean;
/** Callback when a toast message is triggered (eg "link copied") */
onShowToast: (message: string) => void;
className?: string;
/** Optional style overrides for the container*/
style?: React.CSSProperties;

View File

@@ -2,8 +2,8 @@ import invariant from "invariant";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { documentPath } from "~/utils/routeHelpers";
let importingLock = false;
@@ -16,7 +16,6 @@ export default function useImportDocument(
isImporting: boolean;
} {
const { documents } = useStores();
const { showToast } = useToasts();
const [isImporting, setImporting] = React.useState(false);
const { t } = useTranslation();
const history = useHistory();
@@ -55,15 +54,13 @@ export default function useImportDocument(
}
}
} catch (err) {
showToast(`${t("Could not import file")}. ${err.message}`, {
type: "error",
});
toast.error(`${t("Could not import file")}. ${err.message}`);
} finally {
setImporting(false);
importingLock = false;
}
},
[t, documents, history, showToast, collectionId, documentId]
[t, documents, history, collectionId, documentId]
);
return {

View File

@@ -1,8 +1,8 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { QueryNotices } from "@shared/types";
import useQuery from "./useQuery";
import useToasts from "./useToasts";
/**
* Display a toast message based on a notice in the query string. This is usually
@@ -12,13 +12,12 @@ import useToasts from "./useToasts";
export default function useQueryNotices() {
const query = useQuery();
const { t } = useTranslation();
const { showToast } = useToasts();
const notice = query.get("notice") as QueryNotices;
React.useEffect(() => {
switch (notice) {
case QueryNotices.UnsubscribeDocument: {
showToast(
toast.success(
t("Unsubscribed from document", {
type: "success",
})
@@ -27,5 +26,5 @@ export default function useQueryNotices() {
}
default:
}
}, [t, showToast, notice]);
}, [t, notice]);
}

View File

@@ -1,9 +0,0 @@
import useStores from "./useStores";
export default function useToasts() {
const { toasts } = useStores();
return {
showToast: toasts.showToast,
hideToast: toasts.hideToast,
};
}

View File

@@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useMenuState, MenuButton, MenuButtonHTMLProps } from "reakit/Menu";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import { toast } from "sonner";
import { getEventFiles } from "@shared/utils/files";
import Collection from "~/models/Collection";
import ContextMenu, { Placement } from "~/components/ContextMenu";
@@ -29,7 +30,6 @@ import useActionContext from "~/hooks/useActionContext";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { MenuItem } from "~/types";
import { newDocumentPath } from "~/utils/routeHelpers";
@@ -56,7 +56,6 @@ function CollectionMenu({
});
const team = useCurrentTeam();
const { documents, dialogs } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const history = useHistory();
const file = React.useRef<HTMLInputElement>(null);
@@ -116,13 +115,11 @@ function CollectionMenu({
});
history.push(document.url);
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
throw err;
}
},
[history, showToast, collection.id, documents]
[history, collection.id, documents]
);
const handleChangeSort = React.useCallback(

View File

@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useMenuState } from "reakit/Menu";
import { toast } from "sonner";
import Comment from "~/models/Comment";
import CommentDeleteDialog from "~/components/CommentDeleteDialog";
import ContextMenu from "~/components/ContextMenu";
@@ -12,7 +13,6 @@ import Separator from "~/components/ContextMenu/Separator";
import EventBoundary from "~/components/EventBoundary";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { commentPath, urlify } from "~/utils/routeHelpers";
type Props = {
@@ -30,7 +30,6 @@ function CommentMenu({ comment, onEdit, onDelete, className }: Props) {
const menu = useMenuState({
modal: true,
});
const { showToast } = useToasts();
const { documents, dialogs } = useStores();
const { t } = useTranslation();
const can = usePolicy(comment.id);
@@ -47,9 +46,9 @@ function CommentMenu({ comment, onEdit, onDelete, className }: Props) {
const handleCopyLink = React.useCallback(() => {
if (document) {
copy(urlify(commentPath(document, comment)));
showToast(t("Link copied"));
toast.message(t("Link copied"));
}
}, [t, document, comment, showToast]);
}, [t, document, comment]);
return (
<>

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useMenuState, MenuButton, MenuButtonHTMLProps } from "reakit/Menu";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import { toast } from "sonner";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s, ellipsis } from "@shared/styles";
@@ -47,7 +48,6 @@ import useMobile from "~/hooks/useMobile";
import usePolicy from "~/hooks/usePolicy";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { MenuItem } from "~/types";
import { documentEditPath } from "~/utils/routeHelpers";
@@ -79,7 +79,6 @@ function DocumentMenu({
}: Props) {
const user = useCurrentUser();
const { policies, collections, documents, subscriptions } = useStores();
const { showToast } = useToasts();
const menu = useMenuState({
modal,
unstable_preventOverflow: true,
@@ -120,11 +119,9 @@ function DocumentMenu({
}
) => {
await document.restore(options);
showToast(t("Document restored"), {
type: "success",
});
toast.success(t("Document restored"));
},
[showToast, t, document]
[t, document]
);
const collection = document.collectionId
@@ -187,13 +184,11 @@ function DocumentMenu({
);
history.push(importedDocument.url);
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
throw err;
}
},
[history, showToast, collection, documents, document.id]
[history, collection, documents, document.id]
);
return (

View File

@@ -4,6 +4,7 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { useMenuState } from "reakit/Menu";
import { toast } from "sonner";
import Share from "~/models/Share";
import ContextMenu from "~/components/ContextMenu";
import MenuItem from "~/components/ContextMenu/MenuItem";
@@ -11,7 +12,6 @@ import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
import CopyToClipboard from "~/components/CopyToClipboard";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
share: Share;
@@ -22,7 +22,6 @@ function ShareMenu({ share }: Props) {
modal: true,
});
const { shares } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const history = useHistory();
const can = usePolicy(share.id);
@@ -41,23 +40,17 @@ function ShareMenu({ share }: Props) {
try {
await shares.revoke(share);
showToast(t("Share link revoked"), {
type: "info",
});
toast.message(t("Share link revoked"));
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[t, shares, share, showToast]
[t, shares, share]
);
const handleCopy = React.useCallback(() => {
showToast(t("Share link copied"), {
type: "info",
});
}, [t, showToast]);
toast.success(t("Share link copied"));
}, [t]);
return (
<>

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useMenuState } from "reakit/Menu";
import { toast } from "sonner";
import User from "~/models/User";
import ContextMenu from "~/components/ContextMenu";
import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
@@ -18,7 +19,6 @@ import { deleteUserActionFactory } from "~/actions/definitions/users";
import useActionContext from "~/hooks/useActionContext";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
user: User;
@@ -31,7 +31,6 @@ function UserMenu({ user }: Props) {
modal: true,
});
const can = usePolicy(user.id);
const { showToast } = useToasts();
const context = useActionContext({
isContextMenu: true,
});
@@ -129,17 +128,14 @@ function UserMenu({ user }: Props) {
try {
await users.resendInvite(user);
showToast(t(`Invite was resent to ${user.name}`), { type: "success" });
toast.success(t(`Invite was resent to ${user.name}`));
} catch (err) {
showToast(
err.message ?? t(`An error occurred while sending the invite`),
{
type: "error",
}
toast.error(
err.message ?? t(`An error occurred while sending the invite`)
);
}
},
[users, user, t, showToast]
[users, user, t]
);
const handleActivate = React.useCallback(

View File

@@ -1,5 +1,6 @@
import User from "./User";
import Model from "./base/Model";
import Relation from "./decorators/Relation";
class GroupMembership extends Model {
id: string;
@@ -8,6 +9,7 @@ class GroupMembership extends Model {
groupId: string;
@Relation(() => User, { onDelete: "cascade" })
user: User;
}

View File

@@ -1,11 +1,11 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
onSubmit: () => void;
@@ -15,7 +15,6 @@ function APITokenNew({ onSubmit }: Props) {
const [name, setName] = React.useState("");
const [isSaving, setIsSaving] = React.useState(false);
const { apiKeys } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const handleSubmit = React.useCallback(
@@ -27,21 +26,15 @@ function APITokenNew({ onSubmit }: Props) {
await apiKeys.create({
name,
});
showToast(
t("API token created", {
type: "success",
})
);
toast.success(t("API token created"));
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[t, showToast, name, onSubmit, apiKeys]
[t, name, onSubmit, apiKeys]
);
const handleNameChange = React.useCallback((event) => {

View File

@@ -2,11 +2,11 @@ import { observer } from "mobx-react";
import * as React from "react";
import Dropzone from "react-dropzone";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled, { css } from "styled-components";
import LoadingIndicator from "~/components/LoadingIndicator";
import Text from "~/components/Text";
import useImportDocument from "~/hooks/useImportDocument";
import useToasts from "~/hooks/useToasts";
type Props = {
disabled: boolean;
@@ -22,17 +22,13 @@ const DropToImport: React.FC<Props> = ({
collectionId,
}: Props) => {
const { handleFiles, isImporting } = useImportDocument(collectionId);
const { showToast } = useToasts();
const { t } = useTranslation();
const handleRejection = React.useCallback(() => {
showToast(
t("Document not supported try Markdown, Plain text, HTML, or Word"),
{
type: "error",
}
toast.error(
t("Document not supported try Markdown, Plain text, HTML, or Word")
);
}, [t, showToast]);
}, [t]);
return (
<Dropzone

View File

@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import { useState } from "react";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import { CollectionValidation } from "@shared/validations";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
@@ -11,7 +12,6 @@ import Input from "~/components/Input";
import InputSelect from "~/components/InputSelect";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
collectionId: string;
@@ -30,7 +30,6 @@ const CollectionEdit = ({ collectionId, onSubmit }: Props) => {
direction: "asc" | "desc";
}>(collection.sort);
const [isSaving, setIsSaving] = useState(false);
const { showToast } = useToasts();
const { t } = useTranslation();
const handleSubmit = React.useCallback(
@@ -46,18 +45,14 @@ const CollectionEdit = ({ collectionId, onSubmit }: Props) => {
sort,
});
onSubmit();
showToast(t("The collection was updated"), {
type: "success",
});
toast.success(t("The collection was updated"));
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[collection, color, icon, name, onSubmit, showToast, sort, t]
[collection, color, icon, name, onSubmit, sort, t]
);
const handleSortChange = (value: string) => {

View File

@@ -3,6 +3,7 @@ import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { withTranslation, Trans, WithTranslation } from "react-i18next";
import { toast } from "sonner";
import { randomElement } from "@shared/random";
import { CollectionPermission } from "@shared/types";
import { colorPalette } from "@shared/utils/collections";
@@ -66,9 +67,7 @@ class CollectionNew extends React.Component<Props> {
this.props.onSubmit();
history.push(collection.url);
} catch (err) {
this.props.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
this.isSaving = false;
}

View File

@@ -2,6 +2,7 @@ import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import Collection from "~/models/Collection";
import Group from "~/models/Group";
@@ -29,8 +30,7 @@ function AddGroupsToCollection(props: Props) {
useBoolean(false);
const [query, setQuery] = React.useState("");
const { auth, collectionGroupMemberships, groups, policies, toasts } =
useStores();
const { auth, collectionGroupMemberships, groups, policies } = useStores();
const { fetchPage: fetchGroups } = groups;
const { t } = useTranslation();
@@ -55,18 +55,13 @@ function AddGroupsToCollection(props: Props) {
collectionId: collection.id,
groupId: group.id,
});
toasts.showToast(
toast.success(
t("{{ groupName }} was added to the collection", {
groupName: group.name,
}),
{
type: "success",
}
})
);
} catch (err) {
toasts.showToast(t("Could not add user"), {
type: "error",
});
toast.error(t("Could not add user"));
}
};

View File

@@ -1,9 +1,12 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import Collection from "~/models/Collection";
import User from "~/models/User";
import Invite from "~/scenes/Invite";
import Avatar from "~/components/Avatar";
import { AvatarSize } from "~/components/Avatar/Avatar";
import ButtonLink from "~/components/ButtonLink";
import Empty from "~/components/Empty";
import Flex from "~/components/Flex";
@@ -15,7 +18,6 @@ import useBoolean from "~/hooks/useBoolean";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useThrottledCallback from "~/hooks/useThrottledCallback";
import useToasts from "~/hooks/useToasts";
import MemberListItem from "./components/MemberListItem";
type Props = {
@@ -24,7 +26,6 @@ type Props = {
function AddPeopleToCollection({ collection }: Props) {
const { memberships, users } = useStores();
const { showToast } = useToasts();
const team = useCurrentTeam();
const { t } = useTranslation();
const [inviteModalOpen, setInviteModalOpen, setInviteModalClosed] =
@@ -50,18 +51,16 @@ function AddPeopleToCollection({ collection }: Props) {
collectionId: collection.id,
userId: user.id,
});
showToast(
toast.success(
t("{{ userName }} was added to the collection", {
userName: user.name,
}),
{
type: "success",
icon: <Avatar model={user} size={AvatarSize.Toast} />,
}
);
} catch (err) {
showToast(t("Could not add user"), {
type: "error",
});
toast.error(t("Could not add user"));
}
};

View File

@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { CollectionPermission } from "@shared/types";
import Group from "~/models/Group";
@@ -19,7 +20,6 @@ import Text from "~/components/Text";
import useBoolean from "~/hooks/useBoolean";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import AddGroupsToCollection from "./AddGroupsToCollection";
import AddPeopleToCollection from "./AddPeopleToCollection";
import CollectionGroupMemberListItem from "./components/CollectionGroupMemberListItem";
@@ -40,7 +40,6 @@ function CollectionPermissions({ collectionId }: Props) {
groups,
auth,
} = useStores();
const { showToast } = useToasts();
const collection = collections.get(collectionId);
invariant(collection, "Collection not found");
@@ -60,21 +59,16 @@ function CollectionPermissions({ collectionId }: Props) {
collectionId: collection.id,
userId: user.id,
});
showToast(
toast.success(
t(`{{ userName }} was removed from the collection`, {
userName: user.name,
}),
{
type: "success",
}
})
);
} catch (err) {
showToast(t("Could not remove user"), {
type: "error",
});
toast.error(t("Could not remove user"));
}
},
[memberships, showToast, collection, t]
[memberships, collection, t]
);
const handleUpdateUser = React.useCallback(
@@ -85,21 +79,16 @@ function CollectionPermissions({ collectionId }: Props) {
userId: user.id,
permission,
});
showToast(
toast.success(
t(`{{ userName }} permissions were updated`, {
userName: user.name,
}),
{
type: "success",
}
})
);
} catch (err) {
showToast(t("Could not update user"), {
type: "error",
});
toast.error(t("Could not update user"));
}
},
[memberships, showToast, collection, t]
[memberships, collection, t]
);
const handleRemoveGroup = React.useCallback(
@@ -109,21 +98,16 @@ function CollectionPermissions({ collectionId }: Props) {
collectionId: collection.id,
groupId: group.id,
});
showToast(
toast.success(
t(`The {{ groupName }} group was removed from the collection`, {
groupName: group.name,
}),
{
type: "success",
}
})
);
} catch (err) {
showToast(t("Could not remove group"), {
type: "error",
});
toast.error(t("Could not remove group"));
}
},
[collectionGroupMemberships, showToast, collection, t]
[collectionGroupMemberships, collection, t]
);
const handleUpdateGroup = React.useCallback(
@@ -134,21 +118,16 @@ function CollectionPermissions({ collectionId }: Props) {
groupId: group.id,
permission,
});
showToast(
toast.success(
t(`{{ groupName }} permissions were updated`, {
groupName: group.name,
}),
{
type: "success",
}
})
);
} catch (err) {
showToast(t("Could not update user"), {
type: "error",
});
toast.error(t("Could not update user"));
}
},
[collectionGroupMemberships, showToast, collection, t]
[collectionGroupMemberships, collection, t]
);
const handleChangePermission = React.useCallback(
@@ -157,16 +136,12 @@ function CollectionPermissions({ collectionId }: Props) {
await collection.save({
permission,
});
showToast(t("Default access permissions were updated"), {
type: "success",
});
toast.success(t("Default access permissions were updated"));
} catch (err) {
showToast(t("Could not update permissions"), {
type: "error",
});
toast.error(t("Could not update permissions"));
}
},
[collection, showToast, t]
[collection, t]
);
const fetchOptions = React.useMemo(
@@ -182,16 +157,12 @@ function CollectionPermissions({ collectionId }: Props) {
await collection.save({
sharing: ev.target.checked,
});
showToast(t("Public document sharing permissions were updated"), {
type: "success",
});
toast.success(t("Public document sharing permissions were updated"));
} catch (err) {
showToast(t("Could not update public document sharing"), {
type: "error",
});
toast.error(t("Could not update public document sharing"));
}
},
[collection, showToast, t]
[collection, t]
);
const collectionName = collection.name;

View File

@@ -3,6 +3,7 @@ import { action } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { v4 as uuidv4 } from "uuid";
import { CommentValidation } from "@shared/validations";
import Comment from "~/models/Comment";
@@ -15,7 +16,6 @@ import useCurrentUser from "~/hooks/useCurrentUser";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import usePersistedState from "~/hooks/usePersistedState";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import CommentEditor from "./CommentEditor";
import { Bubble } from "./CommentThreadItem";
@@ -65,7 +65,6 @@ function CommentForm({
const [forceRender, setForceRender] = React.useState(0);
const [inputFocused, setInputFocused] = React.useState(autoFocus);
const { t } = useTranslation();
const { showToast } = useToasts();
const { comments } = useStores();
const user = useCurrentUser();
@@ -106,7 +105,7 @@ function CommentForm({
})
.catch(() => {
comment.isNew = true;
showToast(t("Error creating comment"), { type: "error" });
toast.error(t("Error creating comment"));
});
// optimistically update the comment model
@@ -139,7 +138,7 @@ function CommentForm({
comment.save().catch(() => {
comments.remove(comment.id);
comment.isNew = true;
showToast(t("Error creating comment"), { type: "error" });
toast.error(t("Error creating comment"));
});
// optimistically update the comment model

View File

@@ -4,6 +4,7 @@ import { observer } from "mobx-react";
import { darken } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
@@ -17,7 +18,6 @@ import Flex from "~/components/Flex";
import Text from "~/components/Text";
import Time from "~/components/Time";
import useBoolean from "~/hooks/useBoolean";
import useToasts from "~/hooks/useToasts";
import CommentMenu from "~/menus/CommentMenu";
import { hover } from "~/styles";
import CommentEditor from "./CommentEditor";
@@ -85,7 +85,6 @@ function CommentThreadItem({
canReply,
}: Props) {
const { editor } = useDocumentContext();
const { showToast } = useToasts();
const { t } = useTranslation();
const [forceRender, setForceRender] = React.useState(0);
const [data, setData] = React.useState(toJS(comment.data));
@@ -116,7 +115,7 @@ function CommentThreadItem({
});
} catch (error) {
setEditing();
showToast(t("Error updating comment"), { type: "error" });
toast.error(t("Error updating comment"));
}
};

View File

@@ -11,6 +11,7 @@ import {
withRouter,
Redirect,
} from "react-router";
import { toast } from "sonner";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
@@ -176,7 +177,7 @@ class DocumentScene extends React.Component<Props> {
};
onSynced = async () => {
const { toasts, history, location, t } = this.props;
const { history, location, t } = this.props;
const restore = location.state?.restore;
const revisionId = location.state?.revisionId;
const editorRef = this.editor.current;
@@ -191,7 +192,7 @@ class DocumentScene extends React.Component<Props> {
if (response) {
await this.replaceDocument(response.data);
toasts.showToast(t("Document restored"));
toast.success(t("Document restored"));
history.replace(this.props.document.url, history.location.state);
}
};
@@ -316,9 +317,7 @@ class DocumentScene extends React.Component<Props> {
this.props.ui.setActiveDocument(savedDocument);
}
} catch (err) {
this.props.toasts.showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
this.isSaving = false;
this.isPublishing = false;

View File

@@ -3,6 +3,7 @@ import throttle from "lodash/throttle";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import { IndexeddbPersistence } from "y-indexeddb";
import * as Y from "yjs";
import MultiplayerExtension from "@shared/editor/extensions/Multiplayer";
@@ -14,7 +15,6 @@ import useIdle from "~/hooks/useIdle";
import useIsMounted from "~/hooks/useIsMounted";
import usePageVisibility from "~/hooks/usePageVisibility";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { AwarenessChangeEvent } from "~/types";
import Logger from "~/utils/Logger";
import { homePath } from "~/utils/routeHelpers";
@@ -51,7 +51,6 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
const [isLocalSynced, setLocalSynced] = React.useState(false);
const [isRemoteSynced, setRemoteSynced] = React.useState(false);
const [ydoc] = React.useState(() => new Y.Doc());
const { showToast } = useToasts();
const token = auth.collaborationToken;
const isIdle = useIdle();
const isVisible = usePageVisibility();
@@ -180,7 +179,6 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
};
}, [
history,
showToast,
t,
documentId,
ui,
@@ -251,21 +249,17 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
React.useEffect(() => {
function onUnhandledError(event: ErrorEvent) {
if (event.message.includes("URIError: URI malformed")) {
showToast(
toast.error(
t(
"Sorry, the last change could not be persisted please reload the page"
),
{
type: "error",
timeout: 0,
}
)
);
}
}
window.addEventListener("error", onUnhandledError);
return () => window.removeEventListener("error", onUnhandledError);
}, [showToast, t]);
}, [t]);
if (!remoteProvider) {
return null;

View File

@@ -6,6 +6,7 @@ import { ExpandedIcon, GlobeIcon, PadlockIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
import { dateLocale, dateToRelative } from "@shared/utils/date";
@@ -23,7 +24,6 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
import useKeyDown from "~/hooks/useKeyDown";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import useUserLocale from "~/hooks/useUserLocale";
type Props = {
@@ -44,7 +44,6 @@ function SharePopover({
const team = useCurrentTeam();
const { t } = useTranslation();
const { shares, collections } = useStores();
const { showToast } = useToasts();
const [expandedOptions, setExpandedOptions] = React.useState(false);
const [isEditMode, setIsEditMode] = React.useState(false);
const [slugValidationError, setSlugValidationError] = React.useState("");
@@ -100,12 +99,10 @@ function SharePopover({
published: event.currentTarget.checked,
});
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[document.id, shares, showToast]
[document.id, shares]
);
const handleChildDocumentsChange = React.useCallback(
@@ -118,22 +115,18 @@ function SharePopover({
includeChildDocuments: event.currentTarget.checked,
});
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[document.id, shares, showToast]
[document.id, shares]
);
const handleCopied = React.useCallback(() => {
timeout.current = setTimeout(() => {
onRequestClose();
showToast(t("Share link copied"), {
type: "info",
});
toast.message(t("Share link copied"));
}, 250);
}, [t, onRequestClose, showToast]);
}, [t, onRequestClose]);
const handleUrlSlugChange = React.useMemo(
() =>

View File

@@ -2,12 +2,12 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import Document from "~/models/Document";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { collectionPath, documentPath } from "~/utils/routeHelpers";
type Props = {
@@ -21,7 +21,6 @@ function DocumentDelete({ document, onSubmit }: Props) {
const history = useHistory();
const [isDeleting, setDeleting] = React.useState(false);
const [isArchiving, setArchiving] = React.useState(false);
const { showToast } = useToasts();
const canArchive = !document.isDraft && !document.isArchived;
const collection = document.collectionId
? collections.get(document.collectionId)
@@ -57,14 +56,12 @@ function DocumentDelete({ document, onSubmit }: Props) {
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setDeleting(false);
}
},
[showToast, onSubmit, ui, document, documents, history, collection]
[onSubmit, ui, document, documents, history, collection]
);
const handleArchive = React.useCallback(
@@ -76,14 +73,12 @@ function DocumentDelete({ document, onSubmit }: Props) {
await document.archive();
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setArchiving(false);
}
},
[showToast, onSubmit, document]
[onSubmit, document]
);
return (

View File

@@ -2,6 +2,7 @@ import flatten from "lodash/flatten";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { ellipsis } from "@shared/styles";
import { NavigationNode } from "@shared/types";
@@ -12,7 +13,6 @@ import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useCollectionTrees from "~/hooks/useCollectionTrees";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { flattenTree } from "~/utils/tree";
type Props = {
@@ -21,7 +21,6 @@ type Props = {
function DocumentMove({ document }: Props) {
const { dialogs } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const collectionTrees = useCollectionTrees();
const [selectedPath, selectPath] = React.useState<NavigationNode | null>(
@@ -51,9 +50,7 @@ function DocumentMove({ document }: Props) {
const move = async () => {
if (!selectedPath) {
showToast(t("Select a location to move"), {
type: "info",
});
toast.message(t("Select a location to move"));
return;
}
@@ -68,15 +65,11 @@ function DocumentMove({ document }: Props) {
await document.move(collectionId);
}
showToast(t("Document moved"), {
type: "success",
});
toast.success(t("Document moved"));
dialogs.closeAllModals();
} catch (err) {
showToast(t("Couldnt move the document, try again?"), {
type: "error",
});
toast.error(t("Couldnt move the document, try again?"));
}
};

View File

@@ -3,13 +3,13 @@ import * as React from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation, useRouteMatch } from "react-router-dom";
import { toast } from "sonner";
import CenteredContent from "~/components/CenteredContent";
import Flex from "~/components/Flex";
import PlaceholderDocument from "~/components/PlaceholderDocument";
import useCurrentUser from "~/hooks/useCurrentUser";
import useQuery from "~/hooks/useQuery";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { documentEditPath, documentPath } from "~/utils/routeHelpers";
type Props = {
@@ -25,7 +25,6 @@ function DocumentNew({ template }: Props) {
const match = useRouteMatch<{ id?: string }>();
const { t } = useTranslation();
const { documents, collections } = useStores();
const { showToast } = useToasts();
const id = match.params.id || query.get("collectionId");
useEffect(() => {
@@ -56,9 +55,7 @@ function DocumentNew({ template }: Props) {
location.state
);
} catch (err) {
showToast(t("Couldnt create the document, try again?"), {
type: "error",
});
toast.error(t("Couldnt create the document, try again?"));
history.goBack();
}
}

View File

@@ -2,12 +2,12 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import Document from "~/models/Document";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
document: Document;
@@ -18,7 +18,6 @@ function DocumentPermanentDelete({ document, onSubmit }: Props) {
const [isDeleting, setIsDeleting] = React.useState(false);
const { t } = useTranslation();
const { documents } = useStores();
const { showToast } = useToasts();
const history = useHistory();
const handleSubmit = React.useCallback(
@@ -30,20 +29,16 @@ function DocumentPermanentDelete({ document, onSubmit }: Props) {
await documents.delete(document, {
permanent: true,
});
showToast(t("Document permanently deleted"), {
type: "success",
});
toast.success(t("Document permanently deleted"));
onSubmit();
history.push("/trash");
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsDeleting(false);
}
},
[document, onSubmit, showToast, t, history, documents]
[document, onSubmit, t, history, documents]
);
return (

View File

@@ -2,6 +2,7 @@ import flatten from "lodash/flatten";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { ellipsis } from "@shared/styles";
import { NavigationNode } from "@shared/types";
@@ -12,7 +13,6 @@ import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useCollectionTrees from "~/hooks/useCollectionTrees";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { flattenTree } from "~/utils/tree";
type Props = {
@@ -22,7 +22,6 @@ type Props = {
function DocumentPublish({ document }: Props) {
const { dialogs } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const collectionTrees = useCollectionTrees();
const [selectedPath, selectPath] = React.useState<NavigationNode | null>(
@@ -35,9 +34,7 @@ function DocumentPublish({ document }: Props) {
const publish = async () => {
if (!selectedPath) {
showToast(t("Select a location to publish"), {
type: "info",
});
toast.message(t("Select a location to publish"));
return;
}
@@ -54,15 +51,11 @@ function DocumentPublish({ document }: Props) {
document.collectionId = collectionId;
await document.save(undefined, { publish: true });
showToast(t("Document published"), {
type: "success",
});
toast.success(t("Document published"));
dialogs.closeAllModals();
} catch (err) {
showToast(t("Couldnt publish the document, try again?"), {
type: "error",
});
toast.error(t("Couldnt publish the document, try again?"));
}
};

View File

@@ -2,13 +2,13 @@ import { observer } from "mobx-react";
import { useState } from "react";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import { CollectionPermission, NavigationNode } from "@shared/types";
import Collection from "~/models/Collection";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
item:
@@ -33,7 +33,6 @@ type Props = {
function DocumentReparent({ collection, item, onSubmit, onCancel }: Props) {
const [isSaving, setIsSaving] = useState(false);
const { showToast } = useToasts();
const { documents, collections } = useStores();
const { t } = useTranslation();
const prevCollection = collections.get(item.collectionId);
@@ -50,19 +49,15 @@ function DocumentReparent({ collection, item, onSubmit, onCancel }: Props) {
try {
await documents.move(item.id, collection.id);
showToast(t("Document moved"), {
type: "info",
});
toast.message(t("Document moved"));
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[documents, item.id, collection.id, showToast, t, onSubmit]
[documents, item.id, collection.id, t, onSubmit]
);
return (

View File

@@ -2,11 +2,11 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";
import { toast } from "sonner";
import Group from "~/models/Group";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useToasts from "~/hooks/useToasts";
import { settingsPath } from "~/utils/routeHelpers";
type Props = {
@@ -16,7 +16,6 @@ type Props = {
function GroupDelete({ group, onSubmit }: Props) {
const { t } = useTranslation();
const { showToast } = useToasts();
const history = useHistory();
const [isDeleting, setIsDeleting] = React.useState(false);
@@ -29,9 +28,7 @@ function GroupDelete({ group, onSubmit }: Props) {
history.push(settingsPath("groups"));
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsDeleting(false);
}

View File

@@ -1,12 +1,12 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Group from "~/models/Group";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
import Text from "~/components/Text";
import useToasts from "~/hooks/useToasts";
type Props = {
group: Group;
@@ -14,7 +14,6 @@ type Props = {
};
function GroupEdit({ group, onSubmit }: Props) {
const { showToast } = useToasts();
const { t } = useTranslation();
const [name, setName] = React.useState(group.name);
const [isSaving, setIsSaving] = React.useState(false);
@@ -29,14 +28,12 @@ function GroupEdit({ group, onSubmit }: Props) {
});
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[group, onSubmit, showToast, name]
[group, onSubmit, name]
);
const handleNameChange = React.useCallback(

View File

@@ -2,9 +2,12 @@ import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import Group from "~/models/Group";
import User from "~/models/User";
import Invite from "~/scenes/Invite";
import Avatar from "~/components/Avatar";
import { AvatarSize } from "~/components/Avatar/Avatar";
import ButtonLink from "~/components/ButtonLink";
import Empty from "~/components/Empty";
import Flex from "~/components/Flex";
@@ -24,7 +27,7 @@ type Props = {
function AddPeopleToGroup(props: Props) {
const { group } = props;
const { users, auth, groupMemberships, toasts } = useStores();
const { users, auth, groupMemberships } = useStores();
const { t } = useTranslation();
const [query, setQuery] = React.useState("");
@@ -53,18 +56,16 @@ function AddPeopleToGroup(props: Props) {
userId: user.id,
});
toasts.showToast(
toast.success(
t(`{{userName}} was added to the group`, {
userName: user.name,
}),
{
type: "success",
icon: <Avatar model={user} size={AvatarSize.Toast} />,
}
);
} catch (err) {
toasts.showToast(t("Could not add user"), {
type: "error",
});
toast.error(t("Could not add user"));
}
};

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Group from "~/models/Group";
import User from "~/models/User";
import Button from "~/components/Button";
@@ -13,7 +14,6 @@ import Subheading from "~/components/Subheading";
import Text from "~/components/Text";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import AddPeopleToGroup from "./AddPeopleToGroup";
import GroupMemberListItem from "./components/GroupMemberListItem";
@@ -24,7 +24,6 @@ type Props = {
function GroupMembers({ group }: Props) {
const [addModalOpen, setAddModalOpen] = React.useState(false);
const { users, groupMemberships } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const can = usePolicy(group);
@@ -38,18 +37,13 @@ function GroupMembers({ group }: Props) {
groupId: group.id,
userId: user.id,
});
showToast(
toast.success(
t(`{{userName}} was removed from the group`, {
userName: user.name,
}),
{
type: "success",
}
})
);
} catch (err) {
showToast(t("Could not remove user"), {
type: "error",
});
toast.error(t("Could not remove user"));
}
};

View File

@@ -1,6 +1,7 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Group from "~/models/Group";
import GroupMembers from "~/scenes/GroupMembers";
import Button from "~/components/Button";
@@ -9,7 +10,6 @@ import Input from "~/components/Input";
import Modal from "~/components/Modal";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
onSubmit: () => void;
@@ -18,7 +18,6 @@ type Props = {
function GroupNew({ onSubmit }: Props) {
const { groups } = useStores();
const { t } = useTranslation();
const { showToast } = useToasts();
const [name, setName] = React.useState<string | undefined>();
const [isSaving, setIsSaving] = React.useState(false);
const [group, setGroup] = React.useState<Group | undefined>();
@@ -38,9 +37,7 @@ function GroupNew({ onSubmit }: Props) {
await group.save();
setGroup(group);
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}

View File

@@ -3,6 +3,7 @@ import { LinkIcon, CloseIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
import { UserRole } from "@shared/types";
@@ -19,7 +20,6 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
onSubmit: () => void;
@@ -52,7 +52,6 @@ function Invite({ onSubmit }: Props) {
},
]);
const { users } = useStores();
const { showToast } = useToasts();
const user = useCurrentUser();
const team = useCurrentTeam();
const { t } = useTranslation();
@@ -69,23 +68,17 @@ function Invite({ onSubmit }: Props) {
onSubmit();
if (data.sent.length > 0) {
showToast(t("We sent out your invites!"), {
type: "success",
});
toast.success(t("We sent out your invites!"));
} else {
showToast(t("Those email addresses are already invited"), {
type: "success",
});
toast.message(t("Those email addresses are already invited"));
}
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}
},
[onSubmit, showToast, invites, t, users]
[onSubmit, invites, t, users]
);
const handleChange = React.useCallback((ev, index) => {
@@ -98,13 +91,10 @@ function Invite({ onSubmit }: Props) {
const handleAdd = React.useCallback(() => {
if (invites.length >= UserValidation.maxInvitesPerRequest) {
showToast(
toast.message(
t("Sorry, you can only send {{MAX_INVITES}} invites at a time", {
MAX_INVITES: UserValidation.maxInvitesPerRequest,
}),
{
type: "warning",
}
})
);
}
@@ -117,7 +107,7 @@ function Invite({ onSubmit }: Props) {
});
return newInvites;
});
}, [showToast, invites, t]);
}, [invites, t]);
const handleRemove = React.useCallback(
(ev: React.SyntheticEvent, index: number) => {
@@ -133,10 +123,8 @@ function Invite({ onSubmit }: Props) {
const handleCopy = React.useCallback(() => {
setLinkCopied(true);
showToast(t("Share link copied"), {
type: "success",
});
}, [showToast, t]);
toast.success(t("Share link copied"));
}, [t]);
const handleRoleChange = React.useCallback(
(role: UserRole, index: number) => {

View File

@@ -5,6 +5,7 @@ import { TeamIcon } from "outline-icons";
import { useRef, useState } from "react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import { ThemeProvider, useTheme } from "styled-components";
import { buildDarkTheme, buildLightTheme } from "@shared/styles/theme";
import { CustomTheme, TeamPreference } from "@shared/types";
@@ -21,7 +22,6 @@ import Text from "~/components/Text";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import isCloudHosted from "~/utils/isCloudHosted";
import TeamDelete from "../TeamDelete";
import ImageInput from "./components/ImageInput";
@@ -29,7 +29,6 @@ import SettingRow from "./components/SettingRow";
function Details() {
const { auth, dialogs, ui } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const team = useCurrentTeam();
const theme = useTheme();
@@ -76,13 +75,9 @@ function Details() {
customTheme,
},
});
showToast(t("Settings saved"), {
type: "success",
});
toast.success(t("Settings saved"));
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[
@@ -93,7 +88,6 @@ function Details() {
team.preferences,
publicBranding,
customTheme,
showToast,
t,
]
);
@@ -116,16 +110,14 @@ function Details() {
await auth.updateTeam({
avatarUrl,
});
showToast(t("Logo updated"), {
type: "success",
});
toast.success(t("Logo updated"));
};
const handleAvatarError = React.useCallback(
(error: string | null | undefined) => {
showToast(error || t("Unable to upload new logo"));
toast.error(error || t("Unable to upload new logo"));
},
[showToast, t]
[t]
);
const showDeleteWorkspace = () => {

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import { BeakerIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import { TeamPreference } from "@shared/types";
import Heading from "~/components/Heading";
import Scene from "~/components/Scene";
@@ -9,14 +10,12 @@ import Switch from "~/components/Switch";
import Text from "~/components/Text";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import SettingRow from "./components/SettingRow";
function Features() {
const { auth } = useStores();
const team = useCurrentTeam();
const { t } = useTranslation();
const { showToast } = useToasts();
const handlePreferenceChange =
(inverted = false) =>
@@ -27,9 +26,7 @@ function Features() {
};
await auth.updateTeam({ preferences });
showToast(t("Settings saved"), {
type: "success",
});
toast.success(t("Settings saved"));
};
return (

View File

@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useForm } from "react-hook-form";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import { IntegrationType, IntegrationService } from "@shared/types";
import Integration from "~/models/Integration";
import Button from "~/components/Button";
@@ -12,7 +13,6 @@ import Input from "~/components/Input";
import Scene from "~/components/Scene";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import SettingRow from "./components/SettingRow";
type FormData = {
@@ -22,7 +22,6 @@ type FormData = {
function GoogleAnalytics() {
const { integrations } = useStores();
const { t } = useTranslation();
const { showToast } = useToasts();
const integration = find(integrations.orderedData, {
type: IntegrationType.Analytics,
@@ -67,16 +66,12 @@ function GoogleAnalytics() {
await integration?.delete();
}
showToast(t("Settings saved"), {
type: "success",
});
toast.success(t("Settings saved"));
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[integrations, integration, t, showToast]
[integrations, integration, t]
);
return (

View File

@@ -13,6 +13,7 @@ import {
} from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import { NotificationEventType } from "@shared/types";
import Flex from "~/components/Flex";
import Heading from "~/components/Heading";
@@ -23,12 +24,10 @@ import Switch from "~/components/Switch";
import Text from "~/components/Text";
import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser";
import useToasts from "~/hooks/useToasts";
import isCloudHosted from "~/utils/isCloudHosted";
import SettingRow from "./components/SettingRow";
function Notifications() {
const { showToast } = useToasts();
const user = useCurrentUser();
const { t } = useTranslation();
@@ -106,9 +105,7 @@ function Notifications() {
];
const showSuccessMessage = debounce(() => {
showToast(t("Notifications saved"), {
type: "success",
});
toast.success(t("Notifications saved"));
}, 500);
const handleChange = React.useCallback(

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import { SettingsIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import { languageOptions } from "@shared/i18n";
import { TeamPreference, UserPreference } from "@shared/types";
import Button from "~/components/Button";
@@ -13,13 +14,11 @@ import Text from "~/components/Text";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import UserDelete from "../UserDelete";
import SettingRow from "./components/SettingRow";
function Preferences() {
const { t } = useTranslation();
const { showToast } = useToasts();
const { dialogs, auth } = useStores();
const user = useCurrentUser();
const team = useCurrentTeam();
@@ -33,16 +32,12 @@ function Preferences() {
};
await auth.updateUser({ preferences });
showToast(t("Preferences saved"), {
type: "success",
});
toast.success(t("Preferences saved"));
};
const handleLanguageChange = async (language: string) => {
await auth.updateUser({ language });
showToast(t("Preferences saved"), {
type: "success",
});
toast.success(t("Preferences saved"));
};
const showDeleteAccount = () => {

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import { ProfileIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import Input from "~/components/Input";
@@ -9,7 +10,6 @@ import Scene from "~/components/Scene";
import Text from "~/components/Text";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import ImageInput from "./components/ImageInput";
import SettingRow from "./components/SettingRow";
@@ -18,7 +18,6 @@ const Profile = () => {
const user = useCurrentUser();
const form = React.useRef<HTMLFormElement>(null);
const [name, setName] = React.useState<string>(user.name || "");
const { showToast } = useToasts();
const { t } = useTranslation();
const handleSubmit = async (ev: React.SyntheticEvent) => {
@@ -28,13 +27,9 @@ const Profile = () => {
await auth.updateUser({
name,
});
showToast(t("Profile saved"), {
type: "success",
});
toast.success(t("Profile saved"));
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
};
@@ -46,15 +41,11 @@ const Profile = () => {
await auth.updateUser({
avatarUrl,
});
showToast(t("Profile picture updated"), {
type: "success",
});
toast.success(t("Profile picture updated"));
};
const handleAvatarError = (error: string | null | undefined) => {
showToast(error || t("Unable to upload new profile picture"), {
type: "error",
});
toast.error(error || t("Unable to upload new profile picture"));
};
const isValid = form.current?.checkValidity();

View File

@@ -4,6 +4,7 @@ import { CheckboxIcon, EmailIcon, PadlockIcon } from "outline-icons";
import { useState } from "react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import { useTheme } from "styled-components";
import { TeamPreference } from "@shared/types";
import ConfirmationDialog from "~/components/ConfirmationDialog";
@@ -18,7 +19,6 @@ import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import isCloudHosted from "~/utils/isCloudHosted";
import DomainManagement from "./components/DomainManagement";
import SettingRow from "./components/SettingRow";
@@ -27,7 +27,6 @@ function Security() {
const { auth, authenticationProviders, dialogs } = useStores();
const team = useCurrentTeam();
const { t } = useTranslation();
const { showToast } = useToasts();
const theme = useTheme();
const [data, setData] = useState({
sharing: team.sharing,
@@ -53,11 +52,9 @@ function Security() {
const showSuccessMessage = React.useMemo(
() =>
debounce(() => {
showToast(t("Settings saved"), {
type: "success",
});
toast.success(t("Settings saved"));
}, 250),
[showToast, t]
[t]
);
const saveData = React.useCallback(
@@ -67,12 +64,10 @@ function Security() {
await auth.updateTeam(newData);
showSuccessMessage();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[auth, showSuccessMessage, showToast]
[auth, showSuccessMessage]
);
const handleChange = React.useCallback(

View File

@@ -4,6 +4,7 @@ import { BuildingBlocksIcon } from "outline-icons";
import * as React from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { IntegrationService, IntegrationType } from "@shared/types";
import Integration from "~/models/Integration";
import Button from "~/components/Button";
@@ -11,7 +12,6 @@ import Heading from "~/components/Heading";
import Input from "~/components/Input";
import Scene from "~/components/Scene";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import SettingRow from "./components/SettingRow";
type FormData = {
@@ -22,7 +22,6 @@ type FormData = {
function SelfHosted() {
const { integrations } = useStores();
const { t } = useTranslation();
const { showToast } = useToasts();
const integrationDiagrams = find(integrations.orderedData, {
type: IntegrationType.Embed,
@@ -89,16 +88,12 @@ function SelfHosted() {
await integrationGrist?.delete();
}
showToast(t("Settings saved"), {
type: "success",
});
toast.success(t("Settings saved"));
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[integrations, integrationDiagrams, integrationGrist, t, showToast]
[integrations, integrationDiagrams, integrationGrist, t]
);
return (

View File

@@ -1,12 +1,12 @@
import { CopyIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import ApiKey from "~/models/ApiKey";
import Button from "~/components/Button";
import CopyToClipboard from "~/components/CopyToClipboard";
import Flex from "~/components/Flex";
import ListItem from "~/components/List/Item";
import useToasts from "~/hooks/useToasts";
import ApiKeyMenu from "~/menus/ApiKeyMenu";
type Props = {
@@ -15,7 +15,6 @@ type Props = {
const ApiKeyListItem = ({ apiKey }: Props) => {
const { t } = useTranslation();
const { showToast } = useToasts();
const [linkCopied, setLinkCopied] = React.useState<boolean>(false);
React.useEffect(() => {
@@ -28,10 +27,8 @@ const ApiKeyListItem = ({ apiKey }: Props) => {
const handleCopy = React.useCallback(() => {
setLinkCopied(true);
showToast(t("API token copied to clipboard"), {
type: "success",
});
}, [showToast, t]);
toast.message(t("API token copied to clipboard"));
}, [t]);
return (
<ListItem

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import { CloseIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import Button from "~/components/Button";
import Fade from "~/components/Fade";
@@ -11,7 +12,6 @@ import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import SettingRow from "./SettingRow";
type Props = {
@@ -22,7 +22,6 @@ function DomainManagement({ onSuccess }: Props) {
const { auth } = useStores();
const team = useCurrentTeam();
const { t } = useTranslation();
const { showToast } = useToasts();
const [allowedDomains, setAllowedDomains] = React.useState([
...(team.allowedDomains ?? []),
@@ -43,11 +42,9 @@ function DomainManagement({ onSuccess }: Props) {
setExistingDomainsTouched(false);
updateLastKnownDomainCount(allowedDomains.length);
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
}, [auth, allowedDomains, onSuccess, showToast]);
}, [auth, allowedDomains, onSuccess]);
const handleRemoveDomain = async (index: number) => {
const newDomains = allowedDomains.filter((_, i) => index !== i);

View File

@@ -3,13 +3,13 @@ import { NewDocumentIcon } from "outline-icons";
import * as React from "react";
import Dropzone from "react-dropzone";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
import { AttachmentPreset } from "@shared/types";
import Flex from "~/components/Flex";
import LoadingIndicator from "~/components/LoadingIndicator";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { uploadFile } from "~/utils/files";
type Props = {
@@ -23,15 +23,12 @@ type Props = {
function DropToImport({ disabled, onSubmit, children, format }: Props) {
const { t } = useTranslation();
const { collections } = useStores();
const { showToast } = useToasts();
const [isImporting, setImporting] = React.useState(false);
const handleFiles = React.useCallback(
async (files) => {
if (files.length > 1) {
showToast(t("Please choose a single file to import"), {
type: "error",
});
toast.error(t("Please choose a single file to import"));
return;
}
const file = files[0];
@@ -45,27 +42,21 @@ function DropToImport({ disabled, onSubmit, children, format }: Props) {
});
await collections.import(attachment.id, format);
onSubmit();
showToast(
t("Your import is being processed, you can safely leave this page"),
{
type: "success",
timeout: 6000,
}
toast.success(
t("Your import is being processed, you can safely leave this page")
);
} catch (err) {
showToast(err.message);
toast.error(err.message);
} finally {
setImporting(false);
}
},
[t, onSubmit, collections, format, showToast]
[t, onSubmit, collections, format]
);
const handleRejection = React.useCallback(() => {
showToast(t("File not supported please upload a valid ZIP file"), {
type: "error",
});
}, [t, showToast]);
toast.error(t("File not supported please upload a valid ZIP file"));
}, [t]);
if (disabled) {
return children;

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import { ArchiveIcon, DoneIcon, WarningIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useTheme } from "styled-components";
import {
FileOperationFormat,
@@ -16,7 +17,6 @@ import Spinner from "~/components/Spinner";
import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import FileOperationMenu from "~/menus/FileOperationMenu";
type Props = {
@@ -28,7 +28,6 @@ const FileOperationListItem = ({ fileOperation }: Props) => {
const user = useCurrentUser();
const theme = useTheme();
const { dialogs, fileOperations } = useStores();
const { showToast } = useToasts();
const stateMapping = {
[FileOperationState.Creating]: t("Processing"),
@@ -65,16 +64,14 @@ const FileOperationListItem = ({ fileOperation }: Props) => {
await fileOperations.delete(fileOperation);
if (fileOperation.type === FileOperationType.Import) {
showToast(t("Import deleted"));
toast.success(t("Import deleted"));
} else {
showToast(t("Export deleted"));
toast.success(t("Export deleted"));
}
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
}, [fileOperation, fileOperations, showToast, t]);
}, [fileOperation, fileOperations, t]);
const handleConfirmDelete = React.useCallback(async () => {
dialogs.openModal({

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useForm } from "react-hook-form";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
@@ -9,7 +10,6 @@ import Text from "~/components/Text";
import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type FormData = {
code: string;
@@ -22,7 +22,6 @@ type Props = {
function TeamDelete({ onSubmit }: Props) {
const [isWaitingCode, setWaitingCode] = React.useState(false);
const { auth } = useStores();
const { showToast } = useToasts();
const team = useCurrentTeam();
const { t } = useTranslation();
const {
@@ -39,12 +38,10 @@ function TeamDelete({ onSubmit }: Props) {
await auth.requestDeleteTeam();
setWaitingCode(true);
} catch (error) {
showToast(error.message, {
type: "error",
});
toast.error(error.message);
}
},
[auth, showToast]
[auth]
);
const handleSubmit = React.useCallback(
@@ -54,12 +51,10 @@ function TeamDelete({ onSubmit }: Props) {
await auth.logout();
onSubmit();
} catch (error) {
showToast(error.message, {
type: "error",
});
toast.error(error.message);
}
},
[auth, onSubmit, showToast]
[auth, onSubmit]
);
const inputProps = register("code", {

View File

@@ -1,6 +1,7 @@
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import User from "~/models/User";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
@@ -8,7 +9,6 @@ import Input from "~/components/Input";
import Notice from "~/components/Notice";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type Props = {
user: User;
@@ -17,7 +17,6 @@ type Props = {
function TeamNew({ user }: Props) {
const { auth } = useStores();
const { t } = useTranslation();
const { showToast } = useToasts();
const [name, setName] = React.useState("");
const [isSaving, setIsSaving] = React.useState(false);
@@ -32,9 +31,7 @@ function TeamNew({ user }: Props) {
});
}
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
} finally {
setIsSaving(false);
}

View File

@@ -2,13 +2,13 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useForm } from "react-hook-form";
import { useTranslation, Trans } from "react-i18next";
import { toast } from "sonner";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
import Text from "~/components/Text";
import env from "~/env";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
type FormData = {
code: string;
@@ -17,7 +17,6 @@ type FormData = {
function UserDelete() {
const [isWaitingCode, setWaitingCode] = React.useState(false);
const { auth } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const {
register,
@@ -32,13 +31,11 @@ function UserDelete() {
try {
await auth.requestDeleteUser();
setWaitingCode(true);
} catch (error) {
showToast(error.message, {
type: "error",
});
} catch (err) {
toast.error(err.message);
}
},
[auth, showToast]
[auth]
);
const handleSubmit = React.useCallback(
@@ -46,13 +43,11 @@ function UserDelete() {
try {
await auth.deleteUser(data);
await auth.logout();
} catch (error) {
showToast(error.message, {
type: "error",
});
} catch (err) {
toast.error(err.message);
}
},
[auth, showToast]
[auth]
);
const inputProps = register("code", {

View File

@@ -21,7 +21,6 @@ import SearchesStore from "./SearchesStore";
import SharesStore from "./SharesStore";
import StarsStore from "./StarsStore";
import SubscriptionsStore from "./SubscriptionsStore";
import ToastsStore from "./ToastsStore";
import UiStore from "./UiStore";
import UsersStore from "./UsersStore";
import ViewsStore from "./ViewsStore";
@@ -53,7 +52,6 @@ export default class RootStore {
subscriptions: SubscriptionsStore;
users: UsersStore;
views: ViewsStore;
toasts: ToastsStore;
fileOperations: FileOperationsStore;
webhookSubscriptions: WebhookSubscriptionsStore;
@@ -85,7 +83,6 @@ export default class RootStore {
this.users = new UsersStore(this);
this.views = new ViewsStore(this);
this.fileOperations = new FileOperationsStore(this);
this.toasts = new ToastsStore();
this.webhookSubscriptions = new WebhookSubscriptionsStore(this);
}

View File

@@ -1,25 +0,0 @@
import stores from ".";
describe("ToastsStore", () => {
const store = stores.toasts;
test("#add should add messages", () => {
expect(store.orderedData.length).toBe(0);
store.showToast("first error");
store.showToast("second error");
expect(store.orderedData.length).toBe(2);
});
test("#remove should remove messages", () => {
store.toasts.clear();
const id = store.showToast("first error");
store.showToast("second error");
expect(store.orderedData.length).toBe(2);
id && store.hideToast(id);
expect(store.orderedData.length).toBe(1);
expect(store.orderedData[0].message).toBe("second error");
});
});

View File

@@ -1,55 +0,0 @@
import orderBy from "lodash/orderBy";
import { observable, action, computed } from "mobx";
import { v4 as uuidv4 } from "uuid";
import { Toast, ToastOptions } from "~/types";
export default class ToastsStore {
@observable
toasts: Map<string, Toast> = new Map();
lastToastId: string;
@action
showToast = (
message: string,
options: ToastOptions = {
type: "info",
}
) => {
if (!message) {
return;
}
const lastToast = this.toasts.get(this.lastToastId);
if (lastToast?.message === message) {
this.toasts.set(this.lastToastId, {
...lastToast,
reoccurring: lastToast.reoccurring ? ++lastToast.reoccurring : 1,
});
return this.lastToastId;
}
const id = uuidv4();
const createdAt = new Date().toISOString();
this.toasts.set(id, {
id,
message,
createdAt,
type: options.type,
timeout: options.timeout,
action: options.action,
});
this.lastToastId = id;
return id;
};
@action
hideToast = (id: string) => {
this.toasts.delete(id);
};
@computed
get orderedData(): Toast[] {
return orderBy(Array.from(this.toasts.values()), "createdAt", "desc");
}
}

View File

@@ -121,19 +121,6 @@ export type LocationWithState = Location & {
state: Record<string, string>;
};
export type Toast = {
id: string;
createdAt: string;
message: string;
type: "warning" | "error" | "info" | "success" | "loading";
timeout?: number;
reoccurring?: number;
action?: {
text: string;
onClick: React.MouseEventHandler<HTMLSpanElement>;
};
};
export type FetchOptions = {
prefetch?: boolean;
revisionId?: string;
@@ -177,15 +164,6 @@ export type SearchResult = {
document: Document;
};
export type ToastOptions = {
type: "warning" | "error" | "info" | "success" | "loading";
timeout?: number;
action?: {
text: string;
onClick: React.MouseEventHandler<HTMLSpanElement>;
};
};
export type WebsocketEntityDeletedEvent = {
modelId: string;
};

View File

@@ -211,6 +211,7 @@
"socket.io": "^4.7.2",
"socket.io-client": "^4.6.1",
"socket.io-redis": "^6.1.1",
"sonner": "^1.0.3",
"stoppable": "^1.1.0",
"string-replace-to-array": "^2.1.0",
"styled-components": "^5.3.11",

View File

@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import { usePopoverState, PopoverDisclosure } from "reakit/Popover";
import { toast } from "sonner";
import styled from "styled-components";
import { s } from "@shared/styles";
import { IntegrationType } from "@shared/types";
@@ -16,7 +17,6 @@ import ListItem from "~/components/List/Item";
import Popover from "~/components/Popover";
import Switch from "~/components/Switch";
import Text from "~/components/Text";
import useToasts from "~/hooks/useToasts";
type Props = {
integration: Integration<IntegrationType.Post>;
@@ -25,7 +25,6 @@ type Props = {
function SlackListItem({ integration, collection }: Props) {
const { t } = useTranslation();
const { showToast } = useToasts();
const handleChange = async (ev: React.ChangeEvent<HTMLInputElement>) => {
if (ev.target.checked) {
@@ -38,9 +37,7 @@ function SlackListItem({ integration, collection }: Props) {
await integration.save();
showToast(t("Settings saved"), {
type: "success",
});
toast.success(t("Settings saved"));
};
const mapping = {

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import WebhookSubscription from "~/models/WebhookSubscription";
import useToasts from "~/hooks/useToasts";
import WebhookSubscriptionForm from "./WebhookSubscriptionForm";
type Props = {
@@ -16,7 +16,6 @@ interface FormData {
}
function WebhookSubscriptionEdit({ onSubmit, webhookSubscription }: Props) {
const { showToast } = useToasts();
const { t } = useTranslation();
const handleSubmit = React.useCallback(
@@ -31,19 +30,13 @@ function WebhookSubscriptionEdit({ onSubmit, webhookSubscription }: Props) {
await webhookSubscription.save(toSend);
showToast(
t("Webhook updated", {
type: "success",
})
);
toast.success(t("Webhook updated"));
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[t, showToast, onSubmit, webhookSubscription]
[t, onSubmit, webhookSubscription]
);
return (

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import WebhookSubscriptionForm from "./WebhookSubscriptionForm";
type Props = {
@@ -16,7 +16,6 @@ interface FormData {
function WebhookSubscriptionNew({ onSubmit }: Props) {
const { webhookSubscriptions } = useStores();
const { showToast } = useToasts();
const { t } = useTranslation();
const handleSubmit = React.useCallback(
@@ -30,19 +29,13 @@ function WebhookSubscriptionNew({ onSubmit }: Props) {
};
await webhookSubscriptions.create(toSend);
showToast(
t("Webhook created", {
type: "success",
})
);
toast.success(t("Webhook created"));
onSubmit();
} catch (err) {
showToast(err.message, {
type: "error",
});
toast.error(err.message);
}
},
[t, showToast, onSubmit, webhookSubscriptions]
[t, onSubmit, webhookSubscriptions]
);
return <WebhookSubscriptionForm handleSubmit={handleSubmit} />;

View File

@@ -1,5 +1,6 @@
import { Node } from "prosemirror-model";
import { EditorView } from "prosemirror-view";
import { toast } from "sonner";
function findPlaceholderLink(doc: Node, href: string) {
let result: { pos: number; node: Node } | undefined;
@@ -38,11 +39,10 @@ const createAndInsertLink = async function (
options: {
dictionary: any;
onCreateLink: (title: string) => Promise<string>;
onShowToast: (message: string) => void;
}
) {
const { dispatch, state } = view;
const { onCreateLink, onShowToast } = options;
const { onCreateLink } = options;
try {
const url = await onCreateLink(title);
@@ -79,7 +79,7 @@ const createAndInsertLink = async function (
)
);
onShowToast(options.dictionary.createLinkError);
toast.error(options.dictionary.createLinkError);
}
};

View File

@@ -1,5 +1,6 @@
import * as Sentry from "@sentry/react";
import { EditorView } from "prosemirror-view";
import { toast } from "sonner";
import { v4 as uuidv4 } from "uuid";
import FileHelper from "../lib/FileHelper";
import uploadPlaceholderPlugin, {
@@ -19,8 +20,6 @@ export type Options = {
onFileUploadStart?: () => void;
/** Callback fired when the user completes a file upload */
onFileUploadStop?: () => void;
/** Callback fired when a toast needs to be displayed */
onShowToast: (message: string) => void;
/** Attributes to overwrite */
attrs?: {
/** Width to use when inserting image */
@@ -40,13 +39,8 @@ const insertFiles = function (
files: File[],
options: Options
): void {
const {
dictionary,
uploadFile,
onFileUploadStart,
onFileUploadStop,
onShowToast,
} = options;
const { dictionary, uploadFile, onFileUploadStart, onFileUploadStop } =
options;
// okay, we have some dropped files and a handler lets stop this
// event going any further up the stack
@@ -172,7 +166,7 @@ const insertFiles = function (
})
);
onShowToast(error.message || dictionary.fileUploadError);
toast.error(error.message || dictionary.fileUploadError);
})
.finally(() => {
complete++;

View File

@@ -13,6 +13,7 @@ import { Command, EditorState, Plugin } from "prosemirror-state";
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
import * as React from "react";
import ReactDOM from "react-dom";
import { toast } from "sonner";
import { isExternalUrl, sanitizeUrl } from "../../utils/urls";
import findLinkNodes from "../queries/findLinkNodes";
import getMarkRange from "../queries/getMarkRange";
@@ -138,9 +139,7 @@ export default class Link extends Mark {
event
);
} catch (err) {
this.editor.props.onShowToast(
this.options.dictionary.openLinkError
);
toast.error(this.options.dictionary.openLinkError);
}
return true;
}
@@ -177,9 +176,7 @@ export default class Link extends Mark {
);
}
} catch (err) {
this.editor.props.onShowToast(
this.options.dictionary.openLinkError
);
toast.error(this.options.dictionary.openLinkError);
}
});
return cloned;
@@ -246,9 +243,7 @@ export default class Link extends Mark {
this.options.onClickLink(sanitizeUrl(href), event);
}
} catch (err) {
this.editor.props.onShowToast(
this.options.dictionary.openLinkError
);
toast.error(this.options.dictionary.openLinkError);
}
return true;

View File

@@ -56,6 +56,7 @@ import visualbasic from "refractor/lang/visual-basic";
import yaml from "refractor/lang/yaml";
import zig from "refractor/lang/zig";
import { toast } from "sonner";
import { Primitive } from "utility-types";
import { Dictionary } from "~/hooks/useDictionary";
import { UserPreferences } from "../../types";
@@ -130,7 +131,6 @@ export default class CodeFence extends Node {
constructor(options: {
dictionary: Dictionary;
userPreferences?: UserPreferences | null;
onShowToast: (message: string) => void;
}) {
super(options);
}
@@ -197,7 +197,7 @@ export default class CodeFence extends Node {
}
copy(codeBlock.node.textContent);
this.options.onShowToast(this.options.dictionary.codeCopied);
toast.message(this.options.dictionary.codeCopied);
return true;
},
};

View File

@@ -8,6 +8,7 @@ import {
} from "prosemirror-model";
import { Command, Plugin, Selection } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
import { toast } from "sonner";
import { Primitive } from "utility-types";
import Storage from "../../utils/Storage";
import backspaceToParagraph from "../commands/backspaceToParagraph";
@@ -188,7 +189,7 @@ export default class Heading extends Node {
.replace("/edit", "");
copy(normalizedUrl + hash);
this.options.onShowToast(this.options.dictionary.linkCopied);
toast.message(this.options.dictionary.linkCopied);
};
keys({ type, schema }: { type: NodeType; schema: Schema }) {

View File

@@ -125,7 +125,7 @@ export default class SimpleImage extends Node {
}
const { view } = this.editor;
const { node } = state.selection;
const { uploadFile, onFileUploadStart, onFileUploadStop, onShowToast } =
const { uploadFile, onFileUploadStart, onFileUploadStop } =
this.editor.props;
if (!uploadFile) {
@@ -146,7 +146,6 @@ export default class SimpleImage extends Node {
uploadFile,
onFileUploadStart,
onFileUploadStop,
onShowToast,
dictionary: this.options.dictionary,
replaceExisting: true,
attrs: {

View File

@@ -132,6 +132,7 @@
"Submenu": "Submenu",
"Collections could not be loaded, please reload the app": "Collections could not be loaded, please reload the app",
"Default collection": "Default collection",
"Install now": "Install now",
"Deleted Collection": "Deleted Collection",
"Unpin": "Unpin",
"Search collections & documents": "Search collections & documents",
@@ -191,9 +192,9 @@
"{{userName}} published": "{{userName}} published",
"{{userName}} unpublished": "{{userName}} unpublished",
"{{userName}} moved": "{{userName}} moved",
"Your file will be available in {{ location }} soon": "Your file will be available in {{ location }} soon",
"Go to exports": "Go to exports",
"Export started": "Export started",
"Your file will be available in {{ location }} soon": "Your file will be available in {{ location }} soon",
"View": "View",
"A ZIP file containing the images, and documents in the Markdown format.": "A ZIP file containing the images, and documents in the Markdown format.",
"A ZIP file containing the images, and documents as HTML files.": "A ZIP file containing the images, and documents as HTML files.",
"Structured data that can be used to transfer data to another compatible {{ appName }} instance.": "Structured data that can be used to transfer data to another compatible {{ appName }} instance.",

View File

@@ -152,8 +152,8 @@ export const buildLightTheme = (input: Partial<Colors>): DefaultTheme => {
buttonNeutralBorder: darken(0.15, colors.white),
tooltipBackground: colors.almostBlack,
tooltipText: colors.white,
toastBackground: colors.almostBlack,
toastText: colors.white,
toastBackground: colors.white,
toastText: colors.almostBlack,
quote: colors.slateLight,
codeBackground: colors.smoke,
codeBorder: colors.smokeDark,
@@ -215,8 +215,8 @@ export const buildDarkTheme = (input: Partial<Colors>): DefaultTheme => {
buttonNeutralBorder: colors.slateDark,
tooltipBackground: colors.white,
tooltipText: colors.lightBlack,
toastBackground: colors.white,
toastText: colors.lightBlack,
toastBackground: colors.veryDarkBlue,
toastText: colors.almostWhite,
quote: colors.almostWhite,
code: colors.almostWhite,
codeBackground: colors.black75,

102
yarn.lock
View File

@@ -40,14 +40,7 @@
"@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3"
chokidar "^3.4.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
dependencies:
"@babel/highlight" "^7.22.5"
"@babel/code-frame@^7.22.13":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.22.5":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
@@ -81,17 +74,7 @@
json5 "^2.2.2"
semver "^6.3.0"
"@babel/generator@^7.22.5", "@babel/generator@^7.7.2":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7"
integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==
dependencies:
"@babel/types" "^7.22.5"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.23.0":
"@babel/generator@^7.22.5", "@babel/generator@^7.23.0", "@babel/generator@^7.7.2":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
@@ -175,12 +158,7 @@
resolve "^1.14.2"
semver "^6.1.2"
"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
"@babel/helper-environment-visitor@^7.22.20":
"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
@@ -192,15 +170,7 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
dependencies:
"@babel/template" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/helper-function-name@^7.23.0":
"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0", "@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
@@ -291,14 +261,7 @@
dependencies:
"@babel/types" "^7.20.0"
"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08"
integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-split-export-declaration@^7.22.6":
"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.5", "@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
@@ -339,7 +302,7 @@
"@babel/traverse" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/highlight@^7.22.13":
"@babel/highlight@^7.22.13", "@babel/highlight@^7.22.5":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
@@ -348,21 +311,7 @@
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/highlight@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==
dependencies:
"@babel/helper-validator-identifier" "^7.22.5"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.22.5", "@babel/parser@^7.7.0":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea"
integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.5", "@babel/parser@^7.23.0", "@babel/parser@^7.7.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
@@ -1110,16 +1059,7 @@
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.22.5", "@babel/template@^7.3.3":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
dependencies:
"@babel/code-frame" "^7.22.5"
"@babel/parser" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/template@^7.22.15":
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
@@ -1144,16 +1084,7 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
version "7.22.19"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684"
integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.19"
to-fast-properties "^2.0.0"
"@babel/types@^7.23.0":
"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
@@ -7795,15 +7726,7 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
import-fresh@^3.2.1:
version "3.2.2"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e"
integrity sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-fresh@^3.3.0:
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -12189,6 +12112,11 @@ socks@^2.3.3:
ip "^2.0.0"
smart-buffer "^4.2.0"
sonner@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.0.3.tgz#3d5c08f1773c28e98e51dba527350d4ac1c912a2"
integrity sha512-hBoA2zKuYW3lUnpx4K0vAn8j77YuYiwvP9sLQfieNS2pd5FkT20sMyPTDJnl9S+5T27ZJbwQRPiujwvDBwhZQg==
sort-keys@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-5.0.0.tgz#5d775f8ae93ecc29bc7312bbf3acac4e36e3c446"