diff --git a/frontend/__test_support__/mock_fbtoaster.ts b/frontend/__test_support__/mock_fbtoaster.ts
index c714230e8..cb86d6c67 100644
--- a/frontend/__test_support__/mock_fbtoaster.ts
+++ b/frontend/__test_support__/mock_fbtoaster.ts
@@ -1,5 +1,5 @@
jest.resetAllMocks();
-jest.mock("farmbot-toastr", () => ({
+jest.mock("../toast/toast", () => ({
fun: jest.fn(),
init: jest.fn(),
success: jest.fn(),
diff --git a/frontend/__tests__/app_test.tsx b/frontend/__tests__/app_test.tsx
index 016f51973..212c55666 100644
--- a/frontend/__tests__/app_test.tsx
+++ b/frontend/__tests__/app_test.tsx
@@ -17,9 +17,9 @@ import { fakeState } from "../__test_support__/fake_state";
import {
buildResourceIndex
} from "../__test_support__/resource_index_builder";
-import { error } from "farmbot-toastr";
import { ResourceName } from "farmbot";
import { fakeTimeSettings } from "../__test_support__/fake_time_settings";
+import { error } from "../toast/toast";
const FULLY_LOADED: ResourceName[] = [
"Sequence", "Regimen", "FarmEvent", "Point", "Tool", "Device"];
diff --git a/frontend/__tests__/interceptors_test.ts b/frontend/__tests__/interceptors_test.ts
index 6e3c0c396..ef81060a1 100644
--- a/frontend/__tests__/interceptors_test.ts
+++ b/frontend/__tests__/interceptors_test.ts
@@ -28,8 +28,8 @@ import { SafeError } from "../interceptor_support";
import { API } from "../api";
import { auth } from "../__test_support__/fake_state/token";
import { dispatchNetworkUp, dispatchNetworkDown } from "../connectivity";
-import { error } from "farmbot-toastr";
import { Session } from "../session";
+import { error } from "../toast/toast";
const A_STRING = expect.any(String);
diff --git a/frontend/account/__tests__/request_account_exports_test.ts b/frontend/account/__tests__/request_account_exports_test.ts
index e1e123a36..b2b43a44e 100644
--- a/frontend/account/__tests__/request_account_exports_test.ts
+++ b/frontend/account/__tests__/request_account_exports_test.ts
@@ -10,7 +10,7 @@ jest.mock("axios",
import { API } from "../../api";
import { Content } from "../../constants";
import { requestAccountExport, generateFilename } from "../request_account_export";
-import { success } from "farmbot-toastr";
+import { success } from "../../toast/toast";
import axios from "axios";
import { fakeDevice } from "../../__test_support__/resource_index_builder";
diff --git a/frontend/account/components/__tests__/test_change_password.tsx b/frontend/account/components/__tests__/test_change_password.tsx
index 252f9e546..8cdbb1ef7 100644
--- a/frontend/account/components/__tests__/test_change_password.tsx
+++ b/frontend/account/components/__tests__/test_change_password.tsx
@@ -4,7 +4,7 @@ import { mount } from "enzyme";
import { SpecialStatus } from "farmbot";
import * as moxios from "moxios";
import { API } from "../../../api/api";
-import { error } from "farmbot-toastr";
+import { error } from "../../../toast/toast";
describe("", function () {
function testCase() {
diff --git a/frontend/account/components/change_password.tsx b/frontend/account/components/change_password.tsx
index 675390714..50f2d00ac 100644
--- a/frontend/account/components/change_password.tsx
+++ b/frontend/account/components/change_password.tsx
@@ -10,11 +10,11 @@ import { SpecialStatus } from "farmbot";
import Axios from "axios";
import { API } from "../../api/index";
import { prettyPrintApiErrors, equals, trim } from "../../util";
-import { success, error } from "farmbot-toastr/dist";
import { Content } from "../../constants";
import { uniq } from "lodash";
import { BlurablePassword } from "../../ui/blurable_password";
import { t } from "../../i18next_wrapper";
+import { success, error } from "../../toast/toast";
interface PasswordForm {
new_password: string;
diff --git a/frontend/account/dev/__tests__/dev_mode_test.tsx b/frontend/account/dev/__tests__/dev_mode_test.tsx
index 18fc36753..66abd2d7f 100644
--- a/frontend/account/dev/__tests__/dev_mode_test.tsx
+++ b/frontend/account/dev/__tests__/dev_mode_test.tsx
@@ -10,7 +10,7 @@ import { range } from "lodash";
import {
setWebAppConfigValue
} from "../../../config_storage/actions";
-import { warning } from "farmbot-toastr";
+import { warning } from "../../../toast/toast";
describe("", () => {
it("triggers callbacks after 15 clicks", () => {
diff --git a/frontend/account/dev/dev_mode.tsx b/frontend/account/dev/dev_mode.tsx
index e8736d2ff..99693b114 100644
--- a/frontend/account/dev/dev_mode.tsx
+++ b/frontend/account/dev/dev_mode.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { warning } from "farmbot-toastr";
+import { warning } from "../../toast/toast";
import { setWebAppConfigValue } from "../../config_storage/actions";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { DevSettings } from "./dev_support";
diff --git a/frontend/account/index.tsx b/frontend/account/index.tsx
index a6f2a87b4..1936db15d 100644
--- a/frontend/account/index.tsx
+++ b/frontend/account/index.tsx
@@ -10,7 +10,6 @@ import { User } from "../auth/interfaces";
import { edit, save } from "../api/crud";
import { updateNO } from "../resources/actions";
import { deleteUser, resetAccount } from "./actions";
-import { success } from "farmbot-toastr/dist";
import { LabsFeatures } from "./labs/labs_features";
import { requestAccountExport } from "./request_account_export";
import { DevWidget } from "./dev/dev_widget";
@@ -18,6 +17,7 @@ import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { DevMode } from "./dev/dev_mode";
import { t } from "../i18next_wrapper";
import { Content } from "../constants";
+import { success } from "../toast/toast";
const KEYS: (keyof User)[] = ["id", "name", "email", "created_at", "updated_at"];
diff --git a/frontend/account/request_account_export.ts b/frontend/account/request_account_export.ts
index a2ec4de25..75b30d01e 100644
--- a/frontend/account/request_account_export.ts
+++ b/frontend/account/request_account_export.ts
@@ -1,6 +1,6 @@
import { API } from "../api";
import { Content } from "../constants";
-import { success } from "farmbot-toastr";
+import { success } from "../toast/toast";
import axios, { AxiosResponse } from "axios";
import { DeviceAccountSettings } from "farmbot/dist/resources/api_resources";
diff --git a/frontend/app.tsx b/frontend/app.tsx
index d331c7688..1faac6d28 100644
--- a/frontend/app.tsx
+++ b/frontend/app.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import { connect } from "react-redux";
-import { init, error } from "farmbot-toastr";
+import { init, error } from "./toast/toast";
import { NavBar } from "./nav";
import { Everything, TimeSettings } from "./interfaces";
import { LoadingPlant } from "./loading_plant";
diff --git a/frontend/connectivity/__tests__/connect_device/index_test.ts b/frontend/connectivity/__tests__/connect_device/index_test.ts
index 7a2610e13..10bf15335 100644
--- a/frontend/connectivity/__tests__/connect_device/index_test.ts
+++ b/frontend/connectivity/__tests__/connect_device/index_test.ts
@@ -33,7 +33,6 @@ import { onLogs } from "../../log_handlers";
import { Actions, Content } from "../../../constants";
import { Log } from "farmbot/dist/resources/api_resources";
import { ALLOWED_CHANNEL_NAMES, ALLOWED_MESSAGE_TYPES, Farmbot } from "farmbot";
-import { success, error, info, warning } from "farmbot-toastr";
import { dispatchNetworkUp, dispatchNetworkDown } from "../../index";
import { getDevice } from "../../../device";
import { fakeState } from "../../../__test_support__/fake_state";
@@ -41,6 +40,7 @@ import { talk } from "browser-speech";
import { globalQueue } from "../../batch_queue";
import { MessageType } from "../../../sequences/interfaces";
import { FbjsEventName } from "farmbot/dist/constants";
+import { info, error, success, warning } from "../../../toast/toast";
const A_STRING = expect.any(String);
describe("readStatus()", () => {
diff --git a/frontend/connectivity/connect_device.ts b/frontend/connectivity/connect_device.ts
index 5375891ea..7bf923277 100644
--- a/frontend/connectivity/connect_device.ts
+++ b/frontend/connectivity/connect_device.ts
@@ -4,7 +4,7 @@ import { Log } from "farmbot/dist/resources/api_resources";
import { Farmbot, BotStateTree, TaggedResource } from "farmbot";
import { FbjsEventName } from "farmbot/dist/constants";
import { noop } from "lodash";
-import { success, error, info, warning } from "farmbot-toastr";
+import { success, error, info, warning } from "../toast/toast";
import { HardwareState } from "../devices/interfaces";
import { GetState, ReduxAction } from "../redux/interfaces";
import { Content, Actions } from "../constants";
diff --git a/frontend/controls/peripherals/__tests__/index_test.tsx b/frontend/controls/peripherals/__tests__/index_test.tsx
index f1047f210..d77c6fd05 100644
--- a/frontend/controls/peripherals/__tests__/index_test.tsx
+++ b/frontend/controls/peripherals/__tests__/index_test.tsx
@@ -6,7 +6,7 @@ import { PeripheralsProps } from "../../../devices/interfaces";
import { fakePeripheral } from "../../../__test_support__/fake_state/resources";
import { clickButton } from "../../../__test_support__/helpers";
import { SpecialStatus } from "farmbot";
-import { error } from "farmbot-toastr";
+import { error } from "../../../toast/toast";
describe("", () => {
function fakeProps(): PeripheralsProps {
diff --git a/frontend/controls/peripherals/index.tsx b/frontend/controls/peripherals/index.tsx
index 22d86420b..cad85b669 100644
--- a/frontend/controls/peripherals/index.tsx
+++ b/frontend/controls/peripherals/index.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { PeripheralList } from "./peripheral_list";
import { PeripheralForm } from "./peripheral_form";
import { Widget, WidgetBody, WidgetHeader, SaveBtn } from "../../ui/index";
diff --git a/frontend/controls/sensors/__tests__/index_test.tsx b/frontend/controls/sensors/__tests__/index_test.tsx
index 37e6b4e80..23d13d778 100644
--- a/frontend/controls/sensors/__tests__/index_test.tsx
+++ b/frontend/controls/sensors/__tests__/index_test.tsx
@@ -4,9 +4,9 @@ import { Sensors } from "../index";
import { bot } from "../../../__test_support__/fake_state/bot";
import { SensorsProps } from "../../../devices/interfaces";
import { fakeSensor } from "../../../__test_support__/fake_state/resources";
-import { error } from "farmbot-toastr";
import { clickButton } from "../../../__test_support__/helpers";
import { SpecialStatus } from "farmbot";
+import { error } from "../../../toast/toast";
describe("", () => {
function fakeProps(): SensorsProps {
diff --git a/frontend/controls/sensors/index.tsx b/frontend/controls/sensors/index.tsx
index 93223700f..3ed6e9c82 100644
--- a/frontend/controls/sensors/index.tsx
+++ b/frontend/controls/sensors/index.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { SensorList } from "./sensor_list";
import { SensorForm } from "./sensor_form";
import { Widget, WidgetBody, WidgetHeader, SaveBtn } from "../../ui/index";
diff --git a/frontend/controls/webcam/index.tsx b/frontend/controls/webcam/index.tsx
index f8a88f523..51cfb635f 100644
--- a/frontend/controls/webcam/index.tsx
+++ b/frontend/controls/webcam/index.tsx
@@ -4,7 +4,7 @@ import { Edit } from "./edit";
import { WebcamPanelProps } from "./interfaces";
import { TaggedWebcamFeed, SpecialStatus } from "farmbot";
import { edit, save, destroy, init } from "../../api/crud";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { WebcamFeed } from "farmbot/dist/resources/api_resources";
import { t } from "../../i18next_wrapper";
diff --git a/frontend/devices/__tests__/actions_test.ts b/frontend/devices/__tests__/actions_test.ts
index 6151e416f..185a395ee 100644
--- a/frontend/devices/__tests__/actions_test.ts
+++ b/frontend/devices/__tests__/actions_test.ts
@@ -39,7 +39,7 @@ import { Actions } from "../../constants";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
import { API } from "../../api/index";
import axios from "axios";
-import { success, error, warning, info } from "farmbot-toastr";
+import { success, error, warning, info } from "../../toast/toast";
import { edit, save } from "../../api/crud";
describe("checkControllerUpdates()", function () {
diff --git a/frontend/devices/actions.ts b/frontend/devices/actions.ts
index 7f64e9732..c5d88d07e 100644
--- a/frontend/devices/actions.ts
+++ b/frontend/devices/actions.ts
@@ -1,6 +1,6 @@
import axios from "axios";
-import { success, warning, info, error } from "farmbot-toastr";
+import { success, warning, info, error } from "../toast/toast";
import { getDevice } from "../device";
import { Everything } from "../interfaces";
import {
diff --git a/frontend/devices/components/__tests__/mcu_input_box_test.tsx b/frontend/devices/components/__tests__/mcu_input_box_test.tsx
index b095e0312..eed44994c 100644
--- a/frontend/devices/components/__tests__/mcu_input_box_test.tsx
+++ b/frontend/devices/components/__tests__/mcu_input_box_test.tsx
@@ -2,11 +2,11 @@ jest.mock("../../actions", () => ({ updateMCU: jest.fn() }));
import * as React from "react";
import { McuInputBox } from "../mcu_input_box";
-import { warning } from "farmbot-toastr";
import { shallow } from "enzyme";
import { McuInputBoxProps } from "../../interfaces";
import { bot } from "../../../__test_support__/fake_state/bot";
import { updateMCU } from "../../actions";
+import { warning } from "../../../toast/toast";
describe("McuInputBox", () => {
const fakeProps = (): McuInputBoxProps => {
diff --git a/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx b/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx
index a5289ff4c..4a6f7aa10 100644
--- a/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx
+++ b/frontend/devices/components/fbos_settings/__tests__/camera_selection_test.tsx
@@ -9,7 +9,7 @@ import * as React from "react";
import { mount, shallow } from "enzyme";
import { CameraSelection } from "../camera_selection";
import { CameraSelectionProps } from "../interfaces";
-import { info, error } from "farmbot-toastr";
+import { info, error } from "../../../../toast/toast";
describe("", () => {
const fakeProps = (): CameraSelectionProps => {
diff --git a/frontend/devices/components/fbos_settings/board_type.tsx b/frontend/devices/components/fbos_settings/board_type.tsx
index d6e2ff00f..dff9a172f 100644
--- a/frontend/devices/components/fbos_settings/board_type.tsx
+++ b/frontend/devices/components/fbos_settings/board_type.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import { Row, Col, DropDownItem, FBSelect } from "../../../ui";
-import { info } from "farmbot-toastr";
+import { info } from "../../../toast/toast";
import { FirmwareHardware } from "farmbot";
import { ColWidth } from "../farmbot_os_settings";
import { updateConfig } from "../../actions";
diff --git a/frontend/devices/components/fbos_settings/camera_selection.tsx b/frontend/devices/components/fbos_settings/camera_selection.tsx
index c91276f5d..ca747e830 100644
--- a/frontend/devices/components/fbos_settings/camera_selection.tsx
+++ b/frontend/devices/components/fbos_settings/camera_selection.tsx
@@ -3,7 +3,7 @@ import { DropDownItem, Row, Col, FBSelect } from "../../../ui/index";
import {
CameraSelectionProps, CameraSelectionState
} from "./interfaces";
-import { info, success, error } from "farmbot-toastr";
+import { info, success, error } from "../../../toast/toast";
import { getDevice } from "../../../device";
import { ColWidth } from "../farmbot_os_settings";
import { Feature } from "../../interfaces";
diff --git a/frontend/devices/components/fbos_settings/change_ownership_form.tsx b/frontend/devices/components/fbos_settings/change_ownership_form.tsx
index 6da4ab6a8..398bb6a9e 100644
--- a/frontend/devices/components/fbos_settings/change_ownership_form.tsx
+++ b/frontend/devices/components/fbos_settings/change_ownership_form.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import { Row, Col, BlurableInput } from "../../../ui/index";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../../../toast/toast";
import { getDevice } from "../../../device";
import { transferOwnership } from "../../transfer_ownership/transfer_ownership";
import { API } from "../../../api";
diff --git a/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx b/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx
index c886428d9..1790fc70c 100644
--- a/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx
+++ b/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx
@@ -10,7 +10,7 @@ import { updateMCU } from "../../../actions";
import {
fakeFirmwareConfig
} from "../../../../__test_support__/fake_state/resources";
-import { warning, error } from "farmbot-toastr";
+import { error, warning } from "../../../../toast/toast";
describe("", () => {
function testAxisLengthInput(
diff --git a/frontend/devices/components/mcu_input_box.tsx b/frontend/devices/components/mcu_input_box.tsx
index 56d2b8025..eef345506 100644
--- a/frontend/devices/components/mcu_input_box.tsx
+++ b/frontend/devices/components/mcu_input_box.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { warning } from "farmbot-toastr";
+import { warning } from "../../toast/toast";
import { McuInputBoxProps } from "../interfaces";
import { updateMCU } from "../actions";
import { BlurableInput } from "../../ui/index";
diff --git a/frontend/devices/pin_bindings/__tests__/pin_binding_input_group_test.tsx b/frontend/devices/pin_bindings/__tests__/pin_binding_input_group_test.tsx
index 6125165cd..4c4555442 100644
--- a/frontend/devices/pin_bindings/__tests__/pin_binding_input_group_test.tsx
+++ b/frontend/devices/pin_bindings/__tests__/pin_binding_input_group_test.tsx
@@ -25,13 +25,13 @@ import {
PinBindingInputGroup, PinNumberInputGroup, BindingTypeDropDown,
ActionTargetDropDown, SequenceTargetDropDown
} from "../pin_binding_input_group";
-import { error, warning } from "farmbot-toastr";
import {
fakeResourceIndex
} from "../../../sequences/locals_list/test_helpers";
import {
PinBindingType, PinBindingSpecialAction
} from "farmbot/dist/resources/api_resources";
+import { error, warning } from "../../../toast/toast";
describe("", () => {
function fakeProps(): PinBindingInputGroupProps {
diff --git a/frontend/devices/pin_bindings/__tests__/pin_bindings_list_test.tsx b/frontend/devices/pin_bindings/__tests__/pin_bindings_list_test.tsx
index 349b151ed..a63bc53f9 100644
--- a/frontend/devices/pin_bindings/__tests__/pin_bindings_list_test.tsx
+++ b/frontend/devices/pin_bindings/__tests__/pin_bindings_list_test.tsx
@@ -36,8 +36,8 @@ import {
import { destroy } from "../../../api/crud";
import { PinBindingsList } from "../pin_bindings_list";
import { PinBindingsListProps } from "../interfaces";
-import { error } from "farmbot-toastr";
import { sysBtnBindingData } from "../tagged_pin_binding_init";
+import { error } from "../../../toast/toast";
describe("", () => {
function fakeProps(): PinBindingsListProps {
diff --git a/frontend/devices/pin_bindings/pin_binding_input_group.tsx b/frontend/devices/pin_bindings/pin_binding_input_group.tsx
index cb5d9d49a..5677e36cb 100644
--- a/frontend/devices/pin_bindings/pin_binding_input_group.tsx
+++ b/frontend/devices/pin_bindings/pin_binding_input_group.tsx
@@ -11,7 +11,7 @@ import {
import { isNumber, includes } from "lodash";
import { initSave } from "../../api/crud";
import { pinBindingBody } from "./tagged_pin_binding_init";
-import { error, warning } from "farmbot-toastr";
+import { error, warning } from "../../toast/toast";
import {
validGpioPins, sysBindings, generatePinLabel, RpiPinList,
bindingTypeLabelLookup, specialActionLabelLookup, specialActionList,
diff --git a/frontend/devices/pin_bindings/pin_bindings_list.tsx b/frontend/devices/pin_bindings/pin_bindings_list.tsx
index 3b7f9a9da..016b614c3 100644
--- a/frontend/devices/pin_bindings/pin_bindings_list.tsx
+++ b/frontend/devices/pin_bindings/pin_bindings_list.tsx
@@ -4,7 +4,7 @@ import {
generatePinLabel, sortByNameAndPin
} from "./list_and_label_support";
import { destroy } from "../../api/crud";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { Row, Col } from "../../ui";
import { findSequenceById } from "../../resources/selectors";
import { PinBindingColWidth } from "./pin_bindings";
diff --git a/frontend/devices/transfer_ownership/__tests__/create_transfer_cert_failure_test.ts b/frontend/devices/transfer_ownership/__tests__/create_transfer_cert_failure_test.ts
index 5bd33d3b2..4bffa4be1 100644
--- a/frontend/devices/transfer_ownership/__tests__/create_transfer_cert_failure_test.ts
+++ b/frontend/devices/transfer_ownership/__tests__/create_transfer_cert_failure_test.ts
@@ -15,8 +15,8 @@ import { getDevice } from "../../../device";
import {
submitOwnershipChange
} from "../../components/fbos_settings/change_ownership_form";
-import { error } from "farmbot-toastr";
import { API } from "../../../api";
+import { error } from "../../../toast/toast";
API.setBaseUrl("http://foo.bar");
diff --git a/frontend/farm_designer/farm_events/__tests__/edit_fe_form_test.tsx b/frontend/farm_designer/farm_events/__tests__/edit_fe_form_test.tsx
index a17fc872a..fb9115fe9 100644
--- a/frontend/farm_designer/farm_events/__tests__/edit_fe_form_test.tsx
+++ b/frontend/farm_designer/farm_events/__tests__/edit_fe_form_test.tsx
@@ -22,7 +22,6 @@ import {
import { isString, isFunction } from "lodash";
import { repeatOptions } from "../map_state_to_props_add_edit";
import { SpecialStatus, ParameterApplication } from "farmbot";
-import { success, error, warning } from "farmbot-toastr";
import moment from "moment";
import { fakeState } from "../../../__test_support__/fake_state";
import { history } from "../../../history";
@@ -33,6 +32,7 @@ import { fakeVariableNameSet } from "../../../__test_support__/fake_variables";
import { clickButton } from "../../../__test_support__/helpers";
import { destroy } from "../../../api/crud";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
+import { error, success, warning } from "../../../toast/toast";
const mockSequence = fakeSequence();
diff --git a/frontend/farm_designer/farm_events/edit_fe_form.tsx b/frontend/farm_designer/farm_events/edit_fe_form.tsx
index 8ca1676d0..cc52a398a 100644
--- a/frontend/farm_designer/farm_events/edit_fe_form.tsx
+++ b/frontend/farm_designer/farm_events/edit_fe_form.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import moment from "moment";
-import { success, error, warning } from "farmbot-toastr";
+import { success, error, warning } from "../../toast/toast";
import {
TaggedFarmEvent, SpecialStatus, TaggedSequence, TaggedRegimen,
ParameterApplication
diff --git a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.tsx b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.tsx
index 34abfcf58..4a7341d80 100644
--- a/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.tsx
+++ b/frontend/farm_designer/map/layers/plants/__tests__/plant_actions_test.tsx
@@ -29,8 +29,8 @@ import {
fakeMapTransformProps
} from "../../../../../__test_support__/map_transform_props";
import { movePlant } from "../../../../actions";
-import { error } from "farmbot-toastr";
import { fakeCropLiveSearchResult } from "../../../../../__test_support__/fake_crop_search_result";
+import { error } from "../../../../../toast/toast";
describe("newPlantKindAndBody()", () => {
it("returns new PlantTemplate", () => {
diff --git a/frontend/farm_designer/map/layers/plants/plant_actions.tsx b/frontend/farm_designer/map/layers/plants/plant_actions.tsx
index 73a55465f..a0c30008f 100644
--- a/frontend/farm_designer/map/layers/plants/plant_actions.tsx
+++ b/frontend/farm_designer/map/layers/plants/plant_actions.tsx
@@ -1,4 +1,3 @@
-import { error } from "farmbot-toastr";
import { Content } from "../../../../constants";
import { initSave, edit, save } from "../../../../api/crud";
import {
@@ -15,6 +14,7 @@ import { transformXY, round } from "../../util";
import { movePlant } from "../../../actions";
import { cachedCrop } from "../../../../open_farm/cached_crop";
import { t } from "../../../../i18next_wrapper";
+import { error } from "../../../../toast/toast";
/** Return a new plant or plantTemplate object. */
export const newPlantKindAndBody = (props: {
diff --git a/frontend/farm_designer/saved_gardens/__tests__/saved_gardens_test.tsx b/frontend/farm_designer/saved_gardens/__tests__/saved_gardens_test.tsx
index cd25dd872..55f21a26f 100644
--- a/frontend/farm_designer/saved_gardens/__tests__/saved_gardens_test.tsx
+++ b/frontend/farm_designer/saved_gardens/__tests__/saved_gardens_test.tsx
@@ -27,7 +27,6 @@ import { mount, shallow } from "enzyme";
import {
SavedGardens, mapStateToProps, SavedGardensLink, SavedGardenHUD, savedGardenOpen
} from "../saved_gardens";
-import { error } from "farmbot-toastr";
import { clickButton } from "../../../__test_support__/helpers";
import {
fakePlantTemplate, fakeSavedGarden
@@ -40,6 +39,7 @@ import {
import { SavedGardensProps } from "../interfaces";
import { applyGarden, destroySavedGarden, closeSavedGarden } from "../actions";
import { Actions } from "../../../constants";
+import { error } from "../../../toast/toast";
describe("", () => {
const fakeProps = (): SavedGardensProps => ({
diff --git a/frontend/farm_designer/saved_gardens/actions.ts b/frontend/farm_designer/saved_gardens/actions.ts
index 70c227086..0d1aced92 100644
--- a/frontend/farm_designer/saved_gardens/actions.ts
+++ b/frontend/farm_designer/saved_gardens/actions.ts
@@ -1,6 +1,6 @@
import axios from "axios";
import { API } from "../../api";
-import { success, info } from "farmbot-toastr";
+import { success, info } from "../../toast/toast";
import { history } from "../../history";
import { Actions } from "../../constants";
import { destroy, initSave, initSaveGetId } from "../../api/crud";
diff --git a/frontend/farm_designer/saved_gardens/garden_list.tsx b/frontend/farm_designer/saved_gardens/garden_list.tsx
index 7c98ad309..9f0f6ed18 100644
--- a/frontend/farm_designer/saved_gardens/garden_list.tsx
+++ b/frontend/farm_designer/saved_gardens/garden_list.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import { Row, Col, BlurableInput } from "../../ui";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { isNumber, isString } from "lodash";
import { openOrCloseGarden, applyGarden, destroySavedGarden } from "./actions";
import {
diff --git a/frontend/farmware/__tests__/farmware_config_menu_test.tsx b/frontend/farmware/__tests__/farmware_config_menu_test.tsx
index 06e920435..c33d7f4ad 100644
--- a/frontend/farmware/__tests__/farmware_config_menu_test.tsx
+++ b/frontend/farmware/__tests__/farmware_config_menu_test.tsx
@@ -24,7 +24,7 @@ import { FarmwareConfigMenuProps } from "../interfaces";
import { getDevice } from "../../device";
import { toggleWebAppBool } from "../../config_storage/actions";
import { destroyAll } from "../../api/crud";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../../toast/toast";
import { BooleanSetting } from "../../session_keys";
describe("", () => {
diff --git a/frontend/farmware/__tests__/farmware_info_test.tsx b/frontend/farmware/__tests__/farmware_info_test.tsx
index 1e6779170..c55d3beb5 100644
--- a/frontend/farmware/__tests__/farmware_info_test.tsx
+++ b/frontend/farmware/__tests__/farmware_info_test.tsx
@@ -17,7 +17,7 @@ import { destroy } from "../../api/crud";
import {
fakeFarmwareInstallation
} from "../../__test_support__/fake_state/resources";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { retryFetchPackageName } from "../actions";
describe("", () => {
diff --git a/frontend/farmware/farmware_config_menu.tsx b/frontend/farmware/farmware_config_menu.tsx
index d2ab61057..0954e1422 100644
--- a/frontend/farmware/farmware_config_menu.tsx
+++ b/frontend/farmware/farmware_config_menu.tsx
@@ -4,7 +4,7 @@ import { FarmwareConfigMenuProps } from "./interfaces";
import { commandErr } from "../devices/actions";
import { toggleWebAppBool } from "../config_storage/actions";
import { destroyAll } from "../api/crud";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../toast/toast";
import { Feature } from "../devices/interfaces";
import { t } from "../i18next_wrapper";
import { BooleanSetting } from "../session_keys";
diff --git a/frontend/farmware/farmware_info.tsx b/frontend/farmware/farmware_info.tsx
index 357ecffcd..53b42052f 100644
--- a/frontend/farmware/farmware_info.tsx
+++ b/frontend/farmware/farmware_info.tsx
@@ -5,7 +5,7 @@ import { commandErr } from "../devices/actions";
import { Content } from "../constants";
import { ShouldDisplay, Feature } from "../devices/interfaces";
import { destroy } from "../api/crud";
-import { error } from "farmbot-toastr";
+import { error } from "../toast/toast";
import { isPendingInstallation } from "./state_to_props";
import { Popover } from "@blueprintjs/core";
import { retryFetchPackageName } from "./actions";
diff --git a/frontend/farmware/images/__tests__/photos_test.tsx b/frontend/farmware/images/__tests__/photos_test.tsx
index 078a659f8..fbf284edf 100644
--- a/frontend/farmware/images/__tests__/photos_test.tsx
+++ b/frontend/farmware/images/__tests__/photos_test.tsx
@@ -13,9 +13,9 @@ import { fakeImages } from "../../../__test_support__/fake_state/images";
import { destroy } from "../../../api/crud";
import { clickButton } from "../../../__test_support__/helpers";
import { PhotosProps } from "../interfaces";
-import { success, error } from "farmbot-toastr";
import { selectImage } from "../actions";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
+import { success, error } from "../../../toast/toast";
describe("", () => {
const fakeProps = (): PhotosProps => ({
diff --git a/frontend/farmware/images/photos.tsx b/frontend/farmware/images/photos.tsx
index b0c419739..175a06bc5 100644
--- a/frontend/farmware/images/photos.tsx
+++ b/frontend/farmware/images/photos.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import moment from "moment";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../../toast/toast";
import { ImageFlipper } from "./image_flipper";
import { PhotosProps, PhotoButtonsProps } from "./interfaces";
import { getDevice } from "../../device";
diff --git a/frontend/farmware/weed_detector/__tests__/actions_tests.ts b/frontend/farmware/weed_detector/__tests__/actions_tests.ts
index 34af1452b..8b006d650 100644
--- a/frontend/farmware/weed_detector/__tests__/actions_tests.ts
+++ b/frontend/farmware/weed_detector/__tests__/actions_tests.ts
@@ -26,9 +26,9 @@ import { deletePoints } from "../actions";
import { scanImage, test } from "../actions";
import axios from "axios";
import { API } from "../../../api";
-import { success, error } from "farmbot-toastr";
import { times } from "lodash";
import { Actions } from "../../../constants";
+import { error, success } from "../../../toast/toast";
describe("scanImage()", () => {
it("calls out to the device", () => {
diff --git a/frontend/farmware/weed_detector/actions.tsx b/frontend/farmware/weed_detector/actions.tsx
index c17a6f1ee..7bea25b4e 100644
--- a/frontend/farmware/weed_detector/actions.tsx
+++ b/frontend/farmware/weed_detector/actions.tsx
@@ -1,6 +1,6 @@
import axios from "axios";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../../toast/toast";
import { Thunk } from "../../redux/interfaces";
import { API } from "../../api";
import { Progress, ProgressCallback, trim } from "../../util";
diff --git a/frontend/front_page/__tests__/create_account_test.tsx b/frontend/front_page/__tests__/create_account_test.tsx
index ee2e2a929..97722079d 100644
--- a/frontend/front_page/__tests__/create_account_test.tsx
+++ b/frontend/front_page/__tests__/create_account_test.tsx
@@ -15,7 +15,7 @@ import {
} from "../create_account";
import { shallow } from "enzyme";
import { BlurableInput } from "../../ui/index";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../../toast/toast";
import { resendEmail } from "../resend_verification";
import { ResendPanelBody } from "../resend_panel_body";
import { BlurablePassword } from "../../ui/blurable_password";
diff --git a/frontend/front_page/__tests__/front_page_test.tsx b/frontend/front_page/__tests__/front_page_test.tsx
index 320775b6a..54845e8f8 100644
--- a/frontend/front_page/__tests__/front_page_test.tsx
+++ b/frontend/front_page/__tests__/front_page_test.tsx
@@ -32,7 +32,7 @@ import { FrontPage, setField, PartialFormEvent } from "../front_page";
import axios from "axios";
import { API } from "../../api";
import { Session } from "../../session";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../../toast/toast";
import { Content } from "../../constants";
import { AuthState } from "../../auth/interfaces";
import { auth } from "../../__test_support__/fake_state/token";
diff --git a/frontend/front_page/create_account.tsx b/frontend/front_page/create_account.tsx
index 2bff08fae..a4420daa4 100644
--- a/frontend/front_page/create_account.tsx
+++ b/frontend/front_page/create_account.tsx
@@ -10,7 +10,7 @@ import {
} from "../ui/index";
import { resendEmail } from "./resend_verification";
-import { success, error } from "farmbot-toastr";
+import { success, error } from "../toast/toast";
import { bail } from "../util";
import { ResendPanelBody } from "./resend_panel_body";
import { BlurablePassword } from "../ui/blurable_password";
diff --git a/frontend/front_page/front_page.tsx b/frontend/front_page/front_page.tsx
index 38405d083..2ce442d30 100644
--- a/frontend/front_page/front_page.tsx
+++ b/frontend/front_page/front_page.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import axios from "axios";
-import { error as log, success, init as logInit } from "farmbot-toastr";
+import { error as log, success, init as logInit } from "../toast/toast";
import { AuthState } from "../auth/interfaces";
import { prettyPrintApiErrors, attachToRoot } from "../util";
import { API } from "../api";
diff --git a/frontend/interceptors.ts b/frontend/interceptors.ts
index 4f14e4c6d..aff16dd2d 100644
--- a/frontend/interceptors.ts
+++ b/frontend/interceptors.ts
@@ -1,9 +1,4 @@
-
-import { error } from "farmbot-toastr";
-import {
- SafeError,
- isSafeError
-} from "./interceptor_support";
+import { SafeError, isSafeError } from "./interceptor_support";
import { API } from "./api/index";
import { AuthState } from "./auth/interfaces";
import { AxiosRequestConfig, AxiosResponse } from "axios";
@@ -14,6 +9,7 @@ import { outstandingRequests } from "./connectivity/data_consistency";
import { Session } from "./session";
import { get } from "lodash";
import { t } from "./i18next_wrapper";
+import { error } from "./toast/toast";
export function responseFulfilled(input: AxiosResponse): AxiosResponse {
dispatchNetworkUp("user.api", undefined, "responseFulfilled()");
diff --git a/frontend/messages/__tests__/actions_test.ts b/frontend/messages/__tests__/actions_test.ts
index 8be83ca23..c42743ccf 100644
--- a/frontend/messages/__tests__/actions_test.ts
+++ b/frontend/messages/__tests__/actions_test.ts
@@ -15,7 +15,7 @@ jest.mock("../../api/api", () => ({
import axios from "axios";
import { fetchBulletinContent, seedAccount } from "../actions";
-import { info, error } from "farmbot-toastr";
+import { info, error } from "../../toast/toast";
describe("fetchBulletinContent()", () => {
it("fetches data", async () => {
diff --git a/frontend/messages/actions.ts b/frontend/messages/actions.ts
index 6f0358455..0c1176ea3 100644
--- a/frontend/messages/actions.ts
+++ b/frontend/messages/actions.ts
@@ -4,7 +4,7 @@ import { Bulletin } from "./interfaces";
import { DropDownItem } from "../ui";
import { UnsafeError } from "../interfaces";
import { toastErrors } from "../toast_errors";
-import { info } from "farmbot-toastr";
+import { info } from "../toast/toast";
import { t } from "../i18next_wrapper";
const url = (slug: string) => `${API.current.globalBulletinPath}${slug}`;
diff --git a/frontend/password_reset/password_reset.tsx b/frontend/password_reset/password_reset.tsx
index 62b118485..65c0c8bc9 100644
--- a/frontend/password_reset/password_reset.tsx
+++ b/frontend/password_reset/password_reset.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import axios from "axios";
-import { error as log, init as logInit } from "farmbot-toastr";
+import { error as log, init as logInit } from "../toast/toast";
import { prettyPrintApiErrors } from "../util";
import { API } from "../api";
import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../ui/index";
diff --git a/frontend/redux/__tests__/upgrade_reminder_test.ts b/frontend/redux/__tests__/upgrade_reminder_test.ts
index 5ab9fef01..4cb6f71cf 100644
--- a/frontend/redux/__tests__/upgrade_reminder_test.ts
+++ b/frontend/redux/__tests__/upgrade_reminder_test.ts
@@ -1,4 +1,4 @@
-import { info } from "farmbot-toastr";
+import { info } from "../../toast/toast";
describe("createReminderFn", () => {
it("reminds the user as-needed, but never more than once", async () => {
diff --git a/frontend/redux/upgrade_reminder.ts b/frontend/redux/upgrade_reminder.ts
index 344e105c3..f74a2d542 100644
--- a/frontend/redux/upgrade_reminder.ts
+++ b/frontend/redux/upgrade_reminder.ts
@@ -1,4 +1,4 @@
-import { info } from "farmbot-toastr";
+import { info } from "../toast/toast";
import { semverCompare, SemverResult, MinVersionOverride } from "../util";
import { Content } from "../constants";
import { Dictionary } from "lodash";
diff --git a/frontend/regimens/bulk_scheduler/__tests__/actions_test.ts b/frontend/regimens/bulk_scheduler/__tests__/actions_test.ts
index 46418417c..870e949f6 100644
--- a/frontend/regimens/bulk_scheduler/__tests__/actions_test.ts
+++ b/frontend/regimens/bulk_scheduler/__tests__/actions_test.ts
@@ -14,11 +14,11 @@ import {
import { Actions } from "../../../constants";
import { Everything } from "../../../interfaces";
import { ToggleDayParams } from "../interfaces";
-import { error, warning } from "farmbot-toastr";
import { newTaggedResource } from "../../../sync/actions";
import { arrayUnwrap } from "../../../resources/util";
import { overwrite } from "../../../api/crud";
import { fakeVariableNameSet } from "../../../__test_support__/fake_variables";
+import { error, warning } from "../../../toast/toast";
const sequence_id = 23;
const regimen_id = 32;
diff --git a/frontend/regimens/bulk_scheduler/actions.ts b/frontend/regimens/bulk_scheduler/actions.ts
index 40d086e64..277c12087 100644
--- a/frontend/regimens/bulk_scheduler/actions.ts
+++ b/frontend/regimens/bulk_scheduler/actions.ts
@@ -1,5 +1,5 @@
import { isNaN, isNumber } from "lodash";
-import { error, warning, success } from "farmbot-toastr";
+import { error, warning, success } from "../../toast/toast";
import { ReduxAction, Thunk } from "../../redux/interfaces";
import { ToggleDayParams } from "./interfaces";
import { findSequence, findRegimen } from "../../resources/selectors";
diff --git a/frontend/resources/selectors_by_kind.ts b/frontend/resources/selectors_by_kind.ts
index 2119862e5..2fca506fb 100644
--- a/frontend/resources/selectors_by_kind.ts
+++ b/frontend/resources/selectors_by_kind.ts
@@ -27,7 +27,7 @@ import {
sanityCheck,
} from "./tagged_resources";
import { bail } from "../util";
-import { error } from "farmbot-toastr";
+import { error } from "../toast/toast";
import { assertUuid } from "./util";
import { joinKindAndId } from "./reducer_support";
import { findAll } from "./find_all";
diff --git a/frontend/sequences/__tests__/test_button_test.tsx b/frontend/sequences/__tests__/test_button_test.tsx
index 3242d4bf8..9416bbc5b 100644
--- a/frontend/sequences/__tests__/test_button_test.tsx
+++ b/frontend/sequences/__tests__/test_button_test.tsx
@@ -21,7 +21,7 @@ import {
} from "farmbot";
import { mount } from "enzyme";
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
-import { warning } from "farmbot-toastr";
+import { warning } from "../../toast/toast";
import { fakeVariableNameSet } from "../../__test_support__/fake_variables";
import { SequenceMeta } from "../../resources/sequence_meta";
import { clickButton } from "../../__test_support__/helpers";
diff --git a/frontend/sequences/step_buttons/__tests__/index_test.tsx b/frontend/sequences/step_buttons/__tests__/index_test.tsx
index 5ac88a231..7d6c37afd 100644
--- a/frontend/sequences/step_buttons/__tests__/index_test.tsx
+++ b/frontend/sequences/step_buttons/__tests__/index_test.tsx
@@ -4,7 +4,7 @@ import { StepButton, stepClick } from "../index";
import { shallow } from "enzyme";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { Actions } from "../../../constants";
-import { error } from "farmbot-toastr";
+import { error } from "../../../toast/toast";
function props(): StepButtonParams {
return {
diff --git a/frontend/sequences/step_buttons/index.tsx b/frontend/sequences/step_buttons/index.tsx
index 4d55e1461..86681018b 100644
--- a/frontend/sequences/step_buttons/index.tsx
+++ b/frontend/sequences/step_buttons/index.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import { SequenceBodyItem as Step, TaggedSequence } from "farmbot";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
import { StepDragger, NULL_DRAGGER_ID } from "../../draggable/step_dragger";
import { pushStep, closeCommandMenu } from "../actions";
import { StepButtonParams } from "../interfaces";
diff --git a/frontend/sequences/test_button.tsx b/frontend/sequences/test_button.tsx
index eebaa4cd6..42dc32918 100644
--- a/frontend/sequences/test_button.tsx
+++ b/frontend/sequences/test_button.tsx
@@ -12,9 +12,9 @@ import {
} from "./locals_list/variable_support";
import { ResourceIndex, VariableNameSet, UUID } from "../resources/interfaces";
import { ShouldDisplay } from "../devices/interfaces";
-import { warning } from "farmbot-toastr";
import { Actions } from "../constants";
import { t } from "../i18next_wrapper";
+import { warning } from "../toast/toast";
/** Can't test without saving and syncing sequence. */
const saveAndSyncWarning = () =>
diff --git a/frontend/toast/toast.tsx b/frontend/toast/toast.tsx
new file mode 100644
index 000000000..57840960f
--- /dev/null
+++ b/frontend/toast/toast.tsx
@@ -0,0 +1,214 @@
+/**
+ * Warnings and errors fire once, to avoid bombarding the user with repetition.
+ * Eg: "Can"t connect to server!" might get repetitive.
+ */
+const messageQueue: string[] = [];
+
+/**
+ * The function responsible for attaching the messages to the container.
+ */
+const createToast = (message: string, title: string, color: string) => {
+
+ /**
+ * Container element for all of the messages created from init().
+ */
+ const tc = document.querySelector(".toast-container");
+
+ if (!tc) {
+ /**
+ * If there's no container created from the init() function, throw an error.
+ */
+ throw new Error("toast-container is null.");
+ } else {
+
+ /**
+ * Amount of time before each element is removed.
+ */
+ let timer = 7;
+
+ /**
+ * Declare if the user's mouse is hovering over the message.
+ */
+ let isHovered = false;
+
+ /**
+ * Create elements.
+ */
+ const toastEl = document.createElement("div");
+ const titleEl = document.createElement("h4");
+ const messageEl = document.createElement("div");
+ const loaderEl = document.createElement("div");
+ const leftLoaderEl = document.createElement("div");
+ const rightLoaderEl = document.createElement("div");
+ const spinnerLoaderEl = document.createElement("div");
+
+ /**
+ * Fill contents.
+ */
+ titleEl.innerText = title;
+ messageEl.innerText = message;
+
+ /**
+ * Add classes.
+ */
+ toastEl.classList.add("toast");
+ toastEl.classList.add(color);
+ titleEl.classList.add("toast-title");
+ messageEl.classList.add("toast-message");
+ loaderEl.classList.add("toast-loader");
+ leftLoaderEl.classList.add("toast-loader-left");
+ leftLoaderEl.classList.add(color);
+ rightLoaderEl.classList.add("toast-loader-right");
+ spinnerLoaderEl.classList.add("toast-loader-spinner");
+
+ /**
+ * Click (makes the message go away entirely).
+ */
+ toastEl.addEventListener("click", e => {
+ (e.currentTarget as Element).classList.add("poof");
+ setTimeout(() => {
+ if (!tc) {
+ throw (Error("toast-container is null."));
+ } else {
+ tc.removeChild(toastEl);
+ const index = messageQueue.indexOf(message);
+ messageQueue.splice(index, 1);
+ }
+ }, 200);
+ });
+
+ /**
+ * MouseEnter (pauses the timer).
+ */
+ toastEl.addEventListener("mouseenter", e => {
+ const children = (e.currentTarget as HTMLElement).children[2].children;
+ for (let i = 0; i < children.length; i++) {
+ (children[i] as HTMLElement).style.animationPlayState = "paused";
+ }
+ isHovered = true;
+ });
+
+ /**
+ * MouseLeave (resumes the timer).
+ */
+ toastEl.addEventListener("mouseleave", e => {
+ const children = (e.currentTarget as HTMLElement).children[2].children;
+ for (let i = 0; i < children.length; i++) {
+ (children[i] as HTMLElement).style.animationPlayState = "running";
+ }
+ isHovered = false;
+ });
+
+ /**
+ * Append children.
+ */
+ loaderEl.appendChild(leftLoaderEl);
+ loaderEl.appendChild(rightLoaderEl);
+ loaderEl.appendChild(spinnerLoaderEl);
+ toastEl.appendChild(titleEl);
+ toastEl.appendChild(messageEl);
+ toastEl.appendChild(loaderEl);
+ tc.appendChild(toastEl);
+
+ /**
+ * Start timer.
+ */
+ const interval = setInterval(() => {
+ if (timer <= 7) {
+ toastEl.classList.add("active");
+ }
+ if (!isHovered && timer <= .800) {
+ toastEl.classList.add("poof");
+ }
+ if (!isHovered) {
+ timer -= 0.100;
+ if (timer <= 0) {
+ clearInterval(interval);
+ if (toastEl && toastEl.parentNode === tc) {
+ if (!tc) {
+ throw (Error("toast-container is null."));
+ } else {
+ tc.removeChild(toastEl);
+ const index = messageQueue.indexOf(message);
+ messageQueue.splice(index, 1);
+ }
+ }
+ }
+ }
+ }, 100);
+ }
+};
+
+/**
+ * Yellow message with "Warning" as the default title.
+ */
+export const warning = (
+ message: string,
+ title = "Warning",
+ color = "yellow"
+) => {
+ if (messageQueue.indexOf(message) > -1) {
+ console.warn(message);
+ } else {
+ createToast(message, title, color);
+ messageQueue.push(message);
+ }
+};
+
+/**
+ * Red message with "Error" as the default title.
+ */
+export const error = (
+ message: string,
+ title = "Error",
+ color = "red"
+) => {
+ if (messageQueue.indexOf(message) > -1) {
+ console.error(message);
+ } else {
+ createToast(message, title, color);
+ messageQueue.push(message);
+ }
+};
+
+/**
+ * Green message with "Success" as the default title.
+ */
+export const success = (
+ message: string,
+ title = "Success",
+ color = "green"
+) => {
+ createToast(message, title, color);
+};
+
+/**
+ * Red message with "FYI" as the default title.
+ */
+export const info = (
+ message: string,
+ title = "FYI",
+ color = "blue"
+) => {
+ createToast(message, title, color);
+};
+
+/**
+ * Blue message with "Did you know?" as the default title.
+ */
+export const fun = (
+ message: string,
+ title = "Did you know?",
+ color = "dark-blue"
+) => {
+ createToast(message, title, color);
+};
+
+/**
+ * Adds a hidden container div for holding toast messages.
+ */
+export const init = () => {
+ const toastContainer = document.createElement("div");
+ toastContainer.classList.add("toast-container");
+ document.body.appendChild(toastContainer);
+};
diff --git a/frontend/toast_errors.ts b/frontend/toast_errors.ts
index e44184837..1f503071c 100644
--- a/frontend/toast_errors.ts
+++ b/frontend/toast_errors.ts
@@ -1,6 +1,6 @@
import { UnsafeError } from "./interfaces";
-import { error } from "farmbot-toastr";
import { prettyPrintApiErrors } from "./util";
+import { error } from "./toast/toast";
export function toastErrors({ err }: UnsafeError) {
return error(prettyPrintApiErrors(err));
diff --git a/frontend/tos_update/__tests__/component_test.tsx b/frontend/tos_update/__tests__/component_test.tsx
index 08d0495f6..4fa47706c 100644
--- a/frontend/tos_update/__tests__/component_test.tsx
+++ b/frontend/tos_update/__tests__/component_test.tsx
@@ -14,7 +14,7 @@ import { shallow, mount } from "enzyme";
import axios from "axios";
import { API } from "../../api/index";
import { Session } from "../../session";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
type E = React.FormEvent;
diff --git a/frontend/tos_update/component.tsx b/frontend/tos_update/component.tsx
index 1e9b230fb..3980539d8 100644
--- a/frontend/tos_update/component.tsx
+++ b/frontend/tos_update/component.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import axios from "axios";
-import { fun as log, error as logError, init as logInit } from "farmbot-toastr";
+import { fun as log, error as logError, init as logInit } from "../toast/toast";
import { AuthState } from "../auth/interfaces";
import { Session } from "../session";
import { prettyPrintApiErrors } from "../util";
diff --git a/frontend/ui/__tests__/blurable_input_test.tsx b/frontend/ui/__tests__/blurable_input_test.tsx
index 39ec1f7d3..fa302c03a 100644
--- a/frontend/ui/__tests__/blurable_input_test.tsx
+++ b/frontend/ui/__tests__/blurable_input_test.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import { shallow } from "enzyme";
import { BlurableInput, BIProps } from "../blurable_input";
-import { error } from "farmbot-toastr";
+import { error } from "../../toast/toast";
describe("", () => {
const fakeProps = (): BIProps => {
diff --git a/frontend/ui/blurable_input.tsx b/frontend/ui/blurable_input.tsx
index 3a2c62844..da8fce50c 100644
--- a/frontend/ui/blurable_input.tsx
+++ b/frontend/ui/blurable_input.tsx
@@ -1,10 +1,10 @@
import * as React from "react";
import { equals, parseIntInput } from "../util";
import { isNumber } from "lodash";
-import { error } from "farmbot-toastr";
import { InputError } from "./input_error";
import { t } from "../i18next_wrapper";
+import { error } from "../toast/toast";
export interface BIProps {
value: string | number;
diff --git a/package.json b/package.json
index 0ec9a6874..054e09bf9 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,6 @@
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.14.0",
"farmbot": "8.0.1-rc8",
- "farmbot-toastr": "1.0.3",
"i18next": "17.0.4",
"jest": "24.8.0",
"jest-cli": "24.8.0",