refactor #18

Merged
rov merged 1 commits from refactor into main 2026-03-19 15:58:45 -03:00
13 changed files with 698 additions and 584 deletions

View File

@ -32,6 +32,12 @@ export interface ImportMangaResponseDTO {
id: number; id: number;
} }
export interface FileImportRequestDTO {
malId?: string;
aniListId?: string;
files?: Blob[];
}
export interface AuthenticationRequestDTO { export interface AuthenticationRequestDTO {
email: string; email: string;
password: string; password: string;
@ -154,30 +160,30 @@ export interface MangaListDTO {
export interface PageMangaListDTO { export interface PageMangaListDTO {
totalPages?: number; totalPages?: number;
totalElements?: number; totalElements?: number;
pageable?: PageableObject;
first?: boolean;
last?: boolean;
size?: number; size?: number;
content?: MangaListDTO[]; content?: MangaListDTO[];
number?: number; number?: number;
sort?: SortObject; pageable?: PageableObject;
numberOfElements?: number; numberOfElements?: number;
first?: boolean;
last?: boolean;
sort?: SortObject;
empty?: boolean; empty?: boolean;
} }
export interface PageableObject { export interface PageableObject {
offset?: number;
pageNumber?: number; pageNumber?: number;
pageSize?: number; pageSize?: number;
paged?: boolean; paged?: boolean;
unpaged?: boolean; unpaged?: boolean;
offset?: number;
sort?: SortObject; sort?: SortObject;
} }
export interface SortObject { export interface SortObject {
empty?: boolean;
sorted?: boolean; sorted?: boolean;
unsorted?: boolean; unsorted?: boolean;
empty?: boolean;
} }
export interface DefaultResponseDTOMangaDTO { export interface DefaultResponseDTOMangaDTO {
@ -258,26 +264,19 @@ export interface GenreDTO {
name: string; name: string;
} }
export type DownloadChapterArchiveParams = { export type DownloadContentArchiveParams = {
archiveFileType: DownloadChapterArchiveArchiveFileType; contentArchiveFileType: DownloadContentArchiveContentArchiveFileType;
}; };
export type DownloadChapterArchiveArchiveFileType = typeof DownloadChapterArchiveArchiveFileType[keyof typeof DownloadChapterArchiveArchiveFileType]; export type DownloadContentArchiveContentArchiveFileType = typeof DownloadContentArchiveContentArchiveFileType[keyof typeof DownloadContentArchiveContentArchiveFileType];
// eslint-disable-next-line @typescript-eslint/no-redeclare // eslint-disable-next-line @typescript-eslint/no-redeclare
export const DownloadChapterArchiveArchiveFileType = { export const DownloadContentArchiveContentArchiveFileType = {
CBZ: 'CBZ', CBZ: 'CBZ',
CBR: 'CBR', CBR: 'CBR',
} as const; } as const;
export type ImportMultipleFilesBody = {
/** @minLength 1 */
malId: string;
/** List of files to upload */
files: Blob[];
};
export type ResolveMangaIngestReviewParams = { export type ResolveMangaIngestReviewParams = {
id: number; id: number;
malId: string; malId: string;

View File

@ -5,16 +5,20 @@
* OpenAPI spec version: v0 * OpenAPI spec version: v0
*/ */
import { import {
useMutation,
useQuery useQuery
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type { import type {
DataTag, DataTag,
DefinedInitialDataOptions, DefinedInitialDataOptions,
DefinedUseQueryResult, DefinedUseQueryResult,
MutationFunction,
QueryClient, QueryClient,
QueryFunction, QueryFunction,
QueryKey, QueryKey,
UndefinedInitialDataOptions, UndefinedInitialDataOptions,
UseMutationOptions,
UseMutationResult,
UseQueryOptions, UseQueryOptions,
UseQueryResult UseQueryResult
} from '@tanstack/react-query'; } from '@tanstack/react-query';
@ -23,6 +27,7 @@ import type {
DefaultResponseDTOListGenreDTO, DefaultResponseDTOListGenreDTO,
DefaultResponseDTOMangaDTO, DefaultResponseDTOMangaDTO,
DefaultResponseDTOPageMangaListDTO, DefaultResponseDTOPageMangaListDTO,
DefaultResponseDTOVoid,
GetMangasParams GetMangasParams
} from '../api.schemas'; } from '../api.schemas';
@ -33,6 +38,132 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* Update a manga's metadata and cover
* @summary Update manga data
*/
export const updateMangas = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/catalog/mangas/${encodeURIComponent(String(mangaId))}/update`, method: 'POST', signal
},
options);
}
export const getUpdateMangasMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMangas>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof updateMangas>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['updateMangas'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof updateMangas>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return updateMangas(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type UpdateMangasMutationResult = NonNullable<Awaited<ReturnType<typeof updateMangas>>>
export type UpdateMangasMutationError = unknown
/**
* @summary Update manga data
*/
export const useUpdateMangas = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMangas>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof updateMangas>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getUpdateMangasMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Update all manga's metadata and cover
* @summary Update all manga data
*/
export const updateMangas1 = (
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/catalog/mangas/update`, method: 'POST', signal
},
options);
}
export const getUpdateMangas1MutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMangas1>>, TError,void, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof updateMangas1>>, TError,void, TContext> => {
const mutationKey = ['updateMangas1'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof updateMangas1>>, void> = () => {
return updateMangas1(requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type UpdateMangas1MutationResult = NonNullable<Awaited<ReturnType<typeof updateMangas1>>>
export type UpdateMangas1MutationError = unknown
/**
* @summary Update all manga data
*/
export const useUpdateMangas1 = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMangas1>>, TError,void, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof updateMangas1>>,
TError,
void,
TContext
> => {
const mutationOptions = getUpdateMangas1MutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/** /**
* Retrieve a list of mangas with their details. * Retrieve a list of mangas with their details.
* @summary Get a list of mangas * @summary Get a list of mangas

View File

@ -5,23 +5,30 @@
* OpenAPI spec version: v0 * OpenAPI spec version: v0
*/ */
import { import {
useMutation,
useQuery useQuery
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type { import type {
DataTag, DataTag,
DefinedInitialDataOptions, DefinedInitialDataOptions,
DefinedUseQueryResult, DefinedUseQueryResult,
MutationFunction,
QueryClient, QueryClient,
QueryFunction, QueryFunction,
QueryKey, QueryKey,
UndefinedInitialDataOptions, UndefinedInitialDataOptions,
UseMutationOptions,
UseMutationResult,
UseQueryOptions, UseQueryOptions,
UseQueryResult UseQueryResult
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type { import type {
DefaultResponseDTOListMangaContentDTO, DefaultResponseDTOListMangaContentDTO,
DefaultResponseDTOMangaContentImagesDTO DefaultResponseDTOMangaContentImagesDTO,
DefaultResponseDTOVoid,
DownloadContentArchiveParams,
FileImportRequestDTO
} from '../api.schemas'; } from '../api.schemas';
import { customInstance } from '../../api'; import { customInstance } from '../../api';
@ -31,6 +38,147 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* Download content as a compressed file by its ID.
* @summary Download content archive
*/
export const downloadContentArchive = (
mangaContentId: number,
params: DownloadContentArchiveParams,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<Blob>(
{url: `/content/${encodeURIComponent(String(mangaContentId))}/download`, method: 'POST',
params,
responseType: 'blob', signal
},
options);
}
export const getDownloadContentArchiveMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof downloadContentArchive>>, TError,{mangaContentId: number;params: DownloadContentArchiveParams}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof downloadContentArchive>>, TError,{mangaContentId: number;params: DownloadContentArchiveParams}, TContext> => {
const mutationKey = ['downloadContentArchive'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof downloadContentArchive>>, {mangaContentId: number;params: DownloadContentArchiveParams}> = (props) => {
const {mangaContentId,params} = props ?? {};
return downloadContentArchive(mangaContentId,params,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type DownloadContentArchiveMutationResult = NonNullable<Awaited<ReturnType<typeof downloadContentArchive>>>
export type DownloadContentArchiveMutationError = unknown
/**
* @summary Download content archive
*/
export const useDownloadContentArchive = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof downloadContentArchive>>, TError,{mangaContentId: number;params: DownloadContentArchiveParams}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof downloadContentArchive>>,
TError,
{mangaContentId: number;params: DownloadContentArchiveParams},
TContext
> => {
const mutationOptions = getDownloadContentArchiveMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Accepts multiple content files via multipart/form-data and processes them.
* @summary Import multiple files
*/
export const importContentFiles = (
fileImportRequestDTO: FileImportRequestDTO,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
const formData = new FormData();
if(fileImportRequestDTO.malId !== undefined) {
formData.append(`malId`, fileImportRequestDTO.malId)
}
if(fileImportRequestDTO.aniListId !== undefined) {
formData.append(`aniListId`, fileImportRequestDTO.aniListId)
}
if(fileImportRequestDTO.files !== undefined) {
fileImportRequestDTO.files.forEach(value => formData.append(`files`, value));
}
return customInstance<DefaultResponseDTOVoid>(
{url: `/content/import`, method: 'POST',
headers: {'Content-Type': 'multipart/form-data', },
data: formData, signal
},
options);
}
export const getImportContentFilesMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof importContentFiles>>, TError,{data: FileImportRequestDTO}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof importContentFiles>>, TError,{data: FileImportRequestDTO}, TContext> => {
const mutationKey = ['importContentFiles'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof importContentFiles>>, {data: FileImportRequestDTO}> = (props) => {
const {data} = props ?? {};
return importContentFiles(data,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type ImportContentFilesMutationResult = NonNullable<Awaited<ReturnType<typeof importContentFiles>>>
export type ImportContentFilesMutationBody = FileImportRequestDTO
export type ImportContentFilesMutationError = unknown
/**
* @summary Import multiple files
*/
export const useImportContentFiles = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof importContentFiles>>, TError,{data: FileImportRequestDTO}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof importContentFiles>>,
TError,
{data: FileImportRequestDTO},
TContext
> => {
const mutationOptions = getImportContentFilesMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/** /**
* Retrieve the content for a specific manga/content provider combination. * Retrieve the content for a specific manga/content provider combination.
* @summary Get the content for a specific manga/content provider combination * @summary Get the content for a specific manga/content provider combination

View File

@ -1,154 +0,0 @@
/**
* Generated by orval v7.17.0 🍺
* Do not edit manually.
* OpenAPI definition
* OpenAPI spec version: v0
*/
import {
useMutation
} from '@tanstack/react-query';
import type {
MutationFunction,
QueryClient,
UseMutationOptions,
UseMutationResult
} from '@tanstack/react-query';
import type {
DefaultResponseDTOVoid
} from '../api.schemas';
import { customInstance } from '../../api';
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* Remove a manga from favorites for the logged user.
* @summary Unfavorite a manga
*/
export const setUnfavorite = (
id: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/mangas/${encodeURIComponent(String(id))}/unfavorite`, method: 'POST', signal
},
options);
}
export const getSetUnfavoriteMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setUnfavorite>>, TError,{id: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof setUnfavorite>>, TError,{id: number}, TContext> => {
const mutationKey = ['setUnfavorite'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof setUnfavorite>>, {id: number}> = (props) => {
const {id} = props ?? {};
return setUnfavorite(id,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type SetUnfavoriteMutationResult = NonNullable<Awaited<ReturnType<typeof setUnfavorite>>>
export type SetUnfavoriteMutationError = unknown
/**
* @summary Unfavorite a manga
*/
export const useSetUnfavorite = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setUnfavorite>>, TError,{id: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof setUnfavorite>>,
TError,
{id: number},
TContext
> => {
const mutationOptions = getSetUnfavoriteMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Set a manga as favorite for the logged user.
* @summary Favorite a manga
*/
export const setFavorite = (
id: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/mangas/${encodeURIComponent(String(id))}/favorite`, method: 'POST', signal
},
options);
}
export const getSetFavoriteMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setFavorite>>, TError,{id: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof setFavorite>>, TError,{id: number}, TContext> => {
const mutationKey = ['setFavorite'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof setFavorite>>, {id: number}> = (props) => {
const {id} = props ?? {};
return setFavorite(id,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type SetFavoriteMutationResult = NonNullable<Awaited<ReturnType<typeof setFavorite>>>
export type SetFavoriteMutationError = unknown
/**
* @summary Favorite a manga
*/
export const useSetFavorite = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setFavorite>>, TError,{id: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof setFavorite>>,
TError,
{id: number},
TContext
> => {
const mutationOptions = getSetFavoriteMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}

View File

@ -15,8 +15,7 @@ import type {
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type { import type {
DefaultResponseDTOVoid, DefaultResponseDTOVoid
DownloadChapterArchiveParams
} from '../api.schemas'; } from '../api.schemas';
import { customInstance } from '../../api'; import { customInstance } from '../../api';
@ -89,133 +88,4 @@ export const useFetchAllChapters = <TError = unknown,
return useMutation(mutationOptions, queryClient); return useMutation(mutationOptions, queryClient);
} }
/**
* Mark a chapter as read by its ID.
* @summary Mark a chapter as read
*/
export const markAsRead = (
chapterId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/mark-as-read`, method: 'POST', signal
},
options);
}
export const getMarkAsReadMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof markAsRead>>, TError,{chapterId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof markAsRead>>, TError,{chapterId: number}, TContext> => {
const mutationKey = ['markAsRead'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof markAsRead>>, {chapterId: number}> = (props) => {
const {chapterId} = props ?? {};
return markAsRead(chapterId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type MarkAsReadMutationResult = NonNullable<Awaited<ReturnType<typeof markAsRead>>>
export type MarkAsReadMutationError = unknown
/**
* @summary Mark a chapter as read
*/
export const useMarkAsRead = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof markAsRead>>, TError,{chapterId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof markAsRead>>,
TError,
{chapterId: number},
TContext
> => {
const mutationOptions = getMarkAsReadMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Download a chapter as a compressed file by its ID.
* @summary Download chapter archive
*/
export const downloadChapterArchive = (
chapterId: number,
params: DownloadChapterArchiveParams,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<Blob>(
{url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/download`, method: 'POST',
params,
responseType: 'blob', signal
},
options);
}
export const getDownloadChapterArchiveMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof downloadChapterArchive>>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof downloadChapterArchive>>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext> => {
const mutationKey = ['downloadChapterArchive'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof downloadChapterArchive>>, {chapterId: number;params: DownloadChapterArchiveParams}> = (props) => {
const {chapterId,params} = props ?? {};
return downloadChapterArchive(chapterId,params,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type DownloadChapterArchiveMutationResult = NonNullable<Awaited<ReturnType<typeof downloadChapterArchive>>>
export type DownloadChapterArchiveMutationError = unknown
/**
* @summary Download chapter archive
*/
export const useDownloadChapterArchive = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof downloadChapterArchive>>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof downloadChapterArchive>>,
TError,
{chapterId: number;params: DownloadChapterArchiveParams},
TContext
> => {
const mutationOptions = getDownloadChapterArchiveMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}

View File

@ -16,8 +16,6 @@ import type {
import type { import type {
DefaultResponseDTOImportMangaResponseDTO, DefaultResponseDTOImportMangaResponseDTO,
DefaultResponseDTOVoid,
ImportMultipleFilesBody,
ImportRequestDTO ImportRequestDTO
} from '../api.schemas'; } from '../api.schemas';
@ -28,74 +26,6 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* Accepts multiple files via multipart/form-data and processes them.
* @summary Upload multiple files
*/
export const importMultipleFiles = (
importMultipleFilesBody: ImportMultipleFilesBody,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
const formData = new FormData();
formData.append(`malId`, importMultipleFilesBody.malId)
importMultipleFilesBody.files.forEach(value => formData.append(`files`, value));
return customInstance<DefaultResponseDTOVoid>(
{url: `/manga/import/upload`, method: 'POST',
headers: {'Content-Type': 'multipart/form-data', },
data: formData, signal
},
options);
}
export const getImportMultipleFilesMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof importMultipleFiles>>, TError,{data: ImportMultipleFilesBody}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof importMultipleFiles>>, TError,{data: ImportMultipleFilesBody}, TContext> => {
const mutationKey = ['importMultipleFiles'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof importMultipleFiles>>, {data: ImportMultipleFilesBody}> = (props) => {
const {data} = props ?? {};
return importMultipleFiles(data,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type ImportMultipleFilesMutationResult = NonNullable<Awaited<ReturnType<typeof importMultipleFiles>>>
export type ImportMultipleFilesMutationBody = ImportMultipleFilesBody
export type ImportMultipleFilesMutationError = unknown
/**
* @summary Upload multiple files
*/
export const useImportMultipleFiles = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof importMultipleFiles>>, TError,{data: ImportMultipleFilesBody}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof importMultipleFiles>>,
TError,
{data: ImportMultipleFilesBody},
TContext
> => {
const mutationOptions = getImportMultipleFilesMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/** /**
* Imports manga data from content provider into the local database. * Imports manga data from content provider into the local database.
* @summary Import manga from content provider * @summary Import manga from content provider

View File

@ -1,154 +0,0 @@
/**
* Generated by orval v7.17.0 🍺
* Do not edit manually.
* OpenAPI definition
* OpenAPI spec version: v0
*/
import {
useMutation
} from '@tanstack/react-query';
import type {
MutationFunction,
QueryClient,
UseMutationOptions,
UseMutationResult
} from '@tanstack/react-query';
import type {
DefaultResponseDTOVoid
} from '../api.schemas';
import { customInstance } from '../../api';
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* Unfollow the manga specified by its ID.
* @summary Unfollow the manga specified by its ID
*/
export const unfollowManga = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/mangas/${encodeURIComponent(String(mangaId))}/unfollowManga`, method: 'POST', signal
},
options);
}
export const getUnfollowMangaMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof unfollowManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof unfollowManga>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['unfollowManga'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof unfollowManga>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return unfollowManga(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type UnfollowMangaMutationResult = NonNullable<Awaited<ReturnType<typeof unfollowManga>>>
export type UnfollowMangaMutationError = unknown
/**
* @summary Unfollow the manga specified by its ID
*/
export const useUnfollowManga = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof unfollowManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof unfollowManga>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getUnfollowMangaMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Follow the manga specified by its ID.
* @summary Follow the manga specified by its ID
*/
export const followManga = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/mangas/${encodeURIComponent(String(mangaId))}/followManga`, method: 'POST', signal
},
options);
}
export const getFollowMangaMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof followManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof followManga>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['followManga'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof followManga>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return followManga(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type FollowMangaMutationResult = NonNullable<Awaited<ReturnType<typeof followManga>>>
export type FollowMangaMutationError = unknown
/**
* @summary Follow the manga specified by its ID
*/
export const useFollowManga = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof followManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof followManga>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getFollowMangaMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}

View File

@ -0,0 +1,343 @@
/**
* Generated by orval v7.17.0 🍺
* Do not edit manually.
* OpenAPI definition
* OpenAPI spec version: v0
*/
import {
useMutation
} from '@tanstack/react-query';
import type {
MutationFunction,
QueryClient,
UseMutationOptions,
UseMutationResult
} from '@tanstack/react-query';
import type {
DefaultResponseDTOVoid
} from '../api.schemas';
import { customInstance } from '../../api';
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* Unfollow the manga specified by its ID.
* @summary Unfollow the manga specified by its ID
*/
export const unfollowManga = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/user-interaction/manga/${encodeURIComponent(String(mangaId))}/unfollow`, method: 'POST', signal
},
options);
}
export const getUnfollowMangaMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof unfollowManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof unfollowManga>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['unfollowManga'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof unfollowManga>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return unfollowManga(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type UnfollowMangaMutationResult = NonNullable<Awaited<ReturnType<typeof unfollowManga>>>
export type UnfollowMangaMutationError = unknown
/**
* @summary Unfollow the manga specified by its ID
*/
export const useUnfollowManga = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof unfollowManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof unfollowManga>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getUnfollowMangaMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Remove a manga from favorites for the logged user.
* @summary Unfavorite a manga
*/
export const setUnfavorite = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/user-interaction/manga/${encodeURIComponent(String(mangaId))}/unfavorite`, method: 'POST', signal
},
options);
}
export const getSetUnfavoriteMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setUnfavorite>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof setUnfavorite>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['setUnfavorite'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof setUnfavorite>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return setUnfavorite(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type SetUnfavoriteMutationResult = NonNullable<Awaited<ReturnType<typeof setUnfavorite>>>
export type SetUnfavoriteMutationError = unknown
/**
* @summary Unfavorite a manga
*/
export const useSetUnfavorite = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setUnfavorite>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof setUnfavorite>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getSetUnfavoriteMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Follow the manga specified by its ID.
* @summary Follow the manga specified by its ID
*/
export const followManga = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/user-interaction/manga/${encodeURIComponent(String(mangaId))}/follow`, method: 'POST', signal
},
options);
}
export const getFollowMangaMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof followManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof followManga>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['followManga'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof followManga>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return followManga(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type FollowMangaMutationResult = NonNullable<Awaited<ReturnType<typeof followManga>>>
export type FollowMangaMutationError = unknown
/**
* @summary Follow the manga specified by its ID
*/
export const useFollowManga = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof followManga>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof followManga>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getFollowMangaMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Set a manga as favorite for the logged user.
* @summary Favorite a manga
*/
export const setFavorite = (
mangaId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/user-interaction/manga/${encodeURIComponent(String(mangaId))}/favorite`, method: 'POST', signal
},
options);
}
export const getSetFavoriteMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setFavorite>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof setFavorite>>, TError,{mangaId: number}, TContext> => {
const mutationKey = ['setFavorite'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof setFavorite>>, {mangaId: number}> = (props) => {
const {mangaId} = props ?? {};
return setFavorite(mangaId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type SetFavoriteMutationResult = NonNullable<Awaited<ReturnType<typeof setFavorite>>>
export type SetFavoriteMutationError = unknown
/**
* @summary Favorite a manga
*/
export const useSetFavorite = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof setFavorite>>, TError,{mangaId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof setFavorite>>,
TError,
{mangaId: number},
TContext
> => {
const mutationOptions = getSetFavoriteMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}
/**
* Mark content as read by its ID.
* @summary Mark content as read
*/
export const markContentAsRead = (
mangaContentId: number,
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
) => {
return customInstance<DefaultResponseDTOVoid>(
{url: `/user-interaction/content/${encodeURIComponent(String(mangaContentId))}/read`, method: 'POST', signal
},
options);
}
export const getMarkContentAsReadMutationOptions = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof markContentAsRead>>, TError,{mangaContentId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
): UseMutationOptions<Awaited<ReturnType<typeof markContentAsRead>>, TError,{mangaContentId: number}, TContext> => {
const mutationKey = ['markContentAsRead'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof markContentAsRead>>, {mangaContentId: number}> = (props) => {
const {mangaContentId} = props ?? {};
return markContentAsRead(mangaContentId,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type MarkContentAsReadMutationResult = NonNullable<Awaited<ReturnType<typeof markContentAsRead>>>
export type MarkContentAsReadMutationError = unknown
/**
* @summary Mark content as read
*/
export const useMarkContentAsRead = <TError = unknown,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof markContentAsRead>>, TError,{mangaContentId: number}, TContext>, request?: SecondParameter<typeof customInstance>}
, queryClient?: QueryClient): UseMutationResult<
Awaited<ReturnType<typeof markContentAsRead>>,
TError,
{mangaContentId: number},
TContext
> => {
const mutationOptions = getMarkContentAsReadMutationOptions(options);
return useMutation(mutationOptions, queryClient);
}

View File

@ -6,15 +6,12 @@ import type {
MangaListDTO, MangaListDTO,
PageMangaListDTO, PageMangaListDTO,
} from "@/api/generated/api.schemas.ts"; } from "@/api/generated/api.schemas.ts";
import {
useSetFavorite,
useSetUnfavorite,
} from "@/api/generated/favorite-mangas/favorite-mangas.ts";
import { Badge } from "@/components/ui/badge.tsx"; import { Badge } from "@/components/ui/badge.tsx";
import { Button } from "@/components/ui/button.tsx"; import { Button } from "@/components/ui/button.tsx";
import { Card, CardContent } from "@/components/ui/card.tsx"; import { Card, CardContent } from "@/components/ui/card.tsx";
import { useAuth } from "@/contexts/AuthContext.tsx"; import { useAuth } from "@/contexts/AuthContext.tsx";
import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts"; import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts";
import {useSetFavorite, useSetUnfavorite} from "@/api/generated/user-interaction/user-interaction.ts";
interface MangaCardProps { interface MangaCardProps {
manga: MangaListDTO; manga: MangaListDTO;
@ -63,18 +60,18 @@ export const MangaCard = ({ manga, queryKey }: MangaCardProps) => {
const isPendingFavoriteChange = isPendingFavorite || isPendingUnfavorite; const isPendingFavoriteChange = isPendingFavorite || isPendingUnfavorite;
const handleFavoriteClick = useCallback( const handleFavoriteClick = useCallback(
(isFavorite: boolean) => { (isFavorite: boolean) =>
isFavorite isFavorite
? mutateUnfavorite({ id: manga.id }) ? mutateUnfavorite({ mangaId: manga.id })
: mutateFavorite({ id: manga.id }); : mutateFavorite({ mangaId: manga.id })
}, ,
[mutateUnfavorite, manga.id, mutateFavorite], [mutateUnfavorite, manga.id, mutateFavorite],
); );
return ( return (
<Card className="h-full group overflow-hidden border-border bg-card py-0 gap-0 transition-all hover:border-primary/50 hover:shadow-lg hover:shadow-primary/10 cursor-pointer relative"> <Card className="h-full group overflow-hidden border-border bg-card py-0 gap-0 transition-all hover:border-primary/50 hover:shadow-lg hover:shadow-primary/10 cursor-pointer relative">
<CardContent className="p-0"> <CardContent className="p-0">
<div className="relative aspect-[2/3] overflow-hidden bg-muted"> <div className="relative aspect-2/3 overflow-hidden bg-muted">
<Link to={`/manga/${manga.id}`}> <Link to={`/manga/${manga.id}`}>
<img <img
src={ src={

View File

@ -2,7 +2,6 @@ import { FileUp } from "lucide-react";
import type React from "react"; import type React from "react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useImportMultipleFiles } from "@/api/generated/manga-import/manga-import.ts";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
@ -13,6 +12,7 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import {useImportContentFiles} from "@/api/generated/content/content.ts";
interface MangaManualImportDialogProps { interface MangaManualImportDialogProps {
fileImportDialogOpen: boolean; fileImportDialogOpen: boolean;
@ -24,10 +24,11 @@ export const MangaManualImportDialog = ({
onFileImportDialogOpenChange, onFileImportDialogOpenChange,
}: MangaManualImportDialogProps) => { }: MangaManualImportDialogProps) => {
const [malId, setMalId] = useState(""); const [malId, setMalId] = useState("");
const [aniListId, setAniListId] = useState("");
const [dragActive, setDragActive] = useState(false); const [dragActive, setDragActive] = useState(false);
const [files, setFiles] = useState<File[] | null>(null); const [files, setFiles] = useState<File[] | null>(null);
const { mutate, isPending } = useImportMultipleFiles({ const { mutate, isPending } = useImportContentFiles({
mutation: { mutation: {
onSuccess: () => { onSuccess: () => {
setFiles(null); setFiles(null);
@ -45,27 +46,27 @@ export const MangaManualImportDialog = ({
return; return;
} }
if (!malId.trim()) { if (!malId.trim() && !aniListId.trim()) {
alert("Please enter a MyAnimeList manga ID"); alert("Please enter either a AniList or a MyAnimeList ID");
return; return;
} }
let id = malId; // let id = malId;
//
// if (!/^\d+$/.test(malId)) {
// const regex =
// /https?:\/\/(?:www\.)?myanimelist\.net\/(manga)\/(\d+)(?:\/|$)/i;
// const match = malId.match(regex);
//
// if (match) {
// id = match[2];
// } else {
// alert("Invalid MyAnimeList URL or ID");
// return;
// }
// }
if (!/^\d+$/.test(malId)) { mutate({ data: { malId: malId, aniListId: aniListId, files: files } });
const regex =
/https?:\/\/(?:www\.)?myanimelist\.net\/(manga)\/(\d+)(?:\/|$)/i;
const match = malId.match(regex);
if (match) {
id = match[2];
} else {
alert("Invalid MyAnimeList URL or ID");
return;
}
}
mutate({ data: { malId: id, files } });
}; };
const handleDrag = (e: React.DragEvent) => { const handleDrag = (e: React.DragEvent) => {
@ -110,10 +111,21 @@ export const MangaManualImportDialog = ({
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="text-sm font-medium"> <label className="text-sm font-medium">
MyAnimeList Manga URL (or ID) AniList ID
</label> </label>
<Input <Input
placeholder="e.g., https://myanimelist.net/manga/..." placeholder="17"
value={aniListId}
onChange={(e) => setAniListId(e.target.value)}
className="mt-2"
/>
</div>
<div>
<label className="text-sm font-medium">
MyAnimeList ID
</label>
<Input
placeholder="20"
value={malId} value={malId}
onChange={(e) => setMalId(e.target.value)} onChange={(e) => setMalId(e.target.value)}
className="mt-2" className="mt-2"

View File

@ -2,12 +2,9 @@ import { useQueryClient } from "@tanstack/react-query";
import { Check, Database, Download, Eye, Loader2 } from "lucide-react"; import { Check, Database, Download, Eye, Loader2 } from "lucide-react";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import {
useDownloadChapterArchive,
} from "@/api/generated/manga-chapter/manga-chapter.ts";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import ReactCountryFlag from "react-country-flag"; import ReactCountryFlag from "react-country-flag";
import {useGetMangaProviderContent} from "@/api/generated/content/content.ts"; import {useDownloadContentArchive, useGetMangaProviderContent} from "@/api/generated/content/content.ts";
import {useFetchContentProviderContent} from "@/api/generated/ingestion/ingestion.ts"; import {useFetchContentProviderContent} from "@/api/generated/ingestion/ingestion.ts";
interface MangaChapterProps { interface MangaChapterProps {
@ -25,13 +22,13 @@ export const MangaChapter = ({
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutate: mutateDownloadChapterArchive } = useDownloadChapterArchive({ const { mutate: mutateDownloadChapterArchive } = useDownloadContentArchive({
mutation: { mutation: {
onSuccess: (data, { chapterId }) => { onSuccess: (data, { mangaContentId }) => {
const url = window.URL.createObjectURL(data); const url = window.URL.createObjectURL(data);
const link = document.createElement("a"); const link = document.createElement("a");
link.href = url; link.href = url;
link.setAttribute("download", chapterId + ".cbz"); link.setAttribute("download", mangaContentId + ".cbz");
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
link.remove(); link.remove();
@ -129,8 +126,8 @@ export const MangaChapter = ({
variant="outline" variant="outline"
onClick={() => onClick={() =>
mutateDownloadChapterArchive({ mutateDownloadChapterArchive({
chapterId: chapter.id, mangaContentId: chapter.id,
params: { archiveFileType: "CBZ" }, params: { contentArchiveFileType: "CBZ" },
}) })
} }
className="gap-2" className="gap-2"

View File

@ -1,13 +1,11 @@
import { ArrowLeft, ChevronLeft, ChevronRight, Home } from "lucide-react"; import { ArrowLeft, ChevronLeft, ChevronRight, Home } from "lucide-react";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router"; import { useNavigate, useParams } from "react-router";
import {
useMarkAsRead,
} from "@/api/generated/manga-chapter/manga-chapter.ts";
import { ThemeToggle } from "@/components/ThemeToggle.tsx"; import { ThemeToggle } from "@/components/ThemeToggle.tsx";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useReadingTracker } from "@/features/chapter/hooks/useReadingTracker.ts"; import { useReadingTracker } from "@/features/chapter/hooks/useReadingTracker.ts";
import {useGetMangaContentImages} from "@/api/generated/content/content.ts"; import {useGetMangaContentImages} from "@/api/generated/content/content.ts";
import {useMarkContentAsRead} from "@/api/generated/user-interaction/user-interaction.ts";
const Chapter = () => { const Chapter = () => {
const { setCurrentChapterPage, getCurrentChapterPage } = useReadingTracker(); const { setCurrentChapterPage, getCurrentChapterPage } = useReadingTracker();
@ -24,7 +22,7 @@ const Chapter = () => {
const [infiniteScroll, setInfiniteScroll] = useState(true); const [infiniteScroll, setInfiniteScroll] = useState(true);
const { data, isLoading } = useGetMangaContentImages(chapterNumber); const { data, isLoading } = useGetMangaContentImages(chapterNumber);
const { mutate } = useMarkAsRead(); const { mutate } = useMarkContentAsRead();
// For infinite scroll mode // For infinite scroll mode
const [visibleCount, setVisibleCount] = useState(1); const [visibleCount, setVisibleCount] = useState(1);
@ -35,7 +33,7 @@ const Chapter = () => {
if (!data || isLoading) return; if (!data || isLoading) return;
if (currentPage === data.data?.contentImageKeys.length) { if (currentPage === data.data?.contentImageKeys.length) {
mutate({ chapterId: chapterNumber }); mutate({ mangaContentId: chapterNumber });
} }
}, [data, mutate, currentPage]); }, [data, mutate, currentPage]);

View File

@ -13,15 +13,6 @@ import {
import { useCallback } from "react"; import { useCallback } from "react";
import { useNavigate, useParams } from "react-router"; import { useNavigate, useParams } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import {
useSetFavorite,
useSetUnfavorite,
} from "@/api/generated/favorite-mangas/favorite-mangas.ts";
import {
useFollowManga,
useUnfollowManga,
} from "@/api/generated/manga/manga.ts";
// import { useFetchAllChapters } from "@/api/generated/manga-chapter/manga-chapter.ts";
import { ThemeToggle } from "@/components/ThemeToggle.tsx"; import { ThemeToggle } from "@/components/ThemeToggle.tsx";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -38,6 +29,12 @@ import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts"; import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts";
import {useGetManga} from "@/api/generated/catalog/catalog.ts"; import {useGetManga} from "@/api/generated/catalog/catalog.ts";
import {useFetchContentProviderContentList} from "@/api/generated/ingestion/ingestion.ts"; import {useFetchContentProviderContentList} from "@/api/generated/ingestion/ingestion.ts";
import {
useFollowManga,
useSetFavorite,
useSetUnfavorite,
useUnfollowManga
} from "@/api/generated/user-interaction/user-interaction.ts";
const Manga = () => { const Manga = () => {
const { isAuthenticated } = useAuth(); const { isAuthenticated } = useAuth();
@ -86,8 +83,8 @@ const Manga = () => {
const handleFavoriteClick = useCallback( const handleFavoriteClick = useCallback(
(isFavorite: boolean) => (isFavorite: boolean) =>
isFavorite isFavorite
? mutateUnfavorite({ id: mangaData?.data?.id ?? -1 }) ? mutateUnfavorite({ mangaId: mangaData?.data?.id ?? -1 })
: mutateFavorite({ id: mangaData?.data?.id ?? -1 }), : mutateFavorite({ mangaId: mangaData?.data?.id ?? -1 }),
[mutateUnfavorite, mutateFavorite, mangaData?.data?.id], [mutateUnfavorite, mutateFavorite, mangaData?.data?.id],
); );
@ -146,7 +143,7 @@ const Manga = () => {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
{/* Header */} {/* Header */}
<header className="border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <header className="border-b border-border bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60">
<div className="px-8 py-6"> <div className="px-8 py-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
@ -171,7 +168,7 @@ const Manga = () => {
{/* Manga Info Section */} {/* Manga Info Section */}
<div className="grid gap-8 lg:grid-cols-[300px_1fr]"> <div className="grid gap-8 lg:grid-cols-[300px_1fr]">
{/* Cover */} {/* Cover */}
<div className="relative aspect-[2/3] overflow-hidden rounded-lg border border-border bg-muted lg:sticky lg:top-8 lg:h-fit"> <div className="relative aspect-2/3 overflow-hidden rounded-lg border border-border bg-muted lg:sticky lg:top-8 lg:h-fit">
<img <img
src={ src={
(mangaData.data?.coverImageKey && (mangaData.data?.coverImageKey &&