Compare commits

...

2 commits

Author SHA1 Message Date
4c8ed09429
Merge branch 'front' of ssh://meowgit.nekoea.red:22222/nihonium/nyanimedb into front
All checks were successful
Build (frontend build only) / build (push) Successful in 2m35s
2025-12-20 02:30:21 +03:00
b572a6b20c
feat: AuthClient 2025-12-20 02:30:10 +03:00
2 changed files with 62 additions and 1 deletions

View file

@ -0,0 +1,55 @@
import { createClient, createConfig } from "../client";
import type { ClientOptions as ClientOptions2 } from '../types.gen';
import type { Client, RequestOptions, RequestResult } from "../client";
import { refreshTokens } from "../../auth";
import type { ResponseStyle } from "../client";
let refreshPromise: Promise<boolean> | null = null;
async function getRefreshed(): Promise<boolean> {
if (!refreshPromise) {
refreshPromise = (async () => {
try {
const res = await refreshTokens();
// consider refresh successful if res.data exists
return !!res.data;
} catch (err) {
return false; // failed to refresh
}
})();
}
return refreshPromise;
}
const baseClient = createClient(createConfig<ClientOptions2>({ baseUrl: '/api/v1' }));
export const authClient: Client = {
...baseClient,
request: function <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 'method'>
): RequestResult<TData, TError, ThrowOnError, TResponseStyle> {
// Wrap logic inside a Promise to satisfy RequestResult type
return baseClient.request<TData, TError, ThrowOnError, TResponseStyle>(options).catch(async (err: any) => {
if (err?.status === 401) {
const refreshed = await getRefreshed();
if (!refreshed) {
localStorage.clear();
window.location.href = "/login";
throw err;
}
// Retry original request
return baseClient.request<TData, TError, ThrowOnError, TResponseStyle>(options);
}
throw err;
}) as RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
},
};

View file

@ -9,6 +9,7 @@ import { getTitles, type CursorObj, type Title, type TitleSort } from "../../api
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
import { Link } from "react-router-dom";
import { type TitlesFilter, TitlesFilterPanel } from "../../components/TitlesFilterPanel/TitlesFilterPanel";
import { authClient } from "../../api/AuthClient/AuthClient";
const PAGE_SIZE = 10;
@ -51,7 +52,12 @@ export default function TitlesPage() {
offset: PAGE_SIZE,
fields: "all",
},
});
client: authClient,
},);
if (response.response.status === 403) {
}
return {
items: response.data?.data ?? [],