From 4fa67fcc81d2fc2976abf042b60b77356f4af117 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Tue, 21 Oct 2025 20:49:47 -0300 Subject: [PATCH 1/2] feat: add login --- .gitignore | 1 + api/api.ts | 42 +- api/mangamochi.ts | 1970 ++-- app/layout.tsx | 33 +- app/loading.tsx | 2 +- app/login/page.tsx | 173 +- .../[id]/chapter/[chapterNumber]/page.tsx | 83 +- app/manga/[id]/page.tsx | 205 +- app/page.tsx | 73 +- app/profile/page.tsx | 369 +- app/register/page.tsx | 243 +- components/auth-header.tsx | 159 +- components/filter-sidebar.tsx | 89 +- components/manga-card.tsx | 91 +- components/manga-chapter.tsx | 274 +- components/manga-grid.tsx | 8 +- components/manga-pagination.tsx | 53 +- components/theme-provider.tsx | 54 +- components/theme-toggle.tsx | 19 +- components/ui/accordion.tsx | 26 +- components/ui/alert-dialog.tsx | 52 +- components/ui/alert.tsx | 34 +- components/ui/aspect-ratio.tsx | 8 +- components/ui/avatar.tsx | 22 +- components/ui/badge.tsx | 30 +- components/ui/breadcrumb.tsx | 54 +- components/ui/button-group.tsx | 36 +- components/ui/button.tsx | 48 +- components/ui/calendar.tsx | 116 +- components/ui/card.tsx | 48 +- components/ui/carousel.tsx | 174 +- components/ui/chart.tsx | 192 +- components/ui/checkbox.tsx | 16 +- components/ui/collapsible.tsx | 12 +- components/ui/command.tsx | 60 +- components/ui/context-menu.tsx | 64 +- components/ui/dialog.tsx | 50 +- components/ui/drawer.tsx | 54 +- components/ui/dropdown-menu.tsx | 64 +- components/ui/empty.tsx | 50 +- components/ui/field.tsx | 124 +- components/ui/form.tsx | 91 +- components/ui/hover-card.tsx | 20 +- components/ui/input-group.tsx | 106 +- components/ui/input-otp.tsx | 42 +- components/ui/input.tsx | 16 +- components/ui/item.tsx | 104 +- components/ui/kbd.tsx | 18 +- components/ui/label.tsx | 14 +- components/ui/menubar.tsx | 74 +- components/ui/navigation-menu.tsx | 54 +- components/ui/pagination.tsx | 54 +- components/ui/popover.tsx | 22 +- components/ui/progress.tsx | 14 +- components/ui/radio-group.tsx | 20 +- components/ui/resizable.tsx | 24 +- components/ui/scroll-area.tsx | 28 +- components/ui/select.tsx | 58 +- components/ui/separator.tsx | 16 +- components/ui/sheet.tsx | 68 +- components/ui/sidebar.tsx | 420 +- components/ui/skeleton.tsx | 10 +- components/ui/slider.tsx | 20 +- components/ui/sonner.tsx | 37 +- components/ui/spinner.tsx | 12 +- components/ui/switch.tsx | 16 +- components/ui/table.tsx | 56 +- components/ui/tabs.tsx | 24 +- components/ui/textarea.tsx | 12 +- components/ui/toast.tsx | 66 +- components/ui/toaster.tsx | 12 +- components/ui/toggle-group.tsx | 30 +- components/ui/toggle.tsx | 30 +- components/ui/tooltip.tsx | 20 +- components/ui/use-mobile.tsx | 24 +- components/ui/use-toast.ts | 148 +- contexts/auth-context.tsx | 279 +- hooks/use-mobile.ts | 24 +- hooks/use-toast.ts | 148 +- lib/utils.ts | 6 +- next.config.mjs | 4 +- orval.config.ts | 36 +- pnpm-lock.yaml | 8029 ++++++++++++++++- postcss.config.mjs | 6 +- src/components/ui/input.tsx | 10 +- styles/globals.css | 4 +- yarn.lock | 4544 ---------- 87 files changed, 12172 insertions(+), 7973 deletions(-) delete mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 1c4b1cc..7332353 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff +.idea/** .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml diff --git a/api/api.ts b/api/api.ts index dff110d..5411726 100644 --- a/api/api.ts +++ b/api/api.ts @@ -1,22 +1,32 @@ -import axios, {AxiosRequestConfig} from "axios"; +import axios, { AxiosRequestConfig } from "axios"; +import { User } from "@/contexts/auth-context"; export const Api = axios.create({ - baseURL: 'http://localhost:8080', - responseType: 'json', + baseURL: "http://localhost:8080", + responseType: "json", }); -export const customInstance = (config: AxiosRequestConfig, options?: AxiosRequestConfig): Promise => { - const source = axios.CancelToken.source(); - const promise = Api({ - ...config, - ...options, - cancelToken: source.token, - paramsSerializer: {indexes: null} - }).then(({data}) => data); +Api.interceptors.request.use(async (config) => { + const jsonString = localStorage.getItem("userData"); + if (jsonString) { + const userData: User = JSON.parse(jsonString); + config.headers.Authorization = `Bearer ${userData.token}`; + } - // promise.cancel = () => { - // source.cancel("Query was cancelled"); - // } + return config; +}); - return promise; -}; \ No newline at end of file +export const customInstance = ( + config: AxiosRequestConfig, + options?: AxiosRequestConfig, +): Promise => { + const source = axios.CancelToken.source(); + const promise = Api({ + ...config, + ...options, + cancelToken: source.token, + paramsSerializer: { indexes: null }, + }).then(({ data }) => data); + + return promise; +}; diff --git a/api/mangamochi.ts b/api/mangamochi.ts index a42475c..bc1e8d7 100644 --- a/api/mangamochi.ts +++ b/api/mangamochi.ts @@ -4,10 +4,7 @@ * OpenAPI definition * OpenAPI spec version: v0 */ -import { - useMutation, - useQuery -} from '@tanstack/react-query'; +import { useMutation, useQuery } from "@tanstack/react-query"; import type { DataTag, DefinedInitialDataOptions, @@ -20,15 +17,37 @@ import type { UseMutationOptions, UseMutationResult, UseQueryOptions, - UseQueryResult -} from '@tanstack/react-query'; + UseQueryResult, +} from "@tanstack/react-query"; -import { customInstance } from './api'; -export interface AuthenticationRequestDTO { - username?: string; +import { customInstance } from "./api"; +export interface RegistrationRequestDTO { + name?: string; + email?: string; password?: string; } +export interface AuthenticationRequestDTO { + email: string; + password: string; +} + +export type AuthenticationResponseDTORole = + (typeof AuthenticationResponseDTORole)[keyof typeof AuthenticationResponseDTORole]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const AuthenticationResponseDTORole = { + USER: "USER", +} as const; + +export interface AuthenticationResponseDTO { + id: number; + token: string; + email: string; + name: string; + role: AuthenticationResponseDTORole; +} + export interface MangaListDTO { id: number; /** @minLength 1 */ @@ -46,10 +65,10 @@ export interface MangaListDTO { export interface PageMangaListDTO { totalPages?: number; totalElements?: number; - pageable?: PageableObject; size?: number; content?: MangaListDTO[]; number?: number; + pageable?: PageableObject; sort?: SortObject; first?: boolean; last?: boolean; @@ -58,17 +77,17 @@ export interface PageMangaListDTO { } export interface PageableObject { + offset?: number; paged?: boolean; pageNumber?: number; pageSize?: number; - offset?: number; sort?: SortObject; unpaged?: boolean; } export interface SortObject { - sorted?: boolean; empty?: boolean; + sorted?: boolean; unsorted?: boolean; } @@ -119,1005 +138,1524 @@ export interface GenreDTO { } export type DownloadChapterArchiveParams = { -archiveFileType: DownloadChapterArchiveArchiveFileType; + archiveFileType: DownloadChapterArchiveArchiveFileType; }; -export type DownloadChapterArchiveArchiveFileType = typeof DownloadChapterArchiveArchiveFileType[keyof typeof DownloadChapterArchiveArchiveFileType]; - +export type DownloadChapterArchiveArchiveFileType = + (typeof DownloadChapterArchiveArchiveFileType)[keyof typeof DownloadChapterArchiveArchiveFileType]; // eslint-disable-next-line @typescript-eslint/no-redeclare export const DownloadChapterArchiveArchiveFileType = { - CBZ: 'CBZ', - CBR: 'CBR', + CBZ: "CBZ", + CBR: "CBR", } as const; -export type RegisterUser200 = { [key: string]: unknown }; - -export type CreateAuthenticationToken200 = { [key: string]: unknown }; - export type GetMangasParams = { -searchQuery?: string; -genreIds?: number[]; -statuses?: string[]; -/** - * Zero-based page index (0..N) - * @minimum 0 - */ -page?: number; -/** - * The size of the page to be returned - * @minimum 1 - */ -size?: number; -/** - * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. - */ -sort?: string[]; + searchQuery?: string; + genreIds?: number[]; + statuses?: string[]; + /** + * Zero-based page index (0..N) + * @minimum 0 + */ + page?: number; + /** + * The size of the page to be returned + * @minimum 1 + */ + size?: number; + /** + * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + */ + sort?: string[]; }; type SecondParameter unknown> = Parameters[1]; - - /** * Fetch a list of manga chapters for a specific manga/provider combination. * @summary Fetch the available chapters for a specific manga/provider combination */ export const fetchMangaChapters = ( - mangaProviderId: number, - options?: SecondParameter,signal?: AbortSignal + mangaProviderId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-chapters`, method: 'POST', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-chapters`, + method: "POST", + signal, }, - options); - } - + options, + ); +}; +export const getFetchMangaChaptersMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { mangaProviderId: number }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { mangaProviderId: number }, + TContext +> => { + const mutationKey = ["fetchMangaChapters"]; + 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 }; -export const getFetchMangaChaptersMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{mangaProviderId: number}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { mangaProviderId: number } + > = (props) => { + const { mangaProviderId } = props ?? {}; -const mutationKey = ['fetchMangaChapters']; -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}; + return fetchMangaChapters(mangaProviderId, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type FetchMangaChaptersMutationResult = NonNullable< + Awaited> +>; - const mutationFn: MutationFunction>, {mangaProviderId: number}> = (props) => { - const {mangaProviderId} = props ?? {}; +export type FetchMangaChaptersMutationError = unknown; - return fetchMangaChapters(mangaProviderId,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type FetchMangaChaptersMutationResult = NonNullable>> - - export type FetchMangaChaptersMutationError = unknown - - /** +/** * @summary Fetch the available chapters for a specific manga/provider combination */ -export const useFetchMangaChapters = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {mangaProviderId: number}, - TContext - > => { +export const useFetchMangaChapters = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { mangaProviderId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { mangaProviderId: number }, + TContext +> => { + const mutationOptions = getFetchMangaChaptersMutationOptions(options); - const mutationOptions = getFetchMangaChaptersMutationOptions(options); + 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,signal?: AbortSignal + chapterId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(chapterId))}/mark-as-read`, method: 'POST', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(chapterId))}/mark-as-read`, + method: "POST", + signal, }, - options); - } - + options, + ); +}; +export const getMarkAsReadMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { chapterId: number }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + 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 }; -export const getMarkAsReadMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{chapterId: number}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { chapterId: number } + > = (props) => { + const { chapterId } = props ?? {}; -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}; + return markAsRead(chapterId, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type MarkAsReadMutationResult = NonNullable< + Awaited> +>; - const mutationFn: MutationFunction>, {chapterId: number}> = (props) => { - const {chapterId} = props ?? {}; +export type MarkAsReadMutationError = unknown; - return markAsRead(chapterId,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type MarkAsReadMutationResult = NonNullable>> - - export type MarkAsReadMutationError = unknown - - /** +/** * @summary Mark a chapter as read */ -export const useMarkAsRead = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {chapterId: number}, - TContext - > => { +export const useMarkAsRead = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { chapterId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { chapterId: number }, + TContext +> => { + const mutationOptions = getMarkAsReadMutationOptions(options); - const mutationOptions = getMarkAsReadMutationOptions(options); + return useMutation(mutationOptions, queryClient); +}; - return useMutation(mutationOptions, queryClient); - } - /** * @summary Update manga info */ export const updateMangaInfo = ( - mangaId: number, - options?: SecondParameter,signal?: AbortSignal + mangaId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/manga/${encodeURIComponent(String(mangaId))}/info`, method: 'POST', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/manga/${encodeURIComponent(String(mangaId))}/info`, + method: "POST", + signal, }, - options); - } - + options, + ); +}; +export const getUpdateMangaInfoMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { mangaId: number }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { mangaId: number }, + TContext +> => { + const mutationKey = ["updateMangaInfo"]; + 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 }; -export const getUpdateMangaInfoMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{mangaId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{mangaId: number}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { mangaId: number } + > = (props) => { + const { mangaId } = props ?? {}; -const mutationKey = ['updateMangaInfo']; -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}; + return updateMangaInfo(mangaId, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type UpdateMangaInfoMutationResult = NonNullable< + Awaited> +>; - const mutationFn: MutationFunction>, {mangaId: number}> = (props) => { - const {mangaId} = props ?? {}; +export type UpdateMangaInfoMutationError = unknown; - return updateMangaInfo(mangaId,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type UpdateMangaInfoMutationResult = NonNullable>> - - export type UpdateMangaInfoMutationError = unknown - - /** +/** * @summary Update manga info */ -export const useUpdateMangaInfo = (options?: { mutation?:UseMutationOptions>, TError,{mangaId: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {mangaId: number}, - TContext - > => { +export const useUpdateMangaInfo = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { mangaId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { mangaId: number }, + TContext +> => { + const mutationOptions = getUpdateMangaInfoMutationOptions(options); - const mutationOptions = getUpdateMangaInfoMutationOptions(options); + return useMutation(mutationOptions, queryClient); +}; - return useMutation(mutationOptions, queryClient); - } - /** * @summary Download all chapters for a manga provider */ export const downloadAllChapters = ( - mangaProviderId: number, - options?: SecondParameter,signal?: AbortSignal + mangaProviderId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/chapter/${encodeURIComponent(String(mangaProviderId))}/download-all`, method: 'POST', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/chapter/${encodeURIComponent(String(mangaProviderId))}/download-all`, + method: "POST", + signal, }, - options); - } - + options, + ); +}; +export const getDownloadAllChaptersMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { mangaProviderId: number }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { mangaProviderId: number }, + TContext +> => { + const mutationKey = ["downloadAllChapters"]; + 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 }; -export const getDownloadAllChaptersMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{mangaProviderId: number}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { mangaProviderId: number } + > = (props) => { + const { mangaProviderId } = props ?? {}; -const mutationKey = ['downloadAllChapters']; -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}; + return downloadAllChapters(mangaProviderId, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type DownloadAllChaptersMutationResult = NonNullable< + Awaited> +>; - const mutationFn: MutationFunction>, {mangaProviderId: number}> = (props) => { - const {mangaProviderId} = props ?? {}; +export type DownloadAllChaptersMutationError = unknown; - return downloadAllChapters(mangaProviderId,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type DownloadAllChaptersMutationResult = NonNullable>> - - export type DownloadAllChaptersMutationError = unknown - - /** +/** * @summary Download all chapters for a manga provider */ -export const useDownloadAllChapters = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {mangaProviderId: number}, - TContext - > => { +export const useDownloadAllChapters = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { mangaProviderId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { mangaProviderId: number }, + TContext +> => { + const mutationOptions = getDownloadAllChaptersMutationOptions(options); - const mutationOptions = getDownloadAllChaptersMutationOptions(options); + return useMutation(mutationOptions, queryClient); +}; - return useMutation(mutationOptions, queryClient); - } - /** * @summary Fetch chapter */ export const fetchChapter = ( - chapterId: number, - options?: SecondParameter,signal?: AbortSignal + chapterId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/chapter/${encodeURIComponent(String(chapterId))}/fetch`, method: 'POST', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/chapter/${encodeURIComponent(String(chapterId))}/fetch`, + method: "POST", + signal, }, - options); - } - + options, + ); +}; +export const getFetchChapterMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { chapterId: number }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { chapterId: number }, + TContext +> => { + const mutationKey = ["fetchChapter"]; + 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 }; -export const getFetchChapterMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{chapterId: number}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { chapterId: number } + > = (props) => { + const { chapterId } = props ?? {}; -const mutationKey = ['fetchChapter']; -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}; + return fetchChapter(chapterId, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type FetchChapterMutationResult = NonNullable< + Awaited> +>; - const mutationFn: MutationFunction>, {chapterId: number}> = (props) => { - const {chapterId} = props ?? {}; +export type FetchChapterMutationError = unknown; - return fetchChapter(chapterId,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type FetchChapterMutationResult = NonNullable>> - - export type FetchChapterMutationError = unknown - - /** +/** * @summary Fetch chapter */ -export const useFetchChapter = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {chapterId: number}, - TContext - > => { +export const useFetchChapter = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { chapterId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { chapterId: number }, + TContext +> => { + const mutationOptions = getFetchChapterMutationOptions(options); - const mutationOptions = getFetchChapterMutationOptions(options); + return useMutation(mutationOptions, queryClient); +}; - return useMutation(mutationOptions, queryClient); - } - /** * @summary Download chapter archive */ export const downloadChapterArchive = ( - chapterId: number, - params: DownloadChapterArchiveParams, - options?: SecondParameter,signal?: AbortSignal + chapterId: number, + params: DownloadChapterArchiveParams, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/chapter/${encodeURIComponent(String(chapterId))}/download-archive`, method: 'POST', - params, - responseType: 'blob', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/chapter/${encodeURIComponent(String(chapterId))}/download-archive`, + method: "POST", + params, + responseType: "blob", + signal, }, - options); - } - + options, + ); +}; +export const getDownloadChapterArchiveMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { chapterId: number; params: DownloadChapterArchiveParams }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + 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 }; -export const getDownloadChapterArchiveMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { chapterId: number; params: DownloadChapterArchiveParams } + > = (props) => { + const { chapterId, params } = props ?? {}; -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}; + return downloadChapterArchive(chapterId, params, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type DownloadChapterArchiveMutationResult = NonNullable< + Awaited> +>; - const mutationFn: MutationFunction>, {chapterId: number;params: DownloadChapterArchiveParams}> = (props) => { - const {chapterId,params} = props ?? {}; +export type DownloadChapterArchiveMutationError = unknown; - return downloadChapterArchive(chapterId,params,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type DownloadChapterArchiveMutationResult = NonNullable>> - - export type DownloadChapterArchiveMutationError = unknown - - /** +/** * @summary Download chapter archive */ -export const useDownloadChapterArchive = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {chapterId: number;params: DownloadChapterArchiveParams}, - TContext - > => { +export const useDownloadChapterArchive = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { chapterId: number; params: DownloadChapterArchiveParams }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { chapterId: number; params: DownloadChapterArchiveParams }, + TContext +> => { + const mutationOptions = getDownloadChapterArchiveMutationOptions(options); - const mutationOptions = getDownloadChapterArchiveMutationOptions(options); + return useMutation(mutationOptions, queryClient); +}; - return useMutation(mutationOptions, queryClient); - } - +/** + * Register a new user. + * @summary Register user + */ export const registerUser = ( - authenticationRequestDTO: AuthenticationRequestDTO, - options?: SecondParameter,signal?: AbortSignal + registrationRequestDTO: RegistrationRequestDTO, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/auth/register`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: authenticationRequestDTO, signal + return customInstance( + { + url: `http://192.168.1.142:8080/auth/register`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: registrationRequestDTO, + signal, }, - options); - } - + options, + ); +}; +export const getRegisterUserMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: RegistrationRequestDTO }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { data: RegistrationRequestDTO }, + TContext +> => { + const mutationKey = ["registerUser"]; + 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 }; -export const getRegisterUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { data: RegistrationRequestDTO } + > = (props) => { + const { data } = props ?? {}; -const mutationKey = ['registerUser']; -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}; + return registerUser(data, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type RegisterUserMutationResult = NonNullable< + Awaited> +>; +export type RegisterUserMutationBody = RegistrationRequestDTO; +export type RegisterUserMutationError = unknown; - const mutationFn: MutationFunction>, {data: AuthenticationRequestDTO}> = (props) => { - const {data} = props ?? {}; +/** + * @summary Register user + */ +export const useRegisterUser = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: RegistrationRequestDTO }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { data: RegistrationRequestDTO }, + TContext +> => { + const mutationOptions = getRegisterUserMutationOptions(options); - return registerUser(data,requestOptions) - } + return useMutation(mutationOptions, queryClient); +}; - - - - return { mutationFn, ...mutationOptions }} - - export type RegisterUserMutationResult = NonNullable>> - export type RegisterUserMutationBody = AuthenticationRequestDTO - export type RegisterUserMutationError = unknown - - export const useRegisterUser = (options?: { mutation?:UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: AuthenticationRequestDTO}, - TContext - > => { - - const mutationOptions = getRegisterUserMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -export const createAuthenticationToken = ( - authenticationRequestDTO: AuthenticationRequestDTO, - options?: SecondParameter,signal?: AbortSignal +/** + * Authenticate user with email and password. + * @summary Authenticate user + */ +export const authenticateUser = ( + authenticationRequestDTO: AuthenticationRequestDTO, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/auth/login`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: authenticationRequestDTO, signal + return customInstance( + { + url: `http://192.168.1.142:8080/auth/login`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: authenticationRequestDTO, + signal, }, - options); - } - + options, + ); +}; +export const getAuthenticateUserMutationOptions = < + TError = unknown, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AuthenticationRequestDTO }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { data: AuthenticationRequestDTO }, + TContext +> => { + const mutationKey = ["authenticateUser"]; + 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 }; -export const getCreateAuthenticationTokenMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext> => { + const mutationFn: MutationFunction< + Awaited>, + { data: AuthenticationRequestDTO } + > = (props) => { + const { data } = props ?? {}; -const mutationKey = ['createAuthenticationToken']; -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}; + return authenticateUser(data, requestOptions); + }; - + return { mutationFn, ...mutationOptions }; +}; +export type AuthenticateUserMutationResult = NonNullable< + Awaited> +>; +export type AuthenticateUserMutationBody = AuthenticationRequestDTO; +export type AuthenticateUserMutationError = unknown; - const mutationFn: MutationFunction>, {data: AuthenticationRequestDTO}> = (props) => { - const {data} = props ?? {}; +/** + * @summary Authenticate user + */ +export const useAuthenticateUser = ( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AuthenticationRequestDTO }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { data: AuthenticationRequestDTO }, + TContext +> => { + const mutationOptions = getAuthenticateUserMutationOptions(options); - return createAuthenticationToken(data,requestOptions) - } + return useMutation(mutationOptions, queryClient); +}; - - - - return { mutationFn, ...mutationOptions }} - - export type CreateAuthenticationTokenMutationResult = NonNullable>> - export type CreateAuthenticationTokenMutationBody = AuthenticationRequestDTO - export type CreateAuthenticationTokenMutationError = unknown - - export const useCreateAuthenticationToken = (options?: { mutation?:UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: AuthenticationRequestDTO}, - TContext - > => { - - const mutationOptions = getCreateAuthenticationTokenMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - /** * Retrieve a list of mangas with their details. * @summary Get a list of mangas */ export const getMangas = ( - params?: GetMangasParams, - options?: SecondParameter,signal?: AbortSignal + params?: GetMangasParams, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas`, method: 'GET', - params, signal - }, - options); - } - + return customInstance( + { url: `http://192.168.1.142:8080/mangas`, method: "GET", params, signal }, + options, + ); +}; +export const getGetMangasQueryKey = (params?: GetMangasParams) => { + return [ + `http://192.168.1.142:8080/mangas`, + ...(params ? [params] : []), + ] as const; +}; - -export const getGetMangasQueryKey = (params?: GetMangasParams,) => { - return [ - `http://192.168.1.142:8080/mangas`, ...(params ? [params]: []) - ] as const; - } - - -export const getGetMangasQueryOptions = >, TError = unknown>(params?: GetMangasParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} +export const getGetMangasQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + params?: GetMangasParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; -const {query: queryOptions, request: requestOptions} = options ?? {}; + const queryKey = queryOptions?.queryKey ?? getGetMangasQueryKey(params); - const queryKey = queryOptions?.queryKey ?? getGetMangasQueryKey(params); + const queryFn: QueryFunction>> = ({ + signal, + }) => getMangas(params, requestOptions, signal); - + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; - const queryFn: QueryFunction>> = ({ signal }) => getMangas(params, requestOptions, signal); +export type GetMangasQueryResult = NonNullable< + Awaited> +>; +export type GetMangasQueryError = unknown; - - - - - return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type GetMangasQueryResult = NonNullable>> -export type GetMangasQueryError = unknown - - -export function useGetMangas>, TError = unknown>( - params: undefined | GetMangasParams, options: { query:Partial>, TError, TData>> & Pick< +export function useGetMangas< + TData = Awaited>, + TError = unknown, +>( + params: undefined | GetMangasParams, + options: { + query: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< DefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetMangas>, TError = unknown>( - params?: GetMangasParams, options?: { query?:Partial>, TError, TData>> & Pick< + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetMangas< + TData = Awaited>, + TError = unknown, +>( + params?: GetMangasParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< UndefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useGetMangas>, TError = unknown>( - params?: GetMangasParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetMangas< + TData = Awaited>, + TError = unknown, +>( + params?: GetMangasParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; /** * @summary Get a list of mangas */ -export function useGetMangas>, TError = unknown>( - params?: GetMangasParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { +export function useGetMangas< + TData = Awaited>, + TError = unknown, +>( + params?: GetMangasParams, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetMangasQueryOptions(params, options); - const queryOptions = getGetMangasQueryOptions(params,options) + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; + query.queryKey = queryOptions.queryKey; return query; } - - - /** * Retrieve a list of manga chapters for a specific manga/provider combination. * @summary Get the available chapters for a specific manga/provider combination */ export const getMangaChapters = ( - mangaProviderId: number, - options?: SecondParameter,signal?: AbortSignal + mangaProviderId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/chapters`, method: 'GET', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(mangaProviderId))}/chapters`, + method: "GET", + signal, }, - options); - } - + options, + ); +}; +export const getGetMangaChaptersQueryKey = (mangaProviderId?: number) => { + return [ + `http://192.168.1.142:8080/mangas/${mangaProviderId}/chapters`, + ] as const; +}; - -export const getGetMangaChaptersQueryKey = (mangaProviderId?: number,) => { - return [ - `http://192.168.1.142:8080/mangas/${mangaProviderId}/chapters` - ] as const; - } - - -export const getGetMangaChaptersQueryOptions = >, TError = unknown>(mangaProviderId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} +export const getGetMangaChaptersQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + mangaProviderId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; -const {query: queryOptions, request: requestOptions} = options ?? {}; + const queryKey = + queryOptions?.queryKey ?? getGetMangaChaptersQueryKey(mangaProviderId); - const queryKey = queryOptions?.queryKey ?? getGetMangaChaptersQueryKey(mangaProviderId); + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => getMangaChapters(mangaProviderId, requestOptions, signal); - + return { + queryKey, + queryFn, + enabled: !!mangaProviderId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; - const queryFn: QueryFunction>> = ({ signal }) => getMangaChapters(mangaProviderId, requestOptions, signal); +export type GetMangaChaptersQueryResult = NonNullable< + Awaited> +>; +export type GetMangaChaptersQueryError = unknown; - - - - - return { queryKey, queryFn, enabled: !!(mangaProviderId), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type GetMangaChaptersQueryResult = NonNullable>> -export type GetMangaChaptersQueryError = unknown - - -export function useGetMangaChapters>, TError = unknown>( - mangaProviderId: number, options: { query:Partial>, TError, TData>> & Pick< +export function useGetMangaChapters< + TData = Awaited>, + TError = unknown, +>( + mangaProviderId: number, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< DefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetMangaChapters>, TError = unknown>( - mangaProviderId: number, options?: { query?:Partial>, TError, TData>> & Pick< + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetMangaChapters< + TData = Awaited>, + TError = unknown, +>( + mangaProviderId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< UndefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useGetMangaChapters>, TError = unknown>( - mangaProviderId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetMangaChapters< + TData = Awaited>, + TError = unknown, +>( + mangaProviderId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; /** * @summary Get the available chapters for a specific manga/provider combination */ -export function useGetMangaChapters>, TError = unknown>( - mangaProviderId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { +export function useGetMangaChapters< + TData = Awaited>, + TError = unknown, +>( + mangaProviderId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetMangaChaptersQueryOptions( + mangaProviderId, + options, + ); - const queryOptions = getGetMangaChaptersQueryOptions(mangaProviderId,options) + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; + query.queryKey = queryOptions.queryKey; return query; } - - - /** * @summary Get the details of a manga */ export const getManga = ( - mangaId: number, - options?: SecondParameter,signal?: AbortSignal + mangaId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(mangaId))}`, method: 'GET', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(mangaId))}`, + method: "GET", + signal, }, - options); - } - + options, + ); +}; +export const getGetMangaQueryKey = (mangaId?: number) => { + return [`http://192.168.1.142:8080/mangas/${mangaId}`] as const; +}; - -export const getGetMangaQueryKey = (mangaId?: number,) => { - return [ - `http://192.168.1.142:8080/mangas/${mangaId}` - ] as const; - } - - -export const getGetMangaQueryOptions = >, TError = unknown>(mangaId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} +export const getGetMangaQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + mangaId: number, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; -const {query: queryOptions, request: requestOptions} = options ?? {}; + const queryKey = queryOptions?.queryKey ?? getGetMangaQueryKey(mangaId); - const queryKey = queryOptions?.queryKey ?? getGetMangaQueryKey(mangaId); + const queryFn: QueryFunction>> = ({ + signal, + }) => getManga(mangaId, requestOptions, signal); - + return { + queryKey, + queryFn, + enabled: !!mangaId, + ...queryOptions, + } as UseQueryOptions>, TError, TData> & { + queryKey: DataTag; + }; +}; - const queryFn: QueryFunction>> = ({ signal }) => getManga(mangaId, requestOptions, signal); +export type GetMangaQueryResult = NonNullable< + Awaited> +>; +export type GetMangaQueryError = unknown; - - - - - return { queryKey, queryFn, enabled: !!(mangaId), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type GetMangaQueryResult = NonNullable>> -export type GetMangaQueryError = unknown - - -export function useGetManga>, TError = unknown>( - mangaId: number, options: { query:Partial>, TError, TData>> & Pick< +export function useGetManga< + TData = Awaited>, + TError = unknown, +>( + mangaId: number, + options: { + query: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< DefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetManga>, TError = unknown>( - mangaId: number, options?: { query?:Partial>, TError, TData>> & Pick< + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetManga< + TData = Awaited>, + TError = unknown, +>( + mangaId: number, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< UndefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useGetManga>, TError = unknown>( - mangaId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetManga< + TData = Awaited>, + TError = unknown, +>( + mangaId: number, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; /** * @summary Get the details of a manga */ -export function useGetManga>, TError = unknown>( - mangaId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { +export function useGetManga< + TData = Awaited>, + TError = unknown, +>( + mangaId: number, + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetMangaQueryOptions(mangaId, options); - const queryOptions = getGetMangaQueryOptions(mangaId,options) + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; + query.queryKey = queryOptions.queryKey; return query; } - - - /** * Retrieve a list of manga chapters for a specific manga/provider combination. * @summary Get the available chapters for a specific manga/provider combination */ export const getMangaChapterImages = ( - chapterId: number, - options?: SecondParameter,signal?: AbortSignal + chapterId: number, + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(chapterId))}/images`, method: 'GET', signal + return customInstance( + { + url: `http://192.168.1.142:8080/mangas/${encodeURIComponent(String(chapterId))}/images`, + method: "GET", + signal, }, - options); - } - + options, + ); +}; +export const getGetMangaChapterImagesQueryKey = (chapterId?: number) => { + return [`http://192.168.1.142:8080/mangas/${chapterId}/images`] as const; +}; - -export const getGetMangaChapterImagesQueryKey = (chapterId?: number,) => { - return [ - `http://192.168.1.142:8080/mangas/${chapterId}/images` - ] as const; - } - - -export const getGetMangaChapterImagesQueryOptions = >, TError = unknown>(chapterId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} +export const getGetMangaChapterImagesQueryOptions = < + TData = Awaited>, + TError = unknown, +>( + chapterId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, ) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; -const {query: queryOptions, request: requestOptions} = options ?? {}; + const queryKey = + queryOptions?.queryKey ?? getGetMangaChapterImagesQueryKey(chapterId); - const queryKey = queryOptions?.queryKey ?? getGetMangaChapterImagesQueryKey(chapterId); + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => getMangaChapterImages(chapterId, requestOptions, signal); - + return { + queryKey, + queryFn, + enabled: !!chapterId, + ...queryOptions, + } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; - const queryFn: QueryFunction>> = ({ signal }) => getMangaChapterImages(chapterId, requestOptions, signal); +export type GetMangaChapterImagesQueryResult = NonNullable< + Awaited> +>; +export type GetMangaChapterImagesQueryError = unknown; - - - - - return { queryKey, queryFn, enabled: !!(chapterId), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type GetMangaChapterImagesQueryResult = NonNullable>> -export type GetMangaChapterImagesQueryError = unknown - - -export function useGetMangaChapterImages>, TError = unknown>( - chapterId: number, options: { query:Partial>, TError, TData>> & Pick< +export function useGetMangaChapterImages< + TData = Awaited>, + TError = unknown, +>( + chapterId: number, + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< DefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetMangaChapterImages>, TError = unknown>( - chapterId: number, options?: { query?:Partial>, TError, TData>> & Pick< + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetMangaChapterImages< + TData = Awaited>, + TError = unknown, +>( + chapterId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< UndefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useGetMangaChapterImages>, TError = unknown>( - chapterId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetMangaChapterImages< + TData = Awaited>, + TError = unknown, +>( + chapterId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; /** * @summary Get the available chapters for a specific manga/provider combination */ -export function useGetMangaChapterImages>, TError = unknown>( - chapterId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { +export function useGetMangaChapterImages< + TData = Awaited>, + TError = unknown, +>( + chapterId: number, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetMangaChapterImagesQueryOptions(chapterId, options); - const queryOptions = getGetMangaChapterImagesQueryOptions(chapterId,options) + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; + query.queryKey = queryOptions.queryKey; return query; } - - - /** * Retrieve a list of genres. * @summary Get a list of genres */ export const getGenres = ( - - options?: SecondParameter,signal?: AbortSignal + options?: SecondParameter, + signal?: AbortSignal, ) => { - - - return customInstance( - {url: `http://192.168.1.142:8080/genres`, method: 'GET', signal - }, - options); - } - - - + return customInstance( + { url: `http://192.168.1.142:8080/genres`, method: "GET", signal }, + options, + ); +}; export const getGetGenresQueryKey = () => { - return [ - `http://192.168.1.142:8080/genres` - ] as const; - } + return [`http://192.168.1.142:8080/genres`] as const; +}; - -export const getGetGenresQueryOptions = >, TError = unknown>( options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { +export const getGetGenresQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; -const {query: queryOptions, request: requestOptions} = options ?? {}; + const queryKey = queryOptions?.queryKey ?? getGetGenresQueryKey(); - const queryKey = queryOptions?.queryKey ?? getGetGenresQueryKey(); + const queryFn: QueryFunction>> = ({ + signal, + }) => getGenres(requestOptions, signal); - + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; - const queryFn: QueryFunction>> = ({ signal }) => getGenres(requestOptions, signal); +export type GetGenresQueryResult = NonNullable< + Awaited> +>; +export type GetGenresQueryError = unknown; - - - - - return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type GetGenresQueryResult = NonNullable>> -export type GetGenresQueryError = unknown - - -export function useGetGenres>, TError = unknown>( - options: { query:Partial>, TError, TData>> & Pick< +export function useGetGenres< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< DefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetGenres>, TError = unknown>( - options?: { query?:Partial>, TError, TData>> & Pick< + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetGenres< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< UndefinedInitialDataOptions< Awaited>, TError, Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useGetGenres>, TError = unknown>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetGenres< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; /** * @summary Get a list of genres */ -export function useGetGenres>, TError = unknown>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { +export function useGetGenres< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetGenresQueryOptions(options); - const queryOptions = getGetGenresQueryOptions(options) + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; + query.queryKey = queryOptions.queryKey; return query; } diff --git a/app/layout.tsx b/app/layout.tsx index e3ba91b..9a5da29 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,20 +1,20 @@ -"use client" +"use client"; -import type React from "react" -import type { Metadata } from "next" -import { GeistSans } from "geist/font/sans" -import { GeistMono } from "geist/font/mono" -import { Analytics } from "@vercel/analytics/next" -import "./globals.css" -import { ThemeProvider } from "@/components/theme-provider" -import { Suspense } from "react" -import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; -import {AuthProvider} from "@/contexts/auth-context"; +import type React from "react"; +import { GeistSans } from "geist/font/sans"; +import { GeistMono } from "geist/font/mono"; +import { Analytics } from "@vercel/analytics/next"; +import "./globals.css"; +import { ThemeProvider } from "@/components/theme-provider"; +import { Suspense } from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { AuthProvider } from "@/contexts/auth-context"; +import { Toaster } from "@/components/ui/sonner"; export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode + children: React.ReactNode; }>) { const queryClient = new QueryClient(); @@ -23,13 +23,14 @@ export default function RootLayout({ Loading...}> - - {children} - + + + {children} + - ) + ); } diff --git a/app/loading.tsx b/app/loading.tsx index f15322a..4349ac3 100644 --- a/app/loading.tsx +++ b/app/loading.tsx @@ -1,3 +1,3 @@ export default function Loading() { - return null + return null; } diff --git a/app/login/page.tsx b/app/login/page.tsx index de45ec3..77374f0 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,102 +1,97 @@ -"use client" +"use client"; -import type React from "react" +import type React from "react"; -import { useState } from "react" -import { useRouter } from "next/navigation" -import Link from "next/link" -import { useAuth } from "@/contexts/auth-context" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Alert, AlertDescription } from "@/components/ui/alert" -import { AlertCircle } from "lucide-react" +import { useState } from "react"; +import Link from "next/link"; +import { useAuth } from "@/contexts/auth-context"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { AlertCircle } from "lucide-react"; export default function LoginPage() { - const router = useRouter() - const { login } = useAuth() - const [email, setEmail] = useState("") - const [password, setPassword] = useState("") - const [error, setError] = useState("") - const [isLoading, setIsLoading] = useState(false) + const { login, isLoading } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setError("") + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); - if (!email || !password) { - setError("Email and password are required") - return - } - - setIsLoading(true) - try { - await login(email, password) - router.push("/") - } catch (err) { - setError(err instanceof Error ? err.message : "Login failed") - } finally { - setIsLoading(false) - } + if (!email || !password) { + setError("Email and password are required"); + return; } - return ( -
- - - Welcome Back - Sign in to your MangaMochi account - - -
- {error && ( - - - {error} - - )} + await login(email, password); + }; -
- - setEmail(e.target.value)} - disabled={isLoading} - /> -
+ return ( +
+ + + Welcome Back + Sign in to your MangaMochi account + + + + {error && ( + + + {error} + + )} -
- - setPassword(e.target.value)} - disabled={isLoading} - /> -
+
+ + setEmail(e.target.value)} + disabled={isLoading} + /> +
- +
+ + setPassword(e.target.value)} + disabled={isLoading} + /> +
-

- Don't have an account?{" "} - - Create one - -

- -
-
-
- ) + + +

+ Don't have an account?{" "} + + Create one + +

+ +
+
+
+ ); } diff --git a/app/manga/[id]/chapter/[chapterNumber]/page.tsx b/app/manga/[id]/chapter/[chapterNumber]/page.tsx index c9612d1..06ea5ad 100644 --- a/app/manga/[id]/chapter/[chapterNumber]/page.tsx +++ b/app/manga/[id]/chapter/[chapterNumber]/page.tsx @@ -1,24 +1,24 @@ -"use client" +"use client"; -import {useEffect, useState} from "react" -import { useParams, useRouter } from "next/navigation" -import Image from "next/image" -import { ArrowLeft, ChevronLeft, ChevronRight, Home } from "lucide-react" -import { Button } from "@/components/ui/button" -import { ThemeToggle } from "@/components/theme-toggle" -import {useGetMangaChapterImages, useMarkAsRead} from "@/api/mangamochi"; +import { useEffect, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import Image from "next/image"; +import { ArrowLeft, ChevronLeft, ChevronRight, Home } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { useGetMangaChapterImages, useMarkAsRead } from "@/api/mangamochi"; export default function ChapterReaderPage() { - const params = useParams() - const router = useRouter() - const mangaId = Number(params.id) - const chapterNumber = Number(params.chapterNumber) + const params = useParams(); + const router = useRouter(); + const mangaId = Number(params.id); + const chapterNumber = Number(params.chapterNumber); const { data, isLoading } = useGetMangaChapterImages(chapterNumber); const { mutate } = useMarkAsRead(); - const [currentPage, setCurrentPage] = useState(1) + const [currentPage, setCurrentPage] = useState(1); useEffect(() => { if (!data) { @@ -26,7 +26,7 @@ export default function ChapterReaderPage() { } if (currentPage === data.chapterImageKeys.length) { - mutate({chapterId: chapterNumber}); + mutate({ chapterId: chapterNumber }); } }, [data, mutate, currentPage]); @@ -34,28 +34,30 @@ export default function ChapterReaderPage() { return (
-

Manga not found

+

+ Manga not found +

- ) + ); } const goToNextPage = () => { if (currentPage < data?.chapterImageKeys.length) { - setCurrentPage(currentPage + 1) - window.scrollTo({ top: 0, behavior: "smooth" }) + setCurrentPage(currentPage + 1); + window.scrollTo({ top: 0, behavior: "smooth" }); } - } + }; const goToPreviousPage = () => { if (currentPage > 1) { - setCurrentPage(currentPage - 1) - window.scrollTo({ top: 0, behavior: "smooth" }) + setCurrentPage(currentPage - 1); + window.scrollTo({ top: 0, behavior: "smooth" }); } - } + }; return (
@@ -64,17 +66,31 @@ export default function ChapterReaderPage() {
- -
-

{data.mangaTitle}

-

Chapter {chapterNumber}

+

+ {data.mangaTitle} +

+

+ Chapter {chapterNumber} +

@@ -91,14 +107,21 @@ export default function ChapterReaderPage() {
{/* Mobile title */}
-

{data.mangaTitle}

-

Chapter {chapterNumber}

+

+ {data.mangaTitle} +

+

+ Chapter {chapterNumber} +

{/* Manga Page */}
{`Page
- ) + ); } diff --git a/app/manga/[id]/page.tsx b/app/manga/[id]/page.tsx index 27a8042..b7a81ce 100644 --- a/app/manga/[id]/page.tsx +++ b/app/manga/[id]/page.tsx @@ -1,70 +1,91 @@ -"use client" +"use client"; -import { useState } from "react" -import { useParams, useRouter } from "next/navigation" -import Image from "next/image" -import {ArrowLeft, Star, Calendar, Database, BookOpen, ChevronDown} from "lucide-react" -import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" -import { Card, CardContent } from "@/components/ui/card" -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" -import { ThemeToggle } from "@/components/theme-toggle" -import {useFetchMangaChapters, useGetManga} from "@/api/mangamochi"; -import {MangaChapter} from "@/components/manga-chapter"; -import {useQueryClient} from "@tanstack/react-query"; +import { useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import Image from "next/image"; +import { + ArrowLeft, + Star, + Calendar, + Database, + BookOpen, + ChevronDown, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { useFetchMangaChapters, useGetManga } from "@/api/mangamochi"; +import { MangaChapter } from "@/components/manga-chapter"; +import { useQueryClient } from "@tanstack/react-query"; export default function MangaDetailPage() { - const params = useParams() - const router = useRouter() - const mangaId = Number(params.id) + const params = useParams(); + const router = useRouter(); + const mangaId = Number(params.id); const queryClient = useQueryClient(); const { data: mangaData, queryKey } = useGetManga(mangaId); - const { mutate, isPending } = useFetchMangaChapters({mutation: { - onSuccess: () => queryClient.invalidateQueries({queryKey}) - }}) + const { mutate, isPending } = useFetchMangaChapters({ + mutation: { + onSuccess: () => queryClient.invalidateQueries({ queryKey }), + }, + }); - const [openProviders, setOpenProviders] = useState>(new Set()) + const [openProviders, setOpenProviders] = useState>(new Set()); if (!mangaData) { return (
-

Manga not found

+

+ Manga not found +

- ) + ); } const toggleProvider = (providerId: number) => { setOpenProviders((prev) => { - const next = new Set(prev) + const next = new Set(prev); if (next.has(providerId)) { - next.delete(providerId) + next.delete(providerId); } else { - next.add(providerId) + next.add(providerId); } - return next - }) - } + return next; + }); + }; - const author = mangaData.authors.join(', '); + const author = mangaData.authors.join(", "); - const formatter = new Intl.DateTimeFormat('en-US', { - month: '2-digit', - day: '2-digit', - year: 'numeric' + const formatter = new Intl.DateTimeFormat("en-US", { + month: "2-digit", + day: "2-digit", + year: "numeric", }); - const publishedTo = mangaData.publishedTo ? formatter.format(new Date(mangaData.publishedTo)) : null; - const publishedFrom = mangaData.publishedFrom ? formatter.format(new Date(mangaData.publishedFrom)) : null; + const publishedTo = mangaData.publishedTo + ? formatter.format(new Date(mangaData.publishedTo)) + : null; + const publishedFrom = mangaData.publishedFrom + ? formatter.format(new Date(mangaData.publishedFrom)) + : null; - const dateRange = publishedTo ? `${publishedFrom} - ${publishedTo}` : `${publishedFrom} - Present` + const dateRange = publishedTo + ? `${publishedFrom} - ${publishedTo}` + : `${publishedFrom} - Present`; return (
@@ -73,7 +94,11 @@ export default function MangaDetailPage() {
- @@ -91,42 +116,67 @@ export default function MangaDetailPage() {
{/* Cover */}
- {mangaData.title} + {mangaData.title}
{/* Details */}
-

{mangaData.title}

- +

+ {mangaData.title} +

+ {mangaData.status}

{author}

- {mangaData.alternativeTitles && mangaData.alternativeTitles.length > 0 && ( -
-

Alternative Titles

-
- {mangaData.alternativeTitles.map((title, index) => ( - - {title} - - ))} + {mangaData.alternativeTitles && + mangaData.alternativeTitles.length > 0 && ( +
+

+ Alternative Titles +

+
+ {mangaData.alternativeTitles.map((title, index) => ( + + {title} + + ))} +
-
- )} + )} -

{mangaData.synopsis}

+

+ {mangaData.synopsis} +

Rating

-

{mangaData.score}/5.0

+

+ {mangaData.score}/5.0 +

@@ -142,7 +192,9 @@ export default function MangaDetailPage() {

Published

-

{dateRange}

+

+ {dateRange} +

@@ -150,14 +202,20 @@ export default function MangaDetailPage() {

Providers

-

{mangaData.providerCount}

+

+ {mangaData.providerCount} +

{mangaData.genres.map((genre) => ( - + {genre} ))} @@ -166,7 +224,9 @@ export default function MangaDetailPage() {
-

Chapters by Provider

+

+ Chapters by Provider +

{mangaData?.providers.map((provider) => ( @@ -178,12 +238,15 @@ export default function MangaDetailPage() {
-
- +
+
-

{provider.providerName}

+

+ {provider.providerName} +

- {provider.chaptersDownloaded} downloaded • {provider.chaptersAvailable} available + {provider.chaptersDownloaded} downloaded •{" "} + {provider.chaptersAvailable} available

@@ -192,12 +255,15 @@ export default function MangaDetailPage() { size="sm" variant="outline" disabled={isPending} - onClick={() => mutate({mangaProviderId: provider.id})} + onClick={() => + mutate({ mangaProviderId: provider.id }) + } className="gap-2" - > - - Fetch from Provider -
+ > + + Fetch from Provider + +
- + @@ -217,5 +286,5 @@ export default function MangaDetailPage() {
- ) + ); } diff --git a/app/page.tsx b/app/page.tsx index adb40ea..7d85f78 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,27 +1,34 @@ -"use client" +"use client"; -import { useState } from "react" -import { MangaGrid } from "@/components/manga-grid" -import { MangaPagination } from "@/components/manga-pagination" -import { FilterSidebar } from "@/components/filter-sidebar" -import {BookOpen, Search} from "lucide-react" -import { Input } from "@/components/ui/input" -import { ThemeToggle } from "@/components/theme-toggle" -import {useGetMangas} from "@/api/mangamochi"; -import {useAuth} from "@/contexts/auth-context"; -import {AuthHeader} from "@/components/auth-header"; +import { useState } from "react"; +import { MangaGrid } from "@/components/manga-grid"; +import { MangaPagination } from "@/components/manga-pagination"; +import { FilterSidebar } from "@/components/filter-sidebar"; +import { BookOpen, Search } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { useGetMangas } from "@/api/mangamochi"; +import { useAuth } from "@/contexts/auth-context"; +import { AuthHeader } from "@/components/auth-header"; -const ITEMS_PER_PAGE = 12 +const ITEMS_PER_PAGE = 12; export default function HomePage() { - const [currentPage, setCurrentPage] = useState(1) - const [searchQuery, setSearchQuery] = useState("") - const [selectedGenres, setSelectedGenres] = useState([]) - const [selectedStatus, setSelectedStatus] = useState([]) - const [minRating, setMinRating] = useState(0) - const { user } = useAuth() + const [currentPage, setCurrentPage] = useState(1); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedGenres, setSelectedGenres] = useState([]); + const [selectedStatus, setSelectedStatus] = useState([]); + const [minRating, setMinRating] = useState(0); + const { user } = useAuth(); - const mangas = useGetMangas({page: currentPage - 1, size: ITEMS_PER_PAGE, sort: ["id"], searchQuery: searchQuery, statuses: selectedStatus, genreIds: selectedGenres}); + const mangas = useGetMangas({ + page: currentPage - 1, + size: ITEMS_PER_PAGE, + sort: ["id"], + searchQuery: searchQuery, + statuses: selectedStatus, + genreIds: selectedGenres, + }); const totalPages = mangas?.data?.totalPages; @@ -46,8 +53,12 @@ export default function HomePage() {
-

MangaMochi

-

{mangas?.data?.totalElements} titles available

+

+ MangaMochi +

+

+ {mangas?.data?.totalElements} titles available +

@@ -64,8 +75,8 @@ export default function HomePage() { placeholder="Search manga or author..." value={searchQuery} onChange={(e) => { - setSearchQuery(e.target.value) - setCurrentPage(1) + setSearchQuery(e.target.value); + setCurrentPage(1); }} className="pl-10 bg-card border-border" /> @@ -81,20 +92,28 @@ export default function HomePage() { {totalPages && totalPages > 1 && (
- +
)} ) : (
-

No manga found matching your filters.

-

Try adjusting your search or filter criteria.

+

+ No manga found matching your filters. +

+

+ Try adjusting your search or filter criteria. +

)}
- ) + ); } diff --git a/app/profile/page.tsx b/app/profile/page.tsx index cec6bbd..c84d210 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -1,177 +1,216 @@ -"use client" +"use client"; -import { useRouter } from "next/navigation" -import { useAuth } from "@/contexts/auth-context" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { Alert, AlertDescription } from "@/components/ui/alert" -import { LogOut, User, Settings } from "lucide-react" -import { useState } from "react" +import { useRouter } from "next/navigation"; +import { useAuth } from "@/contexts/auth-context"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { LogOut, User, Settings } from "lucide-react"; +import { useState } from "react"; export default function ProfilePage() { - const router = useRouter() - const { user, logout, updatePreferences } = useAuth() - const [itemsPerPage, setItemsPerPage] = useState(user?.preferences.itemsPerPage || 12) - - if (!user) { - return ( -
- - - Access Denied - Please log in to view your profile - - - - - -
- ) - } - - const handleLogout = () => { - logout() - router.push("/") - } - - const handleSavePreferences = () => { - updatePreferences({ itemsPerPage }) - } + const router = useRouter(); + const { user, logout, updatePreferences } = useAuth(); + const [itemsPerPage, setItemsPerPage] = useState( + user?.preferences.itemsPerPage || 12, + ); + if (!user) { return ( -
-
-
-

Profile

- +
+ + + Access Denied + + Please log in to view your profile + + + + + + +
+ ); + } + + const handleLogout = () => { + logout(); + router.push("/"); + }; + + const handleSavePreferences = () => { + updatePreferences({ itemsPerPage }); + }; + + return ( +
+
+
+

Profile

+ +
+ + + + + + Account + + + + Preferences + + Stats + + + + + + Account Information + Your account details + + +
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + Preferences + Customize your experience + + +
+ + + setItemsPerPage(Number.parseInt(e.target.value)) + } + /> +

+ Number of manga to display per page (6-48) +

+
+ +
+
+
+ + + + + Reading Statistics + Your manga reading activity + + +
+
+

+ Favorite Manga +

+

+ {user.favorites.length} +

+
+
+

+ Manga Reading +

+

+ {Object.keys(user.chaptersRead).length} +

+
- - - - - Account - - - - Preferences - - Stats - - - - - - Account Information - Your account details - - -
- - -
-
- - -
-
- - -
-
-
-
- - - - - Preferences - Customize your experience - - -
- - setItemsPerPage(Number.parseInt(e.target.value))} - /> -

Number of manga to display per page (6-48)

-
- -
-
-
- - - - - Reading Statistics - Your manga reading activity - - -
-
-

Favorite Manga

-

{user.favorites.length}

-
-
-

Manga Reading

-

{Object.keys(user.chaptersRead).length}

-
-
- - {user.favorites.length > 0 && ( -
-

Favorite Manga IDs

-
- {user.favorites.map((id) => ( - + {user.favorites.length > 0 && ( +
+

Favorite Manga IDs

+
+ {user.favorites.map((id) => ( + #{id} - ))} -
-
- )} + ))} +
+
+ )} - {Object.keys(user.chaptersRead).length > 0 && ( -
-

Reading Progress

-
- {Object.entries(user.chaptersRead).map(([mangaId, chapter]) => ( -

- Manga #{mangaId}: Chapter {chapter} -

- ))} -
-
- )} + {Object.keys(user.chaptersRead).length > 0 && ( +
+

Reading Progress

+
+ {Object.entries(user.chaptersRead).map( + ([mangaId, chapter]) => ( +

+ Manga #{mangaId}: Chapter {chapter} +

+ ), + )} +
+
+ )} - {user.favorites.length === 0 && Object.keys(user.chaptersRead).length === 0 && ( - - - No reading activity yet. Start adding favorites and tracking chapters! - - - )} -
-
-
-
-
-
- ) + {user.favorites.length === 0 && + Object.keys(user.chaptersRead).length === 0 && ( + + + No reading activity yet. Start adding favorites and + tracking chapters! + + + )} + + + + +
+
+ ); } diff --git a/app/register/page.tsx b/app/register/page.tsx index 41f1d49..7fa40f1 100644 --- a/app/register/page.tsx +++ b/app/register/page.tsx @@ -1,137 +1,136 @@ -"use client" +"use client"; -import type React from "react" +import type React from "react"; -import { useState } from "react" -import { useRouter } from "next/navigation" -import Link from "next/link" -import { useAuth } from "@/contexts/auth-context" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Alert, AlertDescription } from "@/components/ui/alert" -import { AlertCircle } from "lucide-react" +import { useState } from "react"; +import Link from "next/link"; +import { useAuth } from "@/contexts/auth-context"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { AlertCircle } from "lucide-react"; export default function RegisterPage() { - const router = useRouter() - const { register } = useAuth() - const [email, setEmail] = useState("") - const [username, setUsername] = useState("") - const [password, setPassword] = useState("") - const [confirmPassword, setConfirmPassword] = useState("") - const [error, setError] = useState("") - const [isLoading, setIsLoading] = useState(false) + const { register, isLoading } = useAuth(); + const [email, setEmail] = useState(""); + const [name, setName] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [error, setError] = useState(""); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setError("") + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); - if (!email || !username || !password || !confirmPassword) { - setError("All fields are required") - return - } - - if (password !== confirmPassword) { - setError("Passwords do not match") - return - } - - setIsLoading(true) - try { - await register(email, password, username) - router.push("/") - } catch (err) { - setError(err instanceof Error ? err.message : "Registration failed") - } finally { - setIsLoading(false) - } + if (!email || !name || !password || !confirmPassword) { + setError("All fields are required"); + return; } - return ( -
- - - Create Account - Join MangaMochi to track your favorite manga - - -
- {error && ( - - - {error} - - )} + if (password !== confirmPassword) { + setError("Passwords do not match"); + return; + } -
- - setEmail(e.target.value)} - disabled={isLoading} - /> -
+ console.log(email); -
- - setUsername(e.target.value)} - disabled={isLoading} - /> -
+ await register(email, password, name); + }; -
- - setPassword(e.target.value)} - disabled={isLoading} - /> -
+ return ( +
+ + + Create Account + + Join MangaMochi to track your favorite manga + + + + + {error && ( + + + {error} + + )} -
- - setConfirmPassword(e.target.value)} - disabled={isLoading} - /> -
+
+ + setEmail(e.target.value)} + disabled={isLoading} + /> +
- +
+ + setName(e.target.value)} + disabled={isLoading} + /> +
-

- Already have an account?{" "} - - Sign in - -

- -
-
-
- ) +
+ + setPassword(e.target.value)} + disabled={isLoading} + /> +
+ +
+ + setConfirmPassword(e.target.value)} + disabled={isLoading} + /> +
+ + + +

+ Already have an account?{" "} + + Sign in + +

+ +
+
+
+ ); } diff --git a/components/auth-header.tsx b/components/auth-header.tsx index 9a970e9..0634796 100644 --- a/components/auth-header.tsx +++ b/components/auth-header.tsx @@ -1,90 +1,85 @@ -"use client" +"use client"; -import Link from "next/link" -import { useRouter } from "next/navigation" -import { useAuth } from "@/contexts/auth-context" -import { Button } from "@/components/ui/button" +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/contexts/auth-context"; +import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { Avatar, AvatarFallback } from "@/components/ui/avatar" -import { LogOut, User, Settings, LogIn } from "lucide-react" + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { LogOut, User, Settings, LogIn } from "lucide-react"; export function AuthHeader() { - const router = useRouter() - const { user, logout } = useAuth() + const router = useRouter(); + const { user, logout } = useAuth(); - const handleLogout = () => { - logout() - router.push("/") - } - - if (!user) { - return ( - - - - - - - - Sign In - - - - - Sign Up - - - - - ) - } + const handleLogout = () => { + logout(); + router.push("/"); + }; + if (!user) { return ( - - - - - -
-
-

{user.username}

-

{user.email}

-
-
- - - - - Profile - - - - - - Preferences - - - - - - Logout - -
-
- ) + + ); + } + + return ( + + + + + +
+
+

{user.name}

+

{user.email}

+
+
+ + + + + Profile + + + + + + Preferences + + + + + + Logout + +
+
+ ); } diff --git a/components/filter-sidebar.tsx b/components/filter-sidebar.tsx index 08c1aaf..16ebf56 100644 --- a/components/filter-sidebar.tsx +++ b/components/filter-sidebar.tsx @@ -1,28 +1,28 @@ -"use client" +"use client"; -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" -import { Star, X } from "lucide-react" -import {useGetGenres} from "@/api/mangamochi"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { Star, X } from "lucide-react"; +import { useGetGenres } from "@/api/mangamochi"; interface FilterSidebarProps { - selectedGenres: number[] - selectedStatus: string[] - minRating: number - onGenresChange: (genres: number[]) => void - onStatusChange: (status: string[]) => void - onRatingChange: (rating: number) => void + selectedGenres: number[]; + selectedStatus: string[]; + minRating: number; + onGenresChange: (genres: number[]) => void; + onStatusChange: (status: string[]) => void; + onRatingChange: (rating: number) => void; } -const STATUSES = ["Ongoing", "Completed", "Hiatus"] +const STATUSES = ["Ongoing", "Completed", "Hiatus"]; const RATINGS = [ { label: "4.5+ Stars", value: 4.5 }, { label: "4.0+ Stars", value: 4.0 }, { label: "3.5+ Stars", value: 3.5 }, { label: "All Ratings", value: 0 }, -] +]; export function FilterSidebar({ selectedGenres, @@ -32,41 +32,48 @@ export function FilterSidebar({ onStatusChange, onRatingChange, }: FilterSidebarProps) { - const { data: genresData } = useGetGenres(); const toggleGenre = (genre: number) => { if (selectedGenres.includes(genre)) { - onGenresChange(selectedGenres.filter((g) => g !== genre)) + onGenresChange(selectedGenres.filter((g) => g !== genre)); } else { - onGenresChange([...selectedGenres, genre]) + onGenresChange([...selectedGenres, genre]); } - } + }; const toggleStatus = (status: string) => { if (selectedStatus.includes(status)) { - onStatusChange(selectedStatus.filter((s) => s !== status)) + onStatusChange(selectedStatus.filter((s) => s !== status)); } else { - onStatusChange([...selectedStatus, status]) + onStatusChange([...selectedStatus, status]); } - } + }; const clearAllFilters = () => { - onGenresChange([]) - onStatusChange([]) - onRatingChange(0) - } + onGenresChange([]); + onStatusChange([]); + onRatingChange(0); + }; - const hasActiveFilters = selectedGenres.length > 0 || selectedStatus.length > 0 || minRating > 0 + const hasActiveFilters = + selectedGenres.length > 0 || selectedStatus.length > 0 || minRating > 0; return ( - ) + ); } diff --git a/components/manga-card.tsx b/components/manga-card.tsx index 279359c..6530592 100644 --- a/components/manga-card.tsx +++ b/components/manga-card.tsx @@ -1,40 +1,46 @@ -import Link from "next/link" -import Image from "next/image" -import { Star, Calendar, Database } from "lucide-react" -import { Card, CardContent } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" +import Link from "next/link"; +import Image from "next/image"; +import { Star, Calendar, Database } from "lucide-react"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; interface Manga { - id: number - title: string - coverImageKey?: string - status?: string - publishedFrom?: string - publishedTo?: string - providerCount?: number - authors: string[] - genres: string[] - score: number + id: number; + title: string; + coverImageKey?: string; + status?: string; + publishedFrom?: string; + publishedTo?: string; + providerCount?: number; + authors: string[]; + genres: string[]; + score: number; // chapters: number } interface MangaCardProps { - manga: Manga + manga: Manga; } export function MangaCard({ manga }: MangaCardProps) { - const formatter = new Intl.DateTimeFormat('en-US', { - month: '2-digit', - day: '2-digit', - year: 'numeric' + const formatter = new Intl.DateTimeFormat("en-US", { + month: "2-digit", + day: "2-digit", + year: "numeric", }); - const publishedTo = manga.publishedTo ? formatter.format(new Date(manga.publishedTo)) : null; - const publishedFrom = manga.publishedFrom ? formatter.format(new Date(manga.publishedFrom)) : null; + const publishedTo = manga.publishedTo + ? formatter.format(new Date(manga.publishedTo)) + : null; + const publishedFrom = manga.publishedFrom + ? formatter.format(new Date(manga.publishedFrom)) + : null; - const dateRange = publishedTo ? `${publishedFrom} - ${publishedTo}` : `${publishedFrom} - Present` + const dateRange = publishedTo + ? `${publishedFrom} - ${publishedTo}` + : `${publishedFrom} - Present`; - const author = manga.authors.join(', '); + const author = manga.authors.join(", "); return ( @@ -42,7 +48,12 @@ export function MangaCard({ manga }: MangaCardProps) {
{manga.title} -

{manga.title}

+

+ {manga.title} +

{author}

- {publishedFrom && -
- - {dateRange} -
} + {publishedFrom && ( +
+ + {dateRange} +
+ )}
- {manga.score} + + {manga.score} +
{/*Ch. {manga.chapters}*/}
@@ -80,13 +96,18 @@ export function MangaCard({ manga }: MangaCardProps) {
- {manga.providerCount} {manga.providerCount === 1 ? "provider" : "providers"} + {manga.providerCount}{" "} + {manga.providerCount === 1 ? "provider" : "providers"}
{manga.genres.map((genre) => ( - + {genre} ))} @@ -95,5 +116,5 @@ export function MangaCard({ manga }: MangaCardProps) { - ) + ); } diff --git a/components/manga-chapter.tsx b/components/manga-chapter.tsx index 2035bc1..4021bea 100644 --- a/components/manga-chapter.tsx +++ b/components/manga-chapter.tsx @@ -1,143 +1,155 @@ -import {FC, useCallback, useState} from "react"; -import {useDownloadChapterArchive, useFetchChapter, useGetMangaChapters} from "@/api/mangamochi"; -import {Check, Database, Download, Eye, Loader2} from "lucide-react"; -import {Button} from "@/components/ui/button"; -import {useQueryClient} from "@tanstack/react-query"; -import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu"; -import {useRouter} from "next/navigation"; +import { FC, useCallback, useState } from "react"; +import { + useDownloadChapterArchive, + useFetchChapter, + useGetMangaChapters, +} from "@/api/mangamochi"; +import { Check, Database, Download, Eye, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useQueryClient } from "@tanstack/react-query"; +import { useRouter } from "next/navigation"; interface MangaChapterProps { - mangaId: number; - mangaProviderId: number; + mangaId: number; + mangaProviderId: number; } -export const MangaChapter: FC = ({mangaId, mangaProviderId}) => { - const router = useRouter() +export const MangaChapter: FC = ({ + mangaId, + mangaProviderId, +}) => { + const router = useRouter(); - const { isPending, data, queryKey } = useGetMangaChapters(mangaProviderId); + const { isPending, data, queryKey } = useGetMangaChapters(mangaProviderId); - const queryClient = useQueryClient(); + const queryClient = useQueryClient(); - const { mutate: mutateDownloadChapterArchive, isPending: isPendingDownloadChapter } = useDownloadChapterArchive({ - mutation: { - onSuccess: (data, {chapterId}) => { - const url = window.URL.createObjectURL(data); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', chapterId + '.cbz'); - document.body.appendChild(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(url); - } - } - }) + const { + mutate: mutateDownloadChapterArchive, + isPending: isPendingDownloadChapter, + } = useDownloadChapterArchive({ + mutation: { + onSuccess: (data, { chapterId }) => { + const url = window.URL.createObjectURL(data); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", chapterId + ".cbz"); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + }, + }, + }); - const { mutate, isPending: isPendingFetchChapter } = useFetchChapter({ - mutation: { - onSuccess: () => queryClient.invalidateQueries({queryKey}), - onSettled: () => setFetchingId(null) - } - }); + const { mutate, isPending: isPendingFetchChapter } = useFetchChapter({ + mutation: { + onSuccess: () => queryClient.invalidateQueries({ queryKey }), + onSettled: () => setFetchingId(null), + }, + }); - const [fetchingId, setFetchingId] = useState(null); + const [fetchingId, setFetchingId] = useState(null); - const fetchChapter = useCallback((mangaChapterId: number) => { - setFetchingId(mangaChapterId); - mutate({chapterId: mangaChapterId}); - }, [mutate]); + const fetchChapter = useCallback( + (mangaChapterId: number) => { + setFetchingId(mangaChapterId); + mutate({ chapterId: mangaChapterId }); + }, + [mutate], + ); - const handleReadChapter = (chapterNumber: number) => { - router.push(`/manga/${mangaId}/chapter/${chapterNumber}`) - } + const handleReadChapter = (chapterNumber: number) => { + router.push(`/manga/${mangaId}/chapter/${chapterNumber}`); + }; - return (
- { - isPending ? ( -
- - Loading chapters... -
- ) : - data ? ( -
- {data.map((chapter) => { - return ( -
-
-
- {chapter.isRead ? ( - - ) : ( - - {/*{chapter}*/} - - )} -
-
-

{chapter.title}

- {chapter.downloaded && ( -

In database

- )} -
-
- {chapter.downloaded ? ( -
- - - - - - - mutateDownloadChapterArchive({chapterId: chapter.id, params: {archiveFileType: 'CBZ'} })} - > - Download as CBZ - - handleDownloadToDevice(provider.name, chapter.number, "cbr")} - disabled - > - Download as CBR - - - -
- ) : ( - - )} -
- ) - })} -
- ) : null} -
); -}; \ No newline at end of file + return ( +
+ {isPending ? ( +
+ + + Loading chapters... + +
+ ) : data ? ( +
+ {data.map((chapter) => { + return ( +
+
+
+ {chapter.isRead ? ( + + ) : ( + + {/*{chapter}*/} + + )} +
+
+

+ {chapter.title} +

+ {chapter.downloaded && ( +

+ In database +

+ )} +
+
+ {chapter.downloaded ? ( +
+ + +
+ ) : ( + + )} +
+ ); + })} +
+ ) : null} +
+ ); +}; diff --git a/components/manga-grid.tsx b/components/manga-grid.tsx index 904b5cc..c39e5d9 100644 --- a/components/manga-grid.tsx +++ b/components/manga-grid.tsx @@ -1,8 +1,8 @@ -import { MangaCard } from "./manga-card" -import {MangaListDTO} from "@/api/mangamochi"; +import { MangaCard } from "./manga-card"; +import { MangaListDTO } from "@/api/mangamochi"; interface MangaGridProps { - manga: MangaListDTO[] + manga: MangaListDTO[]; } export function MangaGrid({ manga }: MangaGridProps) { @@ -12,5 +12,5 @@ export function MangaGrid({ manga }: MangaGridProps) { ))}
- ) + ); } diff --git a/components/manga-pagination.tsx b/components/manga-pagination.tsx index 3af30bd..ef0d7a2 100644 --- a/components/manga-pagination.tsx +++ b/components/manga-pagination.tsx @@ -1,50 +1,63 @@ -"use client" +"use client"; -import { Button } from "@/components/ui/button" -import { ChevronLeft, ChevronRight } from "lucide-react" +import { Button } from "@/components/ui/button"; +import { ChevronLeft, ChevronRight } from "lucide-react"; interface MangaPaginationProps { - currentPage: number - totalPages: number - onPageChange: (page: number) => void + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; } -export function MangaPagination({ currentPage, totalPages, onPageChange }: MangaPaginationProps) { +export function MangaPagination({ + currentPage, + totalPages, + onPageChange, +}: MangaPaginationProps) { const getPageNumbers = () => { - const pages: (number | string)[] = [] - const showEllipsis = totalPages > 7 + const pages: (number | string)[] = []; + const showEllipsis = totalPages > 7; if (!showEllipsis) { - return Array.from({ length: totalPages }, (_, i) => i + 1) + return Array.from({ length: totalPages }, (_, i) => i + 1); } // Always show first page - pages.push(1) + pages.push(1); if (currentPage > 3) { - pages.push("...") + pages.push("..."); } // Show pages around current page - for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) { - pages.push(i) + for ( + let i = Math.max(2, currentPage - 1); + i <= Math.min(totalPages - 1, currentPage + 1); + i++ + ) { + pages.push(i); } if (currentPage < totalPages - 2) { - pages.push("...") + pages.push("..."); } // Always show last page if (totalPages > 1) { - pages.push(totalPages) + pages.push(totalPages); } - return pages - } + return pages; + }; return (
- @@ -73,5 +86,5 @@ export function MangaPagination({ currentPage, totalPages, onPageChange }: Manga
- ) + ); } diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx index 2081b9c..d14279f 100644 --- a/components/theme-provider.tsx +++ b/components/theme-provider.tsx @@ -1,53 +1,57 @@ -"use client" +"use client"; -import type React from "react" +import type React from "react"; -import { createContext, useContext, useEffect, useState } from "react" +import { createContext, useContext, useEffect, useState } from "react"; -type Theme = "light" | "dark" +type Theme = "light" | "dark"; type ThemeContextType = { - theme: Theme - toggleTheme: () => void -} + theme: Theme; + toggleTheme: () => void; +}; -const ThemeContext = createContext(undefined) +const ThemeContext = createContext(undefined); export function ThemeProvider({ children }: { children: React.ReactNode }) { - const [theme, setTheme] = useState("dark") - const [mounted, setMounted] = useState(false) + const [theme, setTheme] = useState("dark"); + const [mounted, setMounted] = useState(false); useEffect(() => { - setMounted(true) - const savedTheme = localStorage.getItem("theme") as Theme | null + setMounted(true); + const savedTheme = localStorage.getItem("theme") as Theme | null; if (savedTheme) { - setTheme(savedTheme) + setTheme(savedTheme); } - }, []) + }, []); useEffect(() => { if (mounted) { - const root = document.documentElement + const root = document.documentElement; if (theme === "dark") { - root.classList.add("dark") + root.classList.add("dark"); } else { - root.classList.remove("dark") + root.classList.remove("dark"); } - localStorage.setItem("theme", theme) + localStorage.setItem("theme", theme); } - }, [theme, mounted]) + }, [theme, mounted]); const toggleTheme = () => { - setTheme((prev) => (prev === "dark" ? "light" : "dark")) - } + setTheme((prev) => (prev === "dark" ? "light" : "dark")); + }; - return {children} + return ( + + {children} + + ); } export function useTheme() { - const context = useContext(ThemeContext) + const context = useContext(ThemeContext); if (context === undefined) { - throw new Error("useTheme must be used within a ThemeProvider") + throw new Error("useTheme must be used within a ThemeProvider"); } - return context + return context; } diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx index f9167d1..13615a9 100644 --- a/components/theme-toggle.tsx +++ b/components/theme-toggle.tsx @@ -1,14 +1,19 @@ -"use client" +"use client"; -import { Moon, Sun } from "lucide-react" -import { Button } from "@/components/ui/button" -import { useTheme } from "@/components/theme-provider" +import { Moon, Sun } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useTheme } from "@/components/theme-provider"; export function ThemeToggle() { - const { theme, toggleTheme } = useTheme() + const { theme, toggleTheme } = useTheme(); return ( - - ) + ); } diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx index e538a33..6d60d31 100644 --- a/components/ui/accordion.tsx +++ b/components/ui/accordion.tsx @@ -1,15 +1,15 @@ -'use client' +"use client"; -import * as React from 'react' -import * as AccordionPrimitive from '@radix-ui/react-accordion' -import { ChevronDownIcon } from 'lucide-react' +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDownIcon } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function Accordion({ ...props }: React.ComponentProps) { - return + return ; } function AccordionItem({ @@ -19,10 +19,10 @@ function AccordionItem({ return ( - ) + ); } function AccordionTrigger({ @@ -35,7 +35,7 @@ function AccordionTrigger({ svg]:rotate-180', + "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", className, )} {...props} @@ -44,7 +44,7 @@ function AccordionTrigger({ - ) + ); } function AccordionContent({ @@ -58,9 +58,9 @@ function AccordionContent({ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" {...props} > -
{children}
+
{children}
- ) + ); } -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx index 9704452..c37e0cf 100644 --- a/components/ui/alert-dialog.tsx +++ b/components/ui/alert-dialog.tsx @@ -1,15 +1,15 @@ -'use client' +"use client"; -import * as React from 'react' -import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; -import { cn } from '@/lib/utils' -import { buttonVariants } from '@/components/ui/button' +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; function AlertDialog({ ...props }: React.ComponentProps) { - return + return ; } function AlertDialogTrigger({ @@ -17,7 +17,7 @@ function AlertDialogTrigger({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogPortal({ @@ -25,7 +25,7 @@ function AlertDialogPortal({ }: React.ComponentProps) { return ( - ) + ); } function AlertDialogOverlay({ @@ -36,12 +36,12 @@ function AlertDialogOverlay({ - ) + ); } function AlertDialogContent({ @@ -54,42 +54,42 @@ function AlertDialogContent({ - ) + ); } function AlertDialogHeader({ className, ...props -}: React.ComponentProps<'div'>) { +}: React.ComponentProps<"div">) { return (
- ) + ); } function AlertDialogFooter({ className, ...props -}: React.ComponentProps<'div'>) { +}: React.ComponentProps<"div">) { return (
- ) + ); } function AlertDialogTitle({ @@ -99,10 +99,10 @@ function AlertDialogTitle({ return ( - ) + ); } function AlertDialogDescription({ @@ -112,10 +112,10 @@ function AlertDialogDescription({ return ( - ) + ); } function AlertDialogAction({ @@ -127,7 +127,7 @@ function AlertDialogAction({ className={cn(buttonVariants(), className)} {...props} /> - ) + ); } function AlertDialogCancel({ @@ -136,10 +136,10 @@ function AlertDialogCancel({ }: React.ComponentProps) { return ( - ) + ); } export { @@ -154,4 +154,4 @@ export { AlertDialogDescription, AlertDialogAction, AlertDialogCancel, -} +}; diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx index e6751ab..aa7de24 100644 --- a/components/ui/alert.tsx +++ b/components/ui/alert.tsx @@ -1,29 +1,29 @@ -import * as React from 'react' -import { cva, type VariantProps } from 'class-variance-authority' +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; const alertVariants = cva( - 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", { variants: { variant: { - default: 'bg-card text-card-foreground', + default: "bg-card text-card-foreground", destructive: - 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", }, }, defaultVariants: { - variant: 'default', + variant: "default", }, }, -) +); function Alert({ className, variant, ...props -}: React.ComponentProps<'div'> & VariantProps) { +}: React.ComponentProps<"div"> & VariantProps) { return (
- ) + ); } -function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } function AlertDescription({ className, ...props -}: React.ComponentProps<'div'>) { +}: React.ComponentProps<"div">) { return (
- ) + ); } -export { Alert, AlertTitle, AlertDescription } +export { Alert, AlertTitle, AlertDescription }; diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx index 40bb120..c16d6bc 100644 --- a/components/ui/aspect-ratio.tsx +++ b/components/ui/aspect-ratio.tsx @@ -1,11 +1,11 @@ -'use client' +"use client"; -import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; function AspectRatio({ ...props }: React.ComponentProps) { - return + return ; } -export { AspectRatio } +export { AspectRatio }; diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx index aa98465..c4475c2 100644 --- a/components/ui/avatar.tsx +++ b/components/ui/avatar.tsx @@ -1,9 +1,9 @@ -'use client' +"use client"; -import * as React from 'react' -import * as AvatarPrimitive from '@radix-ui/react-avatar' +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function Avatar({ className, @@ -13,12 +13,12 @@ function Avatar({ - ) + ); } function AvatarImage({ @@ -28,10 +28,10 @@ function AvatarImage({ return ( - ) + ); } function AvatarFallback({ @@ -42,12 +42,12 @@ function AvatarFallback({ - ) + ); } -export { Avatar, AvatarImage, AvatarFallback } +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index fc4126b..46f988c 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -1,38 +1,38 @@ -import * as React from 'react' -import { Slot } from '@radix-ui/react-slot' -import { cva, type VariantProps } from 'class-variance-authority' +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; const badgeVariants = cva( - 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", { variants: { variant: { default: - 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", secondary: - 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", destructive: - 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", }, }, defaultVariants: { - variant: 'default', + variant: "default", }, }, -) +); function Badge({ className, variant, asChild = false, ...props -}: React.ComponentProps<'span'> & +}: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : 'span' + const Comp = asChild ? Slot : "span"; return ( - ) + ); } -export { Badge, badgeVariants } +export { Badge, badgeVariants }; diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx index 1750ff2..f63ae19 100644 --- a/components/ui/breadcrumb.tsx +++ b/components/ui/breadcrumb.tsx @@ -1,101 +1,101 @@ -import * as React from 'react' -import { Slot } from '@radix-ui/react-slot' -import { ChevronRight, MoreHorizontal } from 'lucide-react' +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; -function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { - return
- ) + ); } const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( ([, config]) => config.theme || config.color, - ) + ); if (!colorConfig.length) { - return null + return null; } return ( @@ -89,26 +89,26 @@ ${colorConfig .map(([key, itemConfig]) => { const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || - itemConfig.color - return color ? ` --color-${key}: ${color};` : null + itemConfig.color; + return color ? ` --color-${key}: ${color};` : null; }) - .join('\n')} + .join("\n")} } `, ) - .join('\n'), + .join("\n"), }} /> - ) -} + ); +}; -const ChartTooltip = RechartsPrimitive.Tooltip +const ChartTooltip = RechartsPrimitive.Tooltip; function ChartTooltipContent({ active, payload, className, - indicator = 'dot', + indicator = "dot", hideLabel = false, hideIndicator = false, label, @@ -119,41 +119,41 @@ function ChartTooltipContent({ nameKey, labelKey, }: React.ComponentProps & - React.ComponentProps<'div'> & { - hideLabel?: boolean - hideIndicator?: boolean - indicator?: 'line' | 'dot' | 'dashed' - nameKey?: string - labelKey?: string + React.ComponentProps<"div"> & { + hideLabel?: boolean; + hideIndicator?: boolean; + indicator?: "line" | "dot" | "dashed"; + nameKey?: string; + labelKey?: string; }) { - const { config } = useChart() + const { config } = useChart(); const tooltipLabel = React.useMemo(() => { if (hideLabel || !payload?.length) { - return null + return null; } - const [item] = payload - const key = `${labelKey || item?.dataKey || item?.name || 'value'}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) + const [item] = payload; + const key = `${labelKey || item?.dataKey || item?.name || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); const value = - !labelKey && typeof label === 'string' + !labelKey && typeof label === "string" ? config[label as keyof typeof config]?.label || label - : itemConfig?.label + : itemConfig?.label; if (labelFormatter) { return ( -
+
{labelFormatter(value, payload)}
- ) + ); } if (!value) { - return null + return null; } - return
{value}
+ return
{value}
; }, [ label, labelFormatter, @@ -162,34 +162,34 @@ function ChartTooltipContent({ labelClassName, config, labelKey, - ]) + ]); if (!active || !payload?.length) { - return null + return null; } - const nestLabel = payload.length === 1 && indicator !== 'dot' + const nestLabel = payload.length === 1 && indicator !== "dot"; return (
{!nestLabel ? tooltipLabel : null}
{payload.map((item, index) => { - const key = `${nameKey || item.name || item.dataKey || 'value'}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) - const indicatorColor = color || item.payload.fill || item.color + const key = `${nameKey || item.name || item.dataKey || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); + const indicatorColor = color || item.payload.fill || item.color; return (
svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5', - indicator === 'dot' && 'items-center', + "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5", + indicator === "dot" && "items-center", )} > {formatter && item?.value !== undefined && item.name ? ( @@ -202,19 +202,19 @@ function ChartTooltipContent({ !hideIndicator && (
@@ -222,8 +222,8 @@ function ChartTooltipContent({ )}
@@ -241,49 +241,49 @@ function ChartTooltipContent({ )}
- ) + ); })}
- ) + ); } -const ChartLegend = RechartsPrimitive.Legend +const ChartLegend = RechartsPrimitive.Legend; function ChartLegendContent({ className, hideIcon = false, payload, - verticalAlign = 'bottom', + verticalAlign = "bottom", nameKey, -}: React.ComponentProps<'div'> & - Pick & { - hideIcon?: boolean - nameKey?: string +}: React.ComponentProps<"div"> & + Pick & { + hideIcon?: boolean; + nameKey?: string; }) { - const { config } = useChart() + const { config } = useChart(); if (!payload?.length) { - return null + return null; } return (
{payload.map((item) => { - const key = `${nameKey || item.dataKey || 'value'}` - const itemConfig = getPayloadConfigFromPayload(config, item, key) + const key = `${nameKey || item.dataKey || "value"}`; + const itemConfig = getPayloadConfigFromPayload(config, item, key); return (
svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3' + "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3" } > {itemConfig?.icon && !hideIcon ? ( @@ -298,10 +298,10 @@ function ChartLegendContent({ )} {itemConfig?.label}
- ) + ); })}
- ) + ); } // Helper to extract item config from a payload. @@ -310,37 +310,37 @@ function getPayloadConfigFromPayload( payload: unknown, key: string, ) { - if (typeof payload !== 'object' || payload === null) { - return undefined + if (typeof payload !== "object" || payload === null) { + return undefined; } const payloadPayload = - 'payload' in payload && - typeof payload.payload === 'object' && + "payload" in payload && + typeof payload.payload === "object" && payload.payload !== null ? payload.payload - : undefined + : undefined; - let configLabelKey: string = key + let configLabelKey: string = key; if ( key in payload && - typeof payload[key as keyof typeof payload] === 'string' + typeof payload[key as keyof typeof payload] === "string" ) { - configLabelKey = payload[key as keyof typeof payload] as string + configLabelKey = payload[key as keyof typeof payload] as string; } else if ( payloadPayload && key in payloadPayload && - typeof payloadPayload[key as keyof typeof payloadPayload] === 'string' + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" ) { configLabelKey = payloadPayload[ key as keyof typeof payloadPayload - ] as string + ] as string; } return configLabelKey in config ? config[configLabelKey] - : config[key as keyof typeof config] + : config[key as keyof typeof config]; } export { @@ -350,4 +350,4 @@ export { ChartLegend, ChartLegendContent, ChartStyle, -} +}; diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx index 37d340f..ae02cf5 100644 --- a/components/ui/checkbox.tsx +++ b/components/ui/checkbox.tsx @@ -1,10 +1,10 @@ -'use client' +"use client"; -import * as React from 'react' -import * as CheckboxPrimitive from '@radix-ui/react-checkbox' -import { CheckIcon } from 'lucide-react' +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function Checkbox({ className, @@ -14,7 +14,7 @@ function Checkbox({ - ) + ); } -export { Checkbox } +export { Checkbox }; diff --git a/components/ui/collapsible.tsx b/components/ui/collapsible.tsx index 3cbdff6..90935c6 100644 --- a/components/ui/collapsible.tsx +++ b/components/ui/collapsible.tsx @@ -1,11 +1,11 @@ -'use client' +"use client"; -import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; function Collapsible({ ...props }: React.ComponentProps) { - return + return ; } function CollapsibleTrigger({ @@ -16,7 +16,7 @@ function CollapsibleTrigger({ data-slot="collapsible-trigger" {...props} /> - ) + ); } function CollapsibleContent({ @@ -27,7 +27,7 @@ function CollapsibleContent({ data-slot="collapsible-content" {...props} /> - ) + ); } -export { Collapsible, CollapsibleTrigger, CollapsibleContent } +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 4833ca8..ee7450a 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -1,17 +1,17 @@ -'use client' +"use client"; -import * as React from 'react' -import { Command as CommandPrimitive } from 'cmdk' -import { SearchIcon } from 'lucide-react' +import * as React from "react"; +import { Command as CommandPrimitive } from "cmdk"; +import { SearchIcon } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from '@/components/ui/dialog' +} from "@/components/ui/dialog"; function Command({ className, @@ -21,26 +21,26 @@ function Command({ - ) + ); } function CommandDialog({ - title = 'Command Palette', - description = 'Search for a command to run...', + title = "Command Palette", + description = "Search for a command to run...", children, className, showCloseButton = true, ...props }: React.ComponentProps & { - title?: string - description?: string - className?: string - showCloseButton?: boolean + title?: string; + description?: string; + className?: string; + showCloseButton?: boolean; }) { return ( @@ -49,7 +49,7 @@ function CommandDialog({ {description} @@ -57,7 +57,7 @@ function CommandDialog({ - ) + ); } function CommandInput({ @@ -73,13 +73,13 @@ function CommandInput({
- ) + ); } function CommandList({ @@ -90,12 +90,12 @@ function CommandList({ - ) + ); } function CommandEmpty({ @@ -107,7 +107,7 @@ function CommandEmpty({ className="py-6 text-center text-sm" {...props} /> - ) + ); } function CommandGroup({ @@ -118,12 +118,12 @@ function CommandGroup({ - ) + ); } function CommandSeparator({ @@ -133,10 +133,10 @@ function CommandSeparator({ return ( - ) + ); } function CommandItem({ @@ -152,23 +152,23 @@ function CommandItem({ )} {...props} /> - ) + ); } function CommandShortcut({ className, ...props -}: React.ComponentProps<'span'>) { +}: React.ComponentProps<"span">) { return ( - ) + ); } export { @@ -181,4 +181,4 @@ export { CommandItem, CommandShortcut, CommandSeparator, -} +}; diff --git a/components/ui/context-menu.tsx b/components/ui/context-menu.tsx index 9e536f2..ab7a5d0 100644 --- a/components/ui/context-menu.tsx +++ b/components/ui/context-menu.tsx @@ -1,15 +1,15 @@ -'use client' +"use client"; -import * as React from 'react' -import * as ContextMenuPrimitive from '@radix-ui/react-context-menu' -import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react' +import * as React from "react"; +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function ContextMenu({ ...props }: React.ComponentProps) { - return + return ; } function ContextMenuTrigger({ @@ -17,7 +17,7 @@ function ContextMenuTrigger({ }: React.ComponentProps) { return ( - ) + ); } function ContextMenuGroup({ @@ -25,7 +25,7 @@ function ContextMenuGroup({ }: React.ComponentProps) { return ( - ) + ); } function ContextMenuPortal({ @@ -33,13 +33,13 @@ function ContextMenuPortal({ }: React.ComponentProps) { return ( - ) + ); } function ContextMenuSub({ ...props }: React.ComponentProps) { - return + return ; } function ContextMenuRadioGroup({ @@ -50,7 +50,7 @@ function ContextMenuRadioGroup({ data-slot="context-menu-radio-group" {...props} /> - ) + ); } function ContextMenuSubTrigger({ @@ -59,7 +59,7 @@ function ContextMenuSubTrigger({ children, ...props }: React.ComponentProps & { - inset?: boolean + inset?: boolean; }) { return ( - ) + ); } function ContextMenuSubContent({ @@ -85,12 +85,12 @@ function ContextMenuSubContent({ - ) + ); } function ContextMenuContent({ @@ -102,23 +102,23 @@ function ContextMenuContent({ - ) + ); } function ContextMenuItem({ className, inset, - variant = 'default', + variant = "default", ...props }: React.ComponentProps & { - inset?: boolean - variant?: 'default' | 'destructive' + inset?: boolean; + variant?: "default" | "destructive"; }) { return ( - ) + ); } function ContextMenuCheckboxItem({ @@ -157,7 +157,7 @@ function ContextMenuCheckboxItem({ {children} - ) + ); } function ContextMenuRadioItem({ @@ -181,7 +181,7 @@ function ContextMenuRadioItem({ {children} - ) + ); } function ContextMenuLabel({ @@ -189,19 +189,19 @@ function ContextMenuLabel({ inset, ...props }: React.ComponentProps & { - inset?: boolean + inset?: boolean; }) { return ( - ) + ); } function ContextMenuSeparator({ @@ -211,26 +211,26 @@ function ContextMenuSeparator({ return ( - ) + ); } function ContextMenuShortcut({ className, ...props -}: React.ComponentProps<'span'>) { +}: React.ComponentProps<"span">) { return ( - ) + ); } export { @@ -249,4 +249,4 @@ export { ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup, -} +}; diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index 243fb19..7d60dd3 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -1,33 +1,33 @@ -'use client' +"use client"; -import * as React from 'react' -import * as DialogPrimitive from '@radix-ui/react-dialog' -import { XIcon } from 'lucide-react' +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { XIcon } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function Dialog({ ...props }: React.ComponentProps) { - return + return ; } function DialogTrigger({ ...props }: React.ComponentProps) { - return + return ; } function DialogPortal({ ...props }: React.ComponentProps) { - return + return ; } function DialogClose({ ...props }: React.ComponentProps) { - return + return ; } function DialogOverlay({ @@ -38,12 +38,12 @@ function DialogOverlay({ - ) + ); } function DialogContent({ @@ -52,7 +52,7 @@ function DialogContent({ showCloseButton = true, ...props }: React.ComponentProps & { - showCloseButton?: boolean + showCloseButton?: boolean; }) { return ( @@ -60,7 +60,7 @@ function DialogContent({ - ) + ); } -function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } -function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) { +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } function DialogTitle({ @@ -110,10 +110,10 @@ function DialogTitle({ return ( - ) + ); } function DialogDescription({ @@ -123,10 +123,10 @@ function DialogDescription({ return ( - ) + ); } export { @@ -140,4 +140,4 @@ export { DialogPortal, DialogTitle, DialogTrigger, -} +}; diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx index 307bdce..8848866 100644 --- a/components/ui/drawer.tsx +++ b/components/ui/drawer.tsx @@ -1,32 +1,32 @@ -'use client' +"use client"; -import * as React from 'react' -import { Drawer as DrawerPrimitive } from 'vaul' +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function Drawer({ ...props }: React.ComponentProps) { - return + return ; } function DrawerTrigger({ ...props }: React.ComponentProps) { - return + return ; } function DrawerPortal({ ...props }: React.ComponentProps) { - return + return ; } function DrawerClose({ ...props }: React.ComponentProps) { - return + return ; } function DrawerOverlay({ @@ -37,12 +37,12 @@ function DrawerOverlay({ - ) + ); } function DrawerContent({ @@ -56,11 +56,11 @@ function DrawerContent({ - ) + ); } -function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) { +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } -function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) { +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } function DrawerTitle({ @@ -102,10 +102,10 @@ function DrawerTitle({ return ( - ) + ); } function DrawerDescription({ @@ -115,10 +115,10 @@ function DrawerDescription({ return ( - ) + ); } export { @@ -132,4 +132,4 @@ export { DrawerFooter, DrawerTitle, DrawerDescription, -} +}; diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx index a2096fa..0329b9b 100644 --- a/components/ui/dropdown-menu.tsx +++ b/components/ui/dropdown-menu.tsx @@ -1,15 +1,15 @@ -'use client' +"use client"; -import * as React from 'react' -import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' -import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react' +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; function DropdownMenu({ ...props }: React.ComponentProps) { - return + return ; } function DropdownMenuPortal({ @@ -17,7 +17,7 @@ function DropdownMenuPortal({ }: React.ComponentProps) { return ( - ) + ); } function DropdownMenuTrigger({ @@ -28,7 +28,7 @@ function DropdownMenuTrigger({ data-slot="dropdown-menu-trigger" {...props} /> - ) + ); } function DropdownMenuContent({ @@ -42,13 +42,13 @@ function DropdownMenuContent({ data-slot="dropdown-menu-content" sideOffset={sideOffset} className={cn( - 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", className, )} {...props} /> - ) + ); } function DropdownMenuGroup({ @@ -56,17 +56,17 @@ function DropdownMenuGroup({ }: React.ComponentProps) { return ( - ) + ); } function DropdownMenuItem({ className, inset, - variant = 'default', + variant = "default", ...props }: React.ComponentProps & { - inset?: boolean - variant?: 'default' | 'destructive' + inset?: boolean; + variant?: "default" | "destructive"; }) { return ( - ) + ); } function DropdownMenuCheckboxItem({ @@ -105,7 +105,7 @@ function DropdownMenuCheckboxItem({ {children} - ) + ); } function DropdownMenuRadioGroup({ @@ -116,7 +116,7 @@ function DropdownMenuRadioGroup({ data-slot="dropdown-menu-radio-group" {...props} /> - ) + ); } function DropdownMenuRadioItem({ @@ -140,7 +140,7 @@ function DropdownMenuRadioItem({ {children} - ) + ); } function DropdownMenuLabel({ @@ -148,19 +148,19 @@ function DropdownMenuLabel({ inset, ...props }: React.ComponentProps & { - inset?: boolean + inset?: boolean; }) { return ( - ) + ); } function DropdownMenuSeparator({ @@ -170,32 +170,32 @@ function DropdownMenuSeparator({ return ( - ) + ); } function DropdownMenuShortcut({ className, ...props -}: React.ComponentProps<'span'>) { +}: React.ComponentProps<"span">) { return ( - ) + ); } function DropdownMenuSub({ ...props }: React.ComponentProps) { - return + return ; } function DropdownMenuSubTrigger({ @@ -204,7 +204,7 @@ function DropdownMenuSubTrigger({ children, ...props }: React.ComponentProps & { - inset?: boolean + inset?: boolean; }) { return ( - ) + ); } function DropdownMenuSubContent({ @@ -230,12 +230,12 @@ function DropdownMenuSubContent({ - ) + ); } export { @@ -254,4 +254,4 @@ export { DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, -} +}; diff --git a/components/ui/empty.tsx b/components/ui/empty.tsx index 2c57e94..e4a0754 100644 --- a/components/ui/empty.tsx +++ b/components/ui/empty.tsx @@ -1,53 +1,53 @@ -import { cva, type VariantProps } from 'class-variance-authority' +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils"; -function Empty({ className, ...props }: React.ComponentProps<'div'>) { +function Empty({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } -function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) { +function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } const emptyMediaVariants = cva( - 'flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0', + "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0", { variants: { variant: { - default: 'bg-transparent', + default: "bg-transparent", icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6", }, }, defaultVariants: { - variant: 'default', + variant: "default", }, }, -) +); function EmptyMedia({ className, - variant = 'default', + variant = "default", ...props -}: React.ComponentProps<'div'> & VariantProps) { +}: React.ComponentProps<"div"> & VariantProps) { return (
- ) + ); } -function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) { +function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } -function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) { +function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { return (
a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', + "text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", className, )} {...props} /> - ) + ); } -function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) { +function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } export { @@ -101,4 +101,4 @@ export { EmptyDescription, EmptyContent, EmptyMedia, -} +}; diff --git a/components/ui/field.tsx b/components/ui/field.tsx index f4c2f21..9802658 100644 --- a/components/ui/field.tsx +++ b/components/ui/field.tsx @@ -1,88 +1,88 @@ -'use client' +"use client"; -import { useMemo } from 'react' -import { cva, type VariantProps } from 'class-variance-authority' +import { useMemo } from "react"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from '@/lib/utils' -import { Label } from '@/components/ui/label' -import { Separator } from '@/components/ui/separator' +import { cn } from "@/lib/utils"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; -function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) { +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { return (
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3', + "flex flex-col gap-6", + "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", className, )} {...props} /> - ) + ); } function FieldLegend({ className, - variant = 'legend', + variant = "legend", ...props -}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) { +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { return ( - ) + ); } -function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) { +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { return (
[data-slot=field-group]]:gap-4', + "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4", className, )} {...props} /> - ) + ); } const fieldVariants = cva( - 'group/field flex w-full gap-3 data-[invalid=true]:text-destructive', + "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", { variants: { orientation: { - vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'], + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], horizontal: [ - 'flex-row items-center', - '[&>[data-slot=field-label]]:flex-auto', - 'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px', + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", ], responsive: [ - 'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto', - '@md/field-group:[&>[data-slot=field-label]]:flex-auto', - '@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px', + "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", ], }, }, defaultVariants: { - orientation: 'vertical', + orientation: "vertical", }, }, -) +); function Field({ className, - orientation = 'vertical', + orientation = "vertical", ...props -}: React.ComponentProps<'div'> & VariantProps) { +}: React.ComponentProps<"div"> & VariantProps) { return (
- ) + ); } -function FieldContent({ className, ...props }: React.ComponentProps<'div'>) { +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { return (
- ) + ); } function FieldLabel({ @@ -115,57 +115,57 @@ function FieldLabel({