Compare commits
No commits in common. "1cdfc905e4748e8dd5d3f851278f3e7a1addcbf1" and "5f33b87eceeed55f6727983a6b48103d780f499f" have entirely different histories.
1cdfc905e4
...
5f33b87ece
@ -4,6 +4,10 @@
|
||||
* OpenAPI definition
|
||||
* OpenAPI spec version: v0
|
||||
*/
|
||||
export interface UpdateMangaDataCommand {
|
||||
mangaId?: number;
|
||||
}
|
||||
|
||||
export interface DefaultResponseDTOVoid {
|
||||
timestamp?: string;
|
||||
data?: unknown;
|
||||
@ -99,9 +103,9 @@ export interface PageMangaListDTO {
|
||||
|
||||
export interface PageableObject {
|
||||
offset?: number;
|
||||
paged?: boolean;
|
||||
pageNumber?: number;
|
||||
pageSize?: number;
|
||||
paged?: boolean;
|
||||
sort?: SortObject;
|
||||
unpaged?: boolean;
|
||||
}
|
||||
@ -148,8 +152,6 @@ export interface MangaDTO {
|
||||
score: number;
|
||||
providers: MangaProviderDTO[];
|
||||
chapterCount: number;
|
||||
favorite: boolean;
|
||||
following: boolean;
|
||||
}
|
||||
|
||||
export interface MangaProviderDTO {
|
||||
@ -171,8 +173,6 @@ export interface MangaChapterImagesDTO {
|
||||
id: number;
|
||||
/** @minLength 1 */
|
||||
mangaTitle: string;
|
||||
previousChapterId?: number;
|
||||
nextChapterId?: number;
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
* @summary Get a list of mangas
|
||||
*/
|
||||
|
||||
@ -32,9 +32,10 @@ export const MangaDexImportDialog = ({
|
||||
mangaDexDialogOpen,
|
||||
onMangaDexDialogOpenChange,
|
||||
}: MangaDexImportDialogProps) => {
|
||||
const formSchema = z.object({
|
||||
value: z.string().min(1, "Please enter a MangaDex ID or URL."),
|
||||
});
|
||||
const formSchema = z
|
||||
.object({
|
||||
value: z.string().min(1, "Please enter a MangaDex ID or URL."),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
|
||||
@ -1,27 +1,18 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Bell,
|
||||
BellOff,
|
||||
BookOpen,
|
||||
Calendar,
|
||||
ChevronDown,
|
||||
Database,
|
||||
Heart,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
useSetFavorite,
|
||||
useSetUnfavorite,
|
||||
} from "@/api/generated/favorite-mangas/favorite-mangas.ts";
|
||||
import {
|
||||
useFetchMangaChapters,
|
||||
useFollowManga,
|
||||
useGetManga,
|
||||
useUnfollowManga,
|
||||
} from "@/api/generated/manga/manga.ts";
|
||||
import { useFetchAllChapters } from "@/api/generated/manga-chapter/manga-chapter.ts";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle.tsx";
|
||||
@ -33,12 +24,10 @@ import {
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible.tsx";
|
||||
import { useAuth } from "@/contexts/AuthContext.tsx";
|
||||
import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
|
||||
import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts";
|
||||
|
||||
const Manga = () => {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const mangaId = Number(params.mangaId);
|
||||
@ -64,65 +53,6 @@ const Manga = () => {
|
||||
|
||||
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) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background">
|
||||
@ -195,57 +125,16 @@ const Manga = () => {
|
||||
{/* Details */}
|
||||
<div className="space-y-6">
|
||||
<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">
|
||||
{mangaData.data?.title}
|
||||
</h1>
|
||||
<div className="flex gap-4 items-center">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="border border-border bg-card text-foreground max-h-6"
|
||||
>
|
||||
{mangaData.data?.status}
|
||||
</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>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="border border-border bg-card text-foreground"
|
||||
>
|
||||
{mangaData.data?.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
{mangaData.data?.authors.join(", ")}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user