diff --git a/package.json b/package.json
index 170122d7d0..c7af9d8b42 100644
--- a/package.json
+++ b/package.json
@@ -113,6 +113,7 @@
"react-router-navigation-prompt": "^1.8.11",
"react-select": "^3.0.8",
"react-simple-code-editor": "^0.11.0",
+ "react-toastify": "^6.1.0",
"sanitize-filename": "^1.6.3",
"sass": "^1.27.0",
"sass-loader": "7.1.0",
diff --git a/payload.d.ts b/payload.d.ts
index c937b0ef2c..a74058b574 100644
--- a/payload.d.ts
+++ b/payload.d.ts
@@ -130,7 +130,6 @@ declare module "@payloadcms/payload/types" {
admin?: {
useAsTitle?: string;
defaultColumns?: string[];
- disableScrollOnSuccess?: boolean;
components?: any;
},
hooks?: {
diff --git a/src/admin/components/elements/DeleteDocument/index.js b/src/admin/components/elements/DeleteDocument/index.js
index 8016e6290f..3fd192b7ae 100644
--- a/src/admin/components/elements/DeleteDocument/index.js
+++ b/src/admin/components/elements/DeleteDocument/index.js
@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react';
import PropTypes from 'prop-types';
+import { toast } from 'react-toastify';
import { useHistory } from 'react-router-dom';
import { Modal, useModal } from '@faceless-ui/modal';
import { useConfig } from '../../providers/Config';
@@ -8,7 +9,6 @@ import MinimalTemplate from '../../templates/Minimal';
import { useForm } from '../../forms/Form/context';
import useTitle from '../../../hooks/useTitle';
import { requests } from '../../../api';
-import { useStatusList } from '../Status';
import './index.scss';
@@ -30,7 +30,6 @@ const DeleteDocument = (props) => {
} = props;
const { serverURL, routes: { api, admin } } = useConfig();
- const { replaceStatus } = useStatusList();
const { setModified } = useForm();
const [deleting, setDeleting] = useState(false);
const { closeAll, toggle } = useModal();
@@ -41,11 +40,8 @@ const DeleteDocument = (props) => {
const modalSlug = `delete-${id}`;
const addDefaultError = useCallback(() => {
- replaceStatus([{
- message: `There was an error while deleting ${title}. Please check your connection and try again.`,
- type: 'error',
- }]);
- }, [replaceStatus, title]);
+ toast.error(`There was an error while deleting ${title}. Please check your connection and try again.`);
+ }, [title]);
const handleDelete = useCallback(() => {
setDeleting(true);
@@ -72,7 +68,7 @@ const DeleteDocument = (props) => {
closeAll();
if (json.errors) {
- replaceStatus(json.errors);
+ toast.error(json.errors);
}
addDefaultError();
return false;
@@ -80,7 +76,7 @@ const DeleteDocument = (props) => {
return addDefaultError();
}
});
- }, [addDefaultError, closeAll, history, id, replaceStatus, singular, slug, title, admin, api, serverURL, setModified]);
+ }, [addDefaultError, closeAll, history, id, singular, slug, title, admin, api, serverURL, setModified]);
if (id) {
return (
diff --git a/src/admin/components/elements/GenerateConfirmation/index.js b/src/admin/components/elements/GenerateConfirmation/index.js
index 9704d1c6b2..091da4f424 100644
--- a/src/admin/components/elements/GenerateConfirmation/index.js
+++ b/src/admin/components/elements/GenerateConfirmation/index.js
@@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { toast } from 'react-toastify';
import { Modal, useModal } from '@faceless-ui/modal';
import Button from '../Button';
import MinimalTemplate from '../../templates/Minimal';
-import { useStatusList } from '../Status';
import './index.scss';
@@ -16,17 +16,13 @@ const GenerateConfirmation = (props) => {
} = props;
const { toggle } = useModal();
- const { replaceStatus } = useStatusList();
const modalSlug = 'generate-confirmation';
const handleGenerate = () => {
setKey();
toggle(modalSlug);
- replaceStatus([{
- message: 'New API Key Generated.',
- type: 'success',
- }]);
+ toast.success('New API Key Generated.', { autoClose: 3000 });
highlightField(true);
};
diff --git a/src/admin/components/elements/Status/index.js b/src/admin/components/elements/Status/index.js
deleted file mode 100644
index b15c7be7c0..0000000000
--- a/src/admin/components/elements/Status/index.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import React, {
- useReducer, createContext, useContext, useEffect, useCallback,
-} from 'react';
-import { useLocation } from 'react-router-dom';
-import PropTypes from 'prop-types';
-import X from '../../icons/X';
-import reducer from './reducer';
-import './index.scss';
-
-const baseClass = 'status-list';
-
-const Context = createContext({});
-
-const useStatusList = () => useContext(Context);
-
-const StatusListProvider = ({ children }) => {
- const [statusList, dispatchStatus] = useReducer(reducer, []);
- const { pathname, state } = useLocation();
-
- const removeStatus = useCallback((i) => dispatchStatus({ type: 'REMOVE', payload: i }), []);
- const addStatus = useCallback((status) => dispatchStatus({ type: 'ADD', payload: status }), []);
- const clearStatus = useCallback(() => dispatchStatus({ type: 'CLEAR' }), []);
- const replaceStatus = useCallback((status) => dispatchStatus({ type: 'REPLACE', payload: status }), []);
-
- useEffect(() => {
- if (state && state.status) {
- if (Array.isArray(state.status)) {
- replaceStatus(state.status);
- } else {
- replaceStatus([state.status]);
- }
- } else {
- clearStatus();
- }
- }, [addStatus, replaceStatus, clearStatus, state, pathname]);
-
- return (
-
- {children}
-
- );
-};
-
-StatusListProvider.propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- ]).isRequired,
-};
-
-const StatusList = () => {
- const { statusList, removeStatus } = useStatusList();
-
- if (statusList.length > 0) {
- return (
-
- {statusList.map((status, i) => {
- const classes = [
- `${baseClass}__status`,
- `${baseClass}__status--${status.type}`,
- ].join(' ');
-
- return (
- -
- {status.message}
-
-
- );
- })}
-
- );
- }
-
- return null;
-};
-
-export {
- StatusListProvider,
- useStatusList,
-};
-
-export default StatusList;
diff --git a/src/admin/components/elements/Status/index.scss b/src/admin/components/elements/Status/index.scss
deleted file mode 100644
index 883e1c94de..0000000000
--- a/src/admin/components/elements/Status/index.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-@import '../../../scss/styles.scss';
-
-.status-list {
- position: relative;
- z-index: $z-status;
- list-style: none;
- padding: 0;
- margin: 0;
-
- li {
- background: $color-green;
- color: $color-dark-gray;
- padding: base(.5) $baseline;
- margin-bottom: 1px;
- display: flex;
- justify-content: space-between;
- }
-
- button {
- @extend %btn-reset;
- cursor: pointer;
-
- svg {
- width: base(1);
- height: base(1);
- }
-
- &:hover {
- opacity: .5;
- }
-
- &:active,
- &:focus {
- outline: none;
- border: 0;
- }
- }
-
- li.status-list__status--error {
- background: $color-red;
- color: white;
-
- button svg {
- @include color-svg(white);
- }
- }
-}
diff --git a/src/admin/components/elements/Status/reducer.js b/src/admin/components/elements/Status/reducer.js
deleted file mode 100644
index 47a05ccbc5..0000000000
--- a/src/admin/components/elements/Status/reducer.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const statusReducer = (state, action) => {
- switch (action.type) {
- case 'ADD': {
- const newState = [
- ...state,
- action.payload,
- ];
-
- return newState;
- }
-
-
- case 'REMOVE': {
- const statusList = [...state];
- statusList.splice(action.payload, 1);
- return statusList;
- }
-
- case 'CLEAR': {
- return [];
- }
-
- case 'REPLACE': {
- return action.payload;
- }
-
- default:
- return state;
- }
-};
-
-export default statusReducer;
diff --git a/src/admin/components/forms/Form/index.js b/src/admin/components/forms/Form/index.js
index 9cce82bcf5..66749b033a 100644
--- a/src/admin/components/forms/Form/index.js
+++ b/src/admin/components/forms/Form/index.js
@@ -4,8 +4,8 @@ import React, {
import { objectToFormData } from 'object-to-formdata';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
+import { toast } from 'react-toastify';
import { useLocale } from '../../utilities/Locale';
-import { useStatusList } from '../../elements/Status';
import { requests } from '../../../api';
import useThrottledEffect from '../../../hooks/useThrottledEffect';
import { useAuth } from '../../providers/Authentication';
@@ -38,14 +38,12 @@ const Form = (props) => {
disableSuccessStatus,
initialState, // fully formed initial field state
initialData, // values only, paths are required as key - form should build initial state as convenience
- disableScrollOnSuccess,
waitForAutocomplete,
log,
} = props;
const history = useHistory();
const locale = useLocale();
- const { replaceStatus, addStatus, clearStatus } = useStatusList();
const { refreshCookie } = useAuth();
const [modified, setModified] = useState(false);
@@ -111,17 +109,7 @@ const Form = (props) => {
// If not valid, prevent submission
if (!isValid) {
- addStatus({
- message: 'Please correct the fields below.',
- type: 'error',
- });
-
- if (!disableScrollOnSuccess) {
- window.scrollTo({
- top: 0,
- behavior: 'smooth',
- });
- }
+ toast.error('Please correct invalid fields.');
return false;
}
@@ -131,13 +119,6 @@ const Form = (props) => {
return onSubmit(fields, reduceFieldsToValues(fields));
}
- if (!disableScrollOnSuccess) {
- window.scrollTo({
- top: 0,
- behavior: 'smooth',
- });
- }
-
const formData = contextRef.current.createFormData();
try {
@@ -151,7 +132,6 @@ const Form = (props) => {
setProcessing(false);
- clearStatus();
const contentType = res.headers.get('content-type');
const isJSON = contentType && contentType.indexOf('application/json') !== -1;
@@ -182,20 +162,13 @@ const Form = (props) => {
history.push(destination);
} else if (!disableSuccessStatus) {
- replaceStatus([{
- message: json.message || 'Submission successful.',
- type: 'success',
- disappear: 3000,
- }]);
+ toast.success(json.message || 'Submission successful.', { autoClose: 3000 });
}
} else {
contextRef.current = { ...contextRef.current }; // triggers rerender of all components that subscribe to form
if (json.message) {
- addStatus({
- message: json.message,
- type: 'error',
- });
+ toast.error(json.message);
return json;
}
@@ -218,10 +191,7 @@ const Form = (props) => {
});
nonFieldErrors.forEach((err) => {
- addStatus({
- message: err.message || 'An unknown error occurred.',
- type: 'error',
- });
+ toast.error(err.message || 'An unknown error occurred.');
});
return json;
@@ -229,25 +199,17 @@ const Form = (props) => {
const message = errorMessages[res.status] || 'An unknown error occurrred.';
- addStatus({
- message,
- type: 'error',
- });
+ toast.error(message);
}
return json;
} catch (err) {
setProcessing(false);
- return addStatus({
- message: err,
- type: 'error',
- });
+ toast.error(err);
}
}, [
action,
- addStatus,
- clearStatus,
disableSuccessStatus,
disabled,
fields,
@@ -257,8 +219,6 @@ const Form = (props) => {
onSubmit,
onSuccess,
redirect,
- replaceStatus,
- disableScrollOnSuccess,
waitForAutocomplete,
]);
@@ -366,7 +326,6 @@ Form.defaultProps = {
disableSuccessStatus: false,
disabled: false,
initialState: undefined,
- disableScrollOnSuccess: false,
waitForAutocomplete: false,
initialData: undefined,
log: false,
@@ -387,7 +346,6 @@ Form.propTypes = {
redirect: PropTypes.string,
disabled: PropTypes.bool,
initialState: PropTypes.shape({}),
- disableScrollOnSuccess: PropTypes.bool,
waitForAutocomplete: PropTypes.bool,
initialData: PropTypes.shape({}),
log: PropTypes.bool,
diff --git a/src/admin/components/forms/field-types/Upload/Add/index.js b/src/admin/components/forms/field-types/Upload/Add/index.js
index 6e1580e700..9e6bf941ae 100644
--- a/src/admin/components/forms/field-types/Upload/Add/index.js
+++ b/src/admin/components/forms/field-types/Upload/Add/index.js
@@ -45,7 +45,6 @@ const AddUploadModal = (props) => {
action={`${serverURL}${api}/${collection.slug}`}
onSuccess={onSuccess}
disableSuccessStatus
- disableScrollOnSuccess
>
diff --git a/src/admin/components/views/ForgotPassword/index.js b/src/admin/components/views/ForgotPassword/index.js
index e8a49dd4c0..fec30ddd63 100644
--- a/src/admin/components/views/ForgotPassword/index.js
+++ b/src/admin/components/views/ForgotPassword/index.js
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
+import { toast } from 'react-toastify';
import { useConfig } from '../../providers/Config';
import MinimalTemplate from '../../templates/Minimal';
-import StatusList, { useStatusList } from '../../elements/Status';
import Form from '../../forms/Form';
import Email from '../../forms/field-types/Email';
import FormSubmit from '../../forms/Submit';
@@ -15,7 +15,6 @@ import './index.scss';
const baseClass = 'forgot-password';
const ForgotPassword = () => {
- const { addStatus } = useStatusList();
const [hasSubmitted, setHasSubmitted] = useState(false);
const { user } = useAuth();
const {
@@ -32,10 +31,7 @@ const ForgotPassword = () => {
.then(() => {
setHasSubmitted(true);
}, () => {
- addStatus({
- type: 'error',
- message: 'The email provided is not valid.',
- });
+ toast.error('The email provided is not valid.');
});
};
@@ -81,7 +77,6 @@ const ForgotPassword = () => {
return (
-