90 lines
2.1 KiB
TypeScript
90 lines
2.1 KiB
TypeScript
import { type DefaultError, type UseMutationOptions, type queryOptions, useQuery } from "@tanstack/react-query"
|
|
import { useNavigate } from "@tanstack/react-router"
|
|
import { useEffect } from "react"
|
|
|
|
enum ApiErrorCode {
|
|
BadRequestBody = "BadRequestBody",
|
|
LinkUnreachable = "LinkUnreachable",
|
|
UnsupportedWebsite = "UnsupportedWebsite",
|
|
}
|
|
|
|
class BadRequestError extends Error {
|
|
constructor(
|
|
public readonly code: ApiErrorCode,
|
|
public readonly message: string = "Server returned status 400",
|
|
) {
|
|
super()
|
|
}
|
|
}
|
|
class InternalError extends Error {}
|
|
class UnauthenticatedError extends Error {}
|
|
class NotFoundError extends Error {}
|
|
|
|
interface ErrorBody {
|
|
code: string
|
|
message?: string
|
|
}
|
|
|
|
type QueryKey =
|
|
| ["bookmarks" | "tags", ...ReadonlyArray<unknown>]
|
|
| ["bookmarks" | "tags", string, ...ReadonlyArray<unknown>]
|
|
|
|
async function fetchApi(route: string, init?: RequestInit): Promise<Response> {
|
|
const response = await fetch(`${import.meta.env.VITE_API_URL}/api${route}`, {
|
|
credentials: "include",
|
|
...init,
|
|
})
|
|
switch (response.status) {
|
|
case 200:
|
|
case 204:
|
|
return response
|
|
case 400: {
|
|
const body: ErrorBody = await response.json()
|
|
throw new BadRequestError(body.code as ApiErrorCode, body.message)
|
|
}
|
|
case 401: {
|
|
throw new UnauthenticatedError()
|
|
}
|
|
case 404:
|
|
throw new NotFoundError()
|
|
default:
|
|
throw new InternalError()
|
|
}
|
|
}
|
|
|
|
function useAuthenticatedQuery<TData>(queryKey: QueryKey, fn: () => Promise<TData>) {
|
|
const query = useQuery({
|
|
queryKey,
|
|
queryFn: fn,
|
|
retry: false,
|
|
})
|
|
|
|
const navigate = useNavigate()
|
|
|
|
useEffect(() => {
|
|
if (query.error && query.error instanceof UnauthenticatedError) {
|
|
navigate({ to: "/login", replace: true })
|
|
}
|
|
}, [query.error, navigate])
|
|
|
|
return query
|
|
}
|
|
|
|
function mutationOptions<TData = unknown, TError = DefaultError, TVariables = void, TContext = unknown>(
|
|
options: UseMutationOptions<TData, TError, TVariables, TContext>,
|
|
): UseMutationOptions<TData, TError, TVariables, TContext> {
|
|
return options
|
|
}
|
|
|
|
export {
|
|
ApiErrorCode,
|
|
BadRequestError,
|
|
InternalError,
|
|
UnauthenticatedError,
|
|
NotFoundError,
|
|
fetchApi,
|
|
useAuthenticatedQuery,
|
|
mutationOptions,
|
|
}
|
|
export type { QueryKey }
|