Compare commits
No commits in common. "1cdfc905e4748e8dd5d3f851278f3e7a1addcbf1" and "5f33b87eceeed55f6727983a6b48103d780f499f" have entirely different histories.
1cdfc905e4
...
5f33b87ece
@ -4,6 +4,10 @@
|
|||||||
* OpenAPI definition
|
* OpenAPI definition
|
||||||
* OpenAPI spec version: v0
|
* OpenAPI spec version: v0
|
||||||
*/
|
*/
|
||||||
|
export interface UpdateMangaDataCommand {
|
||||||
|
mangaId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DefaultResponseDTOVoid {
|
export interface DefaultResponseDTOVoid {
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
data?: unknown;
|
data?: unknown;
|
||||||
@ -99,9 +103,9 @@ export interface PageMangaListDTO {
|
|||||||
|
|
||||||
export interface PageableObject {
|
export interface PageableObject {
|
||||||
offset?: number;
|
offset?: number;
|
||||||
paged?: boolean;
|
|
||||||
pageNumber?: number;
|
pageNumber?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
paged?: boolean;
|
||||||
sort?: SortObject;
|
sort?: SortObject;
|
||||||
unpaged?: boolean;
|
unpaged?: boolean;
|
||||||
}
|
}
|
||||||
@ -148,8 +152,6 @@ export interface MangaDTO {
|
|||||||
score: number;
|
score: number;
|
||||||
providers: MangaProviderDTO[];
|
providers: MangaProviderDTO[];
|
||||||
chapterCount: number;
|
chapterCount: number;
|
||||||
favorite: boolean;
|
|
||||||
following: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MangaProviderDTO {
|
export interface MangaProviderDTO {
|
||||||
@ -171,8 +173,6 @@ export interface MangaChapterImagesDTO {
|
|||||||
id: number;
|
id: number;
|
||||||
/** @minLength 1 */
|
/** @minLength 1 */
|
||||||
mangaTitle: string;
|
mangaTitle: string;
|
||||||
previousChapterId?: number;
|
|
||||||
nextChapterId?: number;
|
|
||||||
chapterImageKeys: string[];
|
chapterImageKeys: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
src/api/generated/dev-controller/dev-controller.ts
Normal file
86
src/api/generated/dev-controller/dev-controller.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v7.15.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 {
|
||||||
|
UpdateMangaDataCommand
|
||||||
|
} from '../api.schemas';
|
||||||
|
|
||||||
|
import { customInstance } from '../../api';
|
||||||
|
|
||||||
|
|
||||||
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const sendRecord = (
|
||||||
|
updateMangaDataCommand: UpdateMangaDataCommand,
|
||||||
|
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
|
||||||
|
|
||||||
|
return customInstance<string>(
|
||||||
|
{url: `/records`, method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json', },
|
||||||
|
data: updateMangaDataCommand, signal
|
||||||
|
},
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getSendRecordMutationOptions = <TError = unknown,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof sendRecord>>, TError,{data: UpdateMangaDataCommand}, TContext>, request?: SecondParameter<typeof customInstance>}
|
||||||
|
): UseMutationOptions<Awaited<ReturnType<typeof sendRecord>>, TError,{data: UpdateMangaDataCommand}, TContext> => {
|
||||||
|
|
||||||
|
const mutationKey = ['sendRecord'];
|
||||||
|
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 sendRecord>>, {data: UpdateMangaDataCommand}> = (props) => {
|
||||||
|
const {data} = props ?? {};
|
||||||
|
|
||||||
|
return sendRecord(data,requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions }}
|
||||||
|
|
||||||
|
export type SendRecordMutationResult = NonNullable<Awaited<ReturnType<typeof sendRecord>>>
|
||||||
|
export type SendRecordMutationBody = UpdateMangaDataCommand
|
||||||
|
export type SendRecordMutationError = unknown
|
||||||
|
|
||||||
|
export const useSendRecord = <TError = unknown,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof sendRecord>>, TError,{data: UpdateMangaDataCommand}, TContext>, request?: SecondParameter<typeof customInstance>}
|
||||||
|
, queryClient?: QueryClient): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof sendRecord>>,
|
||||||
|
TError,
|
||||||
|
{data: UpdateMangaDataCommand},
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
|
||||||
|
const mutationOptions = getSendRecordMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions, queryClient);
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,154 +0,0 @@
|
|||||||
/**
|
|
||||||
* Generated by orval v7.15.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];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue the retrieval of the manga lists from the content providers
|
|
||||||
* @summary Queue update manga list
|
|
||||||
*/
|
|
||||||
export const updateMangaList = (
|
|
||||||
|
|
||||||
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
|
|
||||||
) => {
|
|
||||||
|
|
||||||
|
|
||||||
return customInstance<DefaultResponseDTOVoid>(
|
|
||||||
{url: `/management/update-manga-list`, method: 'POST', signal
|
|
||||||
},
|
|
||||||
options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const getUpdateMangaListMutationOptions = <TError = unknown,
|
|
||||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMangaList>>, TError,void, TContext>, request?: SecondParameter<typeof customInstance>}
|
|
||||||
): UseMutationOptions<Awaited<ReturnType<typeof updateMangaList>>, TError,void, TContext> => {
|
|
||||||
|
|
||||||
const mutationKey = ['updateMangaList'];
|
|
||||||
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 updateMangaList>>, void> = () => {
|
|
||||||
|
|
||||||
|
|
||||||
return updateMangaList(requestOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { mutationFn, ...mutationOptions }}
|
|
||||||
|
|
||||||
export type UpdateMangaListMutationResult = NonNullable<Awaited<ReturnType<typeof updateMangaList>>>
|
|
||||||
|
|
||||||
export type UpdateMangaListMutationError = unknown
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Queue update manga list
|
|
||||||
*/
|
|
||||||
export const useUpdateMangaList = <TError = unknown,
|
|
||||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMangaList>>, TError,void, TContext>, request?: SecondParameter<typeof customInstance>}
|
|
||||||
, queryClient?: QueryClient): UseMutationResult<
|
|
||||||
Awaited<ReturnType<typeof updateMangaList>>,
|
|
||||||
TError,
|
|
||||||
void,
|
|
||||||
TContext
|
|
||||||
> => {
|
|
||||||
|
|
||||||
const mutationOptions = getUpdateMangaListMutationOptions(options);
|
|
||||||
|
|
||||||
return useMutation(mutationOptions, queryClient);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Triggers the cleanup of untracked S3 images
|
|
||||||
* @summary Cleanup unused S3 images
|
|
||||||
*/
|
|
||||||
export const imageCleanup = (
|
|
||||||
|
|
||||||
options?: SecondParameter<typeof customInstance>,signal?: AbortSignal
|
|
||||||
) => {
|
|
||||||
|
|
||||||
|
|
||||||
return customInstance<DefaultResponseDTOVoid>(
|
|
||||||
{url: `/management/image-cleanup`, method: 'POST', signal
|
|
||||||
},
|
|
||||||
options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const getImageCleanupMutationOptions = <TError = unknown,
|
|
||||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof imageCleanup>>, TError,void, TContext>, request?: SecondParameter<typeof customInstance>}
|
|
||||||
): UseMutationOptions<Awaited<ReturnType<typeof imageCleanup>>, TError,void, TContext> => {
|
|
||||||
|
|
||||||
const mutationKey = ['imageCleanup'];
|
|
||||||
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 imageCleanup>>, void> = () => {
|
|
||||||
|
|
||||||
|
|
||||||
return imageCleanup(requestOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { mutationFn, ...mutationOptions }}
|
|
||||||
|
|
||||||
export type ImageCleanupMutationResult = NonNullable<Awaited<ReturnType<typeof imageCleanup>>>
|
|
||||||
|
|
||||||
export type ImageCleanupMutationError = unknown
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Cleanup unused S3 images
|
|
||||||
*/
|
|
||||||
export const useImageCleanup = <TError = unknown,
|
|
||||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof imageCleanup>>, TError,void, TContext>, request?: SecondParameter<typeof customInstance>}
|
|
||||||
, queryClient?: QueryClient): UseMutationResult<
|
|
||||||
Awaited<ReturnType<typeof imageCleanup>>,
|
|
||||||
TError,
|
|
||||||
void,
|
|
||||||
TContext
|
|
||||||
> => {
|
|
||||||
|
|
||||||
const mutationOptions = getImageCleanupMutationOptions(options);
|
|
||||||
|
|
||||||
return useMutation(mutationOptions, queryClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -102,132 +102,6 @@ export const useFetchMangaChapters = <TError = unknown,
|
|||||||
return useMutation(mutationOptions, queryClient);
|
return useMutation(mutationOptions, queryClient);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -32,7 +32,8 @@ export const MangaDexImportDialog = ({
|
|||||||
mangaDexDialogOpen,
|
mangaDexDialogOpen,
|
||||||
onMangaDexDialogOpenChange,
|
onMangaDexDialogOpenChange,
|
||||||
}: MangaDexImportDialogProps) => {
|
}: MangaDexImportDialogProps) => {
|
||||||
const formSchema = z.object({
|
const formSchema = z
|
||||||
|
.object({
|
||||||
value: z.string().min(1, "Please enter a MangaDex ID or URL."),
|
value: z.string().min(1, "Please enter a MangaDex ID or URL."),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,18 @@
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Bell,
|
|
||||||
BellOff,
|
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Calendar,
|
Calendar,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Database,
|
Database,
|
||||||
Heart,
|
|
||||||
Star,
|
Star,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useCallback, useState } from "react";
|
import { useState } 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 {
|
import {
|
||||||
useFetchMangaChapters,
|
useFetchMangaChapters,
|
||||||
useFollowManga,
|
|
||||||
useGetManga,
|
useGetManga,
|
||||||
useUnfollowManga,
|
|
||||||
} from "@/api/generated/manga/manga.ts";
|
} from "@/api/generated/manga/manga.ts";
|
||||||
import { useFetchAllChapters } from "@/api/generated/manga-chapter/manga-chapter.ts";
|
import { useFetchAllChapters } from "@/api/generated/manga-chapter/manga-chapter.ts";
|
||||||
import { ThemeToggle } from "@/components/ThemeToggle.tsx";
|
import { ThemeToggle } from "@/components/ThemeToggle.tsx";
|
||||||
@ -33,12 +24,10 @@ import {
|
|||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible.tsx";
|
} from "@/components/ui/collapsible.tsx";
|
||||||
import { useAuth } from "@/contexts/AuthContext.tsx";
|
|
||||||
import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
|
import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
|
||||||
import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts";
|
import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts";
|
||||||
|
|
||||||
const Manga = () => {
|
const Manga = () => {
|
||||||
const { isAuthenticated } = useAuth();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const mangaId = Number(params.mangaId);
|
const mangaId = Number(params.mangaId);
|
||||||
@ -64,65 +53,6 @@ const Manga = () => {
|
|||||||
|
|
||||||
const [openProviders, setOpenProviders] = useState<Set<number>>(new Set());
|
const [openProviders, setOpenProviders] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
const { mutate: mutateFavorite, isPending: isPendingFavorite } =
|
|
||||||
useSetFavorite({
|
|
||||||
mutation: {
|
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: mutateUnfavorite, isPending: isPendingUnfavorite } =
|
|
||||||
useSetUnfavorite({
|
|
||||||
mutation: {
|
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isPendingFavoriteChange = isPendingFavorite || isPendingUnfavorite;
|
|
||||||
|
|
||||||
const handleFavoriteClick = useCallback(
|
|
||||||
(isFavorite: boolean) => {
|
|
||||||
isFavorite
|
|
||||||
? mutateUnfavorite({ id: mangaData?.data?.id ?? -1 })
|
|
||||||
: mutateFavorite({ id: mangaData?.data?.id ?? -1 });
|
|
||||||
},
|
|
||||||
[mutateUnfavorite, mutateFavorite, mangaData?.data?.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { mutate: mutateFollow, isPending: isPendingFollow } = useFollowManga({
|
|
||||||
mutation: {
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey });
|
|
||||||
toast.success(
|
|
||||||
"We will notify you when new content if available for this manga.",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: mutateUnfollow, isPending: isPendingUnfollow } =
|
|
||||||
useUnfollowManga({
|
|
||||||
mutation: {
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey });
|
|
||||||
toast.success(
|
|
||||||
"You will no longer received notifications for this manga.",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isPendingFollowChange = isPendingFollow || isPendingUnfollow;
|
|
||||||
|
|
||||||
const handleFollowClick = useCallback(
|
|
||||||
(isFollowing: boolean) => {
|
|
||||||
isFollowing
|
|
||||||
? mutateUnfollow({ mangaId: mangaData?.data?.id ?? -1 })
|
|
||||||
: mutateFollow({ mangaId: mangaData?.data?.id ?? -1 });
|
|
||||||
},
|
|
||||||
[mangaData?.data?.id, mutateUnfollow, mutateFollow],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!mangaData) {
|
if (!mangaData) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-background">
|
<div className="flex min-h-screen items-center justify-center bg-background">
|
||||||
@ -195,57 +125,16 @@ const Manga = () => {
|
|||||||
{/* Details */}
|
{/* Details */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-3 flex items-center justify-between gap-4">
|
<div className="mb-3 flex items-start justify-between gap-4">
|
||||||
<h1 className="text-balance text-4xl font-bold tracking-tight text-foreground">
|
<h1 className="text-balance text-4xl font-bold tracking-tight text-foreground">
|
||||||
{mangaData.data?.title}
|
{mangaData.data?.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex gap-4 items-center">
|
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="border border-border bg-card text-foreground max-h-6"
|
className="border border-border bg-card text-foreground"
|
||||||
>
|
>
|
||||||
{mangaData.data?.status}
|
{mangaData.data?.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{isAuthenticated && (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
variant="outline"
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
handleFollowClick(
|
|
||||||
mangaData?.data?.following || false,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
disabled={isPendingFollowChange}
|
|
||||||
>
|
|
||||||
{mangaData?.data?.following ? <BellOff /> : <Bell />}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
variant="outline"
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
handleFavoriteClick(
|
|
||||||
mangaData?.data?.favorite || false,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
disabled={isPendingFavoriteChange}
|
|
||||||
>
|
|
||||||
<Heart
|
|
||||||
className={`h-4 w-4 transition-colors ${
|
|
||||||
mangaData?.data?.favorite
|
|
||||||
? "fill-red-500 text-red-500"
|
|
||||||
: "text-muted-foreground hover:text-red-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg text-muted-foreground">
|
<p className="text-lg text-muted-foreground">
|
||||||
{mangaData.data?.authors.join(", ")}
|
{mangaData.data?.authors.join(", ")}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user