diff --git a/front/src/components/context-menus.tsx b/front/src/components/context-menus.tsx index ca89af3e..8c6a2fc7 100644 --- a/front/src/components/context-menus.tsx +++ b/front/src/components/context-menus.tsx @@ -23,7 +23,6 @@ import Refresh from "@material-symbols/svg-400/rounded/autorenew.svg"; import Info from "@material-symbols/svg-400/rounded/info.svg"; import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg"; import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { ComponentProps } from "react"; import { useTranslation } from "react-i18next"; import { Platform } from "react-native"; @@ -31,7 +30,7 @@ import { useYoshiki } from "yoshiki/native"; import { WatchStatusV } from "~/models"; import { HR, IconButton, Menu, tooltip } from "~/primitives"; import { useAccount } from "~/providers/account-context"; -import { queryFn } from "~/query"; +import { useMutation } from "~/query"; // import { useDownloader } from "../../packages/ui/src/downloadses/ui/src/downloads"; export const EpisodesContext = ({ @@ -53,22 +52,19 @@ export const EpisodesContext = ({ const { css } = useYoshiki(); const { t } = useTranslation(); - const queryClient = useQueryClient(); const mutation = useMutation({ - mutationFn: (newStatus: WatchStatusV | null) => - queryFn({ - path: [type, slug, "watchStatus", newStatus && `?status=${newStatus}`], - method: newStatus ? "POST" : "DELETE", - }), - onSettled: async () => await queryClient.invalidateQueries({ queryKey: [type, slug] }), + path: [type, slug, "watchStatus"], + compute: (newStatus: WatchStatusV | null) => ({ + method: newStatus ? "POST" : "DELETE", + params: newStatus ? { status: newStatus } : undefined, + }), + invalidate: [type, slug], }); const metadataRefreshMutation = useMutation({ - mutationFn: () => - queryFn({ - path: [type, slug, "refresh"], - method: "POST", - }), + method: "POST", + path: [type, slug, "refresh"], + invalidate: null, }); return ( diff --git a/front/src/providers/account-context.tsx b/front/src/providers/account-context.tsx index 8fbfdc10..4bb6e8da 100644 --- a/front/src/providers/account-context.tsx +++ b/front/src/providers/account-context.tsx @@ -1,10 +1,10 @@ import { createContext, useContext } from "react"; -import { ServerInfoP, type Account, type Token } from "~/models"; +import { type Account, ServerInfoP, type Token } from "~/models"; import { useFetch } from "~/query"; export const AccountContext = createContext<{ apiUrl: string; - authToken: Token | null; + authToken: string | null; //Token | null; selectedAccount: Account | null; accounts: (Account & { select: () => void; remove: () => void })[]; }>({ apiUrl: "api", authToken: null, selectedAccount: null, accounts: [] }); diff --git a/front/src/query/query.tsx b/front/src/query/query.tsx index dd56fb5d..00dfacb4 100644 --- a/front/src/query/query.tsx +++ b/front/src/query/query.tsx @@ -1,4 +1,11 @@ -import { QueryClient, dehydrate, useInfiniteQuery, useQuery } from "@tanstack/react-query"; +import { + QueryClient, + dehydrate, + useInfiniteQuery, + useQuery, + useQueryClient, + useMutation as useRQMutation, +} from "@tanstack/react-query"; import { useContext } from "react"; import { Platform } from "react-native"; import type { z } from "zod"; @@ -14,7 +21,7 @@ const cleanSlash = (str: string | null, keepFirst = false) => { return str.replace(/^\/|\/$/g, ""); }; -export const queryFn = async (context: { +const queryFn = async (context: { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; url: string; body?: object; @@ -22,7 +29,7 @@ export const queryFn = async (context: { plainText?: boolean; authToken: string | null; parser?: Parser; - signal: AbortSignal; + signal?: AbortSignal; }): Promise> => { if (Platform.OS === "web" && typeof window === "undefined" && context.url.startsWith("/api")) context.url = `${ssrApiUrl}/${context.url.substring(4)}`; @@ -88,13 +95,6 @@ export const queryFn = async (context: { return parsed.data; }; -export type MutationParam = { - params?: Record; - body?: object; - path: string[]; - method: "POST" | "DELETE"; -}; - export const createQueryClient = () => new QueryClient({ defaultOptions: { @@ -105,15 +105,6 @@ export const createQueryClient = () => refetchOnReconnect: false, retry: false, }, - mutations: { - // mutationFn: (({ method, path, body, params }: MutationParam) => { - // return queryFn({ - // method, - // url: keyToUrl(toQueryKey({ path, params })), - // body, - // }); - // }) as any, - }, }, }); @@ -130,7 +121,7 @@ export type QueryIdentifier = { }; }; -export const toQueryKey = (query: { +const toQueryKey = (query: { apiUrl: string; path: (string | undefined)[]; params?: { [query: string]: boolean | number | string | string[] | undefined }; @@ -162,7 +153,7 @@ export const useFetch = (query: QueryIdentifier) => { url: keyToUrl(key), parser: query.parser, signal: ctx.signal, - authToken: authToken?.access_token ?? null, + authToken: authToken ?? null, ...query.options, }), placeholderData: query.placeholderData as any, @@ -181,7 +172,7 @@ export const useInfiniteFetch = (query: QueryIdentifier) = url: (ctx.pageParam as string) ?? keyToUrl(key), parser: Paged(query.parser), signal: ctx.signal, - authToken: authToken?.access_token ?? null, + authToken: authToken ?? null, ...query.options, }), getNextPageParam: (page: Page) => page?.next || undefined, @@ -221,7 +212,7 @@ export const prefetch = async (...queries: QueryIdentifier[]) => { url: keyToUrl(key), parser: Paged(query.parser), signal: ctx.signal, - authToken: authToken?.access_token ?? null, + authToken: authToken ?? null, ...query.options, }), initialPageParam: undefined, @@ -234,7 +225,7 @@ export const prefetch = async (...queries: QueryIdentifier[]) => { url: keyToUrl(key), parser: query.parser, signal: ctx.signal, - authToken: authToken?.access_token ?? null, + authToken: authToken ?? null, ...query.options, }), }); @@ -243,3 +234,46 @@ export const prefetch = async (...queries: QueryIdentifier[]) => { setServerData("queryState", dehydrate(client)); return client; }; + +type MutationParams = { + method?: "POST" | "PUT" | "DELETE"; + path?: string[]; + params?: { [query: string]: boolean | number | string | string[] | undefined }; + body?: object; +}; + +export const useMutation = ({ + compute, + invalidate, + ...queryParams +}: MutationParams & { + compute?: (param: T) => MutationParams; + invalidate: string[] | null; +}) => { + const { apiUrl, authToken } = useContext(AccountContext); + const queryClient = useQueryClient(); + const mutation = useRQMutation({ + mutationFn: (param: T) => { + const { method, path, params, body } = { + ...queryParams, + ...compute?.(param), + } as Required; + + return queryFn({ + method, + url: keyToUrl(toQueryKey({ apiUrl, path, params })), + body, + authToken, + }); + }, + onSuccess: invalidate + ? async () => + await queryClient.invalidateQueries({ + queryKey: toQueryKey({ apiUrl, path: invalidate }), + }) + : undefined, + // TODO: Do something + // onError: () => {} + }); + return mutation; +}; diff --git a/front/src/ui/errors/unauthorized.tsx b/front/src/ui/errors/unauthorized.tsx index 7d6c44bd..143bc3c6 100644 --- a/front/src/ui/errors/unauthorized.tsx +++ b/front/src/ui/errors/unauthorized.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { useYoshiki } from "yoshiki/native"; import { Button, Icon, Link, P, ts } from "~/primitives"; -import { useAccount } from "~/providers/account-provider"; +import { useAccount } from "~/providers/account-context"; export const Unauthorized = ({ missing }: { missing: string[] }) => { const { t } = useTranslation();