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

View file

@ -1,10 +1,17 @@
import formatDistanceToNowStrict from 'date-fns/formatDistanceToNowStrict'
import React from 'react'
import { useTable, useFilters, useGlobalFilter, useSortBy, usePagination } from 'react-table'
import APIClient from '../api/APIClient'
import { useQuery } from 'react-query'
import { EmptyListState } from '../components/emptystates'
import { ReleaseStatusCell } from './Releases'
import * as React from "react";
import { useQuery } from "react-query";
import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
import {
useTable,
useFilters,
useGlobalFilter,
useSortBy,
usePagination
} from "react-table";
import APIClient from "../api/APIClient";
import { EmptyListState } from "../components/emptystates";
import { ReleaseStatusCell } from "./Releases";
export function Dashboard() {
return (
@ -366,7 +373,6 @@ export function SelectColumnFilter({
// };
export function StatusPill({ value }: 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_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>,
}
return (
statusMap[value]
);
};
export function AgeCell({ value, column, row }: any) {
return statusMap[value];
}
export function AgeCell({ value }: any) {
const formatDate = formatDistanceToNowStrict(
new Date(value),
{ 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 (
<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 (
<div className="text-sm font-medium text-gray-900 dark:text-gray-500" title={value}>{value}</div>
)
@ -464,55 +467,67 @@ function Table({ columns, data }: any) {
<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">
<thead className="bg-gray-50 dark:bg-gray-800">
{headerGroups.map((headerGroup: { getHeaderGroupProps: () => JSX.IntrinsicAttributes & React.ClassAttributes<HTMLTableRowElement> & React.HTMLAttributes<HTMLTableRowElement>; headers: any[] }) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
{...column.getHeaderProps(column.getSortByToggleProps())}
>
<div className="flex items-center justify-between">
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.isSorted
? column.isSortedDesc
? <SortDownIcon 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" />
)}
</span>
</div>
</th>
))}
</tr>
))}
{headerGroups.map((headerGroup) => {
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
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
// we can add them into the header props
<th
key={`${rowKey}-${columnKey}`}
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
{...columnRest}
>
<div className="flex items-center justify-between">
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.isSorted ? (
column.isSortedDesc ? (
<SortDownIcon 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" />
)}
</span>
</div>
</th>
);
})}
</tr>
);
})}
</thead>
<tbody
{...getTableBodyProps()}
className="divide-y divide-gray-200 dark:divide-gray-700"
>
{page.map((row: any, i: any) => { // new
prepareRow(row)
{page.map((row: any) => {
prepareRow(row);
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
return (
<tr {...row.getRowProps()}>
<tr key={bodyRowKey} {...bodyRowRest}>
{row.cells.map((cell: any) => {
return (
<td
{...cell.getCellProps()}
className="px-6 py-4 whitespace-nowrap"
role="cell"
>
{cell.column.Cell.name === "defaultRenderer"
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
: cell.render('Cell')
}
</td>
)
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
return (
<td
key={cellRowKey}
className="px-6 py-4 whitespace-nowrap"
role="cell"
{...cellRowRest}
>
{cell.column.Cell.name === "defaultRenderer"
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
: cell.render('Cell')
}
</td>
)
})}
</tr>
)

View file

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

View file

@ -1,13 +1,23 @@
import { BanIcon, ExclamationCircleIcon } from "@heroicons/react/outline"
import ClockIcon from "@heroicons/react/outline/ClockIcon"
import { ChevronDoubleLeftIcon, ChevronLeftIcon, ChevronRightIcon, ChevronDoubleRightIcon, CheckIcon } from "@heroicons/react/solid"
import { formatDistanceToNowStrict } from "date-fns"
import React from "react"
import { useQuery } from "react-query"
import { useTable, useSortBy, usePagination } from "react-table"
import APIClient from "../api/APIClient"
import { EmptyListState } from "../components/emptystates"
import { classNames, simplifyDate } from "../utils"
import * as React from "react";
import { useQuery } from "react-query";
import { formatDistanceToNowStrict } from "date-fns";
import { useTable, useSortBy, usePagination } from "react-table";
import {
ClockIcon,
BanIcon,
ExclamationCircleIcon
} from "@heroicons/react/outline";
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() {
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>,
}
return (
statusMap[value]
);
};
return statusMap[value];
}
export function AgeCell({ value, column, row }: any) {
export function AgeCell({ value }: any) {
const formatDate = formatDistanceToNowStrict(
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 (
<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;
}
export function ReleaseStatusCell({ value, column, row }: ReleaseStatusCellProps) {
export function ReleaseStatusCell({ value }: ReleaseStatusCellProps) {
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">
<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 (
<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">
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
{headerGroups.map((headerGroup: { getHeaderGroupProps: () => JSX.IntrinsicAttributes & React.ClassAttributes<HTMLTableRowElement> & React.HTMLAttributes<HTMLTableRowElement>; headers: any[] }) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
{...column.getHeaderProps(column.getSortByToggleProps())}
>
<div className="flex items-center justify-between">
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.isSorted
? column.isSortedDesc
? <SortDownIcon 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" />
)}
</span>
</div>
</th>
))}
</tr>
))}
{headerGroups.map((headerGroup) => {
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
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
// we can add them into the header props
<th
key={`${rowKey}-${columnKey}`}
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
{...columnRest}
>
<div className="flex items-center justify-between">
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.isSorted ? (
column.isSortedDesc ? (
<SortDownIcon 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" />
)}
</span>
</div>
</th>
);
})}
</tr>
);
})}
</thead>
<tbody
{...getTableBodyProps()}
className="divide-y divide-gray-200 dark:divide-gray-700"
>
{page.map((row: any, i: any) => { // new
{page.map((row: any) => { // new
prepareRow(row)
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
return (
<tr {...row.getRowProps()}>
<tr key={bodyRowKey} {...bodyRowRest}>
{row.cells.map((cell: any) => {
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
return (
<td
{...cell.getCellProps()}
key={cellRowKey}
className="px-6 py-4 whitespace-nowrap"
role="cell"
{...cellRowRest}
>
{cell.column.Cell.name === "defaultRenderer"
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
: cell.render('Cell')
}
</td>
)
);
})}
</tr>
)
);
})}
</tbody>
</table>

View file

@ -21,14 +21,12 @@ const subNavigation = [
function SubNavLink({item, url}: any) {
const location = useLocation();
const {pathname} = location;
const { pathname } = location;
const splitLocation = pathname.split("/");
// 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 (
<NavLink
key={item.name}
@ -62,8 +60,7 @@ function SidebarNav({subNavigation, url}: any) {
}
export default function Settings() {
let {url} = useRouteMatch();
const { url } = useRouteMatch();
return (
<main className="relative -mt-48">
<header className="py-10">

View file

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

View file

@ -1,8 +1,5 @@
import { Fragment, useRef } from "react";
import { Dialog, Transition, Switch as SwitchBasic } from "@headlessui/react";
import { ChevronDownIcon, ChevronRightIcon, } from '@heroicons/react/solid'
import { EmptyListState } from "../../components/emptystates";
import { useMutation, useQuery } from "react-query";
import {
NavLink,
Route,
@ -12,23 +9,46 @@ import {
useParams,
useRouteMatch
} from "react-router-dom";
import { useToggle } from "../../hooks/hooks";
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 { toast } from "react-hot-toast";
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 { 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 = [
{ name: 'General', href: '', current: true },
@ -46,8 +66,7 @@ function TabNavLink({ item, url }: any) {
const splitLocation = pathname.split("/");
// 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 (
<NavLink
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 cancelModalButtonRef = useRef(null);
@ -111,19 +130,19 @@ const FormButtonsGroup = ({ values, deleteAction, reset, dirty }: any) => {
}
export default function FilterDetails() {
let { url } = useRouteMatch();
let history = useHistory();
let { filterId }: any = useParams();
const history = useHistory();
const { url } = useRouteMatch();
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,
refetchOnWindowFocus: false,
onError: err => {
history.push("./")
}
},
)
onError: () => history.push("./")
}
);
const updateMutation = useMutation((filter: Filter) => APIClient.filters.update(filter), {
onSuccess: (filter) => {
@ -160,11 +179,9 @@ export default function FilterDetails() {
}
const handleMobileNav = (e: any, href: string) => {
let s = history.location.pathname.split(/((?:\/.*?)?\/filters\/\d)/gi)
let p = buildPath(s[1], href)
history.push(p)
const s = history.location.pathname.split(/((?:\/.*?)?\/filters\/\d)/gi);
const p = buildPath(s[1], href);
history.push(p);
}
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,
value: {
id: v.id,
@ -581,7 +598,7 @@ function FilterActions({ filter, values }: FilterActionsProps) {
}
)
let newAction = {
const newAction = {
name: "new action",
enabled: true,
type: "TEST",

View file

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

View file

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

View file

@ -58,7 +58,8 @@ function DownloadClientSettings() {
refetchOnWindowFocus: false
})
if (error) return (<p>'An error has occurred: '</p>);
if (error)
return (<p>An error has occurred: </p>);
return (
<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 { useQuery } from "react-query";
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 (
<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 = () => {
const regexRef = useRef<HTMLInputElement>(null);