Feature: Toast Notification System (#25)

* Add react-hot-toaster to dependencies

* Enable TailwindCSS 'jit' mode

* Add Toast component

* Add Toaster context for react-hot-toast

* Add toast notification for queries, form validation fix

* Add new animations for Toast component

* fix: nickserv account validation

Co-authored-by: Ludvig Lundgren <hello@ludviglundgren.se>
This commit is contained in:
smallobject 2021-08-31 19:53:42 +03:00 committed by GitHub
parent 00f956870b
commit 11fcf1ead9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 195 additions and 14 deletions

View file

@ -11,7 +11,8 @@ import { queryClient } from "../../App";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
import { toast } from 'react-hot-toast'
import Toast from '../../components/notifications/Toast';
interface props {
isOpen: boolean;
toggle: any;
@ -28,9 +29,12 @@ function IndexerAddForm({ isOpen, toggle }: props) {
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.create(indexer), {
onSuccess: () => {
queryClient.invalidateQueries(['indexer']);
toast.custom((t) => <Toast type="success" body="Indexer was added" t={t} />)
sleep(1500)
toggle()
},
onError: () => {
toast.custom((t) => <Toast type="error" body="Indexer could not be added" t={t} />)
}
})

View file

@ -12,6 +12,9 @@ import APIClient from "../../api/APIClient";
import { queryClient } from "../../App";
import { PasswordFieldWide } from "../../components/inputs/wide";
import { toast } from 'react-hot-toast'
import Toast from '../../components/notifications/Toast';
interface props {
isOpen: boolean;
toggle: any;
@ -24,6 +27,7 @@ function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
onSuccess: () => {
queryClient.invalidateQueries(['indexer']);
toast.custom((t) => <Toast type="success" body={`${indexer.name} was updated successfully`} t={t}/>)
sleep(1500)
toggle()
@ -33,6 +37,7 @@ function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
onSuccess: () => {
queryClient.invalidateQueries(['indexer']);
toast.custom((t) => <Toast type="success" body={`${indexer.name} was deleted.`} t={t}/>)
}
})

View file

@ -14,17 +14,31 @@ import {classNames} from "../../styles/utils";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
import { toast } from 'react-hot-toast';
import Toast from '../../components/notifications/Toast';
type FormValues = {
name: string
server: string
nickserv: {
account: string
}
port: number
}
function IrcNetworkAddForm({isOpen, toggle}: any) {
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
onSuccess: data => {
onSuccess: (data) => {
queryClient.invalidateQueries(['networks']);
toast.custom((t) => <Toast type="success" body="IRC Network added" t={t} />)
toggle()
}
},
onError: () => {
toast.custom((t) => <Toast type="error" body="IRC Network could not be added" t={t}/>)
},
})
const onSubmit = (data: any) => {
console.log(data)
// easy way to split textarea lines into array of strings for each newline.
// parse on the field didn't really work.
let cmds = data.connect_commands && data.connect_commands.length > 0 ? data.connect_commands.replace(/\r\n/g,"\n").split("\n") : [];
@ -35,22 +49,34 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
};
const validate = (values: any) => {
const errors = {} as any;
const errors = {
nickserv: {
account: null,
}
} as any;
if (!values.name) {
errors.name = "Required";
}
if (!values.port) {
errors.port = "Required";
}
if (!values.server) {
errors.server = "Required";
}
if(!values.nickserv?.account) {
errors.nickserv.account = "Required";
}
return errors;
}
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
<Dialog as="div" static className="fixed inset-0 overflow-hidden transition-all" open={isOpen} onClose={toggle}>
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/>
@ -73,6 +99,9 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
server: "",
tls: false,
pass: "",
nickserv: {
account: ""
}
}}
mutators={{
...arrayMutators
@ -119,7 +148,7 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
<div>
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" required={true} />
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="tls" label="TLS"/>
@ -127,7 +156,7 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
<TextFieldWide name="nickserv.account" label="NickServ Account" placeholder="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
<PasswordFieldWide name="invite_command" label="Invite command" />

View file

@ -16,12 +16,16 @@ import { DeleteModal } from "../../components/modals";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
import { toast } from 'react-hot-toast';
import Toast from '../../components/notifications/Toast';
function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
onSuccess: () => {
queryClient.invalidateQueries(['networks']);
toast.custom((t) => <Toast type="success" body={`${network.name} was updated successfully`} t={t} />)
toggle()
}
})
@ -29,6 +33,8 @@ function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
const deleteMutation = useMutation((id: number) => APIClient.irc.deleteNetwork(id), {
onSuccess: () => {
queryClient.invalidateQueries(['networks']);
toast.custom((t) => <Toast type="success" body={`${network.name} was deleted.`} t={t} />)
toggle()
}
})
@ -52,7 +58,11 @@ function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
};
const validate = (values: any) => {
const errors = {} as any;
const errors = {
nickserv: {
account: null,
}
} as any;
if (!values.name) {
errors.name = "Required";
@ -66,7 +76,7 @@ function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
errors.port = "Required";
}
if (!values.nickserv.account) {
if(!values.nickserv?.account) {
errors.nickserv.account = "Required";
}

View file

@ -17,6 +17,9 @@ import { DownloadClientTypeOptions } from "../../../domain/constants";
import { RadioFieldsetWide } from "../../../components/inputs/wide";
import { componentMap } from "./shared";
import { toast } from 'react-hot-toast'
import Toast from '../../../components/notifications/Toast';
function DownloadClientAddForm({ isOpen, toggle }: any) {
const [isTesting, setIsTesting] = useState(false);
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
@ -27,8 +30,13 @@ function DownloadClientAddForm({ isOpen, toggle }: any) {
{
onSuccess: () => {
queryClient.invalidateQueries(["downloadClients"]);
toast.custom((t) => <Toast type="success" body="Client was added" t={t} />)
toggle();
},
onError: () => {
toast.custom((t) => <Toast type="error" body="Client could not be added" t={t} />)
}
}
);
@ -53,6 +61,7 @@ function DownloadClientAddForm({ isOpen, toggle }: any) {
});
},
onError: (error) => {
console.log('not added')
setIsTesting(false);
setIsErrorTest(true);
sleep(2500).then(() => {

View file

@ -16,6 +16,10 @@ import { componentMap } from "./shared";
import { RadioFieldsetWide } from "../../../components/inputs/wide";
import { DeleteModal } from "../../../components/modals";
import { toast } from 'react-hot-toast'
import Toast from '../../../components/notifications/Toast';
function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
const [isTesting, setIsTesting] = useState(false);
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
@ -27,7 +31,7 @@ function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
{
onSuccess: () => {
queryClient.invalidateQueries(["downloadClients"]);
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t} />)
toggle();
},
}
@ -38,6 +42,7 @@ function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
{
onSuccess: () => {
queryClient.invalidateQueries();
toast.custom((t) => <Toast type="success" body={`${client.name} was deleted.`} t={t}/>)
toggleDeleteModal();
},
}