From 6e5385a490c1a465761a4388c29ec8fb1687e93b Mon Sep 17 00:00:00 2001 From: ze0s <43699394+zze0s@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:26:27 +0200 Subject: [PATCH] refactor(web): replace pkg react-query with tanstack/react-query (#868) * refactor: move to tanstack/react-query and fix cache * refactor(releases): move to tanstack/react-query * refactor(logs): move to tanstack/react-query * refactor(base): move to tanstack/react-query * refactor(base): move to tanstack/react-query * refactor(dashboard): move to tanstack/react-query * refactor(auth): move to tanstack/react-query * refactor(filters): move to tanstack/react-query * refactor(settings): move to tanstack/react-query * chore(pkg): add tanstack/react-query * refactor(filters): move to tanstack/react-query * refactor: move to tanstack/react-query * refactor: invalidate queries * chore(pkg): remove old react-query * chore: change imports to root prefixes * build: remove needs web from test * set enableReinitialize to true to fix formik caching issues * fix all property for apiKeys const * fix toast when enabling/disabling feed --------- Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com> --- .github/workflows/release.yml | 9 +- web/package.json | 3 +- web/src/App.tsx | 4 +- web/src/api/APIClient.ts | 6 +- web/src/components/alerts/NotFound.tsx | 2 +- web/src/components/data-table/Buttons.tsx | 2 +- web/src/components/data-table/Cells.tsx | 2 +- web/src/components/debug.tsx | 2 +- web/src/components/fields/text.tsx | 2 +- web/src/components/inputs/common.tsx | 4 +- web/src/components/inputs/input.tsx | 6 +- web/src/components/inputs/input_wide.tsx | 6 +- web/src/components/inputs/radio.tsx | 2 +- web/src/components/inputs/select.tsx | 10 +- web/src/components/inputs/select_wide.tsx | 2 +- web/src/components/inputs/switch.tsx | 5 +- web/src/components/inputs/text.tsx | 4 +- web/src/components/notifications/Toast.tsx | 4 +- web/src/components/panels/index.tsx | 5 +- web/src/components/tooltips/Tooltip.tsx | 2 +- web/src/domain/constants.ts | 2 +- web/src/domain/routes.tsx | 24 +- web/src/forms/filters/FilterAddForm.tsx | 33 +-- web/src/forms/settings/APIKeyAddForm.tsx | 32 ++- .../forms/settings/DownloadClientForms.tsx | 229 ++++++++---------- web/src/forms/settings/FeedForms.tsx | 118 +++++---- web/src/forms/settings/IndexerForms.tsx | 163 +++++++------ web/src/forms/settings/IrcForms.tsx | 139 +++++------ web/src/forms/settings/NotificationForms.tsx | 117 ++++----- web/src/screens/Base.tsx | 61 +++-- web/src/screens/Logs.tsx | 35 ++- web/src/screens/Settings.tsx | 2 +- web/src/screens/auth/login.tsx | 45 ++-- web/src/screens/auth/onboarding.tsx | 16 +- web/src/screens/dashboard/ActivityTable.tsx | 33 +-- web/src/screens/dashboard/Stats.tsx | 17 +- web/src/screens/filters/action.tsx | 22 +- web/src/screens/filters/details.tsx | 96 ++++---- web/src/screens/filters/list.tsx | 136 +++++------ web/src/screens/releases/Filters.tsx | 22 +- web/src/screens/releases/ReleaseTable.tsx | 90 ++++--- web/src/screens/settings/Api.tsx | 61 +++-- web/src/screens/settings/Application.tsx | 76 +++--- web/src/screens/settings/DownloadClient.tsx | 62 ++--- web/src/screens/settings/Feed.tsx | 74 +++--- web/src/screens/settings/Indexer.tsx | 38 +-- web/src/screens/settings/Irc.tsx | 93 +++---- web/src/screens/settings/Logs.tsx | 41 ++-- web/src/screens/settings/Notifications.tsx | 30 ++- web/src/screens/settings/Releases.tsx | 21 +- web/src/utils/Context.ts | 1 - web/tsconfig.json | 3 +- web/vite.config.ts | 15 +- web/yarn.lock | 189 +++++++-------- 54 files changed, 1101 insertions(+), 1117 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f55f18d..4a72aa9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,19 +44,12 @@ jobs: test: name: Test runs-on: ubuntu-latest - needs: web steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Download web production build - uses: actions/download-artifact@v3 - with: - name: web-dist - path: web/dist - # 1.20 is the last version to support Windows < 10, Server < 2016, and MacOS < 1.15. - name: Set up Go uses: actions/setup-go@v4 @@ -107,7 +100,7 @@ jobs: path: dist/* goreleaser: - name: Build & publish binaries and images + name: Build and publish binaries if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest needs: [web, test] diff --git a/web/package.json b/web/package.json index b4ab85f..544ca16 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,8 @@ "@headlessui/react": "^1.6.4", "@heroicons/react": "^2.0.11", "@hookform/error-message": "^2.0.0", + "@tanstack/react-query": "^4.29.3", + "@tanstack/react-query-devtools": "^4.29.3", "date-fns": "^2.28.0", "formik": "^2.2.9", "react": "^18.2.0", @@ -22,7 +24,6 @@ "react-multi-select-component": "^4.2.9", "react-popper-tooltip": "^4.4.2", "react-portal": "^4.2.2", - "react-query": "^3.39.1", "react-ridge-state": "4.2.2", "react-router-dom": "^6.3.0", "react-select": "^5.3.2", diff --git a/web/src/App.tsx b/web/src/App.tsx index f132cb8..736b1a2 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,5 +1,5 @@ -import { QueryClient, QueryClientProvider, useQueryErrorResetBoundary } from "react-query"; -import { ReactQueryDevtools } from "react-query/devtools"; +import { QueryClient, QueryClientProvider, useQueryErrorResetBoundary } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ErrorBoundary } from "react-error-boundary"; import { toast, Toaster } from "react-hot-toast"; diff --git a/web/src/api/APIClient.ts b/web/src/api/APIClient.ts index 04998bf..0341b60 100644 --- a/web/src/api/APIClient.ts +++ b/web/src/api/APIClient.ts @@ -34,6 +34,8 @@ export async function HttpClient( // Show an error toast to notify the user what occurred return Promise.reject(new Error("Unauthorized")); + } else if (response.status === 404) { + return Promise.reject(new Error("Not found")); } return Promise.reject(new Error(await response.text())); @@ -50,7 +52,7 @@ export async function HttpClient( const appClient = { Get: (endpoint: string) => HttpClient(endpoint, "GET"), Post: (endpoint: string, data: PostBody = undefined) => HttpClient(endpoint, "POST", { body: data }), - Put: (endpoint: string, data: PostBody) => HttpClient(endpoint, "PUT", { body: data }), + Put: (endpoint: string, data: PostBody) => HttpClient(endpoint, "PUT", { body: data }), Patch: (endpoint: string, data: PostBody = undefined) => HttpClient(endpoint, "PATCH", { body: data }), Delete: (endpoint: string) => HttpClient(endpoint, "DELETE") }; @@ -113,7 +115,7 @@ export const APIClient = { }, getByID: (id: number) => appClient.Get(`api/filters/${id}`), create: (filter: Filter) => appClient.Post("api/filters", filter), - update: (filter: Filter) => appClient.Put(`api/filters/${filter.id}`, filter), + update: (filter: Filter) => appClient.Put(`api/filters/${filter.id}`, filter), duplicate: (id: number) => appClient.Get(`api/filters/${id}/duplicate`), toggleEnable: (id: number, enabled: boolean) => appClient.Put(`api/filters/${id}/enabled`, { enabled }), delete: (id: number) => appClient.Delete(`api/filters/${id}`) diff --git a/web/src/components/alerts/NotFound.tsx b/web/src/components/alerts/NotFound.tsx index d7b8629..3bb0230 100644 --- a/web/src/components/alerts/NotFound.tsx +++ b/web/src/components/alerts/NotFound.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import logo from "@/logo.png"; +import logo from "@app/logo.png"; export const NotFound = () => { return ( diff --git a/web/src/components/data-table/Buttons.tsx b/web/src/components/data-table/Buttons.tsx index bec0601..5789e62 100644 --- a/web/src/components/data-table/Buttons.tsx +++ b/web/src/components/data-table/Buttons.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { classNames } from "../../utils"; +import { classNames } from "@utils"; interface ButtonProps { className?: string; diff --git a/web/src/components/data-table/Cells.tsx b/web/src/components/data-table/Cells.tsx index 8321b8d..df22cb1 100644 --- a/web/src/components/data-table/Cells.tsx +++ b/web/src/components/data-table/Cells.tsx @@ -3,7 +3,7 @@ import { formatDistanceToNowStrict } from "date-fns"; import { CheckIcon } from "@heroicons/react/24/solid"; import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline"; -import { classNames, simplifyDate } from "../../utils"; +import { classNames, simplifyDate } from "@utils"; import { Tooltip } from "../tooltips/Tooltip"; interface CellProps { diff --git a/web/src/components/debug.tsx b/web/src/components/debug.tsx index 16bcdc3..43cf041 100644 --- a/web/src/components/debug.tsx +++ b/web/src/components/debug.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { SettingsContext } from "../utils/Context"; +import { SettingsContext } from "@utils/Context"; interface DebugProps { values: unknown; diff --git a/web/src/components/fields/text.tsx b/web/src/components/fields/text.tsx index 2595f49..36c2b0f 100644 --- a/web/src/components/fields/text.tsx +++ b/web/src/components/fields/text.tsx @@ -1,4 +1,4 @@ -import { useToggle } from "../../hooks/hooks"; +import { useToggle } from "@hooks/hooks"; import { CheckIcon, DocumentDuplicateIcon, EyeIcon, EyeSlashIcon } from "@heroicons/react/24/outline"; import { useState } from "react"; diff --git a/web/src/components/inputs/common.tsx b/web/src/components/inputs/common.tsx index 75b92b0..2459422 100644 --- a/web/src/components/inputs/common.tsx +++ b/web/src/components/inputs/common.tsx @@ -1,6 +1,6 @@ import { Field, FieldProps } from "formik"; -import { classNames } from "../../utils"; -import { CustomTooltip } from "../tooltips/CustomTooltip"; +import { classNames } from "@utils"; +import { CustomTooltip } from "@components/tooltips/CustomTooltip"; interface ErrorFieldProps { name: string; diff --git a/web/src/components/inputs/input.tsx b/web/src/components/inputs/input.tsx index 552b06a..c1813cf 100644 --- a/web/src/components/inputs/input.tsx +++ b/web/src/components/inputs/input.tsx @@ -1,8 +1,8 @@ import { Field, FieldProps, useFormikContext } from "formik"; -import { classNames } from "../../utils"; +import { classNames } from "@utils"; import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; -import { useToggle } from "../../hooks/hooks"; -import { CustomTooltip } from "../tooltips/CustomTooltip"; +import { useToggle } from "@hooks/hooks"; +import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { useEffect } from "react"; type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; diff --git a/web/src/components/inputs/input_wide.tsx b/web/src/components/inputs/input_wide.tsx index ba8564a..6b75a74 100644 --- a/web/src/components/inputs/input_wide.tsx +++ b/web/src/components/inputs/input_wide.tsx @@ -1,13 +1,13 @@ import type { FieldProps, FieldValidator } from "formik"; import { Field } from "formik"; -import { classNames } from "../../utils"; -import { useToggle } from "../../hooks/hooks"; +import { classNames } from "@utils"; +import { useToggle } from "@hooks/hooks"; import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; import { Switch } from "@headlessui/react"; import { ErrorField, RequiredField } from "./common"; import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select"; import { SelectFieldProps } from "./select"; -import { CustomTooltip } from "../tooltips/CustomTooltip"; +import { CustomTooltip } from "@components/tooltips/CustomTooltip"; interface TextFieldWideProps { name: string; diff --git a/web/src/components/inputs/radio.tsx b/web/src/components/inputs/radio.tsx index e190a0a..cabf204 100644 --- a/web/src/components/inputs/radio.tsx +++ b/web/src/components/inputs/radio.tsx @@ -1,6 +1,6 @@ import { Field, useFormikContext } from "formik"; import { RadioGroup } from "@headlessui/react"; -import { classNames } from "../../utils"; +import { classNames } from "@utils"; export interface radioFieldsetOption { label: string; diff --git a/web/src/components/inputs/select.tsx b/web/src/components/inputs/select.tsx index 6fe8dfa..c571ef0 100644 --- a/web/src/components/inputs/select.tsx +++ b/web/src/components/inputs/select.tsx @@ -4,9 +4,9 @@ import { Listbox, Transition } from "@headlessui/react"; import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/solid"; import { MultiSelect as RMSC } from "react-multi-select-component"; -import { classNames, COL_WIDTHS } from "../../utils"; -import { SettingsContext } from "../../utils/Context"; -import { CustomTooltip } from "../tooltips/CustomTooltip"; +import { classNames, COL_WIDTHS } from "@utils"; +import { SettingsContext } from "@utils/Context"; +import { CustomTooltip } from "@components/tooltips/CustomTooltip"; export interface MultiSelectOption { value: string | number; @@ -265,8 +265,8 @@ export const Select = ({ }: SelectFieldProps) => { return (
diff --git a/web/src/components/inputs/select_wide.tsx b/web/src/components/inputs/select_wide.tsx index 256b190..e9093a1 100644 --- a/web/src/components/inputs/select_wide.tsx +++ b/web/src/components/inputs/select_wide.tsx @@ -1,7 +1,7 @@ import type { FieldProps } from "formik"; import { Field } from "formik"; import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select"; -import { OptionBasicTyped } from "../../domain/constants"; +import { OptionBasicTyped } from "@domain/constants"; import CreatableSelect from "react-select/creatable"; import { CustomTooltip } from "../tooltips/CustomTooltip"; diff --git a/web/src/components/inputs/switch.tsx b/web/src/components/inputs/switch.tsx index b0e6690..00eb628 100644 --- a/web/src/components/inputs/switch.tsx +++ b/web/src/components/inputs/switch.tsx @@ -2,7 +2,8 @@ import React from "react"; import type { FieldInputProps, FieldMetaProps, FieldProps, FormikProps, FormikValues } from "formik"; import { Field } from "formik"; import { Switch as HeadlessSwitch } from "@headlessui/react"; -import { classNames } from "../../utils"; + +import { classNames } from "@utils"; import { CustomTooltip } from "../tooltips/CustomTooltip"; type SwitchProps = { @@ -82,7 +83,7 @@ const SwitchGroup = ({ }: SwitchGroupProps) => ( {label &&
-
{label} diff --git a/web/src/components/inputs/text.tsx b/web/src/components/inputs/text.tsx index 853c8e6..e4a2ba5 100644 --- a/web/src/components/inputs/text.tsx +++ b/web/src/components/inputs/text.tsx @@ -1,7 +1,7 @@ import React, { FC, forwardRef, ReactNode } from "react"; import { DeepMap, FieldError, Path, RegisterOptions, UseFormRegister } from "react-hook-form"; -import { classNames, get } from "../../utils"; -import { useToggle } from "../../hooks/hooks"; +import { classNames, get } from "@utils"; +import { useToggle } from "@hooks/hooks"; import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; import { ErrorMessage } from "@hookform/error-message"; import type { FieldValues } from "react-hook-form"; diff --git a/web/src/components/notifications/Toast.tsx b/web/src/components/notifications/Toast.tsx index 24ca7ae..ba97761 100644 --- a/web/src/components/notifications/Toast.tsx +++ b/web/src/components/notifications/Toast.tsx @@ -1,7 +1,7 @@ import { FC } from "react"; import { CheckCircleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/solid"; import { toast, Toast as Tooast } from "react-hot-toast"; -import { classNames } from "../../utils"; +import { classNames } from "@utils"; type Props = { type: "error" | "success" | "warning" @@ -26,7 +26,7 @@ const Toast: FC = ({ type, body, t }) => ( {type === "error" && "Error"} {type === "warning" && "Warning"}

-

{body}

+ {body}
-
{ }; export const LogFiles = () => { - const { isLoading, data } = useQuery( - ["log-files"], - () => APIClient.logs.files(), - { - retry: false, - refetchOnWindowFocus: false, - onError: err => console.log(err) - } - ); + const { isLoading, data } = useQuery({ + queryKey: ["log-files"], + queryFn: () => APIClient.logs.files(), + retry: false, + refetchOnWindowFocus: false, + onError: err => console.log(err) + }); return (
@@ -224,10 +221,8 @@ const LogFilesItem = ({ file }: LogFilesItemProps) => { URL.revokeObjectURL(url); setIsDownloading(false); }; - return ( -
  • diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx index 86cc401..42bca2d 100644 --- a/web/src/screens/Settings.tsx +++ b/web/src/screens/Settings.tsx @@ -10,7 +10,7 @@ import { Square3Stack3DIcon } from "@heroicons/react/24/outline"; -import { classNames } from "../utils"; +import { classNames } from "@utils"; interface NavTabType { name: string; diff --git a/web/src/screens/auth/login.tsx b/web/src/screens/auth/login.tsx index 8bc4efc..20a1138 100644 --- a/web/src/screens/auth/login.tsx +++ b/web/src/screens/auth/login.tsx @@ -1,14 +1,15 @@ import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import { useMutation } from "react-query"; -import logo from "../../logo.png"; -import { APIClient } from "../../api/APIClient"; -import { AuthContext } from "../../utils/Context"; -import { PasswordInput, TextInput } from "../../components/inputs/text"; -import { Tooltip } from "react-tooltip"; -import Toast from "@/components/notifications/Toast"; +import { useMutation } from "@tanstack/react-query"; import toast from "react-hot-toast"; +import { Tooltip } from "react-tooltip"; + +import logo from "@app/logo.png"; +import { APIClient } from "@api/APIClient"; +import { AuthContext } from "@utils/Context"; +import { PasswordInput, TextInput } from "@components/inputs/text"; +import Toast from "@components/notifications/Toast"; type LoginFormFields = { username: string; @@ -37,23 +38,21 @@ export const Login = () => { .catch(() => { /*don't log to console PAHLLEEEASSSE*/ }); }, []); - const loginMutation = useMutation( - (data: LoginFormFields) => APIClient.auth.login(data.username, data.password), - { - onSuccess: (_, variables: LoginFormFields) => { - setAuthContext({ - username: variables.username, - isLoggedIn: true - }); - navigate("/"); - }, - onError: () => { - toast.custom((t) => ( - - )); - } + const loginMutation = useMutation({ + mutationFn: (data: LoginFormFields) => APIClient.auth.login(data.username, data.password), + onSuccess: (_, variables: LoginFormFields) => { + setAuthContext({ + username: variables.username, + isLoggedIn: true + }); + navigate("/"); + }, + onError: () => { + toast.custom((t) => ( + + )); } - ); + }); const onSubmit = (data: LoginFormFields) => loginMutation.mutate(data); diff --git a/web/src/screens/auth/onboarding.tsx b/web/src/screens/auth/onboarding.tsx index d7d999f..a8e2942 100644 --- a/web/src/screens/auth/onboarding.tsx +++ b/web/src/screens/auth/onboarding.tsx @@ -1,10 +1,10 @@ import { Form, Formik } from "formik"; -import { useMutation } from "react-query"; +import { useMutation } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; -import { APIClient } from "../../api/APIClient"; -import { TextField, PasswordField } from "../../components/inputs"; -import logo from "../../logo.png"; +import { APIClient } from "@api/APIClient"; +import { TextField, PasswordField } from "@components/inputs"; +import logo from "@app/logo.png"; interface InputValues { username: string; @@ -33,10 +33,10 @@ export const Onboarding = () => { const navigate = useNavigate(); - const mutation = useMutation( - (data: InputValues) => APIClient.auth.onboard(data.username, data.password1), - { onSuccess: () => navigate("/") } - ); + const mutation = useMutation({ + mutationFn: (data: InputValues) => APIClient.auth.onboard(data.username, data.password1), + onSuccess: () => navigate("/") + }); return (
    diff --git a/web/src/screens/dashboard/ActivityTable.tsx b/web/src/screens/dashboard/ActivityTable.tsx index 4f8bd00..1187a3d 100644 --- a/web/src/screens/dashboard/ActivityTable.tsx +++ b/web/src/screens/dashboard/ActivityTable.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useQuery } from "react-query"; +import { useQuery } from "@tanstack/react-query"; import { useTable, useFilters, @@ -8,11 +8,10 @@ import { usePagination, FilterProps, Column } from "react-table"; -import { APIClient } from "../../api/APIClient"; -import { EmptyListState } from "../../components/emptystates"; - -import * as Icons from "../../components/Icons"; -import * as DataTable from "../../components/data-table"; +import { APIClient } from "@api/APIClient"; +import { EmptyListState } from "@components/emptystates"; +import * as Icons from "@components/Icons"; +import * as DataTable from "@components/data-table"; // This is a custom filter UI for selecting // a unique option from a list @@ -74,8 +73,9 @@ function Table({ columns, data }: TableProps) { usePagination ); - if (!page.length) + if (!page.length) { return ; + } // Render the UI for your table return ( @@ -178,24 +178,25 @@ export const ActivityTable = () => { } ], []); - const { isLoading, data } = useQuery( - "dash_recent_releases", - () => APIClient.release.findRecent(), - { refetchOnWindowFocus: false } - ); + const { isLoading, data } = useQuery({ + queryKey: ["dash_recent_releases"], + queryFn: APIClient.release.findRecent, + refetchOnWindowFocus: false + }); - if (isLoading) + if (isLoading) { return (

    -   +  

    - +
    ); - + } + return (

    diff --git a/web/src/screens/dashboard/Stats.tsx b/web/src/screens/dashboard/Stats.tsx index 7d3b52e..c9aa437 100644 --- a/web/src/screens/dashboard/Stats.tsx +++ b/web/src/screens/dashboard/Stats.tsx @@ -1,6 +1,6 @@ -import { useQuery } from "react-query"; -import { APIClient } from "../../api/APIClient"; -import { classNames } from "../../utils"; +import { useQuery } from "@tanstack/react-query"; +import { APIClient } from "@api/APIClient"; +import { classNames } from "@utils"; interface StatsItemProps { name: string; @@ -28,17 +28,18 @@ const StatsItem = ({ name, placeholder, value }: StatsItemProps) => ( ); export const Stats = () => { - const { isLoading, data } = useQuery( - "dash_release_stats", - () => APIClient.release.stats(), - { refetchOnWindowFocus: false } - ); + const { isLoading, data } = useQuery({ + queryKey: ["dash_release_stats"], + queryFn: APIClient.release.stats, + refetchOnWindowFocus: false + }); return (

    Stats

    +
    {/* */} diff --git a/web/src/screens/filters/action.tsx b/web/src/screens/filters/action.tsx index 6265d51..22d5edf 100644 --- a/web/src/screens/filters/action.tsx +++ b/web/src/screens/filters/action.tsx @@ -1,21 +1,21 @@ -import { AlertWarning } from "../../components/alerts"; -import { DownloadClientSelect, NumberField, Select, SwitchGroup, TextField } from "../../components/inputs"; -import { ActionContentLayoutOptions, ActionRtorrentRenameOptions, ActionTypeNameMap, ActionTypeOptions } from "../../domain/constants"; +import { AlertWarning } from "@components/alerts"; +import { DownloadClientSelect, NumberField, Select, SwitchGroup, TextField } from "@components/inputs"; +import { ActionContentLayoutOptions, ActionRtorrentRenameOptions, ActionTypeNameMap, ActionTypeOptions } from "@domain/constants"; import React, { Fragment, useRef, useEffect, useState } from "react"; -import { useQuery } from "react-query"; -import { APIClient } from "../../api/APIClient"; +import { useQuery } from "@tanstack/react-query"; +import { APIClient } from "@api/APIClient"; import { Field, FieldArray, FieldProps, FormikValues } from "formik"; -import { EmptyListState } from "../../components/emptystates"; -import { useToggle } from "../../hooks/hooks"; -import { classNames } from "../../utils"; +import { EmptyListState } from "@components/emptystates"; +import { useToggle } from "@hooks/hooks"; +import { classNames } from "@utils"; import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react"; import { ChevronRightIcon } from "@heroicons/react/24/solid"; -import { DeleteModal } from "../../components/modals"; +import { DeleteModal } from "@components/modals"; import { CollapsableSection } from "./details"; -import { CustomTooltip } from "../../components/tooltips/CustomTooltip"; +import { CustomTooltip } from "@components/tooltips/CustomTooltip"; import { Link } from "react-router-dom"; import { useFormikContext } from "formik"; -import { TextArea } from "../../components/inputs/input"; +import { TextArea } from "@components/inputs/input"; interface FilterActionsProps { filter: Filter; diff --git a/web/src/screens/filters/details.tsx b/web/src/screens/filters/details.tsx index 35ea2ac..ad754d5 100644 --- a/web/src/screens/filters/details.tsx +++ b/web/src/screens/filters/details.tsx @@ -1,5 +1,5 @@ import React, { useRef } from "react"; -import { useMutation, useQuery, useQueryClient } from "react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { NavLink, Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom"; import { toast } from "react-hot-toast"; import { Form, Formik, FormikValues, useFormikContext } from "formik"; @@ -20,10 +20,10 @@ import { SOURCES_MUSIC_OPTIONS, SOURCES_OPTIONS, tagsMatchLogicOptions -} from "../../domain/constants"; -import { APIClient } from "../../api/APIClient"; -import { useToggle } from "../../hooks/hooks"; -import { classNames } from "../../utils"; +} from "@app/domain/constants"; +import { APIClient } from "@api/APIClient"; +import { useToggle } from "@hooks/hooks"; +import { classNames } from "@utils"; import { CheckboxField, @@ -34,13 +34,14 @@ import { SwitchGroup, TextField, RegexField -} from "../../components/inputs"; -import DEBUG from "../../components/debug"; -import Toast from "../../components/notifications/Toast"; -import { DeleteModal } from "../../components/modals"; -import { TitleSubtitle } from "../../components/headings"; -import { TextArea } from "../../components/inputs/input"; +} from "@components/inputs"; +import DEBUG from "@components/debug"; +import Toast from "@components/notifications/Toast"; +import { DeleteModal } from "@components/modals"; +import { TitleSubtitle } from "@components/headings"; +import { TextArea } from "@components/inputs/input"; import { FilterActions } from "./action"; +import { filterKeys } from "./list"; interface tabType { name: string; @@ -144,37 +145,49 @@ export default function FilterDetails() { const navigate = useNavigate(); const { filterId } = useParams<{ filterId: string }>(); - const { isLoading, data: filter } = useQuery( - ["filters", filterId], - () => APIClient.filters.getByID(parseInt(filterId ?? "0")), - { - retry: false, - refetchOnWindowFocus: false, - onError: () => navigate("./") - } - ); + if (filterId === "0" || undefined) { + navigate("/filters"); + } - const updateMutation = useMutation( - (filter: Filter) => APIClient.filters.update(filter), - { - onSuccess: (_, currentFilter) => { - toast.custom((t) => ( - - )); - queryClient.refetchQueries(["filters"]); - // queryClient.invalidateQueries(["filters", currentFilter.id]); - } - } - ); + const id = parseInt(filterId!); - const deleteMutation = useMutation((id: number) => APIClient.filters.delete(id), { + const { isLoading, data: filter } = useQuery({ + queryKey: filterKeys.detail(id), + queryFn: ({ queryKey }) => APIClient.filters.getByID(queryKey[2]), + refetchOnWindowFocus: false, + onError: () => { + navigate("/filters"); + } + }); + + const updateMutation = useMutation({ + mutationFn: (filter: Filter) => APIClient.filters.update(filter), + onSuccess: (newFilter, variables) => { + queryClient.setQueryData(filterKeys.detail(variables.id), newFilter); + + queryClient.setQueryData(filterKeys.lists(), (previous) => { + if (previous) { + return previous.map((filter: Filter) => (filter.id === variables.id ? newFilter : filter)); + } + }); + + toast.custom((t) => ( + + )); + } + }); + + const deleteMutation = useMutation({ + mutationFn: (id: number) => APIClient.filters.delete(id), onSuccess: () => { + // Invalidate filters just in case, most likely not necessary but can't hurt. + queryClient.invalidateQueries({ queryKey: filterKeys.lists() }); + queryClient.invalidateQueries({ queryKey: filterKeys.detail(id) }); + toast.custom((t) => ( )); - // Invalidate filters just in case, most likely not necessary but can't hurt. - queryClient.invalidateQueries(["filters"]); // redirect navigate("/filters"); @@ -234,7 +247,7 @@ export default function FilterDetails() { initialValues={{ id: filter.id, name: filter.name, - enabled: filter.enabled || false, + enabled: filter.enabled, min_size: filter.min_size, max_size: filter.max_size, delay: filter.delay, @@ -298,6 +311,7 @@ export default function FilterDetails() { external_webhook_expect_status: filter.external_webhook_expect_status || 0 } as Filter} onSubmit={handleSubmit} + enableReinitialize={true} > {({ values, dirty, resetForm }) => (
    @@ -322,11 +336,11 @@ export default function FilterDetails() { } export function General(){ - const { isLoading, data: indexers } = useQuery( - ["filters", "indexer_list"], - () => APIClient.indexers.getOptions(), - { refetchOnWindowFocus: false } - ); + const { isLoading, data: indexers } = useQuery({ + queryKey: ["filters", "indexer_list"], + queryFn: APIClient.indexers.getOptions, + refetchOnWindowFocus: false + }); const opts = indexers && indexers.length > 0 ? indexers.map(v => ({ label: v.name, diff --git a/web/src/screens/filters/list.tsx b/web/src/screens/filters/list.tsx index 3011519..df21861 100644 --- a/web/src/screens/filters/list.tsx +++ b/web/src/screens/filters/list.tsx @@ -2,11 +2,10 @@ import { Dispatch, FC, Fragment, MouseEventHandler, useReducer, useRef, useState import { Link } from "react-router-dom"; import { toast } from "react-hot-toast"; import { Listbox, Menu, Switch, Transition } from "@headlessui/react"; -import { useMutation, useQuery, useQueryClient } from "react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { FormikValues } from "formik"; import { useCallback } from "react"; import { Tooltip } from "react-tooltip"; -import { FilterListContext, FilterListState } from "../../utils/Context"; import { ArrowsRightLeftIcon, CheckIcon, @@ -18,15 +17,25 @@ import { ChatBubbleBottomCenterTextIcon, TrashIcon } from "@heroicons/react/24/outline"; -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 { EmptyListState } from "../../components/emptystates"; -import { DeleteModal } from "../../components/modals"; import { ArrowDownTrayIcon } from "@heroicons/react/24/solid"; +import { FilterListContext, FilterListState } from "@utils/Context"; +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 { EmptyListState } from "@components/emptystates"; +import { DeleteModal } from "@components/modals"; + +export const filterKeys = { + all: ["filters"] as const, + lists: () => [...filterKeys.all, "list"] as const, + list: (indexers: string[], sortOrder: string) => [...filterKeys.lists(), { indexers, sortOrder }] as const, + details: () => [...filterKeys.all, "detail"] as const, + detail: (id: number) => [...filterKeys.details(), id] as const +}; + enum ActionType { INDEXER_FILTER_CHANGE = "INDEXER_FILTER_CHANGE", INDEXER_FILTER_RESET = "INDEXER_FILTER_RESET", @@ -63,11 +72,7 @@ const FilterListReducer = (state: FilterListState, action: Actions): FilterListS } }; -interface FilterProps { - values?: FormikValues; -} - -export default function Filters({}: FilterProps){ +export default function Filters() { const queryClient = useQueryClient(); const [createFilterIsOpen, setCreateFilterIsOpen] = useState(false); @@ -115,7 +120,7 @@ export default function Filters({}: FilterProps){ await APIClient.filters.create(newFilter); // Update the filter list - queryClient.invalidateQueries("filters"); + queryClient.invalidateQueries({ queryKey: filterKeys.lists() }); toast.custom((t) => ); setShowImportModal(false); @@ -146,7 +151,7 @@ export default function Filters({}: FilterProps){ }} > - Add Filter + Add Filter @@ -172,7 +177,7 @@ export default function Filters({}: FilterProps){ } w-full text-left py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-500`} onClick={() => setShowImportModal(true)} > - Import Filter + Import Filter )} @@ -250,11 +255,11 @@ function FilterList({ toggleCreateFilter }: any) { filterListState ); - const { error, data } = useQuery( - ["filters", indexerFilter, sortOrder], - () => APIClient.filters.find(indexerFilter, sortOrder), - { refetchOnWindowFocus: false } - ); + const { data, error } = useQuery({ + queryKey: filterKeys.list(indexerFilter, sortOrder), + queryFn: ({ queryKey }) => APIClient.filters.find(queryKey[2].indexers, queryKey[2].sortOrder), + refetchOnWindowFocus: false + }); useEffect(() => { FilterListContext.set({ indexerFilter, sortOrder, status }); @@ -284,9 +289,13 @@ function FilterList({ toggleCreateFilter }: any) { {data && data.length > 0 ? (
      - {filtered.filtered.map((filter: Filter, idx) => ( - - ))} + {filtered.filtered.length > 0 + ? filtered.filtered.map((filter: Filter, idx) => ( + + )) + + : + }
    ) : ( @@ -444,28 +453,25 @@ const FilterItemDropdown = ({ filter, onToggle }: FilterItemDropdownProps) => { const queryClient = useQueryClient(); const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); - const deleteMutation = useMutation( - (id: number) => APIClient.filters.delete(id), - { - onSuccess: () => { - queryClient.invalidateQueries(["filters"]); - queryClient.invalidateQueries(["filters", filter.id]); - toast.custom((t) => ); - } + const deleteMutation = useMutation({ + mutationFn: (id: number) => APIClient.filters.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: filterKeys.lists() }); + queryClient.invalidateQueries({ queryKey: filterKeys.detail(filter.id) }); + + toast.custom((t) => ); } - ); + }); - const duplicateMutation = useMutation( - (id: number) => APIClient.filters.duplicate(id), - { - onSuccess: () => { - queryClient.invalidateQueries(["filters"]); + const duplicateMutation = useMutation({ + mutationFn: (id: number) => APIClient.filters.duplicate(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: filterKeys.lists() }); - toast.custom((t) => ); - } + toast.custom((t) => ); } - ); + }); return ( @@ -634,26 +640,22 @@ interface FilterListItemProps { } function FilterListItem({ filter, values, idx }: FilterListItemProps) { - const [enabled, setEnabled] = useState(filter.enabled); const queryClient = useQueryClient(); - const updateMutation = useMutation( - (status: boolean) => APIClient.filters.toggleEnable(filter.id, status), - { - onSuccess: () => { - toast.custom((t) => ); + const updateMutation = useMutation({ + mutationFn: (status: boolean) => APIClient.filters.toggleEnable(filter.id, status), + onSuccess: () => { + toast.custom((t) => ); - // We need to invalidate both keys here. - // The filters key is used on the /filters page, - // while the ["filter", filter.id] key is used on the details page. - queryClient.invalidateQueries(["filters"]); - queryClient.invalidateQueries(["filters", filter?.id]); - } + // We need to invalidate both keys here. + // The filters key is used on the /filters page, + // while the ["filter", filter.id] key is used on the details page. + queryClient.invalidateQueries({ queryKey: filterKeys.lists() }); + queryClient.invalidateQueries({ queryKey: filterKeys.detail(filter.id) }); } - ); + }); const toggleActive = (status: boolean) => { - setEnabled(status); updateMutation.mutate(status); }; @@ -671,10 +673,10 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) { className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-100" > @@ -682,7 +684,7 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) { - + )} @@ -848,14 +850,12 @@ const ListboxFilter = ({ // a unique option from a list const IndexerSelectFilter = ({ dispatch }: any) => { - const { data, isSuccess } = useQuery( - "indexers_options", - () => APIClient.indexers.getOptions(), - { - keepPreviousData: true, - staleTime: Infinity - } - ); + const { data, isSuccess } = useQuery({ + queryKey: ["filters","indexers_options"], + queryFn: () => APIClient.indexers.getOptions(), + keepPreviousData: true, + staleTime: Infinity + }); const setFilter = (value: string) => { if (value == undefined || value == "") { diff --git a/web/src/screens/releases/Filters.tsx b/web/src/screens/releases/Filters.tsx index 3698f3a..833c705 100644 --- a/web/src/screens/releases/Filters.tsx +++ b/web/src/screens/releases/Filters.tsx @@ -1,11 +1,11 @@ import * as React from "react"; -import { useQuery } from "react-query"; +import { useQuery } from "@tanstack/react-query"; import { Listbox, Transition } from "@headlessui/react"; import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/solid"; -import { APIClient } from "../../api/APIClient"; -import { classNames } from "../../utils"; -import { PushStatusOptions } from "../../domain/constants"; +import { APIClient } from "@api/APIClient"; +import { classNames } from "@utils"; +import { PushStatusOptions } from "@domain/constants"; import { FilterProps } from "react-table"; import { DebounceInput } from "react-debounce-input"; @@ -62,14 +62,12 @@ const ListboxFilter = ({ export const IndexerSelectColumnFilter = ({ column: { filterValue, setFilter, id } }: FilterProps) => { - const { data, isSuccess } = useQuery( - "indexer_options", - () => APIClient.release.indexerOptions(), - { - keepPreviousData: true, - staleTime: Infinity - } - ); + const { data, isSuccess } = useQuery({ + queryKey: ["indexer_options"], + queryFn: () => APIClient.release.indexerOptions(), + keepPreviousData: true, + staleTime: Infinity + }); // Render a multi-select box return ( diff --git a/web/src/screens/releases/ReleaseTable.tsx b/web/src/screens/releases/ReleaseTable.tsx index 985743b..c6c63b5 100644 --- a/web/src/screens/releases/ReleaseTable.tsx +++ b/web/src/screens/releases/ReleaseTable.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useQuery } from "react-query"; +import { useQuery } from "@tanstack/react-query"; import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table"; import { ChevronDoubleLeftIcon, @@ -8,16 +8,25 @@ import { ChevronRightIcon } from "@heroicons/react/24/solid"; -import { APIClient } from "../../api/APIClient"; -import { EmptyListState } from "../../components/emptystates"; +import { APIClient } from "@api/APIClient"; +import { EmptyListState } from "@components/emptystates"; -import * as Icons from "../../components/Icons"; -import * as DataTable from "../../components/data-table"; +import * as Icons from "@components/Icons"; +import * as DataTable from "@components/data-table"; import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./Filters"; -import { classNames } from "../../utils"; +import { classNames } from "@utils"; import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; -import { Tooltip } from "../../components/tooltips/Tooltip"; +import { Tooltip } from "@components/tooltips/Tooltip"; + +export const releaseKeys = { + all: ["releases"] as const, + lists: () => [...releaseKeys.all, "list"] as const, + list: (pageIndex: number, pageSize: number, filters: ReleaseFilter[]) => [...releaseKeys.lists(), { pageIndex, pageSize, filters }] as const, + details: () => [...releaseKeys.all, "detail"] as const, + detail: (id: number) => [...releaseKeys.details(), id] as const +}; + type TableState = { queryPageIndex: number; @@ -120,14 +129,12 @@ export const ReleaseTable = () => { const [{ queryPageIndex, queryPageSize, totalCount, queryFilters }, dispatch] = React.useReducer(TableReducer, initialState); - const { isLoading, error, data, isSuccess } = useQuery( - ["releases", queryPageIndex, queryPageSize, queryFilters], - () => APIClient.release.findQuery(queryPageIndex * queryPageSize, queryPageSize, queryFilters), - { - keepPreviousData: true, - staleTime: 5000 - } - ); + const { isLoading, error, data, isSuccess } = useQuery({ + queryKey: releaseKeys.list(queryPageIndex, queryPageSize, queryFilters), + queryFn: () => APIClient.release.findQuery(queryPageIndex * queryPageSize, queryPageSize, queryFilters), + keepPreviousData: true, + staleTime: 5000 + }); // Use the state and functions returned from useTable to build your UI const { @@ -192,10 +199,11 @@ export const ReleaseTable = () => { dispatch({ type: ActionType.FILTER_CHANGED, payload: filters }); }, [filters]); - if (error) + if (error) { return

    Error

    ; + } - if (isLoading) + if (isLoading) { return (
    @@ -211,9 +219,7 @@ export const ReleaseTable = () => { - - - - + - + - + - + @@ -255,27 +263,32 @@ export const ReleaseTable = () => {

    Loading release table...

    - + - + - + - + - + @@ -292,7 +305,8 @@ export const ReleaseTable = () => {
    - Page {pageIndex + 1} of {pageOptions.length} + Page {pageIndex + 1} of {pageOptions.length}
    @@ -349,9 +363,11 @@ export const ReleaseTable = () => {
    ); + } - if (!data) + if (!data) { return ; + } // Render the UI for your table return ( diff --git a/web/src/screens/settings/Api.tsx b/web/src/screens/settings/Api.tsx index 2c0f5cf..a697cb4 100644 --- a/web/src/screens/settings/Api.tsx +++ b/web/src/screens/settings/Api.tsx @@ -1,20 +1,31 @@ import { useRef } from "react"; -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { KeyField } from "../../components/fields/text"; -import { DeleteModal } from "../../components/modals"; -import APIKeyAddForm from "../../forms/settings/APIKeyAddForm"; -import Toast from "../../components/notifications/Toast"; -import { APIClient } from "../../api/APIClient"; -import { useToggle } from "../../hooks/hooks"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "react-hot-toast"; -import { classNames } from "../../utils"; import { TrashIcon } from "@heroicons/react/24/outline"; -import { EmptySimple } from "../../components/emptystates"; + +import { KeyField } from "@components/fields/text"; +import { DeleteModal } from "@components/modals"; +import APIKeyAddForm from "@forms/settings/APIKeyAddForm"; +import Toast from "@components/notifications/Toast"; +import { APIClient } from "@api/APIClient"; +import { useToggle } from "@hooks/hooks"; +import { classNames } from "@utils"; +import { EmptySimple } from "@components/emptystates"; + +export const apiKeys = { + all: ["api_keys"] as const, + lists: () => [...apiKeys.all, "list"] as const, + details: () => [...apiKeys.all, "detail"] as const, + // detail: (id: number) => [...apiKeys.details(), id] as const + detail: (id: string) => [...apiKeys.details(), id] as const +}; function APISettings() { const [addFormIsOpen, toggleAddForm] = useToggle(false); - const { data } = useQuery(["apikeys"], () => APIClient.apikeys.getAll(), { + const { data } = useQuery({ + queryKey: apiKeys.lists(), + queryFn: APIClient.apikeys.getAll, retry: false, refetchOnWindowFocus: false, onError: (err) => console.log(err) @@ -57,7 +68,7 @@ function APISettings() { - {data && data.map((k) => )} + {data && data.map((k, idx) => )} ) : ( @@ -83,23 +94,21 @@ function APIListItem({ apikey }: ApiKeyItemProps) { const queryClient = useQueryClient(); - const deleteMutation = useMutation( - (key: string) => APIClient.apikeys.delete(key), - { - onSuccess: () => { - queryClient.invalidateQueries(["apikeys"]); - queryClient.invalidateQueries(["apikeys", apikey.key]); + const deleteMutation = useMutation({ + mutationFn: (key: string) => APIClient.apikeys.delete(key), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: apiKeys.lists() }); + queryClient.invalidateQueries({ queryKey: apiKeys.detail(apikey.key) }); - toast.custom((t) => ( - - )); - } + toast.custom((t) => ( + + )); } - ); + }); return (
  • diff --git a/web/src/screens/settings/Application.tsx b/web/src/screens/settings/Application.tsx index 4896a8e..fac01e5 100644 --- a/web/src/screens/settings/Application.tsx +++ b/web/src/screens/settings/Application.tsx @@ -1,10 +1,11 @@ -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { APIClient } from "../../api/APIClient"; -import { Checkbox } from "../../components/Checkbox"; -import { SettingsContext } from "../../utils/Context"; -import { GithubRelease } from "../../types/Update"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "react-hot-toast"; -import Toast from "../../components/notifications/Toast"; + +import { APIClient } from "@api/APIClient"; +import { Checkbox } from "@components/Checkbox"; +import { SettingsContext } from "@utils/Context"; +import { GithubRelease } from "@app/types/Update"; +import Toast from "@components/notifications/Toast"; interface RowItemProps { label: string; @@ -47,8 +48,9 @@ const RowItemNumber = ({ label, value, title, unit }: RowItemNumberProps) => { }; const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => { - if (!value) + if (!value) { return null; + } return (
    @@ -68,49 +70,41 @@ const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => { function ApplicationSettings() { const [settings, setSettings] = SettingsContext.use(); - const { isLoading, data } = useQuery( - ["config"], - () => APIClient.config.get(), - { - retry: false, - refetchOnWindowFocus: false, - onError: err => console.log(err) - } - ); + const { isLoading, data } = useQuery({ + queryKey: ["config"], + queryFn: APIClient.config.get, + retry: false, + refetchOnWindowFocus: false, + onError: err => console.log(err) + }); - const { data: updateData } = useQuery( - ["updates"], - () => APIClient.updates.getLatestRelease(), - { - retry: false, - refetchOnWindowFocus: false, - onError: err => console.log(err) - } - ); + const { data: updateData } = useQuery({ + queryKey: ["updates"], + queryFn: APIClient.updates.getLatestRelease, + retry: false, + refetchOnWindowFocus: false, + onError: err => console.log(err) + }); const queryClient = useQueryClient(); - const checkUpdateMutation = useMutation( - () => APIClient.updates.check(), - { - onSuccess: () => { - queryClient.invalidateQueries(["updates"]); - } + const checkUpdateMutation = useMutation({ + mutationFn: APIClient.updates.check, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["updates"] }); } - ); + }); - const toggleCheckUpdateMutation = useMutation( - (value: boolean) => APIClient.config.update({ check_for_updates: value }), - { - onSuccess: () => { - toast.custom((t) => ); + const toggleCheckUpdateMutation = useMutation({ + mutationFn: (value: boolean) => APIClient.config.update({ check_for_updates: value }), + onSuccess: () => { + toast.custom((t) => ); - queryClient.invalidateQueries(["config"]); + queryClient.invalidateQueries({ queryKey: ["config"] }); - checkUpdateMutation.mutate(); - } + checkUpdateMutation.mutate(); } - ); + }); return (
    diff --git a/web/src/screens/settings/DownloadClient.tsx b/web/src/screens/settings/DownloadClient.tsx index 08a0835..cd974b8 100644 --- a/web/src/screens/settings/DownloadClient.tsx +++ b/web/src/screens/settings/DownloadClient.tsx @@ -1,14 +1,23 @@ -import { useToggle } from "../../hooks/hooks"; -import { Switch } from "@headlessui/react"; -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { classNames } from "../../utils"; -import { DownloadClientAddForm, DownloadClientUpdateForm } from "../../forms"; -import { EmptySimple } from "../../components/emptystates"; -import { APIClient } from "../../api/APIClient"; -import { DownloadClientTypeNameMap } from "../../domain/constants"; -import toast from "react-hot-toast"; -import Toast from "../../components/notifications/Toast"; import { useState, useMemo } from "react"; +import { Switch } from "@headlessui/react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import toast from "react-hot-toast"; + +import { useToggle } from "@hooks/hooks"; +import { classNames } from "@utils"; +import { DownloadClientAddForm, DownloadClientUpdateForm } from "@forms"; +import { EmptySimple } from "@components/emptystates"; +import { APIClient } from "@api/APIClient"; +import { DownloadClientTypeNameMap } from "@domain/constants"; +import Toast from "@components/notifications/Toast"; + +export const clientKeys = { + all: ["download_clients"] as const, + lists: () => [...clientKeys.all, "list"] as const, + // list: (indexers: string[], sortOrder: string) => [...clientKeys.lists(), { indexers, sortOrder }] as const, + details: () => [...clientKeys.all, "detail"] as const, + detail: (id: number) => [...clientKeys.details(), id] as const +}; interface DLSettingsItemProps { client: DownloadClient; @@ -61,7 +70,6 @@ function useSort(items: ListItemProps["clients"][], config?: SortConfig) { } setSortConfig({ key, direction }); }; - const getSortIndicator = (key: keyof ListItemProps["clients"]) => { if (!sortConfig || sortConfig.key !== key) { @@ -79,15 +87,14 @@ function DownloadClientSettingsListItem({ client }: DLSettingsItemProps) { const queryClient = useQueryClient(); - const mutation = useMutation( - (client: DownloadClient) => APIClient.download_clients.update(client), - { - onSuccess: () => { - queryClient.invalidateQueries(["downloadClients"]); - toast.custom((t) => ); - } + const mutation = useMutation({ + mutationFn: (client: DownloadClient) => APIClient.download_clients.update(client), + onSuccess: () => { + toast.custom((t) => ); + + queryClient.invalidateQueries({ queryKey: clientKeys.lists() }); } - ); + }); const onToggleMutation = (newState: boolean) => { mutation.mutate({ @@ -139,11 +146,11 @@ function DownloadClientSettingsListItem({ client }: DLSettingsItemProps) { function DownloadClientSettings() { const [addClientIsOpen, toggleAddClient] = useToggle(false); - const { error, data } = useQuery( - "downloadClients", - () => APIClient.download_clients.getAll(), - { refetchOnWindowFocus: false } - ); + const { error, data } = useQuery({ + queryKey: clientKeys.lists(), + queryFn: APIClient.download_clients.getAll, + refetchOnWindowFocus: false + }); const sortedClients = useSort(data || []); @@ -151,10 +158,8 @@ function DownloadClientSettings() { return

    Failed to fetch download clients

    ; } - return (
    -
    @@ -177,8 +182,8 @@ function DownloadClientSettings() {
    - {sortedClients.items.length > 0 ? -
    + {sortedClients.items.length > 0 + ?
    - ); } diff --git a/web/src/screens/settings/Feed.tsx b/web/src/screens/settings/Feed.tsx index 04913e4..6f006f7 100644 --- a/web/src/screens/settings/Feed.tsx +++ b/web/src/screens/settings/Feed.tsx @@ -1,13 +1,7 @@ -import { useToggle } from "../../hooks/hooks"; -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { APIClient } from "../../api/APIClient"; -import { Menu, Switch, Transition } from "@headlessui/react"; - -import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "../../utils"; import { Fragment, useRef, useState, useMemo } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Menu, Switch, Transition } from "@headlessui/react"; import { toast } from "react-hot-toast"; -import Toast from "../../components/notifications/Toast"; -import { DeleteModal } from "../../components/modals"; import { ArrowsRightLeftIcon, DocumentTextIcon, @@ -15,10 +9,24 @@ import { PencilSquareIcon, TrashIcon } from "@heroicons/react/24/outline"; -import { FeedUpdateForm } from "../../forms/settings/FeedForms"; -import { EmptySimple } from "../../components/emptystates"; + +import { APIClient } from "@api/APIClient"; +import { useToggle } from "@hooks/hooks"; +import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "@utils"; +import Toast from "@components/notifications/Toast"; +import { DeleteModal } from "@components/modals"; +import { FeedUpdateForm } from "@forms/settings/FeedForms"; +import { EmptySimple } from "@components/emptystates"; import { ImplementationBadges } from "./Indexer"; +export const feedKeys = { + all: ["feeds"] as const, + lists: () => [...feedKeys.all, "list"] as const, + // list: (indexers: string[], sortOrder: string) => [...feedKeys.lists(), { indexers, sortOrder }] as const, + details: () => [...feedKeys.all, "detail"] as const, + detail: (id: number) => [...feedKeys.details(), id] as const +}; + interface SortConfig { key: keyof ListItemProps["feed"] | "enabled"; direction: "ascending" | "descending"; @@ -27,8 +35,6 @@ interface SortConfig { function useSort(items: ListItemProps["feed"][], config?: SortConfig) { const [sortConfig, setSortConfig] = useState(config); - - const sortedItems = useMemo(() => { if (!sortConfig) { return items; @@ -76,11 +82,11 @@ function useSort(items: ListItemProps["feed"][], config?: SortConfig) { } function FeedSettings() { - const { data } = useQuery( - "feeds", - () => APIClient.feeds.find(), - { refetchOnWindowFocus: false } - ); + const { data } = useQuery({ + queryKey: feedKeys.lists(), + queryFn: APIClient.feeds.find, + refetchOnWindowFocus: false + }); const sortedFeeds = useSort(data || []); @@ -142,19 +148,15 @@ function ListItem({ feed }: ListItemProps) { const [enabled, setEnabled] = useState(feed.enabled); const queryClient = useQueryClient(); - const updateMutation = useMutation( - (status: boolean) => APIClient.feeds.toggleEnable(feed.id, status), - { - onSuccess: () => { - toast.custom((t) => ); + const updateMutation = useMutation({ + mutationFn: (status: boolean) => APIClient.feeds.toggleEnable(feed.id, status), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: feedKeys.lists() }); + queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) }); - queryClient.invalidateQueries(["feeds"]); - queryClient.invalidateQueries(["feeds", feed?.id]); - } + toast.custom((t) => ); } - ); + }); const toggleActive = (status: boolean) => { setEnabled(status); @@ -227,17 +229,15 @@ const FeedItemDropdown = ({ const queryClient = useQueryClient(); const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); - const deleteMutation = useMutation( - (id: number) => APIClient.feeds.delete(id), - { - onSuccess: () => { - queryClient.invalidateQueries(["feeds"]); - queryClient.invalidateQueries(["feeds", feed.id]); + const deleteMutation = useMutation({ + mutationFn: (id: number) => APIClient.feeds.delete(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: feedKeys.lists() }); + queryClient.invalidateQueries({ queryKey: feedKeys.detail(feed.id) }); - toast.custom((t) => ); - } + toast.custom((t) => ); } - ); + }); return ( diff --git a/web/src/screens/settings/Indexer.tsx b/web/src/screens/settings/Indexer.tsx index 6e87548..bcb5741 100644 --- a/web/src/screens/settings/Indexer.tsx +++ b/web/src/screens/settings/Indexer.tsx @@ -1,12 +1,21 @@ -import { useToggle } from "../../hooks/hooks"; -import { useQuery } from "react-query"; -import { IndexerAddForm, IndexerUpdateForm } from "../../forms"; -import { Switch } from "@headlessui/react"; -import { classNames } from "../../utils"; -import { EmptySimple } from "../../components/emptystates"; -import { APIClient } from "../../api/APIClient"; -import { componentMapType } from "../../forms/settings/DownloadClientForms"; import { useState, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Switch } from "@headlessui/react"; + +import { IndexerAddForm, IndexerUpdateForm } from "@forms"; +import { useToggle } from "@hooks/hooks"; +import { classNames } from "@utils"; +import { EmptySimple } from "@components/emptystates"; +import { APIClient } from "@api/APIClient"; +import { componentMapType } from "@forms/settings/DownloadClientForms"; + +export const indexerKeys = { + all: ["indexers"] as const, + lists: () => [...indexerKeys.all, "list"] as const, + // list: (indexers: string[], sortOrder: string) => [...indexerKeys.lists(), { indexers, sortOrder }] as const, + details: () => [...indexerKeys.all, "detail"] as const, + detail: (id: number) => [...indexerKeys.details(), id] as const +}; interface SortConfig { key: keyof ListItemProps["indexer"] | "enabled"; @@ -149,16 +158,17 @@ const ListItem = ({ indexer }: ListItemProps) => { function IndexerSettings() { const [addIndexerIsOpen, toggleAddIndexer] = useToggle(false); - const { error, data } = useQuery( - "indexer", - () => APIClient.indexers.getAll(), - { refetchOnWindowFocus: false } - ); + const { error, data } = useQuery({ + queryKey: indexerKeys.lists(), + queryFn: APIClient.indexers.getAll, + refetchOnWindowFocus: false + }); const sortedIndexers = useSort(data || []); - if (error) + if (error) { return (

    An error has occurred

    ); + } return (
    diff --git a/web/src/screens/settings/Irc.tsx b/web/src/screens/settings/Irc.tsx index dcc0eda..b8648b2 100644 --- a/web/src/screens/settings/Irc.tsx +++ b/web/src/screens/settings/Irc.tsx @@ -1,16 +1,8 @@ -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { classNames, IsEmptyDate, simplifyDate } from "../../utils"; -import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms"; -import { useToggle } from "../../hooks/hooks"; -import { APIClient } from "../../api/APIClient"; -import { EmptySimple } from "../../components/emptystates"; +import { Fragment, useRef, useState, useMemo } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid"; import { Menu, Switch, Transition } from "@headlessui/react"; -import { Fragment, useRef } from "react"; -import { DeleteModal } from "../../components/modals"; -import { useState, useMemo } from "react"; import { toast } from "react-hot-toast"; -import Toast from "../../components/notifications/Toast"; import { ArrowsPointingInIcon, ArrowsPointingOutIcon, @@ -20,6 +12,22 @@ import { TrashIcon } from "@heroicons/react/24/outline"; +import { classNames, IsEmptyDate, simplifyDate } from "@utils"; +import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "@forms"; +import { useToggle } from "@hooks/hooks"; +import { APIClient } from "@api/APIClient"; +import { EmptySimple } from "@components/emptystates"; +import { DeleteModal } from "@components/modals"; +import Toast from "@components/notifications/Toast"; + +export const ircKeys = { + all: ["irc_networks"] as const, + lists: () => [...ircKeys.all, "list"] as const, + // list: (indexers: string[], sortOrder: string) => [...ircKeys.lists(), { indexers, sortOrder }] as const, + details: () => [...ircKeys.all, "detail"] as const, + detail: (id: number) => [...ircKeys.details(), id] as const +}; + interface SortConfig { key: keyof ListItemProps["network"] | "enabled"; direction: "ascending" | "descending"; @@ -79,10 +87,11 @@ const IrcSettings = () => { const [expandNetworks, toggleExpand] = useToggle(false); const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false); - const { data } = useQuery("networks", () => APIClient.irc.getNetworks(), { + const { data } = useQuery({ + queryKey: ircKeys.lists(), + queryFn: APIClient.irc.getNetworks, refetchOnWindowFocus: false, - // Refetch every 3 seconds - refetchInterval: 3000 + refetchInterval: 3000 // Refetch every 3 seconds }); const sortedNetworks = useSort(data || []); @@ -204,18 +213,17 @@ const ListItem = ({ idx, network, expanded }: ListItemProps) => { const queryClient = useQueryClient(); - const mutation = useMutation( - (network: IrcNetwork) => APIClient.irc.updateNetwork(network), - { - onSuccess: () => { - queryClient.invalidateQueries(["networks"]); - toast.custom((t) => ); - } + const updateMutation = useMutation({ + mutationFn: (network: IrcNetwork) => APIClient.irc.updateNetwork(network), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ircKeys.lists() }); + + toast.custom((t) => ); } - ); + }); const onToggleMutation = (newState: boolean) => { - mutation.mutate({ + updateMutation.mutate({ ...network, enabled: newState }); @@ -399,37 +407,30 @@ const ListItemDropdown = ({ const queryClient = useQueryClient(); const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); - const deleteMutation = useMutation( - (id: number) => APIClient.irc.deleteNetwork(id), - { - onSuccess: () => { - queryClient.invalidateQueries(["networks"]); - queryClient.invalidateQueries(["networks", network.id]); - toast.custom((t) => ); + const deleteMutation = useMutation({ + mutationFn: (id: number) => APIClient.irc.deleteNetwork(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ircKeys.lists() }); + queryClient.invalidateQueries({ queryKey: ircKeys.detail(network.id) }); - toggleDeleteModal(); - } + toast.custom((t) => ); + + toggleDeleteModal(); } - ); + }); - const restartMutation = useMutation( - (id: number) => APIClient.irc.restartNetwork(id), - { - onSuccess: () => { - toast.custom((t) => ); + const restartMutation = useMutation({ + mutationFn: (id: number) => APIClient.irc.restartNetwork(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ircKeys.lists() }); + queryClient.invalidateQueries({ queryKey: ircKeys.detail(network.id) }); - queryClient.invalidateQueries(["networks"]); - queryClient.invalidateQueries(["networks", network.id]); - } + toast.custom((t) => ); } - ); + }); - const restart = (id: number) => { - restartMutation.mutate(id); - }; + const restart = (id: number) => restartMutation.mutate(id); return ( diff --git a/web/src/screens/settings/Logs.tsx b/web/src/screens/settings/Logs.tsx index de38040..52af346 100644 --- a/web/src/screens/settings/Logs.tsx +++ b/web/src/screens/settings/Logs.tsx @@ -1,10 +1,11 @@ -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { APIClient } from "../../api/APIClient"; -import { GithubRelease } from "../../types/Update"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "react-hot-toast"; -import Toast from "../../components/notifications/Toast"; import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select"; -import { LogLevelOptions, SelectOption } from "../../domain/constants"; + +import { APIClient } from "@api/APIClient"; +import { GithubRelease } from "@app/types/Update"; +import Toast from "@components/notifications/Toast"; +import { LogLevelOptions, SelectOption } from "@domain/constants"; import { LogFiles } from "../Logs"; interface RowItemProps { @@ -121,28 +122,24 @@ const RowItemSelect = ({ id, title, label, value, options, onChange }: any) => { }; function LogSettings() { - const { isLoading, data } = useQuery( - ["config"], - () => APIClient.config.get(), - { - retry: false, - refetchOnWindowFocus: false, - onError: err => console.log(err) - } - ); + const { isLoading, data } = useQuery({ + queryKey: ["config"], + queryFn: APIClient.config.get, + retry: false, + refetchOnWindowFocus: false, + onError: err => console.log(err) + }); const queryClient = useQueryClient(); - const setLogLevelUpdateMutation = useMutation( - (value: string) => APIClient.config.update({ log_level: value }), - { - onSuccess: () => { - toast.custom((t) => ); + const setLogLevelUpdateMutation = useMutation({ + mutationFn: (value: string) => APIClient.config.update({ log_level: value }), + onSuccess: () => { + toast.custom((t) => ); - queryClient.invalidateQueries(["config"]); - } + queryClient.invalidateQueries({ queryKey: ["config"] }); } - ); + }); return (
    diff --git a/web/src/screens/settings/Notifications.tsx b/web/src/screens/settings/Notifications.tsx index fbde0eb..5b9682e 100644 --- a/web/src/screens/settings/Notifications.tsx +++ b/web/src/screens/settings/Notifications.tsx @@ -1,19 +1,27 @@ -import { useQuery } from "react-query"; -import { APIClient } from "../../api/APIClient"; -import { EmptySimple } from "../../components/emptystates"; -import { useToggle } from "../../hooks/hooks"; -import { NotificationAddForm, NotificationUpdateForm } from "../../forms/settings/NotificationForms"; +import { useQuery } from "@tanstack/react-query"; import { Switch } from "@headlessui/react"; -import { classNames } from "../../utils"; -import { componentMapType } from "../../forms/settings/DownloadClientForms"; + +import { APIClient } from "@api/APIClient"; +import { EmptySimple } from "@components/emptystates"; +import { useToggle } from "@hooks/hooks"; +import { NotificationAddForm, NotificationUpdateForm } from "@forms/settings/NotificationForms"; +import { classNames } from "@utils"; +import { componentMapType } from "@forms/settings/DownloadClientForms"; + +export const notificationKeys = { + all: ["notifications"] as const, + lists: () => [...notificationKeys.all, "list"] as const, + details: () => [...notificationKeys.all, "detail"] as const, + detail: (id: number) => [...notificationKeys.details(), id] as const +}; function NotificationSettings() { const [addNotificationsIsOpen, toggleAddNotifications] = useToggle(false); - const { data } = useQuery( - "notifications", - () => APIClient.notifications.getAll(), - { refetchOnWindowFocus: false } + const { data } = useQuery({ + queryKey: notificationKeys.lists(), + queryFn: APIClient.notifications.getAll, + refetchOnWindowFocus: false } ); return ( diff --git a/web/src/screens/settings/Releases.tsx b/web/src/screens/settings/Releases.tsx index d85778f..68b0831 100644 --- a/web/src/screens/settings/Releases.tsx +++ b/web/src/screens/settings/Releases.tsx @@ -1,29 +1,30 @@ import { useRef } from "react"; -import { useMutation, useQueryClient } from "react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "react-hot-toast"; -import { APIClient } from "../../api/APIClient"; -import Toast from "../../components/notifications/Toast"; -import { useToggle } from "../../hooks/hooks"; -import { DeleteModal } from "../../components/modals"; + +import { APIClient } from "@api/APIClient"; +import Toast from "@components/notifications/Toast"; +import { useToggle } from "@hooks/hooks"; +import { DeleteModal } from "@components/modals"; +import { releaseKeys } from "@screens/releases/ReleaseTable"; function ReleaseSettings() { const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); const queryClient = useQueryClient(); - const deleteMutation = useMutation(() => APIClient.release.delete(), { + const deleteMutation = useMutation({ + mutationFn: APIClient.release.delete, onSuccess: () => { toast.custom((t) => ( )); // Invalidate filters just in case, most likely not necessary but can't hurt. - queryClient.invalidateQueries("releases"); + queryClient.invalidateQueries({ queryKey: releaseKeys.lists() }); } }); - const deleteAction = () => { - deleteMutation.mutate(); - }; + const deleteAction = () => deleteMutation.mutate(); const cancelModalButtonRef = useRef(null); diff --git a/web/src/utils/Context.ts b/web/src/utils/Context.ts index e076e60..2931dc3 100644 --- a/web/src/utils/Context.ts +++ b/web/src/utils/Context.ts @@ -1,6 +1,5 @@ import { newRidgeState } from "react-ridge-state"; - export const InitializeGlobalContext = () => { const auth_ctx = localStorage.getItem("auth"); if (auth_ctx) diff --git a/web/tsconfig.json b/web/tsconfig.json index 26632c1..642af79 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -21,7 +21,8 @@ "jsx": "react-jsx", "noFallthroughCasesInSwitch": true, "paths": { - "@/*": ["./src/*"] + "@*": ["./src/*"], + "@app*": ["./src/*"] } }, "include": [ diff --git a/web/vite.config.ts b/web/vite.config.ts index 985eba9..d519716 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -12,9 +12,18 @@ export default ({ mode }: { mode: any }) => { base: "", plugins: [react()], resolve: { - alias: { - "@": fileURLToPath(new URL("./src", import.meta.url)) - } + alias: [ + { find: "@", replacement: fileURLToPath(new URL("./src/", import.meta.url)) }, + { find: "@app", replacement: fileURLToPath(new URL("./src/", import.meta.url)) }, + { find: "@components", replacement: fileURLToPath(new URL("./src/components", import.meta.url)) }, + { find: "@forms", replacement: fileURLToPath(new URL("./src/forms", import.meta.url)) }, + { find: "@hooks", replacement: fileURLToPath(new URL("./src/hooks", import.meta.url)) }, + { find: "@api", replacement: fileURLToPath(new URL("./src/api", import.meta.url)) }, + { find: "@screens", replacement: fileURLToPath(new URL("./src/screens", import.meta.url)) }, + { find: "@utils", replacement: fileURLToPath(new URL("./src/utils", import.meta.url)) }, + { find: "@types", replacement: fileURLToPath(new URL("./src/types", import.meta.url)) }, + { find: "@domain", replacement: fileURLToPath(new URL("./src/domain", import.meta.url)) } + ] }, server: { port: 3000, diff --git a/web/yarn.lock b/web/yarn.lock index c8530b6..46d5d2c 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -48,7 +48,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -703,6 +703,56 @@ __metadata: languageName: node linkType: hard +"@tanstack/match-sorter-utils@npm:^8.7.0": + version: 8.8.4 + resolution: "@tanstack/match-sorter-utils@npm:8.8.4" + dependencies: + remove-accents: 0.4.2 + checksum: d005f500754f52ef94966cbbe4217f26e7e3c07291faa2578b06bca9a5abe01689569994c37a1d01c6e783addf5ffbb28fa82eba7961d36eabf43ec43d1e496b + languageName: node + linkType: hard + +"@tanstack/query-core@npm:4.29.1": + version: 4.29.1 + resolution: "@tanstack/query-core@npm:4.29.1" + checksum: e7a5a73e5e743411e39348bee3ded4b6c5dd3a70009e72e067e257d5f3d612bfe8df0998cddff6ad7a078e0bcf7a3c92ded669758ca697055472ce910e23a368 + languageName: node + linkType: hard + +"@tanstack/react-query-devtools@npm:^4.29.3": + version: 4.29.3 + resolution: "@tanstack/react-query-devtools@npm:4.29.3" + dependencies: + "@tanstack/match-sorter-utils": ^8.7.0 + superjson: ^1.10.0 + use-sync-external-store: ^1.2.0 + peerDependencies: + "@tanstack/react-query": 4.29.3 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: fd52531b4ef8776fdb7f6957216a1a933f3507d7e6b20ca63d6a2fe78e25658b6d1a712a71cca04cd29715bc66c2952312434190faedab1b3702cb71527460ee + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^4.29.3": + version: 4.29.3 + resolution: "@tanstack/react-query@npm:4.29.3" + dependencies: + "@tanstack/query-core": 4.29.1 + use-sync-external-store: ^1.2.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: "*" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 7d33fe001ba6ec2332c28749312e5268e6bc060ea0e7c3faa37a28a12fd4b4fddd3803fdfb4e6551997f06d4cd32568e39d9cc9a26c19f40cfef276fad1eaaee + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -1234,13 +1284,6 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:^1.6.16": - version: 1.6.51 - resolution: "big-integer@npm:1.6.51" - checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -1276,22 +1319,6 @@ __metadata: languageName: node linkType: hard -"broadcast-channel@npm:^3.4.1": - version: 3.7.0 - resolution: "broadcast-channel@npm:3.7.0" - dependencies: - "@babel/runtime": ^7.7.2 - detect-node: ^2.1.0 - js-sha3: 0.8.0 - microseconds: 0.2.0 - nano-time: 1.0.0 - oblivious-set: 1.0.0 - rimraf: 3.0.2 - unload: 2.2.0 - checksum: 803794c48dcce7f03aca69797430bd8b1c4cfd70b7de22079cd89567eeffaa126a1db98c7c2d86af8131d9bb41ed367c0fef96dfb446151c927b831572c621fc - languageName: node - linkType: hard - "browserslist@npm:^4.21.5": version: 4.21.5 resolution: "browserslist@npm:4.21.5" @@ -1500,6 +1527,15 @@ __metadata: languageName: node linkType: hard +"copy-anything@npm:^3.0.2": + version: 3.0.3 + resolution: "copy-anything@npm:3.0.3" + dependencies: + is-what: ^4.1.8 + checksum: d456dc5ec98dee7c7cf87d809eac30dc2ac942acd4cf970fab394e280ceb6dd7a8a7a5a44fcbcc50e0206658de3cc20b92863562f5797930bb2619f164f4c182 + languageName: node + linkType: hard + "cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" @@ -1613,13 +1649,6 @@ __metadata: languageName: node linkType: hard -"detect-node@npm:^2.0.4, detect-node@npm:^2.1.0": - version: 2.1.0 - resolution: "detect-node@npm:2.1.0" - checksum: 832184ec458353e41533ac9c622f16c19f7c02d8b10c303dfd3a756f56be93e903616c0bb2d4226183c9351c15fc0b3dba41a17a2308262afabcfa3776e6ae6e - languageName: node - linkType: hard - "didyoumean@npm:^1.2.2": version: 1.2.2 resolution: "didyoumean@npm:1.2.2" @@ -2956,6 +2985,13 @@ __metadata: languageName: node linkType: hard +"is-what@npm:^4.1.8": + version: 4.1.8 + resolution: "is-what@npm:4.1.8" + checksum: b9bec3acff102d14ad467f4c74c9886af310fa160e07a63292c8c181e6768c7c4c1054644e13d67185b963644e4a513bce8c6b8ce3d3ca6f9488a69fccad5f97 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -2979,13 +3015,6 @@ __metadata: languageName: node linkType: hard -"js-sha3@npm:0.8.0": - version: 0.8.0 - resolution: "js-sha3@npm:0.8.0" - checksum: 75df77c1fc266973f06cce8309ce010e9e9f07ec35ab12022ed29b7f0d9c8757f5a73e1b35aa24840dced0dea7059085aa143d817aea9e188e2a80d569d9adce - languageName: node - linkType: hard - "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -3193,16 +3222,6 @@ __metadata: languageName: node linkType: hard -"match-sorter@npm:^6.0.2": - version: 6.3.1 - resolution: "match-sorter@npm:6.3.1" - dependencies: - "@babel/runtime": ^7.12.5 - remove-accents: 0.4.2 - checksum: a4b02b676ac4ce64a89a091539ee4a70a802684713bcf06f2b70787927f510fe8a2adc849f9288857a90906083ad303467e530e8723b4a9756df9994fc164550 - languageName: node - linkType: hard - "memoize-one@npm:^6.0.0": version: 6.0.0 resolution: "memoize-one@npm:6.0.0" @@ -3234,13 +3253,6 @@ __metadata: languageName: node linkType: hard -"microseconds@npm:0.2.0": - version: 0.2.0 - resolution: "microseconds@npm:0.2.0" - checksum: 22bfa8553f92c7d95afff6de0aeb2aecf750680d41b8c72b02098ccc5bbbb0a384380ff539292dbd3788f5dfc298682f9d38a2b4c101f5ee2c9471d53934c5fa - languageName: node - linkType: hard - "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -3393,15 +3405,6 @@ __metadata: languageName: node linkType: hard -"nano-time@npm:1.0.0": - version: 1.0.0 - resolution: "nano-time@npm:1.0.0" - dependencies: - big-integer: ^1.6.16 - checksum: eef8548546cc1020625f8e44751a7263e9eddf0412a6a1a6c80a8d2be2ea7973622804a977cdfe796807b85b20ff6c8ba340e8dd20effcc7078193ed5edbb5d4 - languageName: node - linkType: hard - "nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" @@ -3588,13 +3591,6 @@ __metadata: languageName: node linkType: hard -"oblivious-set@npm:1.0.0": - version: 1.0.0 - resolution: "oblivious-set@npm:1.0.0" - checksum: f31740ea9c3a8242ad2324e4ebb9a35359fbc2e6e7131731a0fc1c8b7b1238eb07e4c8c631a38535243a7b8e3042b7e89f7dc2a95d2989afd6f80bd5793b0aab - languageName: node - linkType: hard - "once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -4009,24 +4005,6 @@ __metadata: languageName: node linkType: hard -"react-query@npm:^3.39.1": - version: 3.39.3 - resolution: "react-query@npm:3.39.3" - dependencies: - "@babel/runtime": ^7.5.5 - broadcast-channel: ^3.4.1 - match-sorter: ^6.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - checksum: d2de6a0992dbf039ff2de564de1ae6361f8ac7310159dae42ec16f833b79c05caedced187235c42373ac331cc5f2fe9e2b31b14ae75a815e86d86e30ca9887ad - languageName: node - linkType: hard - "react-ridge-state@npm:4.2.2": version: 4.2.2 resolution: "react-ridge-state@npm:4.2.2" @@ -4260,7 +4238,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:3.0.2, rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -4588,6 +4566,15 @@ __metadata: languageName: node linkType: hard +"superjson@npm:^1.10.0": + version: 1.12.3 + resolution: "superjson@npm:1.12.3" + dependencies: + copy-anything: ^3.0.2 + checksum: 3549cc1d03e93745632d8114f91ed1668d81a0cf4c618f8f89a1b06f426a9cd1a2879f0e79469a6a193fd19dcea9a8fecff6215d12527b98c40c67cd98f185d3 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -4826,16 +4813,6 @@ __metadata: languageName: node linkType: hard -"unload@npm:2.2.0": - version: 2.2.0 - resolution: "unload@npm:2.2.0" - dependencies: - "@babel/runtime": ^7.6.2 - detect-node: ^2.0.4 - checksum: 88ba950c5ff83ab4f9bbd8f63bbf19ba09687ed3c434efd43b7338cc595bc574df8f9b155ee6eee7a435de3d3a4a226726988428977a68ba4907045f1fac5d41 - languageName: node - linkType: hard - "update-browserslist-db@npm:^1.0.10": version: 1.0.11 resolution: "update-browserslist-db@npm:1.0.11" @@ -4871,6 +4848,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.2.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -4934,6 +4920,8 @@ __metadata: "@heroicons/react": ^2.0.11 "@hookform/error-message": ^2.0.0 "@tailwindcss/forms": ^0.5.2 + "@tanstack/react-query": ^4.29.3 + "@tanstack/react-query-devtools": ^4.29.3 "@types/node": ^18.0.0 "@types/react": ^18.0.12 "@types/react-dom": ^18.0.5 @@ -4962,7 +4950,6 @@ __metadata: react-multi-select-component: ^4.2.9 react-popper-tooltip: ^4.4.2 react-portal: ^4.2.2 - react-query: ^3.39.1 react-ridge-state: 4.2.2 react-router-dom: ^6.3.0 react-select: ^5.3.2
  • {