chore: add eslint and cleanup (#118)

* refactor: modified existing react imports to conform with the recommended approach of not using the default export directly, since it will be deprecated in one of the future releases. see https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html for more info. note: react types don't require importing of react.
refactor: cleaned up some of the imports

* feat: added eslint and fixed all the errors/warning. eslint can now be invoked by running "npm run lint".
chore: updated .gitignore not to include unnecessary artefacts.
refactor: re-organized some of the imports.

* refactor: converted remaining few typed functional components to proper prop argument structure.

* fix: fixed small react-query invalidation bug for the FilterDetails component.

Co-authored-by: anonymous <anonymous>
This commit is contained in:
stacksmash76 2022-02-08 18:10:47 +01:00 committed by GitHub
parent d1f08903d1
commit fe06363530
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 463 additions and 343 deletions

6
.gitignore vendored
View file

@ -20,8 +20,14 @@ Thumbs.db
# Other # Other
.idea .idea
.yarn
node_modules/ node_modules/
web/build web/build
bin/ bin/
log/ log/
dist/ dist/
# If needed, package-lock.json shall be added
# manually using an explicit git add command.
package-lock.json
# Ditto for yarn, except we're using npm.
yarn.lock

View file

@ -26,13 +26,9 @@
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject",
}, "lint": "esw src/ --ext \".ts,.tsx,.js,.jsx\" --color",
"eslintConfig": { "lint:watch": "npm run lint -- --watch"
"extends": [
"react-app",
"react-app/jest"
]
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@ -57,9 +53,70 @@
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"@types/react-table": "^7.7.7", "@types/react-table": "^7.7.7",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"eslint": "^8.8.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-watch": "^8.0.0",
"postcss": "^8.4.6", "postcss": "^8.4.6",
"tailwindcss": "^3.0.18", "tailwindcss": "^3.0.18",
"typescript": "^4.1.2" "typescript": "^4.1.2"
},
"eslintConfig": {
"root": true,
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
],
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
}
},
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"node": {
"extensions": [
".ts",
".tsx",
".js",
".jsx"
]
}
}
},
"env": {
"browser": true,
"node": true,
"jquery": false
},
"globals": {}
} }
} }

View file

@ -39,4 +39,4 @@ export function App() {
) : null} ) : null}
</QueryClientProvider> </QueryClientProvider>
) )
}; }

View file

@ -1,7 +1,7 @@
import {baseUrl, sseBaseUrl} from "../utils"; import {baseUrl, sseBaseUrl} from "../utils";
function baseClient(endpoint: string, method: string, { body, ...customConfig}: any = {}) { function baseClient(endpoint: string, method: string, { body, ...customConfig}: any = {}) {
let baseURL = baseUrl() const baseURL = baseUrl()
const headers = {'content-type': 'application/json'} const headers = {'content-type': 'application/json'}
const config = { const config = {

View file

@ -1,4 +1,3 @@
import React from "react";
import { Field } from "formik"; import { Field } from "formik";
interface ErrorFieldProps { interface ErrorFieldProps {
@ -7,7 +6,7 @@ interface ErrorFieldProps {
subscribe?: any; subscribe?: any;
} }
const ErrorField: React.FC<ErrorFieldProps> = ({ name, classNames }) => ( const ErrorField = ({ name, classNames }: ErrorFieldProps) => (
<Field name={name} subscribe={{ touched: true, error: true }}> <Field name={name} subscribe={{ touched: true, error: true }}>
{({ meta: { touched, error } }: any) => {({ meta: { touched, error } }: any) =>
touched && error ? <span className={classNames}>{error}</span> : null touched && error ? <span className={classNames}>{error}</span> : null
@ -21,7 +20,11 @@ interface CheckboxFieldProps {
sublabel?: string; sublabel?: string;
} }
const CheckboxField: React.FC<CheckboxFieldProps> = ({ name, label, sublabel }) => ( const CheckboxField = ({
name,
label,
sublabel
}: CheckboxFieldProps) => (
<div className="relative flex items-start"> <div className="relative flex items-start">
<div className="flex items-center h-5"> <div className="flex items-center h-5">
<Field <Field

View file

@ -1,4 +1,3 @@
import React from "react";
import { Field } from "formik"; import { Field } from "formik";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid"; import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
@ -11,11 +10,16 @@ interface TextFieldProps {
label?: string; label?: string;
placeholder?: string; placeholder?: string;
columns?: COL_WIDTHS; columns?: COL_WIDTHS;
className?: string;
autoComplete?: string; autoComplete?: string;
} }
const TextField: React.FC<TextFieldProps> = ({ name, label, placeholder, columns, className, autoComplete }) => ( export const TextField = ({
name,
label,
placeholder,
columns,
autoComplete
}: TextFieldProps) => (
<div <div
className={classNames( className={classNames(
columns ? `col-span-${columns}` : "col-span-12" columns ? `col-span-${columns}` : "col-span-12"
@ -55,14 +59,22 @@ interface PasswordFieldProps {
label?: string; label?: string;
placeholder?: string; placeholder?: string;
columns?: COL_WIDTHS; columns?: COL_WIDTHS;
className?: string;
autoComplete?: string; autoComplete?: string;
defaultValue?: string; defaultValue?: string;
help?: string; help?: string;
required?: boolean; required?: boolean;
} }
const PasswordField: React.FC<PasswordFieldProps> = ({ name, label, placeholder, defaultValue, columns, className, autoComplete, help, required }) => { export const PasswordField = ({
name,
label,
placeholder,
defaultValue,
columns,
autoComplete,
help,
required
}: PasswordFieldProps) => {
const [isVisible, toggleVisibility] = useToggle(false) const [isVisible, toggleVisibility] = useToggle(false)
return ( return (
@ -113,17 +125,13 @@ interface NumberFieldProps {
name: string; name: string;
label?: string; label?: string;
placeholder?: string; placeholder?: string;
className?: string;
required?: boolean;
} }
const NumberField: React.FC<NumberFieldProps> = ({ export const NumberField = ({
name, name,
label, label,
placeholder, placeholder
required, }: NumberFieldProps) => (
className,
}) => (
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide"> <label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
{label} {label}
@ -155,5 +163,3 @@ const NumberField: React.FC<NumberFieldProps> = ({
</Field> </Field>
</div> </div>
); );
export { TextField, PasswordField, NumberField };

View file

@ -1,5 +1,5 @@
import React from "react"; import { Field } from "formik";
import { Field, FieldProps } from "formik"; import type { FieldProps } from "formik";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { useToggle } from "../../hooks/hooks"; import { useToggle } from "../../hooks/hooks";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid"; import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
@ -12,12 +12,19 @@ interface TextFieldWideProps {
help?: string; help?: string;
placeholder?: string; placeholder?: string;
defaultValue?: string; defaultValue?: string;
className?: string;
required?: boolean; required?: boolean;
hidden?: boolean; hidden?: boolean;
} }
const TextFieldWide: React.FC<TextFieldWideProps> = ({ name, label, help, placeholder, defaultValue, required, hidden, className }) => ( export const TextFieldWide = ({
name,
label,
help,
placeholder,
defaultValue,
required,
hidden
}: TextFieldWideProps) => (
<div hidden={hidden} className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5"> <div hidden={hidden} className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div> <div>
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"> <label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
@ -56,7 +63,14 @@ interface PasswordFieldWideProps {
required?: boolean; required?: boolean;
} }
function PasswordFieldWide({ name, label, placeholder, defaultValue, help, required }: PasswordFieldWideProps) { export const PasswordFieldWide = ({
name,
label,
placeholder,
defaultValue,
help,
required
}: PasswordFieldWideProps) => {
const [isVisible, toggleVisibility] = useToggle(false) const [isVisible, toggleVisibility] = useToggle(false)
return ( return (
@ -103,21 +117,17 @@ interface NumberFieldWideProps {
help?: string; help?: string;
placeholder?: string; placeholder?: string;
defaultValue?: number; defaultValue?: number;
className?: string;
required?: boolean; required?: boolean;
hidden?: boolean;
} }
const NumberFieldWide: React.FC<NumberFieldWideProps> = ({ export const NumberFieldWide = ({
name, name,
label, label,
placeholder, placeholder,
help, help,
defaultValue, defaultValue,
required, required
hidden, }: NumberFieldWideProps) => (
className,
}) => (
<div className="px-4 space-y-1 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5"> <div className="px-4 space-y-1 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div> <div>
<label <label
@ -165,7 +175,12 @@ interface SwitchGroupWideProps {
className?: string; className?: string;
} }
const SwitchGroupWide: React.FC<SwitchGroupWideProps> = ({ name, label, description, defaultValue }) => ( export const SwitchGroupWide = ({
name,
label,
description,
defaultValue
}: SwitchGroupWideProps) => (
<ul className="mt-2 divide-y divide-gray-200 dark:divide-gray-700"> <ul className="mt-2 divide-y divide-gray-200 dark:divide-gray-700">
<Switch.Group as="li" className="py-4 flex items-center justify-between"> <Switch.Group as="li" className="py-4 flex items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col">
@ -214,4 +229,3 @@ const SwitchGroupWide: React.FC<SwitchGroupWideProps> = ({ name, label, descript
</ul> </ul>
) )
export { NumberFieldWide, TextFieldWide, PasswordFieldWide, SwitchGroupWide };

View file

@ -9,17 +9,15 @@ interface MultiSelectProps {
label?: string; label?: string;
options?: [] | any; options?: [] | any;
name: string; name: string;
className?: string;
columns?: COL_WIDTHS; columns?: COL_WIDTHS;
} }
const MultiSelect: React.FC<MultiSelectProps> = ({ export const MultiSelect = ({
name, name,
label, label,
options, options,
className,
columns, columns,
}) => ( }: MultiSelectProps) => (
<div <div
className={classNames( className={classNames(
columns ? `col-span-${columns}` : "col-span-12" columns ? `col-span-${columns}` : "col-span-12"
@ -44,9 +42,8 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
labelledBy={name} labelledBy={name}
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value === item))} value={field.value && field.value.map((item: any) => options.find((o: any) => o.value === item))}
onChange={(values: any) => { onChange={(values: any) => {
let am = values && values.map((i: any) => i.value) const am = values && values.map((i: any) => i.value);
setFieldValue(field.name, am);
setFieldValue(field.name, am)
}} }}
className="dark:bg-gray-700 dark" className="dark:bg-gray-700 dark"
/> />
@ -55,13 +52,12 @@ const MultiSelect: React.FC<MultiSelectProps> = ({
</div> </div>
); );
const IndexerMultiSelect: React.FC<MultiSelectProps> = ({ export const IndexerMultiSelect = ({
name, name,
label, label,
options, options,
className,
columns, columns,
}) => ( }: MultiSelectProps) => (
<div <div
className={classNames( className={classNames(
columns ? `col-span-${columns}` : "col-span-12" columns ? `col-span-${columns}` : "col-span-12"
@ -86,9 +82,8 @@ const IndexerMultiSelect: React.FC<MultiSelectProps> = ({
labelledBy={name} labelledBy={name}
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value?.id === item.id))} value={field.value && field.value.map((item: any) => options.find((o: any) => o.value?.id === item.id))}
onChange={(values: any) => { onChange={(values: any) => {
let am = values && values.map((i: any) => i.value) const am = values && values.map((i: any) => i.value);
setFieldValue(field.name, am);
setFieldValue(field.name, am)
}} }}
className="dark:bg-gray-700 dark" className="dark:bg-gray-700 dark"
/> />
@ -103,8 +98,10 @@ interface DownloadClientSelectProps {
clients: DownloadClient[]; clients: DownloadClient[];
} }
export default function DownloadClientSelect({ export function DownloadClientSelect({
name, action, clients, name,
action,
clients
}: DownloadClientSelectProps) { }: DownloadClientSelectProps) {
return ( return (
<div className="col-span-6 sm:col-span-6"> <div className="col-span-6 sm:col-span-6">
@ -212,7 +209,12 @@ interface SelectFieldProps {
options: SelectFieldOption[]; options: SelectFieldOption[];
} }
function Select({ name, label, optionDefaultText, options }: SelectFieldProps) { export const Select = ({
name,
label,
optionDefaultText,
options
}: SelectFieldProps) => {
return ( return (
<div className="col-span-6"> <div className="col-span-6">
<Field name={name} type="select"> <Field name={name} type="select">
@ -309,7 +311,12 @@ function Select({ name, label, optionDefaultText, options }: SelectFieldProps) {
); );
} }
function SelectWide({ name, label, optionDefaultText, options }: SelectFieldProps) { export const SelectWide = ({
name,
label,
optionDefaultText,
options
}: SelectFieldProps) => {
return ( return (
<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">
@ -409,5 +416,3 @@ function SelectWide({ name, label, optionDefaultText, options }: SelectFieldProp
</div> </div>
); );
} }
export { MultiSelect, Select, SelectWide, DownloadClientSelect, IndexerMultiSelect }

View file

@ -1,6 +1,12 @@
import React, { InputHTMLAttributes } from 'react' import { Field } from "formik";
import { Switch as HeadlessSwitch } from '@headlessui/react' import type {
import { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikValues, Field } from 'formik' FieldInputProps,
FieldMetaProps,
FieldProps,
FormikProps,
FormikValues
} from "formik";
import { Switch as HeadlessSwitch } from "@headlessui/react";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
type SwitchProps<V = any> = { type SwitchProps<V = any> = {
@ -13,14 +19,14 @@ type SwitchProps<V = any> = {
meta?: FieldMetaProps<V> meta?: FieldMetaProps<V>
} }
export const Switch: React.FC<SwitchProps> = ({ export const Switch = ({
label, label,
checked: $checked, checked: $checked,
disabled = false, disabled = false,
onChange: $onChange, onChange: $onChange,
field, field,
form, form,
}) => { }: SwitchProps) => {
const checked = field?.checked ?? $checked const checked = field?.checked ?? $checked
return ( return (
@ -55,19 +61,22 @@ export const Switch: React.FC<SwitchProps> = ({
) )
} }
export type SwitchFormikProps = SwitchProps & FieldProps & InputHTMLAttributes<HTMLInputElement> export type SwitchFormikProps = SwitchProps & FieldProps & React.InputHTMLAttributes<HTMLInputElement>;
export const SwitchFormik: React.FC<SwitchProps> = args => <Switch {...args} /> export const SwitchFormik = (props: SwitchProps) => <Switch {...props} />
interface SwitchGroupProps { interface SwitchGroupProps {
name: string; name: string;
label?: string; label?: string;
description?: string; description?: string;
defaultValue?: boolean;
className?: string; className?: string;
} }
const SwitchGroup: React.FC<SwitchGroupProps> = ({ name, label, description, defaultValue }) => ( const SwitchGroup = ({
name,
label,
description
}: SwitchGroupProps) => (
<ul className="mt-2 divide-y divide-gray-200"> <ul className="mt-2 divide-y divide-gray-200">
<HeadlessSwitch.Group as="li" className="py-4 flex items-center justify-between"> <HeadlessSwitch.Group as="li" className="py-4 flex items-center justify-between">
{label && <div className="flex flex-col"> {label && <div className="flex flex-col">

View file

@ -108,7 +108,7 @@ declare module 'react-table' {
UseResizeColumnsColumnProps<D>, UseResizeColumnsColumnProps<D>,
UseSortByColumnProps<D> {} UseSortByColumnProps<D> {}
export interface Cell<D extends Record<string, unknown> = Record<string, unknown>, V = any> export interface Cell<D extends Record<string, unknown> = Record<string, unknown>>
extends UseGroupByCellProps<D>, extends UseGroupByCellProps<D>,
UseRowStateCellProps<D> {} UseRowStateCellProps<D> {}

View file

@ -1,14 +1,15 @@
import { Fragment, useEffect } from "react"; import { Fragment } from "react";
import { useMutation } from "react-query"; import { useMutation } from "react-query";
import { queryClient } from "../../App"; import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid"; import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import DEBUG from "../../components/debug"; import { Field, Form, Formik } from "formik";
import APIClient from "../../api/APIClient"; import type { FieldProps } from "formik";
import { toast } from 'react-hot-toast' import { queryClient } from "../../App";
import APIClient from "../../api/APIClient";
import DEBUG from "../../components/debug";
import Toast from '../../components/notifications/Toast'; import Toast from '../../components/notifications/Toast';
import { Field, FieldProps, Form, Formik } from "formik";
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), {
@ -20,23 +21,8 @@ function FilterAddForm({ isOpen, toggle }: any) {
} }
}) })
useEffect(() => { const handleSubmit = (data: any) => mutation.mutate(data);
// console.log("render add action form") const validate = (values: any) => values.name ? {} : { name: "Required" };
}, []);
const handleSubmit = (data: any) => {
mutation.mutate(data)
}
const validate = (values: any) => {
const errors = {} as any;
if (!values.name) {
errors.name = "Required";
}
return errors;
}
return ( return (
<Transition.Root show={isOpen} as={Fragment}> <Transition.Root show={isOpen} as={Fragment}>

View file

@ -300,7 +300,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
}); });
}); });
}, },
onError: (error) => { onError: () => {
console.log('not added') console.log('not added')
setIsTesting(false); setIsTesting(false);
setIsErrorTest(true); setIsErrorTest(true);
@ -319,7 +319,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
testClientMutation.mutate(data); testClientMutation.mutate(data);
}; };
let initialValues: InitialValues = { const initialValues: InitialValues = {
name: "", name: "",
type: "QBITTORRENT", type: "QBITTORRENT",
enabled: true, enabled: true,
@ -480,7 +480,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
}); });
}); });
}, },
onError: (error) => { onError: () => {
setIsTesting(false); setIsTesting(false);
setIsErrorTest(true); setIsErrorTest(true);
sleep(2500).then(() => { sleep(2500).then(() => {
@ -505,7 +505,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
testClientMutation.mutate(data); testClientMutation.mutate(data);
}; };
let initialValues = { const initialValues = {
id: client.id, id: client.id,
name: client.name, name: client.name,
type: client.type, type: client.type,

View file

@ -1,18 +1,24 @@
import { Fragment } from "react"; import { Fragment } from "react";
import { toast } from "react-hot-toast";
import { useMutation, useQuery } from "react-query"; import { useMutation, useQuery } from "react-query";
import { sleep } from "../../utils"; import Select, { components } from "react-select";
import { Field, Form, Formik } from "formik";
import type { FieldProps } from "formik";
import { XIcon } from "@heroicons/react/solid"; import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { Field, FieldProps, Form, Formik } from "formik";
import DEBUG from "../../components/debug";
import Select, { components } from "react-select";
import { queryClient } from "../../App";
import APIClient from "../../api/APIClient";
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide } from "../../components/inputs/input_wide";
import { toast } from 'react-hot-toast' import { sleep } from "../../utils";
import Toast from '../../components/notifications/Toast'; import { queryClient } from "../../App";
import DEBUG from "../../components/debug";
import APIClient from "../../api/APIClient";
import {
TextFieldWide,
PasswordFieldWide,
SwitchGroupWide
} from "../../components/inputs/input_wide";
import { SlideOver } from "../../components/panels"; import { SlideOver } from "../../components/panels";
import Toast from '../../components/notifications/Toast';
const Input = (props: any) => { const Input = (props: any) => {
return ( return (
@ -67,28 +73,19 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
} }
}) })
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), { const ircMutation = useMutation(
onSuccess: (data) => { (network: Network) => APIClient.irc.createNetwork(network)
// console.log("irc mutation: ", data); );
// queryClient.invalidateQueries(['networks']);
// sleep(1500)
// toggle()
}
})
const onSubmit = (formData: any) => { const onSubmit = (formData: any) => {
let ind = data && data.find(i => i.identifier === formData.identifier) const ind = data && data.find(i => i.identifier === formData.identifier);
if (!ind)
return;
if (!ind) { const channels: Channel[] = [];
return
}
let channels: Channel[] = []
if (ind.irc.channels.length) { if (ind.irc.channels.length) {
ind.irc.channels.forEach(element => { ind.irc.channels.forEach(element => {
channels.push({ name: element, password: "" }) channels.push({ name: element, password: "" });
}); });
} }
@ -105,17 +102,13 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
} }
mutation.mutate(formData, { mutation.mutate(formData, {
onSuccess: (data) => { onSuccess: () => ircMutation.mutate(network)
// create irc });
ircMutation.mutate(network)
}
})
}; };
const renderSettingFields = (indexer: string) => { const renderSettingFields = (indexer: string) => {
if (indexer !== "") { if (indexer !== "") {
let ind = data && data.find(i => i.identifier === indexer) const ind = data && data.find(i => i.identifier === indexer);
return ( return (
<div key="opt"> <div key="opt">
{ind && ind.settings && ind.settings.map((f: any, idx: number) => { {ind && ind.settings && ind.settings.map((f: any, idx: number) => {
@ -140,10 +133,8 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
} }
const renderIrcSettingFields = (indexer: string) => { const renderIrcSettingFields = (indexer: string) => {
if (indexer !== "") { if (indexer !== "") {
let ind = data && data.find(i => i.identifier === indexer) const ind = data && data.find(i => i.identifier === indexer);
return ( return (
<Fragment> <Fragment>
{ind && ind.irc && ind.irc.settings && ( {ind && ind.irc && ind.irc.settings && (
@ -366,7 +357,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
) )
} }
let initialValues = { const initialValues = {
id: indexer.id, id: indexer.id,
name: indexer.name, name: indexer.name,
enabled: indexer.enabled, enabled: indexer.enabled,
@ -390,7 +381,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
onSubmit={onSubmit} onSubmit={onSubmit}
initialValues={initialValues} initialValues={initialValues}
> >
{({ values }: any) => ( {() => (
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700"> <div className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5"> <div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div> <div>

View file

@ -1,15 +1,20 @@
import { useMutation } from "react-query"; import { useMutation } from "react-query";
import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid"; import { XIcon } from "@heroicons/react/solid";
import { queryClient } from "../../App"; import { Field, FieldArray } from "formik";
import type { FieldProps } from "formik";
import { Field, FieldArray, FieldProps } from "formik"; import { queryClient } from "../../App";
import APIClient from "../../api/APIClient"; import APIClient from "../../api/APIClient";
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide, NumberFieldWide } from "../../components/inputs/input_wide"; import {
TextFieldWide,
import { toast } from 'react-hot-toast'; PasswordFieldWide,
import Toast from '../../components/notifications/Toast'; SwitchGroupWide,
NumberFieldWide
} from "../../components/inputs/input_wide";
import { SlideOver } from "../../components/panels"; import { SlideOver } from "../../components/panels";
import Toast from '../../components/notifications/Toast';
function ChannelsFieldArray({ values }: any) { function ChannelsFieldArray({ values }: any) {
return ( return (
@ -79,7 +84,7 @@ function ChannelsFieldArray({ values }: any) {
export function IrcNetworkAddForm({ isOpen, toggle }: any) { export 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: () => {
queryClient.invalidateQueries(['networks']); queryClient.invalidateQueries(['networks']);
toast.custom((t) => <Toast type="success" body="IRC Network added" t={t} />) toast.custom((t) => <Toast type="success" body="IRC Network added" t={t} />)
toggle() toggle()
@ -92,11 +97,15 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
const onSubmit = (data: any) => { const onSubmit = (data: any) => {
// 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") : []; const cmds = (
data.connect_commands = cmds data.connect_commands && data.connect_commands.length > 0 ?
console.log("formated", data) data.connect_commands.replace(/\r\n/g, "\n").split("\n") :
[]
);
data.connect_commands = cmds;
console.log("formated", data);
mutation.mutate(data) mutation.mutate(data);
}; };
const validate = (values: any) => { const validate = (values: any) => {

View file

@ -1,9 +1,8 @@
import React from "react"; import { useState, useCallback } from "react";
export function useToggle(initialValue = false): [boolean, () => void] {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
export function useToggle(initialValue: boolean = false): [boolean, () => void] {
const [value, setValue] = React.useState<boolean>(initialValue);
const toggle = React.useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle]; return [value, toggle];
} }

View file

@ -1,9 +1,8 @@
import React from 'react'; import { StrictMode } from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import './index.css'; import "./index.css";
import { App } from "./App"; import { App } from "./App";
import { InitializeGlobalContext } from "./utils/Context"; import { InitializeGlobalContext } from "./utils/Context";
declare global { declare global {
@ -16,8 +15,8 @@ window.APP = window.APP || {};
InitializeGlobalContext(); InitializeGlobalContext();
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <StrictMode>
<App /> <App />
</React.StrictMode>, </StrictMode>,
document.getElementById("root") document.getElementById("root")
); );

View file

@ -1,4 +1,4 @@
import { ReportHandler } from 'web-vitals'; import type { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) => { const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {

View file

@ -1,10 +1,17 @@
import formatDistanceToNowStrict from 'date-fns/formatDistanceToNowStrict' import * as React from "react";
import React from 'react' import { useQuery } from "react-query";
import { useTable, useFilters, useGlobalFilter, useSortBy, usePagination } from 'react-table' import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
import APIClient from '../api/APIClient' import {
import { useQuery } from 'react-query' useTable,
import { EmptyListState } from '../components/emptystates' useFilters,
import { ReleaseStatusCell } from './Releases' useGlobalFilter,
useSortBy,
usePagination
} from "react-table";
import APIClient from "../api/APIClient";
import { EmptyListState } from "../components/emptystates";
import { ReleaseStatusCell } from "./Releases";
export function Dashboard() { export function Dashboard() {
return ( return (
@ -366,7 +373,6 @@ export function SelectColumnFilter({
// }; // };
export function StatusPill({ value }: any) { export function StatusPill({ value }: any) {
const statusMap: any = { const statusMap: any = {
"FILTER_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-blue-100 text-blue-800 ">Approved</span>, "FILTER_APPROVED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-blue-100 text-blue-800 ">Approved</span>,
"FILTER_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-red-100 text-red-800">Rejected</span>, "FILTER_REJECTED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-red-100 text-red-800">Rejected</span>,
@ -376,13 +382,10 @@ export function StatusPill({ value }: any) {
"MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>, "MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>,
} }
return ( return statusMap[value];
statusMap[value] }
);
};
export function AgeCell({ value, column, row }: any) {
export function AgeCell({ value }: any) {
const formatDate = formatDistanceToNowStrict( const formatDate = formatDistanceToNowStrict(
new Date(value), new Date(value),
{ addSuffix: true } { addSuffix: true }
@ -393,13 +396,13 @@ export function AgeCell({ value, column, row }: any) {
) )
} }
export function ReleaseCell({ value, column, row }: any) { export function ReleaseCell({ value }: any) {
return ( return (
<div className="text-sm font-medium text-gray-900 dark:text-gray-300">{value}</div> <div className="text-sm font-medium text-gray-900 dark:text-gray-300">{value}</div>
) )
} }
export function IndexerCell({ value, column, row }: any) { export function IndexerCell({ value }: any) {
return ( return (
<div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div> <div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div>
) )
@ -464,48 +467,60 @@ function Table({ columns, data }: any) {
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg"> <div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"> <table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800"> <thead className="bg-gray-50 dark:bg-gray-800">
{headerGroups.map((headerGroup: { getHeaderGroupProps: () => JSX.IntrinsicAttributes & React.ClassAttributes<HTMLTableRowElement> & React.HTMLAttributes<HTMLTableRowElement>; headers: any[] }) => ( {headerGroups.map((headerGroup) => {
<tr {...headerGroup.getHeaderGroupProps()}> const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
{headerGroup.headers.map(column => ( return (
<tr key={rowKey} {...rowRest}>
{headerGroup.headers.map((column) => {
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
return (
// Add the sorting props to control sorting. For this example // Add the sorting props to control sorting. For this example
// we can add them into the header props // we can add them into the header props
<th <th
key={`${rowKey}-${columnKey}`}
scope="col" scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
{...column.getHeaderProps(column.getSortByToggleProps())} {...columnRest}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{column.render('Header')} {column.render('Header')}
{/* Add a sort direction indicator */} {/* Add a sort direction indicator */}
<span> <span>
{column.isSorted {column.isSorted ? (
? column.isSortedDesc column.isSortedDesc ? (
? <SortDownIcon className="w-4 h-4 text-gray-400" /> <SortDownIcon className="w-4 h-4 text-gray-400" />
: <SortUpIcon className="w-4 h-4 text-gray-400" /> ) : (
: ( <SortUpIcon className="w-4 h-4 text-gray-400" />
)
) : (
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" /> <SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
)} )}
</span> </span>
</div> </div>
</th> </th>
))} );
})}
</tr> </tr>
))} );
})}
</thead> </thead>
<tbody <tbody
{...getTableBodyProps()} {...getTableBodyProps()}
className="divide-y divide-gray-200 dark:divide-gray-700" className="divide-y divide-gray-200 dark:divide-gray-700"
> >
{page.map((row: any, i: any) => { // new {page.map((row: any) => {
prepareRow(row) prepareRow(row);
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
return ( return (
<tr {...row.getRowProps()}> <tr key={bodyRowKey} {...bodyRowRest}>
{row.cells.map((cell: any) => { {row.cells.map((cell: any) => {
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
return ( return (
<td <td
{...cell.getCellProps()} key={cellRowKey}
className="px-6 py-4 whitespace-nowrap" className="px-6 py-4 whitespace-nowrap"
role="cell" role="cell"
{...cellRowRest}
> >
{cell.column.Cell.name === "defaultRenderer" {cell.column.Cell.name === "defaultRenderer"
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div> ? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>

View file

@ -20,13 +20,12 @@ export default function Logs() {
const es = APIClient.events.logs() const es = APIClient.events.logs()
es.onmessage = (event) => { es.onmessage = (event) => {
let d: LogEvent = JSON.parse(event.data) const d = JSON.parse(event.data) as LogEvent;
setLogs(prevState => ([...prevState, d]));
setLogs(prevState => ([...prevState, d])) scrollToBottom();
scrollToBottom()
} }
return () => { return () => {
es.close() es.close();
} }
}, [setLogs]); }, [setLogs]);

View file

@ -1,13 +1,23 @@
import { BanIcon, ExclamationCircleIcon } from "@heroicons/react/outline" import * as React from "react";
import ClockIcon from "@heroicons/react/outline/ClockIcon" import { useQuery } from "react-query";
import { ChevronDoubleLeftIcon, ChevronLeftIcon, ChevronRightIcon, ChevronDoubleRightIcon, CheckIcon } from "@heroicons/react/solid" import { formatDistanceToNowStrict } from "date-fns";
import { formatDistanceToNowStrict } from "date-fns" import { useTable, useSortBy, usePagination } from "react-table";
import React from "react" import {
import { useQuery } from "react-query" ClockIcon,
import { useTable, useSortBy, usePagination } from "react-table" BanIcon,
import APIClient from "../api/APIClient" ExclamationCircleIcon
import { EmptyListState } from "../components/emptystates" } from "@heroicons/react/outline";
import { classNames, simplifyDate } from "../utils" import {
ChevronDoubleLeftIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronDoubleRightIcon,
CheckIcon
} from "@heroicons/react/solid";
import APIClient from "../api/APIClient";
import { EmptyListState } from "../components/emptystates";
import { classNames, simplifyDate } from "../utils";
export function Releases() { export function Releases() {
return ( return (
@ -95,13 +105,11 @@ export function StatusPill({ value }: any) {
"MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>, "MIXED": <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase bg-yellow-100 text-yellow-800">MIXED</span>,
} }
return ( return statusMap[value];
statusMap[value] }
);
};
export function AgeCell({ value, column, row }: any) { export function AgeCell({ value }: any) {
const formatDate = formatDistanceToNowStrict( const formatDate = formatDistanceToNowStrict(
new Date(value), new Date(value),
@ -113,7 +121,7 @@ export function AgeCell({ value, column, row }: any) {
) )
} }
export function ReleaseCell({ value, column, row }: any) { export function ReleaseCell({ value }: any) {
return ( return (
<div className="text-sm font-medium text-gray-900 dark:text-gray-300" title={value}>{value}</div> <div className="text-sm font-medium text-gray-900 dark:text-gray-300" title={value}>{value}</div>
) )
@ -125,7 +133,7 @@ interface ReleaseStatusCellProps {
row: any; row: any;
} }
export function ReleaseStatusCell({ value, column, row }: ReleaseStatusCellProps) { export function ReleaseStatusCell({ value }: ReleaseStatusCellProps) {
const statusMap: any = { const statusMap: any = {
"PUSH_ERROR": <span className="mr-1 inline-flex items-center rounded text-xs font-semibold uppercase bg-pink-100 text-pink-800 hover:bg-pink-300 cursor-pointer"> "PUSH_ERROR": <span className="mr-1 inline-flex items-center rounded text-xs font-semibold uppercase bg-pink-100 text-pink-800 hover:bg-pink-300 cursor-pointer">
<ExclamationCircleIcon className="h-5 w-5" aria-hidden="true" /> <ExclamationCircleIcon className="h-5 w-5" aria-hidden="true" />
@ -147,7 +155,7 @@ export function ReleaseStatusCell({ value, column, row }: ReleaseStatusCellProps
) )
} }
export function IndexerCell({ value, column, row }: any) { export function IndexerCell({ value }: any) {
return ( return (
<div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div> <div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div>
) )
@ -317,58 +325,70 @@ function Table() {
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg"> <div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"> <table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800"> <thead className="bg-gray-50 dark:bg-gray-800">
{headerGroups.map((headerGroup: { getHeaderGroupProps: () => JSX.IntrinsicAttributes & React.ClassAttributes<HTMLTableRowElement> & React.HTMLAttributes<HTMLTableRowElement>; headers: any[] }) => ( {headerGroups.map((headerGroup) => {
<tr {...headerGroup.getHeaderGroupProps()}> const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
{headerGroup.headers.map(column => ( return (
<tr key={rowKey} {...rowRest}>
{headerGroup.headers.map((column) => {
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
return (
// Add the sorting props to control sorting. For this example // Add the sorting props to control sorting. For this example
// we can add them into the header props // we can add them into the header props
<th <th
key={`${rowKey}-${columnKey}`}
scope="col" scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
{...column.getHeaderProps(column.getSortByToggleProps())} {...columnRest}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{column.render('Header')} {column.render('Header')}
{/* Add a sort direction indicator */} {/* Add a sort direction indicator */}
<span> <span>
{column.isSorted {column.isSorted ? (
? column.isSortedDesc column.isSortedDesc ? (
? <SortDownIcon className="w-4 h-4 text-gray-400" /> <SortDownIcon className="w-4 h-4 text-gray-400" />
: <SortUpIcon className="w-4 h-4 text-gray-400" /> ) : (
: ( <SortUpIcon className="w-4 h-4 text-gray-400" />
)
) : (
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" /> <SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
)} )}
</span> </span>
</div> </div>
</th> </th>
))} );
})}
</tr> </tr>
))} );
})}
</thead> </thead>
<tbody <tbody
{...getTableBodyProps()} {...getTableBodyProps()}
className="divide-y divide-gray-200 dark:divide-gray-700" className="divide-y divide-gray-200 dark:divide-gray-700"
> >
{page.map((row: any, i: any) => { // new {page.map((row: any) => { // new
prepareRow(row) prepareRow(row)
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
return ( return (
<tr {...row.getRowProps()}> <tr key={bodyRowKey} {...bodyRowRest}>
{row.cells.map((cell: any) => { {row.cells.map((cell: any) => {
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
return ( return (
<td <td
{...cell.getCellProps()} key={cellRowKey}
className="px-6 py-4 whitespace-nowrap" className="px-6 py-4 whitespace-nowrap"
role="cell" role="cell"
{...cellRowRest}
> >
{cell.column.Cell.name === "defaultRenderer" {cell.column.Cell.name === "defaultRenderer"
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div> ? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
: cell.render('Cell') : cell.render('Cell')
} }
</td> </td>
) );
})} })}
</tr> </tr>
) );
})} })}
</tbody> </tbody>
</table> </table>

View file

@ -21,14 +21,12 @@ const subNavigation = [
function SubNavLink({item, url}: any) { function SubNavLink({item, url}: any) {
const location = useLocation(); const location = useLocation();
const { pathname } = location; const { pathname } = location;
const splitLocation = pathname.split("/"); const splitLocation = pathname.split("/");
// we need to clean the / if it's a base root path // we need to clean the / if it's a base root path
let too = item.href ? `${url}/${item.href}` : url const too = item.href ? `${url}/${item.href}` : url
return ( return (
<NavLink <NavLink
key={item.name} key={item.name}
@ -62,8 +60,7 @@ function SidebarNav({subNavigation, url}: any) {
} }
export default function Settings() { export default function Settings() {
let {url} = useRouteMatch(); const { url } = useRouteMatch();
return ( return (
<main className="relative -mt-48"> <main className="relative -mt-48">
<header className="py-10"> <header className="py-10">

View file

@ -13,7 +13,7 @@ function Logout() {
useEffect( useEffect(
() => { () => {
APIClient.auth.logout().then(r => { APIClient.auth.logout().then(() => {
setAuthContext({ username: "", isLoggedIn: false }); setAuthContext({ username: "", isLoggedIn: false });
removeCookie("user_session"); removeCookie("user_session");
history.push('/login'); history.push('/login');

View file

@ -1,8 +1,5 @@
import { Fragment, useRef } from "react"; import { Fragment, useRef } from "react";
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react"; import { useMutation, useQuery } from "react-query";
import { ChevronDownIcon, ChevronRightIcon, } from '@heroicons/react/solid'
import { EmptyListState } from "../../components/emptystates";
import { import {
NavLink, NavLink,
Route, Route,
@ -12,23 +9,46 @@ import {
useParams, useParams,
useRouteMatch useRouteMatch
} from "react-router-dom"; } from "react-router-dom";
import { useToggle } from "../../hooks/hooks"; import { toast } from "react-hot-toast";
import { useMutation, useQuery } from "react-query";
import { queryClient } from "../../App";
import { CONTAINER_OPTIONS, CODECS_OPTIONS, RESOLUTION_OPTIONS, SOURCES_OPTIONS, ActionTypeNameMap, ActionTypeOptions, HDR_OPTIONS, FORMATS_OPTIONS, SOURCES_MUSIC_OPTIONS, QUALITY_MUSIC_OPTIONS, RELEASE_TYPE_MUSIC_OPTIONS } from "../../domain/constants";
import DEBUG from "../../components/debug";
import { TitleSubtitle } from "../../components/headings";
import { buildPath, classNames } from "../../utils";
import APIClient from "../../api/APIClient";
import { toast } from 'react-hot-toast'
import Toast from '../../components/notifications/Toast';
import { Field, FieldArray, Form, Formik } from "formik"; import { Field, FieldArray, Form, Formik } from "formik";
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
import { ChevronDownIcon, ChevronRightIcon, } from "@heroicons/react/solid";
import {
CONTAINER_OPTIONS,
CODECS_OPTIONS,
RESOLUTION_OPTIONS,
SOURCES_OPTIONS,
ActionTypeNameMap,
ActionTypeOptions,
HDR_OPTIONS,
FORMATS_OPTIONS,
SOURCES_MUSIC_OPTIONS,
QUALITY_MUSIC_OPTIONS,
RELEASE_TYPE_MUSIC_OPTIONS
} from "../../domain/constants";
import { queryClient } from "../../App";
import APIClient from "../../api/APIClient";
import { useToggle } from "../../hooks/hooks";
import { buildPath, classNames } from "../../utils";
import {
NumberField,
TextField,
SwitchGroup,
Select,
MultiSelect,
DownloadClientSelect,
IndexerMultiSelect,
CheckboxField
} from "../../components/inputs";
import DEBUG from "../../components/debug";
import Toast from "../../components/notifications/Toast";
import { AlertWarning } from "../../components/alerts"; import { AlertWarning } from "../../components/alerts";
import { DeleteModal } from "../../components/modals"; import { DeleteModal } from "../../components/modals";
import { NumberField, TextField, SwitchGroup, Select, MultiSelect, DownloadClientSelect, IndexerMultiSelect, CheckboxField } from "../../components/inputs"; import { TitleSubtitle } from "../../components/headings";
import { EmptyListState } from "../../components/emptystates";
const tabs = [ const tabs = [
{ name: 'General', href: '', current: true }, { name: 'General', href: '', current: true },
@ -46,8 +66,7 @@ function TabNavLink({ item, url }: any) {
const splitLocation = pathname.split("/"); const splitLocation = pathname.split("/");
// we need to clean the / if it's a base root path // we need to clean the / if it's a base root path
let too = item.href ? `${url}/${item.href}` : url const too = item.href ? `${url}/${item.href}` : url
return ( return (
<NavLink <NavLink
key={item.name} key={item.name}
@ -64,7 +83,7 @@ function TabNavLink({ item, url }: any) {
) )
} }
const FormButtonsGroup = ({ values, deleteAction, reset, dirty }: any) => { const FormButtonsGroup = ({ values, deleteAction, reset }: any) => {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const cancelModalButtonRef = useRef(null); const cancelModalButtonRef = useRef(null);
@ -111,19 +130,19 @@ const FormButtonsGroup = ({ values, deleteAction, reset, dirty }: any) => {
} }
export default function FilterDetails() { export default function FilterDetails() {
let { url } = useRouteMatch(); const history = useHistory();
let history = useHistory(); const { url } = useRouteMatch();
let { filterId }: any = useParams(); const { filterId } = useParams<{ filterId: string }>();
const { isLoading, data: filter } = useQuery<Filter, Error>(['filter', parseInt(filterId)], () => APIClient.filters.getByID(parseInt(filterId)), const { isLoading, data: filter } = useQuery<Filter, Error>(
['filter', +filterId],
() => APIClient.filters.getByID(parseInt(filterId)),
{ {
retry: false, retry: false,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
onError: err => { onError: () => history.push("./")
history.push("./")
} }
}, );
)
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), { const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
onSuccess: (filter) => { onSuccess: (filter) => {
@ -160,11 +179,9 @@ export default function FilterDetails() {
} }
const handleMobileNav = (e: any, href: string) => { const handleMobileNav = (e: any, href: string) => {
let s = history.location.pathname.split(/((?:\/.*?)?\/filters\/\d)/gi) const s = history.location.pathname.split(/((?:\/.*?)?\/filters\/\d)/gi);
const p = buildPath(s[1], href);
let p = buildPath(s[1], href) history.push(p);
history.push(p)
} }
return ( return (
@ -304,7 +321,7 @@ function General() {
} }
) )
let opts = indexers && indexers.length > 0 ? indexers.map(v => ({ const opts = indexers && indexers.length > 0 ? indexers.map(v => ({
label: v.name, label: v.name,
value: { value: {
id: v.id, id: v.id,
@ -581,7 +598,7 @@ function FilterActions({ filter, values }: FilterActionsProps) {
} }
) )
let newAction = { const newAction = {
name: "new action", name: "new action",
enabled: true, enabled: true,
type: "TEST", type: "TEST",

View file

@ -1,18 +1,16 @@
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom";
import { toast } from "react-hot-toast";
import { Switch } from "@headlessui/react"; import { Switch } from "@headlessui/react";
import { EmptyListState } from "../../components/emptystates";
import {
Link,
} from "react-router-dom";
import { useToggle } from "../../hooks/hooks";
import { useMutation, useQuery } from "react-query"; import { useMutation, useQuery } from "react-query";
import { queryClient } from "../../App";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { FilterAddForm } from "../../forms"; import { FilterAddForm } from "../../forms";
import { useToggle } from "../../hooks/hooks";
import APIClient from "../../api/APIClient"; import APIClient from "../../api/APIClient";
import Toast from "../../components/notifications/Toast"; import Toast from "../../components/notifications/Toast";
import toast from "react-hot-toast"; import { EmptyListState } from "../../components/emptystates";
import { queryClient } from "../../App";
export default function Filters() { export default function Filters() {
const [createFilterIsOpen, toggleCreateFilter] = useToggle(false) const [createFilterIsOpen, toggleCreateFilter] = useToggle(false)
@ -27,7 +25,8 @@ export default function Filters() {
return null return null
} }
if (error) return (<p>'An error has occurred: '</p>) if (error)
return (<p>An error has occurred: </p>);
return ( return (
<main className="-mt-48 "> <main className="-mt-48 ">

View file

@ -1,9 +1,4 @@
import React from "react";
function ActionSettings() { function ActionSettings() {
// const [addClientIsOpen, toggleAddClient] = useToggle(false)
return ( return (
<div className="divide-y divide-gray-200 lg:col-span-9"> <div className="divide-y divide-gray-200 lg:col-span-9">

View file

@ -58,7 +58,8 @@ function DownloadClientSettings() {
refetchOnWindowFocus: false refetchOnWindowFocus: false
}) })
if (error) return (<p>'An error has occurred: '</p>); if (error)
return (<p>An error has occurred: </p>);
return ( return (
<div className="divide-y divide-gray-200 lg:col-span-9"> <div className="divide-y divide-gray-200 lg:col-span-9">

View file

@ -1,4 +1,3 @@
import { useEffect } from "react";
import { useToggle } from "../../hooks/hooks"; import { useToggle } from "../../hooks/hooks";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { IndexerAddForm, IndexerUpdateForm } from "../../forms"; import { IndexerAddForm, IndexerUpdateForm } from "../../forms";
@ -52,10 +51,8 @@ function IndexerSettings() {
} }
) )
useEffect(() => { if (error)
}, []); return (<p>An error has occurred</p>);
if (error) return (<p>An error has occurred</p>)
return ( return (
<div className="divide-y divide-gray-200 lg:col-span-9"> <div className="divide-y divide-gray-200 lg:col-span-9">

View file

@ -1,4 +1,4 @@
import React, { useRef, useState } from "react"; import { useRef, useState } from "react";
export const RegexPlayground = () => { export const RegexPlayground = () => {
const regexRef = useRef<HTMLInputElement>(null); const regexRef = useRef<HTMLInputElement>(null);

View file

@ -25,14 +25,10 @@ export function baseUrl() {
// get sseBaseUrl for SSE // get sseBaseUrl for SSE
export function sseBaseUrl() { export function sseBaseUrl() {
let {origin} = window.location if (process.env.NODE_ENV === "development")
return `http://localhost:8989/`;
let env = process.env.NODE_ENV return `${window.location.origin}${baseUrl()}`;
if (env === "development") {
return `http://localhost:8989/`
}
return `${origin}${baseUrl()}`
} }
export function buildPath(...args: string[]): string { export function buildPath(...args: string[]): string {