Create useMigration helper

This commit is contained in:
Zoe Roux 2025-06-18 12:05:10 +02:00
parent 36abadc2cc
commit 3a9cb262f8
No known key found for this signature in database
4 changed files with 71 additions and 41 deletions

View File

@ -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 (

View File

@ -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: [] });

View File

@ -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 <Parser extends z.ZodTypeAny>(context: {
const queryFn = async <Parser extends z.ZodTypeAny>(context: {
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
url: string;
body?: object;
@ -22,7 +29,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(context: {
plainText?: boolean;
authToken: string | null;
parser?: Parser;
signal: AbortSignal;
signal?: AbortSignal;
}): Promise<z.infer<Parser>> => {
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 <Parser extends z.ZodTypeAny>(context: {
return parsed.data;
};
export type MutationParam = {
params?: Record<string, number | string>;
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<T = unknown, Ret = T> = {
};
};
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 = <Data,>(query: QueryIdentifier<Data>) => {
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 = <Data, Ret>(query: QueryIdentifier<Data, Ret>) =
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<Data>) => 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 = <T = void,>({
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<MutationParams>;
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;
};

View File

@ -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();