mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
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:
parent
00f956870b
commit
11fcf1ead9
15 changed files with 195 additions and 14 deletions
|
@ -22,6 +22,7 @@
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"react-final-form-arrays": "^3.1.3",
|
"react-final-form-arrays": "^3.1.3",
|
||||||
|
"react-hot-toast": "^2.1.1",
|
||||||
"react-multi-select-component": "^4.0.2",
|
"react-multi-select-component": "^4.0.2",
|
||||||
"react-query": "^3.18.1",
|
"react-query": "^3.18.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {useEffect, useState} from "react";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import {Redirect} from "react-router-dom";
|
import {Redirect} from "react-router-dom";
|
||||||
import APIClient from "../api/APIClient";
|
import APIClient from "../api/APIClient";
|
||||||
|
import { Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
export default function Layout({auth=false, authFallback="/login", children}: any) {
|
export default function Layout({auth=false, authFallback="/login", children}: any) {
|
||||||
const [loggedIn, setLoggedIn] = useRecoilState(isLoggedIn);
|
const [loggedIn, setLoggedIn] = useRecoilState(isLoggedIn);
|
||||||
|
@ -28,6 +29,7 @@ export default function Layout({auth=false, authFallback="/login", children}: an
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{auth && !loggedIn ? <Redirect to={authFallback} /> : (
|
{auth && !loggedIn ? <Redirect to={authFallback} /> : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
<Toaster position="top-right" />
|
||||||
{children}
|
{children}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
52
web/src/components/notifications/Toast.tsx
Normal file
52
web/src/components/notifications/Toast.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { XIcon, CheckCircleIcon, ExclamationIcon, ExclamationCircleIcon } from '@heroicons/react/solid'
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: 'error' | 'success' | 'warning'
|
||||||
|
body?: string
|
||||||
|
t?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Toast: FC<Props> = ({
|
||||||
|
type,
|
||||||
|
body,
|
||||||
|
t
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`${
|
||||||
|
t.visible ? 'animate-enter' : 'animate-leave'
|
||||||
|
} max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden transition-all`}>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
{type === 'success' && <CheckCircleIcon className="h-6 w-6 text-green-400" aria-hidden="true" />}
|
||||||
|
{type === 'error' && <ExclamationCircleIcon className="h-6 w-6 text-red-400" aria-hidden="true" />}
|
||||||
|
{type === 'warning' && <ExclamationIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />}
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 w-0 flex-1 pt-0.5">
|
||||||
|
<p className="text-sm font-medium text-gray-900">
|
||||||
|
{type === 'success' && "Success"}
|
||||||
|
{type === 'error' && "Error"}
|
||||||
|
{type === 'warning' && "Warning"}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm text-gray-500">{body}</p>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 flex-shrink-0 flex">
|
||||||
|
<button
|
||||||
|
className="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||||
|
onClick={() => {
|
||||||
|
toast.dismiss(t.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
<XIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Toast;
|
|
@ -17,6 +17,9 @@ import {
|
||||||
RadioFieldsetWide,
|
RadioFieldsetWide,
|
||||||
} from "../../components/inputs/wide";
|
} from "../../components/inputs/wide";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../components/notifications/Toast';
|
||||||
|
|
||||||
interface DownloadClientSelectProps {
|
interface DownloadClientSelectProps {
|
||||||
name: string;
|
name: string;
|
||||||
clients: DownloadClient[];
|
clients: DownloadClient[];
|
||||||
|
@ -135,6 +138,8 @@ function FilterActionAddForm({ filter, isOpen, toggle, clients }: props) {
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["filter", filter.id]);
|
queryClient.invalidateQueries(["filter", filter.id]);
|
||||||
|
toast.custom((t) => <Toast type="success" body="Action was added" t={t} />)
|
||||||
|
|
||||||
sleep(500).then(() => toggle());
|
sleep(500).then(() => toggle());
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ import {
|
||||||
} from "../../components/inputs/wide";
|
} from "../../components/inputs/wide";
|
||||||
import { DownloadClientSelect } from "./FilterActionAddForm";
|
import { DownloadClientSelect } from "./FilterActionAddForm";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../components/notifications/Toast';
|
||||||
|
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
filter: Filter;
|
filter: Filter;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -38,6 +42,8 @@ function FilterActionUpdateForm({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// console.log("add action");
|
// console.log("add action");
|
||||||
queryClient.invalidateQueries(["filter", filter.id]);
|
queryClient.invalidateQueries(["filter", filter.id]);
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was updated successfully`} t={t} />)
|
||||||
|
|
||||||
sleep(1500);
|
sleep(1500);
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
|
|
|
@ -8,12 +8,18 @@ import {Field, Form} from "react-final-form";
|
||||||
import DEBUG from "../../components/debug";
|
import DEBUG from "../../components/debug";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../components/notifications/Toast';
|
||||||
|
|
||||||
|
|
||||||
const required = (value: any) => (value ? undefined : 'Required')
|
const required = (value: any) => (value ? undefined : 'Required')
|
||||||
|
|
||||||
function FilterAddForm({isOpen, toggle}: any) {
|
function FilterAddForm({isOpen, toggle}: any) {
|
||||||
const mutation = useMutation((filter: Filter) => APIClient.filters.create(filter), {
|
const mutation = useMutation((filter: Filter) => APIClient.filters.create(filter), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries('filter');
|
queryClient.invalidateQueries('filter');
|
||||||
|
toast.custom((t) => <Toast type="success" body="Filter was added" t={t} />)
|
||||||
|
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,8 @@ import { queryClient } from "../../App";
|
||||||
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../components/notifications/Toast';
|
||||||
interface props {
|
interface props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
toggle: any;
|
toggle: any;
|
||||||
|
@ -28,9 +29,12 @@ function IndexerAddForm({ isOpen, toggle }: props) {
|
||||||
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.create(indexer), {
|
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.create(indexer), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(['indexer']);
|
queryClient.invalidateQueries(['indexer']);
|
||||||
|
toast.custom((t) => <Toast type="success" body="Indexer was added" t={t} />)
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
toggle()
|
toggle()
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.custom((t) => <Toast type="error" body="Indexer could not be added" t={t} />)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ import APIClient from "../../api/APIClient";
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
import { PasswordFieldWide } from "../../components/inputs/wide";
|
import { PasswordFieldWide } from "../../components/inputs/wide";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../components/notifications/Toast';
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
toggle: any;
|
toggle: any;
|
||||||
|
@ -24,6 +27,7 @@ function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
|
||||||
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
|
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(['indexer']);
|
queryClient.invalidateQueries(['indexer']);
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${indexer.name} was updated successfully`} t={t}/>)
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
toggle()
|
toggle()
|
||||||
|
@ -33,6 +37,7 @@ function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
|
const deleteMutation = useMutation((id: number) => APIClient.indexers.delete(id), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(['indexer']);
|
queryClient.invalidateQueries(['indexer']);
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${indexer.name} was deleted.`} t={t}/>)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -14,17 +14,31 @@ import {classNames} from "../../styles/utils";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
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) {
|
function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
|
||||||
onSuccess: data => {
|
onSuccess: (data) => {
|
||||||
queryClient.invalidateQueries(['networks']);
|
queryClient.invalidateQueries(['networks']);
|
||||||
|
toast.custom((t) => <Toast type="success" body="IRC Network added" t={t} />)
|
||||||
toggle()
|
toggle()
|
||||||
}
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.custom((t) => <Toast type="error" body="IRC Network could not be added" t={t}/>)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
// easy way to split textarea lines into array of strings for each newline.
|
// easy way to split textarea lines into array of strings for each newline.
|
||||||
// parse on the field didn't really work.
|
// 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") : [];
|
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 validate = (values: any) => {
|
||||||
const errors = {} as any;
|
const errors = {
|
||||||
|
nickserv: {
|
||||||
|
account: null,
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
if (!values.name) {
|
if (!values.name) {
|
||||||
errors.name = "Required";
|
errors.name = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!values.port) {
|
||||||
|
errors.port = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
if (!values.server) {
|
if (!values.server) {
|
||||||
errors.server = "Required";
|
errors.server = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!values.nickserv?.account) {
|
||||||
|
errors.nickserv.account = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={Fragment}>
|
<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">
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
<Dialog.Overlay className="absolute inset-0"/>
|
<Dialog.Overlay className="absolute inset-0"/>
|
||||||
|
|
||||||
|
@ -73,6 +99,9 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
server: "",
|
server: "",
|
||||||
tls: false,
|
tls: false,
|
||||||
pass: "",
|
pass: "",
|
||||||
|
nickserv: {
|
||||||
|
account: ""
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
mutators={{
|
mutators={{
|
||||||
...arrayMutators
|
...arrayMutators
|
||||||
|
@ -119,7 +148,7 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
|
<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">
|
<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"/>
|
<SwitchGroup name="tls" label="TLS"/>
|
||||||
|
@ -127,7 +156,7 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
|
||||||
|
|
||||||
<PasswordFieldWide name="pass" label="Password" help="Network password" />
|
<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="nickserv.password" label="NickServ Password" />
|
||||||
|
|
||||||
<PasswordFieldWide name="invite_command" label="Invite command" />
|
<PasswordFieldWide name="invite_command" label="Invite command" />
|
||||||
|
|
|
@ -16,12 +16,16 @@ import { DeleteModal } from "../../components/modals";
|
||||||
import APIClient from "../../api/APIClient";
|
import APIClient from "../../api/APIClient";
|
||||||
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
|
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) {
|
function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
||||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
||||||
|
|
||||||
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
|
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(['networks']);
|
queryClient.invalidateQueries(['networks']);
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${network.name} was updated successfully`} t={t} />)
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -29,6 +33,8 @@ function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.irc.deleteNetwork(id), {
|
const deleteMutation = useMutation((id: number) => APIClient.irc.deleteNetwork(id), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(['networks']);
|
queryClient.invalidateQueries(['networks']);
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${network.name} was deleted.`} t={t} />)
|
||||||
|
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -52,7 +58,11 @@ function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const validate = (values: any) => {
|
const validate = (values: any) => {
|
||||||
const errors = {} as any;
|
const errors = {
|
||||||
|
nickserv: {
|
||||||
|
account: null,
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
if (!values.name) {
|
if (!values.name) {
|
||||||
errors.name = "Required";
|
errors.name = "Required";
|
||||||
|
@ -66,7 +76,7 @@ function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
|
||||||
errors.port = "Required";
|
errors.port = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.nickserv.account) {
|
if(!values.nickserv?.account) {
|
||||||
errors.nickserv.account = "Required";
|
errors.nickserv.account = "Required";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,9 @@ import { DownloadClientTypeOptions } from "../../../domain/constants";
|
||||||
import { RadioFieldsetWide } from "../../../components/inputs/wide";
|
import { RadioFieldsetWide } from "../../../components/inputs/wide";
|
||||||
import { componentMap } from "./shared";
|
import { componentMap } from "./shared";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../../components/notifications/Toast';
|
||||||
|
|
||||||
function DownloadClientAddForm({ isOpen, toggle }: any) {
|
function DownloadClientAddForm({ isOpen, toggle }: any) {
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
||||||
|
@ -27,8 +30,13 @@ function DownloadClientAddForm({ isOpen, toggle }: any) {
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["downloadClients"]);
|
queryClient.invalidateQueries(["downloadClients"]);
|
||||||
|
toast.custom((t) => <Toast type="success" body="Client was added" t={t} />)
|
||||||
|
|
||||||
toggle();
|
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) => {
|
onError: (error) => {
|
||||||
|
console.log('not added')
|
||||||
setIsTesting(false);
|
setIsTesting(false);
|
||||||
setIsErrorTest(true);
|
setIsErrorTest(true);
|
||||||
sleep(2500).then(() => {
|
sleep(2500).then(() => {
|
||||||
|
|
|
@ -16,6 +16,10 @@ import { componentMap } from "./shared";
|
||||||
import { RadioFieldsetWide } from "../../../components/inputs/wide";
|
import { RadioFieldsetWide } from "../../../components/inputs/wide";
|
||||||
import { DeleteModal } from "../../../components/modals";
|
import { DeleteModal } from "../../../components/modals";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../../../components/notifications/Toast';
|
||||||
|
|
||||||
|
|
||||||
function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
|
||||||
|
@ -27,7 +31,7 @@ function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["downloadClients"]);
|
queryClient.invalidateQueries(["downloadClients"]);
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t} />)
|
||||||
toggle();
|
toggle();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -38,6 +42,7 @@ function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries();
|
queryClient.invalidateQueries();
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${client.name} was deleted.`} t={t}/>)
|
||||||
toggleDeleteModal();
|
toggleDeleteModal();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,3 +15,35 @@ code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes enter {
|
||||||
|
0% {
|
||||||
|
transform: scale(.9);
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-enter {
|
||||||
|
animation: enter .2s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes leave {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(.9);
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-leave {
|
||||||
|
animation: leave .15s ease-in forwards
|
||||||
|
}
|
|
@ -29,6 +29,10 @@ import { FilterAddForm, FilterActionAddForm} from "../forms";
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import APIClient from "../api/APIClient";
|
import APIClient from "../api/APIClient";
|
||||||
|
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import Toast from '../components/notifications/Toast';
|
||||||
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{name: 'General', href: '', current: true},
|
{name: 'General', href: '', current: true},
|
||||||
// { name: 'TV', href: 'tv', current: false },
|
// { name: 'TV', href: 'tv', current: false },
|
||||||
|
@ -443,6 +447,8 @@ function FilterTabGeneral({filter}: FilterTabGeneralProps) {
|
||||||
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// queryClient.setQueryData(['filter', filter.id], data)
|
// queryClient.setQueryData(['filter', filter.id], data)
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was updated successfully`} t={t} />)
|
||||||
|
|
||||||
queryClient.invalidateQueries(["filter",filter.id]);
|
queryClient.invalidateQueries(["filter",filter.id]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -451,6 +457,8 @@ function FilterTabGeneral({filter}: FilterTabGeneralProps) {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// invalidate filters
|
// invalidate filters
|
||||||
queryClient.invalidateQueries("filter");
|
queryClient.invalidateQueries("filter");
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was deleted`} t={t} />)
|
||||||
|
|
||||||
// redirect
|
// redirect
|
||||||
history.push("/filters")
|
history.push("/filters")
|
||||||
}
|
}
|
||||||
|
@ -568,6 +576,7 @@ function FilterTabMoviesTvNew2({filter}: FilterTabGeneralProps) {
|
||||||
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// queryClient.setQueryData(['filter', filter.id], data)
|
// queryClient.setQueryData(['filter', filter.id], data)
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was updated successfully`} t={t} />)
|
||||||
queryClient.invalidateQueries(["filter",filter.id]);
|
queryClient.invalidateQueries(["filter",filter.id]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -575,6 +584,7 @@ function FilterTabMoviesTvNew2({filter}: FilterTabGeneralProps) {
|
||||||
const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), {
|
const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// invalidate filters
|
// invalidate filters
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was deleted`} t={t} />)
|
||||||
queryClient.invalidateQueries("filter");
|
queryClient.invalidateQueries("filter");
|
||||||
// redirect
|
// redirect
|
||||||
history.push("/filters")
|
history.push("/filters")
|
||||||
|
@ -677,6 +687,8 @@ function FilterTabAdvanced({filter}: FilterTabGeneralProps) {
|
||||||
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// queryClient.setQueryData(['filter', filter.id], data)
|
// queryClient.setQueryData(['filter', filter.id], data)
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was updated successfully`} t={t} />)
|
||||||
|
|
||||||
queryClient.invalidateQueries(["filter",filter.id]);
|
queryClient.invalidateQueries(["filter",filter.id]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -685,6 +697,8 @@ function FilterTabAdvanced({filter}: FilterTabGeneralProps) {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// invalidate filters
|
// invalidate filters
|
||||||
queryClient.invalidateQueries("filter");
|
queryClient.invalidateQueries("filter");
|
||||||
|
toast.custom((t) => <Toast type="success" body={`${filter.name} was deleted`} t={t} />)
|
||||||
|
|
||||||
// redirect
|
// redirect
|
||||||
history.push("/filters")
|
history.push("/filters")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const colors = require('tailwindcss/colors')
|
const colors = require('tailwindcss/colors')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
mode: 'jit',
|
||||||
purge: {
|
purge: {
|
||||||
content: [
|
content: [
|
||||||
'./src/**/*.{tsx,ts,html,css}',
|
'./src/**/*.{tsx,ts,html,css}',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue